From 8722a9920c1f6119bf6e769cba270e63097f8e25 Mon Sep 17 00:00:00 2001
From: chai <215380520@qq.com>
Date: Thu, 23 May 2024 10:08:29 +0800
Subject: + astar project
---
.../com.arongranberg.astar/Graphs/Grid.meta | 8 +
.../Graphs/Grid/GraphCollision.cs | 387 ++
.../Graphs/Grid/GraphCollision.cs.meta | 11 +
.../Graphs/Grid/GridAdjacencyMapper.cs | 37 +
.../Graphs/Grid/GridAdjacencyMapper.cs.meta | 11 +
.../Graphs/Grid/GridGraphScanData.cs | 700 ++++
.../Graphs/Grid/GridGraphScanData.cs.meta | 11 +
.../Graphs/Grid/GridIterationUtilities.cs | 261 ++
.../Graphs/Grid/GridIterationUtilities.cs.meta | 11 +
.../com.arongranberg.astar/Graphs/Grid/Jobs.meta | 8 +
.../Graphs/Grid/Jobs/JobAllocateNodes.cs | 57 +
.../Graphs/Grid/Jobs/JobAllocateNodes.cs.meta | 11 +
.../Grid/Jobs/JobCalculateGridConnections.cs | 219 ++
.../Grid/Jobs/JobCalculateGridConnections.cs.meta | 11 +
.../Graphs/Grid/Jobs/JobCheckCollisions.cs | 33 +
.../Graphs/Grid/Jobs/JobCheckCollisions.cs.meta | 11 +
.../Graphs/Grid/Jobs/JobColliderHitsToBooleans.cs | 28 +
.../Grid/Jobs/JobColliderHitsToBooleans.cs.meta | 11 +
.../Graphs/Grid/Jobs/JobCopyBuffers.cs | 50 +
.../Graphs/Grid/Jobs/JobCopyBuffers.cs.meta | 11 +
.../Graphs/Grid/Jobs/JobErosion.cs | 190 +
.../Graphs/Grid/Jobs/JobErosion.cs.meta | 11 +
.../Grid/Jobs/JobFilterDiagonalConnections.cs | 128 +
.../Grid/Jobs/JobFilterDiagonalConnections.cs.meta | 11 +
.../Grid/Jobs/JobMergeRaycastCollisionHits.cs | 31 +
.../Grid/Jobs/JobMergeRaycastCollisionHits.cs.meta | 11 +
.../Graphs/Grid/Jobs/JobNodeGridLayout.cs | 36 +
.../Graphs/Grid/Jobs/JobNodeGridLayout.cs.meta | 11 +
.../Graphs/Grid/Jobs/JobNodeWalkability.cs | 73 +
.../Graphs/Grid/Jobs/JobNodeWalkability.cs.meta | 11 +
.../Graphs/Grid/Jobs/JobPrepareCapsuleCommands.cs | 45 +
.../Grid/Jobs/JobPrepareCapsuleCommands.cs.meta | 11 +
.../Graphs/Grid/Jobs/JobPrepareGridRaycast.cs | 61 +
.../Graphs/Grid/Jobs/JobPrepareGridRaycast.cs.meta | 11 +
.../Graphs/Grid/Jobs/JobPrepareRaycasts.cs | 51 +
.../Graphs/Grid/Jobs/JobPrepareRaycasts.cs.meta | 11 +
.../Graphs/Grid/Jobs/JobPrepareSphereCommands.cs | 42 +
.../Grid/Jobs/JobPrepareSphereCommands.cs.meta | 11 +
.../Graphs/Grid/Jobs/JobRaycastAll.cs | 121 +
.../Graphs/Grid/Jobs/JobRaycastAll.cs.meta | 11 +
.../Graphs/Grid/Jobs/JobReadNodeData.cs | 93 +
.../Graphs/Grid/Jobs/JobReadNodeData.cs.meta | 11 +
.../Graphs/Grid/Jobs/JobWriteNodeData.cs | 91 +
.../Graphs/Grid/Jobs/JobWriteNodeData.cs.meta | 11 +
.../com.arongranberg.astar/Graphs/Grid/Rules.meta | 8 +
.../Graphs/Grid/Rules/GridGraphRules.cs | 293 ++
.../Graphs/Grid/Rules/GridGraphRules.cs.meta | 11 +
.../Graphs/Grid/Rules/RuleAnglePenalty.cs | 81 +
.../Graphs/Grid/Rules/RuleAnglePenalty.cs.meta | 11 +
.../Graphs/Grid/Rules/RuleElevationPenalty.cs | 76 +
.../Graphs/Grid/Rules/RuleElevationPenalty.cs.meta | 11 +
.../Graphs/Grid/Rules/RulePerLayerModifications.cs | 79 +
.../Grid/Rules/RulePerLayerModifications.cs.meta | 11 +
.../Graphs/Grid/Rules/RuleTexture.cs | 181 +
.../Graphs/Grid/Rules/RuleTexture.cs.meta | 11 +
.../com.arongranberg.astar/Graphs/GridGraph.cs | 3681 ++++++++++++++++++++
.../Graphs/GridGraph.cs.meta | 7 +
.../Graphs/LayerGridGraph.cs | 246 ++
.../Graphs/LayerGridGraph.cs.meta | 7 +
.../com.arongranberg.astar/Graphs/LinkGraph.cs | 167 +
.../Graphs/LinkGraph.cs.meta | 11 +
.../com.arongranberg.astar/Graphs/NavGraph.cs | 469 +++
.../com.arongranberg.astar/Graphs/NavGraph.cs.meta | 7 +
.../com.arongranberg.astar/Graphs/NavMeshGraph.cs | 372 ++
.../Graphs/NavMeshGraph.cs.meta | 7 +
.../com.arongranberg.astar/Graphs/Navmesh.meta | 8 +
.../Graphs/Navmesh/AABBTree.cs | 381 ++
.../Graphs/Navmesh/AABBTree.cs.meta | 7 +
.../Graphs/Navmesh/BBTree.cs | 578 +++
.../Graphs/Navmesh/BBTree.cs.meta | 7 +
.../Graphs/Navmesh/ColliderMeshBuilder2D.cs | 336 ++
.../Graphs/Navmesh/ColliderMeshBuilder2D.cs.meta | 11 +
.../Graphs/Navmesh/Jobs.meta | 8 +
.../Graphs/Navmesh/Jobs/JobBuildNodes.cs | 92 +
.../Graphs/Navmesh/Jobs/JobBuildNodes.cs.meta | 11 +
.../Navmesh/Jobs/JobBuildTileMeshFromVertices.cs | 105 +
.../Jobs/JobBuildTileMeshFromVertices.cs.meta | 11 +
.../Navmesh/Jobs/JobBuildTileMeshFromVoxels.cs | 288 ++
.../Jobs/JobBuildTileMeshFromVoxels.cs.meta | 11 +
.../Jobs/JobCalculateTriangleConnections.cs | 73 +
.../Jobs/JobCalculateTriangleConnections.cs.meta | 11 +
.../Graphs/Navmesh/Jobs/JobConnectTiles.cs | 159 +
.../Graphs/Navmesh/Jobs/JobConnectTiles.cs.meta | 11 +
.../Graphs/Navmesh/Jobs/JobConvertAreasToTags.cs | 23 +
.../Navmesh/Jobs/JobConvertAreasToTags.cs.meta | 11 +
.../Graphs/Navmesh/Jobs/JobCreateTiles.cs | 115 +
.../Graphs/Navmesh/Jobs/JobCreateTiles.cs.meta | 11 +
.../Navmesh/Jobs/JobTransformTileCoordinates.cs | 32 +
.../Jobs/JobTransformTileCoordinates.cs.meta | 11 +
.../Graphs/Navmesh/Jobs/JobWriteNodeConnections.cs | 60 +
.../Navmesh/Jobs/JobWriteNodeConnections.cs.meta | 11 +
.../Graphs/Navmesh/NavmeshTile.cs | 106 +
.../Graphs/Navmesh/NavmeshTile.cs.meta | 12 +
.../Graphs/Navmesh/RecastBuilder.cs | 49 +
.../Graphs/Navmesh/RecastBuilder.cs.meta | 11 +
.../Graphs/Navmesh/RecastMeshGatherer.cs | 1134 ++++++
.../Graphs/Navmesh/RecastMeshGatherer.cs.meta | 11 +
.../Graphs/Navmesh/TileBuilder.cs | 366 ++
.../Graphs/Navmesh/TileBuilder.cs.meta | 11 +
.../Graphs/Navmesh/TileHandler.cs | 1258 +++++++
.../Graphs/Navmesh/TileHandler.cs.meta | 8 +
.../Graphs/Navmesh/TileLayout.cs | 96 +
.../Graphs/Navmesh/TileLayout.cs.meta | 11 +
.../Graphs/Navmesh/TileMesh.cs | 41 +
.../Graphs/Navmesh/TileMesh.cs.meta | 11 +
.../Graphs/Navmesh/TileMeshes.cs | 193 +
.../Graphs/Navmesh/TileMeshes.cs.meta | 11 +
.../Graphs/Navmesh/Voxels.meta | 2 +
.../Graphs/Navmesh/Voxels/CompactVoxelField.cs | 132 +
.../Navmesh/Voxels/CompactVoxelField.cs.meta | 11 +
.../Graphs/Navmesh/Voxels/LinkedVoxelField.cs | 295 ++
.../Graphs/Navmesh/Voxels/LinkedVoxelField.cs.meta | 11 +
.../Graphs/Navmesh/Voxels/VoxelContour.cs | 710 ++++
.../Graphs/Navmesh/Voxels/VoxelContour.cs.meta | 11 +
.../Graphs/Navmesh/Voxels/VoxelMesh.cs | 542 +++
.../Graphs/Navmesh/Voxels/VoxelMesh.cs.meta | 11 +
.../Graphs/Navmesh/Voxels/VoxelPolygonClipper.cs | 205 ++
.../Navmesh/Voxels/VoxelPolygonClipper.cs.meta | 12 +
.../Graphs/Navmesh/Voxels/VoxelRasterization.cs | 484 +++
.../Navmesh/Voxels/VoxelRasterization.cs.meta | 11 +
.../Graphs/Navmesh/Voxels/VoxelRegion.cs | 813 +++++
.../Graphs/Navmesh/Voxels/VoxelRegion.cs.meta | 11 +
.../com.arongranberg.astar/Graphs/NavmeshBase.cs | 1736 +++++++++
.../Graphs/NavmeshBase.cs.meta | 12 +
.../com.arongranberg.astar/Graphs/Nodes.meta | 2 +
.../Graphs/Nodes/GridNode.cs | 447 +++
.../Graphs/Nodes/GridNode.cs.meta | 7 +
.../Graphs/Nodes/GridNodeBase.cs | 534 +++
.../Graphs/Nodes/GridNodeBase.cs.meta | 12 +
.../Graphs/Nodes/LevelGridNode.cs | 403 +++
.../Graphs/Nodes/LevelGridNode.cs.meta | 11 +
.../Graphs/Nodes/PointNode.cs | 220 ++
.../Graphs/Nodes/PointNode.cs.meta | 8 +
.../Graphs/Nodes/TriangleMeshNode.cs | 673 ++++
.../Graphs/Nodes/TriangleMeshNode.cs.meta | 8 +
.../com.arongranberg.astar/Graphs/Point.meta | 8 +
.../Graphs/Point/PointKDTree.cs | 309 ++
.../Graphs/Point/PointKDTree.cs.meta | 12 +
.../com.arongranberg.astar/Graphs/PointGraph.cs | 772 ++++
.../Graphs/PointGraph.cs.meta | 7 +
.../com.arongranberg.astar/Graphs/RecastGraph.cs | 1392 ++++++++
.../Graphs/RecastGraph.cs.meta | 7 +
.../com.arongranberg.astar/Graphs/Utilities.meta | 2 +
.../Graphs/Utilities/EuclideanEmbedding.cs | 442 +++
.../Graphs/Utilities/EuclideanEmbedding.cs.meta | 8 +
.../Graphs/Utilities/GraphTransform.cs | 575 +++
.../Graphs/Utilities/GraphTransform.cs.meta | 12 +
.../Graphs/Utilities/GridLookup.cs | 220 ++
.../Graphs/Utilities/GridLookup.cs.meta | 12 +
.../Graphs/Utilities/NavMeshRenderer.cs | 4 +
.../Graphs/Utilities/NavMeshRenderer.cs.meta | 7 +
.../Graphs/Utilities/RecastMeshObj.cs | 311 ++
.../Graphs/Utilities/RecastMeshObj.cs.meta | 7 +
.../Graphs/Utilities/UtilityJobs.cs | 341 ++
.../Graphs/Utilities/UtilityJobs.cs.meta | 11 +
155 files changed, 26232 insertions(+)
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GraphCollision.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GraphCollision.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridAdjacencyMapper.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridAdjacencyMapper.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridGraphScanData.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridGraphScanData.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridIterationUtilities.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridIterationUtilities.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobAllocateNodes.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobAllocateNodes.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCalculateGridConnections.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCalculateGridConnections.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCheckCollisions.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCheckCollisions.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobColliderHitsToBooleans.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobColliderHitsToBooleans.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCopyBuffers.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCopyBuffers.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobErosion.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobErosion.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobFilterDiagonalConnections.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobFilterDiagonalConnections.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobMergeRaycastCollisionHits.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobMergeRaycastCollisionHits.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeGridLayout.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeGridLayout.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeWalkability.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeWalkability.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareCapsuleCommands.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareCapsuleCommands.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareGridRaycast.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareGridRaycast.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareRaycasts.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareRaycasts.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareSphereCommands.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareSphereCommands.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobRaycastAll.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobRaycastAll.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobReadNodeData.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobReadNodeData.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobWriteNodeData.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobWriteNodeData.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/GridGraphRules.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/GridGraphRules.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleAnglePenalty.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleAnglePenalty.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleElevationPenalty.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleElevationPenalty.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RulePerLayerModifications.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RulePerLayerModifications.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleTexture.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleTexture.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/GridGraph.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/GridGraph.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/LayerGridGraph.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/LayerGridGraph.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/LinkGraph.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/LinkGraph.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/NavGraph.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/NavGraph.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/NavMeshGraph.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/NavMeshGraph.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/AABBTree.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/AABBTree.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/BBTree.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/BBTree.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/ColliderMeshBuilder2D.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/ColliderMeshBuilder2D.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Jobs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Jobs/JobBuildNodes.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Jobs/JobBuildNodes.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Jobs/JobBuildTileMeshFromVertices.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Jobs/JobBuildTileMeshFromVertices.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Jobs/JobBuildTileMeshFromVoxels.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Jobs/JobBuildTileMeshFromVoxels.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Jobs/JobCalculateTriangleConnections.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Jobs/JobCalculateTriangleConnections.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Jobs/JobConnectTiles.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Jobs/JobConnectTiles.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Jobs/JobConvertAreasToTags.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Jobs/JobConvertAreasToTags.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Jobs/JobCreateTiles.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Jobs/JobCreateTiles.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Jobs/JobTransformTileCoordinates.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Jobs/JobTransformTileCoordinates.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Jobs/JobWriteNodeConnections.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Jobs/JobWriteNodeConnections.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/NavmeshTile.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/NavmeshTile.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/RecastBuilder.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/RecastBuilder.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/RecastMeshGatherer.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/RecastMeshGatherer.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/TileBuilder.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/TileBuilder.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/TileHandler.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/TileHandler.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/TileLayout.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/TileLayout.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/TileMesh.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/TileMesh.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/TileMeshes.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/TileMeshes.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/CompactVoxelField.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/CompactVoxelField.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/LinkedVoxelField.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/LinkedVoxelField.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelContour.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelContour.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelMesh.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelMesh.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelPolygonClipper.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelPolygonClipper.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelRasterization.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelRasterization.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelRegion.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelRegion.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/NavmeshBase.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/NavmeshBase.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Nodes.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Nodes/GridNode.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Nodes/GridNode.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Nodes/GridNodeBase.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Nodes/GridNodeBase.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Nodes/LevelGridNode.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Nodes/LevelGridNode.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Nodes/PointNode.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Nodes/PointNode.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Nodes/TriangleMeshNode.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Nodes/TriangleMeshNode.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Point.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Point/PointKDTree.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Point/PointKDTree.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/PointGraph.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/PointGraph.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/RecastGraph.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/RecastGraph.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Utilities.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Utilities/EuclideanEmbedding.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Utilities/EuclideanEmbedding.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Utilities/GraphTransform.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Utilities/GraphTransform.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Utilities/GridLookup.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Utilities/GridLookup.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Utilities/NavMeshRenderer.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Utilities/NavMeshRenderer.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Utilities/RecastMeshObj.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Utilities/RecastMeshObj.cs.meta
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Utilities/UtilityJobs.cs
create mode 100644 Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Utilities/UtilityJobs.cs.meta
(limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs')
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid.meta
new file mode 100644
index 0000000..0415f7c
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 339245ab27a1142a3b80c511ca17ad70
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GraphCollision.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GraphCollision.cs
new file mode 100644
index 0000000..a21f179
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GraphCollision.cs
@@ -0,0 +1,387 @@
+using UnityEngine;
+using System.Collections.Generic;
+using Unity.Collections;
+
+namespace Pathfinding.Graphs.Grid {
+ using Pathfinding.Util;
+ using Pathfinding.Graphs.Grid.Jobs;
+ using Pathfinding.Jobs;
+
+ ///
+ /// Handles collision checking for graphs.
+ /// Mostly used by grid based graphs
+ ///
+ [System.Serializable]
+ public class GraphCollision {
+ ///
+ /// Collision shape to use.
+ /// See:
+ ///
+ public ColliderType type = ColliderType.Capsule;
+
+ ///
+ /// Diameter of capsule or sphere when checking for collision.
+ /// When checking for collisions the system will check if any colliders
+ /// overlap a specific shape at the node's position. The shape is determined
+ /// by the field.
+ ///
+ /// A diameter of 1 means that the shape has a diameter equal to the node's width,
+ /// or in other words it is equal to .
+ ///
+ /// If is set to Ray, this does not affect anything.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ public float diameter = 1F;
+
+ ///
+ /// Height of capsule or length of ray when checking for collision.
+ /// If is set to Sphere, this does not affect anything.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Warning: In contrast to Unity's capsule collider and character controller this height does not include the end spheres of the capsule, but only the cylinder part.
+ /// This is mostly for historical reasons.
+ ///
+ public float height = 2F;
+
+ ///
+ /// Height above the ground that collision checks should be done.
+ /// For example, if the ground was found at y=0, collisionOffset = 2
+ /// type = Capsule and height = 3 then the physics system
+ /// will be queried to see if there are any colliders in a capsule
+ /// for which the bottom sphere that is made up of is centered at y=2
+ /// and the top sphere has its center at y=2+3=5.
+ ///
+ /// If type = Sphere then the sphere's center would be at y=2 in this case.
+ ///
+ public float collisionOffset;
+
+ ///
+ /// Direction of the ray when checking for collision.
+ /// If is not Ray, this does not affect anything
+ ///
+ /// Deprecated: Only the Both mode is supported now.
+ ///
+ [System.Obsolete("Only the Both mode is supported now")]
+ public RayDirection rayDirection = RayDirection.Both;
+
+ /// Layers to be treated as obstacles.
+ public LayerMask mask;
+
+ /// Layers to be included in the height check.
+ public LayerMask heightMask = -1;
+
+ ///
+ /// The height to check from when checking height ('ray length' in the inspector).
+ ///
+ /// As the image below visualizes, different ray lengths can make the ray hit different things.
+ /// The distance is measured up from the graph plane.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ public float fromHeight = 100;
+
+ ///
+ /// Toggles thick raycast.
+ /// See: https://docs.unity3d.com/ScriptReference/Physics.SphereCast.html
+ ///
+ public bool thickRaycast;
+
+ ///
+ /// Diameter of the thick raycast in nodes.
+ /// 1 equals
+ ///
+ public float thickRaycastDiameter = 1;
+
+ /// Make nodes unwalkable when no ground was found with the height raycast. If height raycast is turned off, this doesn't affect anything.
+ public bool unwalkableWhenNoGround = true;
+
+ ///
+ /// Use Unity 2D Physics API.
+ ///
+ /// If enabled, the 2D Physics API will be used, and if disabled, the 3D Physics API will be used.
+ ///
+ /// This changes the collider types (see from 3D versions to their corresponding 2D versions. For example the sphere shape becomes a circle.
+ ///
+ /// The setting will be ignored when 2D physics is used.
+ ///
+ /// See: http://docs.unity3d.com/ScriptReference/Physics2D.html
+ ///
+ public bool use2D;
+
+ /// Toggle collision check
+ public bool collisionCheck = true;
+
+ ///
+ /// Toggle height check. If false, the grid will be flat.
+ ///
+ /// This setting will be ignored when 2D physics is used.
+ ///
+ public bool heightCheck = true;
+
+ ///
+ /// Direction to use as UP.
+ /// See: Initialize
+ ///
+ public Vector3 up;
+
+ ///
+ /// * .
+ /// See: Initialize
+ ///
+ private Vector3 upheight;
+
+ /// Used for 2D collision queries
+ private ContactFilter2D contactFilter;
+
+ ///
+ /// Just so that the Physics2D.OverlapPoint method has some buffer to store things in.
+ /// We never actually read from this array, so we don't even care if this is thread safe.
+ ///
+ private static Collider2D[] dummyArray = new Collider2D[1];
+
+ ///
+ /// * scale * 0.5.
+ /// Where scale usually is
+ /// See: Initialize
+ ///
+ private float finalRadius;
+
+ ///
+ /// * scale * 0.5.
+ /// Where scale usually is See: Initialize
+ ///
+ private float finalRaycastRadius;
+
+ /// Offset to apply after each raycast to make sure we don't hit the same point again in CheckHeightAll
+ public const float RaycastErrorMargin = 0.005F;
+
+ ///
+ /// Sets up several variables using the specified matrix and scale.
+ /// See: GraphCollision.up
+ /// See: GraphCollision.upheight
+ /// See: GraphCollision.finalRadius
+ /// See: GraphCollision.finalRaycastRadius
+ ///
+ public void Initialize (GraphTransform transform, float scale) {
+ up = (transform.Transform(Vector3.up) - transform.Transform(Vector3.zero)).normalized;
+ upheight = up*height;
+ finalRadius = diameter*scale*0.5F;
+ finalRaycastRadius = thickRaycastDiameter*scale*0.5F;
+ contactFilter = new ContactFilter2D { layerMask = mask, useDepth = false, useLayerMask = true, useNormalAngle = false, useTriggers = false };
+ }
+
+ ///
+ /// Returns true if the position is not obstructed.
+ /// If is false, this will always return true.
+ ///
+ public bool Check (Vector3 position) {
+ if (!collisionCheck) {
+ return true;
+ }
+
+ if (use2D) {
+ switch (type) {
+ case ColliderType.Capsule:
+ case ColliderType.Sphere:
+ return Physics2D.OverlapCircle(position, finalRadius, contactFilter, dummyArray) == 0;
+ default:
+ return Physics2D.OverlapPoint(position, contactFilter, dummyArray) == 0;
+ }
+ }
+
+ position += up*collisionOffset;
+ switch (type) {
+ case ColliderType.Capsule:
+ return !Physics.CheckCapsule(position, position+upheight, finalRadius, mask, QueryTriggerInteraction.Ignore);
+ case ColliderType.Sphere:
+ return !Physics.CheckSphere(position, finalRadius, mask, QueryTriggerInteraction.Ignore);
+ default:
+ return !Physics.Raycast(position, up, height, mask, QueryTriggerInteraction.Ignore) && !Physics.Raycast(position+upheight, -up, height, mask, QueryTriggerInteraction.Ignore);
+ }
+ }
+
+ ///
+ /// Returns the position with the correct height.
+ /// If is false, this will return position.
+ ///
+ public Vector3 CheckHeight (Vector3 position) {
+ RaycastHit hit;
+ bool walkable;
+
+ return CheckHeight(position, out hit, out walkable);
+ }
+
+ ///
+ /// Returns the position with the correct height.
+ /// If is false, this will return position.
+ /// walkable will be set to false if nothing was hit.
+ /// The ray will check a tiny bit further than to the grids base to avoid floating point errors when the ground is exactly at the base of the grid
+ ///
+ public Vector3 CheckHeight (Vector3 position, out RaycastHit hit, out bool walkable) {
+ walkable = true;
+
+ if (!heightCheck || use2D) {
+ hit = new RaycastHit();
+ return position;
+ }
+
+ if (thickRaycast) {
+ var ray = new Ray(position+up*fromHeight, -up);
+ if (Physics.SphereCast(ray, finalRaycastRadius, out hit, fromHeight+0.005F, heightMask, QueryTriggerInteraction.Ignore)) {
+ return VectorMath.ClosestPointOnLine(ray.origin, ray.origin+ray.direction, hit.point);
+ }
+
+ walkable &= !unwalkableWhenNoGround;
+ } else {
+ // Cast a ray from above downwards to try to find the ground
+ if (Physics.Raycast(position+up*fromHeight, -up, out hit, fromHeight+0.005F, heightMask, QueryTriggerInteraction.Ignore)) {
+ return hit.point;
+ }
+
+ walkable &= !unwalkableWhenNoGround;
+ }
+ return position;
+ }
+
+ /// Internal buffer used by
+ RaycastHit[] hitBuffer = new RaycastHit[8];
+
+ ///
+ /// Returns all hits when checking height for position.
+ /// Warning: Does not work well with thick raycast, will only return an object a single time
+ ///
+ /// Warning: The returned array is ephermal. It will be invalidated when this method is called again.
+ /// If you need persistent results you should copy it.
+ ///
+ /// The returned array may be larger than the actual number of hits, the numHits out parameter indicates how many hits there actually were.
+ ///
+ public RaycastHit[] CheckHeightAll (Vector3 position, out int numHits) {
+ if (!heightCheck || use2D) {
+ hitBuffer[0] = new RaycastHit {
+ point = position,
+ distance = 0,
+ };
+ numHits = 1;
+ return hitBuffer;
+ }
+
+ // Cast a ray from above downwards to try to find the ground
+#if UNITY_2017_1_OR_NEWER
+ numHits = Physics.RaycastNonAlloc(position+up*fromHeight, -up, hitBuffer, fromHeight+0.005F, heightMask, QueryTriggerInteraction.Ignore);
+ if (numHits == hitBuffer.Length) {
+ // Try again with a larger buffer
+ hitBuffer = new RaycastHit[hitBuffer.Length*2];
+ return CheckHeightAll(position, out numHits);
+ }
+ return hitBuffer;
+#else
+ var result = Physics.RaycastAll(position+up*fromHeight, -up, fromHeight+0.005F, heightMask, QueryTriggerInteraction.Ignore);
+ numHits = result.Length;
+ return result;
+#endif
+ }
+
+ ///
+ /// Returns if the position is obstructed for all nodes using the Ray collision checking method.
+ /// collisionCheckResult[i] = true if there were no obstructions, false otherwise
+ ///
+ public void JobCollisionRay (NativeArray nodePositions, NativeArray collisionCheckResult, Vector3 up, Allocator allocationMethod, JobDependencyTracker dependencyTracker) {
+ var collisionRaycastCommands1 = dependencyTracker.NewNativeArray(nodePositions.Length, allocationMethod);
+ var collisionRaycastCommands2 = dependencyTracker.NewNativeArray(nodePositions.Length, allocationMethod);
+ var collisionHits1 = dependencyTracker.NewNativeArray(nodePositions.Length, allocationMethod);
+ var collisionHits2 = dependencyTracker.NewNativeArray(nodePositions.Length, allocationMethod);
+
+ // Fire rays from above down to the nodes' positions
+ new JobPrepareRaycasts {
+ origins = nodePositions,
+ originOffset = up * (height + collisionOffset),
+ direction = -up,
+ distance = height,
+ mask = mask,
+ physicsScene = Physics.defaultPhysicsScene,
+ raycastCommands = collisionRaycastCommands1,
+ }.Schedule(dependencyTracker);
+
+ // Fire rays from the node up towards the sky
+ new JobPrepareRaycasts {
+ origins = nodePositions,
+ originOffset = up * collisionOffset,
+ direction = up,
+ distance = height,
+ mask = mask,
+ physicsScene = Physics.defaultPhysicsScene,
+ raycastCommands = collisionRaycastCommands2,
+ }.Schedule(dependencyTracker);
+
+ dependencyTracker.ScheduleBatch(collisionRaycastCommands1, collisionHits1, 2048);
+ dependencyTracker.ScheduleBatch(collisionRaycastCommands2, collisionHits2, 2048);
+
+ new JobMergeRaycastCollisionHits {
+ hit1 = collisionHits1,
+ hit2 = collisionHits2,
+ result = collisionCheckResult,
+ }.Schedule(dependencyTracker);
+ }
+
+#if UNITY_2022_2_OR_NEWER
+ public void JobCollisionCapsule (NativeArray nodePositions, NativeArray collisionCheckResult, Vector3 up, Allocator allocationMethod, JobDependencyTracker dependencyTracker) {
+ var commands = dependencyTracker.NewNativeArray(nodePositions.Length, allocationMethod);
+ var collisionHits = dependencyTracker.NewNativeArray(nodePositions.Length, allocationMethod);
+ new JobPrepareCapsuleCommands {
+ origins = nodePositions,
+ originOffset = up * collisionOffset,
+ direction = up * height,
+ radius = finalRadius,
+ mask = mask,
+ commands = commands,
+ physicsScene = Physics.defaultPhysicsScene,
+ }.Schedule(dependencyTracker);
+ dependencyTracker.ScheduleBatch(commands, collisionHits, 2048);
+ new JobColliderHitsToBooleans {
+ hits = collisionHits,
+ result = collisionCheckResult,
+ }.Schedule(dependencyTracker);
+ }
+
+ public void JobCollisionSphere (NativeArray nodePositions, NativeArray collisionCheckResult, Vector3 up, Allocator allocationMethod, JobDependencyTracker dependencyTracker) {
+ var commands = dependencyTracker.NewNativeArray(nodePositions.Length, allocationMethod);
+ var collisionHits = dependencyTracker.NewNativeArray(nodePositions.Length, allocationMethod);
+ new JobPrepareSphereCommands {
+ origins = nodePositions,
+ originOffset = up * collisionOffset,
+ radius = finalRadius,
+ mask = mask,
+ commands = commands,
+ physicsScene = Physics.defaultPhysicsScene,
+ }.Schedule(dependencyTracker);
+ dependencyTracker.ScheduleBatch(commands, collisionHits, 2048);
+ new JobColliderHitsToBooleans {
+ hits = collisionHits,
+ result = collisionCheckResult,
+ }.Schedule(dependencyTracker);
+ }
+#endif
+ }
+
+ ///
+ /// Determines collision check shape.
+ /// See:
+ ///
+ public enum ColliderType {
+ /// Uses a Sphere, Physics.CheckSphere. In 2D this is a circle instead.
+ Sphere,
+ /// Uses a Capsule, Physics.CheckCapsule. This will behave identically to the Sphere mode in 2D.
+ Capsule,
+ /// Uses a Ray, Physics.Linecast. In 2D this is a single point instead.
+ Ray
+ }
+
+ /// Determines collision check ray direction
+ public enum RayDirection {
+ Up, /// < Casts the ray from the bottom upwards
+ Down, /// < Casts the ray from the top downwards
+ Both /// < Casts two rays in both directions
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GraphCollision.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GraphCollision.cs.meta
new file mode 100644
index 0000000..373fbfc
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GraphCollision.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b75c44e59ef4c7545af071adedd2ee03
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridAdjacencyMapper.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridAdjacencyMapper.cs
new file mode 100644
index 0000000..bba0a60
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridAdjacencyMapper.cs
@@ -0,0 +1,37 @@
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Collections.LowLevel.Unsafe;
+using Unity.Jobs;
+using Unity.Mathematics;
+
+namespace Pathfinding.Graphs.Grid {
+ public interface GridAdjacencyMapper {
+ int LayerCount(IntBounds bounds);
+ int GetNeighbourIndex(int nodeIndexXZ, int nodeIndex, int direction, NativeArray nodeConnections, NativeArray neighbourOffsets, int layerStride);
+ bool HasConnection(int nodeIndex, int direction, NativeArray nodeConnections);
+ }
+
+ public struct FlatGridAdjacencyMapper : GridAdjacencyMapper {
+ public int LayerCount (IntBounds bounds) {
+ UnityEngine.Assertions.Assert.IsTrue(bounds.size.y == 1);
+ return 1;
+ }
+ public int GetNeighbourIndex (int nodeIndexXZ, int nodeIndex, int direction, NativeArray nodeConnections, NativeArray neighbourOffsets, int layerStride) {
+ return nodeIndex + neighbourOffsets[direction];
+ }
+ public bool HasConnection (int nodeIndex, int direction, NativeArray nodeConnections) {
+ return ((nodeConnections[nodeIndex] >> direction) & 0x1) != 0;
+ }
+ }
+
+ public struct LayeredGridAdjacencyMapper : GridAdjacencyMapper {
+ public int LayerCount(IntBounds bounds) => bounds.size.y;
+ public int GetNeighbourIndex (int nodeIndexXZ, int nodeIndex, int direction, NativeArray nodeConnections, NativeArray neighbourOffsets, int layerStride) {
+ return nodeIndexXZ + neighbourOffsets[direction] + (int)((nodeConnections[nodeIndex] >> LevelGridNode.ConnectionStride*direction) & LevelGridNode.ConnectionMask) * layerStride;
+ }
+ public bool HasConnection (int nodeIndex, int direction, NativeArray nodeConnections) {
+ return ((nodeConnections[nodeIndex] >> LevelGridNode.ConnectionStride*direction) & LevelGridNode.ConnectionMask) != LevelGridNode.NoConnection;
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridAdjacencyMapper.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridAdjacencyMapper.cs.meta
new file mode 100644
index 0000000..053f5a7
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridAdjacencyMapper.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6c5c520c833b3b444b17fcb264c08a7a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridGraphScanData.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridGraphScanData.cs
new file mode 100644
index 0000000..e1a4fbb
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridGraphScanData.cs
@@ -0,0 +1,700 @@
+using UnityEngine;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Mathematics;
+using Pathfinding.Util;
+using UnityEngine.Profiling;
+using System.Collections.Generic;
+using Pathfinding.Jobs;
+using Pathfinding.Graphs.Grid.Jobs;
+using Unity.Jobs.LowLevel.Unsafe;
+
+namespace Pathfinding.Graphs.Grid {
+ public struct GridGraphNodeData {
+ public Allocator allocationMethod;
+ public int numNodes;
+ ///
+ /// Bounds for the part of the graph that this data represents.
+ /// For example if the first layer of a layered grid graph is being updated between x=10 and x=20, z=5 and z=15
+ /// then this will be IntBounds(xmin=10, ymin=0, zmin=5, xmax=20, ymax=0, zmax=15)
+ ///
+ public IntBounds bounds;
+ ///
+ /// Number of layers that the data contains.
+ /// For a non-layered grid graph this will always be 1.
+ ///
+ public int layers => bounds.size.y;
+
+ ///
+ /// Positions of all nodes.
+ ///
+ /// Data is valid in these passes:
+ /// - BeforeCollision: Valid
+ /// - BeforeConnections: Valid
+ /// - AfterConnections: Valid
+ /// - AfterErosion: Valid
+ /// - PostProcess: Valid
+ ///
+ public NativeArray positions;
+
+ ///
+ /// Bitpacked connections of all nodes.
+ ///
+ /// Connections are stored in different formats depending on .
+ /// You can use and to access connections for the different data layouts.
+ ///
+ /// Data is valid in these passes:
+ /// - BeforeCollision: Invalid
+ /// - BeforeConnections: Invalid
+ /// - AfterConnections: Valid
+ /// - AfterErosion: Valid (but will be overwritten)
+ /// - PostProcess: Valid
+ ///
+ public NativeArray connections;
+
+ ///
+ /// Bitpacked connections of all nodes.
+ ///
+ /// Data is valid in these passes:
+ /// - BeforeCollision: Valid
+ /// - BeforeConnections: Valid
+ /// - AfterConnections: Valid
+ /// - AfterErosion: Valid
+ /// - PostProcess: Valid
+ ///
+ public NativeArray penalties;
+
+ ///
+ /// Tags of all nodes
+ ///
+ /// Data is valid in these passes:
+ /// - BeforeCollision: Valid (but if erosion uses tags then it will be overwritten later)
+ /// - BeforeConnections: Valid (but if erosion uses tags then it will be overwritten later)
+ /// - AfterConnections: Valid (but if erosion uses tags then it will be overwritten later)
+ /// - AfterErosion: Valid
+ /// - PostProcess: Valid
+ ///
+ public NativeArray tags;
+
+ ///
+ /// Normals of all nodes.
+ /// If height testing is disabled the normal will be (0,1,0) for all nodes.
+ /// If a node doesn't exist (only happens in layered grid graphs) or if the height raycast didn't hit anything then the normal will be (0,0,0).
+ ///
+ /// Data is valid in these passes:
+ /// - BeforeCollision: Valid
+ /// - BeforeConnections: Valid
+ /// - AfterConnections: Valid
+ /// - AfterErosion: Valid
+ /// - PostProcess: Valid
+ ///
+ public NativeArray normals;
+
+ ///
+ /// Walkability of all nodes before erosion happens.
+ ///
+ /// Data is valid in these passes:
+ /// - BeforeCollision: Valid (it will be combined with collision testing later)
+ /// - BeforeConnections: Valid
+ /// - AfterConnections: Valid
+ /// - AfterErosion: Valid
+ /// - PostProcess: Valid
+ ///
+ public NativeArray walkable;
+
+ ///
+ /// Walkability of all nodes after erosion happens. This is the final walkability of the nodes.
+ /// If no erosion is used then the data will just be copied from the array.
+ ///
+ /// Data is valid in these passes:
+ /// - BeforeCollision: Invalid
+ /// - BeforeConnections: Invalid
+ /// - AfterConnections: Invalid
+ /// - AfterErosion: Valid
+ /// - PostProcess: Valid
+ ///
+ public NativeArray walkableWithErosion;
+
+
+ ///
+ /// True if the data may have multiple layers.
+ /// For layered data the nodes are laid out as `data[y*width*depth + z*width + x]`.
+ /// For non-layered data the nodes are laid out as `data[z*width + x]` (which is equivalent to the above layout assuming y=0).
+ ///
+ /// This also affects how node connections are stored. You can use and to access
+ /// connections for the different data layouts.
+ ///
+ public bool layeredDataLayout;
+
+ public void AllocateBuffers (JobDependencyTracker dependencyTracker) {
+ Profiler.BeginSample("Allocating buffers");
+ // Allocate buffers for jobs
+ // Allocating buffers with uninitialized memory is much faster if no jobs assume anything about their contents
+ if (dependencyTracker != null) {
+ positions = dependencyTracker.NewNativeArray(numNodes, allocationMethod, NativeArrayOptions.UninitializedMemory);
+ normals = dependencyTracker.NewNativeArray(numNodes, allocationMethod, NativeArrayOptions.UninitializedMemory);
+ connections = dependencyTracker.NewNativeArray(numNodes, allocationMethod, NativeArrayOptions.UninitializedMemory);
+ penalties = dependencyTracker.NewNativeArray(numNodes, allocationMethod, NativeArrayOptions.UninitializedMemory);
+ walkable = dependencyTracker.NewNativeArray(numNodes, allocationMethod, NativeArrayOptions.UninitializedMemory);
+ walkableWithErosion = dependencyTracker.NewNativeArray(numNodes, allocationMethod, NativeArrayOptions.UninitializedMemory);
+ tags = dependencyTracker.NewNativeArray(numNodes, allocationMethod, NativeArrayOptions.ClearMemory);
+ } else {
+ positions = new NativeArray(numNodes, allocationMethod, NativeArrayOptions.UninitializedMemory);
+ normals = new NativeArray(numNodes, allocationMethod, NativeArrayOptions.UninitializedMemory);
+ connections = new NativeArray(numNodes, allocationMethod, NativeArrayOptions.UninitializedMemory);
+ penalties = new NativeArray(numNodes, allocationMethod, NativeArrayOptions.UninitializedMemory);
+ walkable = new NativeArray(numNodes, allocationMethod, NativeArrayOptions.UninitializedMemory);
+ walkableWithErosion = new NativeArray(numNodes, allocationMethod, NativeArrayOptions.UninitializedMemory);
+ tags = new NativeArray(numNodes, allocationMethod, NativeArrayOptions.ClearMemory);
+ }
+ Profiler.EndSample();
+ }
+
+ public void TrackBuffers (JobDependencyTracker dependencyTracker) {
+ if (positions.IsCreated) dependencyTracker.Track(positions);
+ if (normals.IsCreated) dependencyTracker.Track(normals);
+ if (connections.IsCreated) dependencyTracker.Track(connections);
+ if (penalties.IsCreated) dependencyTracker.Track(penalties);
+ if (walkable.IsCreated) dependencyTracker.Track(walkable);
+ if (walkableWithErosion.IsCreated) dependencyTracker.Track(walkableWithErosion);
+ if (tags.IsCreated) dependencyTracker.Track(tags);
+ }
+
+ public void PersistBuffers (JobDependencyTracker dependencyTracker) {
+ dependencyTracker.Persist(positions);
+ dependencyTracker.Persist(normals);
+ dependencyTracker.Persist(connections);
+ dependencyTracker.Persist(penalties);
+ dependencyTracker.Persist(walkable);
+ dependencyTracker.Persist(walkableWithErosion);
+ dependencyTracker.Persist(tags);
+ }
+
+ public void Dispose () {
+ bounds = default;
+ numNodes = 0;
+ if (positions.IsCreated) positions.Dispose();
+ if (normals.IsCreated) normals.Dispose();
+ if (connections.IsCreated) connections.Dispose();
+ if (penalties.IsCreated) penalties.Dispose();
+ if (walkable.IsCreated) walkable.Dispose();
+ if (walkableWithErosion.IsCreated) walkableWithErosion.Dispose();
+ if (tags.IsCreated) tags.Dispose();
+ }
+
+ public JobHandle Rotate2D (int dx, int dz, JobHandle dependency) {
+ var size = bounds.size;
+ unsafe {
+ var jobs = stackalloc JobHandle[7];
+ jobs[0] = positions.Rotate3D(size, dx, dz).Schedule(dependency);
+ jobs[1] = normals.Rotate3D(size, dx, dz).Schedule(dependency);
+ jobs[2] = connections.Rotate3D(size, dx, dz).Schedule(dependency);
+ jobs[3] = penalties.Rotate3D(size, dx, dz).Schedule(dependency);
+ jobs[4] = walkable.Rotate3D(size, dx, dz).Schedule(dependency);
+ jobs[5] = walkableWithErosion.Rotate3D(size, dx, dz).Schedule(dependency);
+ jobs[6] = tags.Rotate3D(size, dx, dz).Schedule(dependency);
+ return JobHandleUnsafeUtility.CombineDependencies(jobs, 7);
+ }
+ }
+
+ public void ResizeLayerCount (int layerCount, JobDependencyTracker dependencyTracker) {
+ if (layerCount > layers) {
+ var oldData = this;
+ this.bounds.max.y = layerCount;
+ this.numNodes = bounds.volume;
+ this.AllocateBuffers(dependencyTracker);
+ // Ensure the normals for the upper layers are zeroed out.
+ // All other node data in the upper layers can be left uninitialized.
+ this.normals.MemSet(float4.zero).Schedule(dependencyTracker);
+ this.walkable.MemSet(false).Schedule(dependencyTracker);
+ this.walkableWithErosion.MemSet(false).Schedule(dependencyTracker);
+ new JobCopyBuffers {
+ input = oldData,
+ output = this,
+ copyPenaltyAndTags = true,
+ bounds = oldData.bounds,
+ }.Schedule(dependencyTracker);
+ }
+ if (layerCount < layers) {
+ throw new System.ArgumentException("Cannot reduce the number of layers");
+ }
+ }
+
+ struct LightReader : GridIterationUtilities.ISliceAction {
+ public GridNodeBase[] nodes;
+ public UnsafeSpan nodePositions;
+ public UnsafeSpan nodeWalkable;
+
+ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+ public void Execute (uint outerIdx, uint innerIdx) {
+ // The data bounds may have more layers than the existing nodes if a new layer is being added.
+ // We can only copy from the nodes that exist.
+ if (outerIdx < nodes.Length) {
+ var node = nodes[outerIdx];
+ if (node != null) {
+ nodePositions[innerIdx] = (Vector3)node.position;
+ nodeWalkable[innerIdx] = node.Walkable;
+ return;
+ }
+ }
+
+ // Fallback in case the node was null (only happens for layered grid graphs),
+ // or if we are adding more layers to the graph, in which case we are outside
+ // the bounds of the nodes array.
+ nodePositions[innerIdx] = Vector3.zero;
+ nodeWalkable[innerIdx] = false;
+ }
+ }
+
+ public void ReadFromNodesForConnectionCalculations (GridNodeBase[] nodes, Slice3D slice, JobHandle nodesDependsOn, NativeArray graphNodeNormals, JobDependencyTracker dependencyTracker) {
+ bounds = slice.slice;
+ numNodes = slice.slice.volume;
+
+ Profiler.BeginSample("Allocating buffers");
+ positions = new NativeArray(numNodes, allocationMethod, NativeArrayOptions.UninitializedMemory);
+ normals = new NativeArray(numNodes, allocationMethod, NativeArrayOptions.UninitializedMemory);
+ connections = new NativeArray(numNodes, allocationMethod, NativeArrayOptions.UninitializedMemory);
+ walkableWithErosion = new NativeArray(numNodes, allocationMethod, NativeArrayOptions.UninitializedMemory);
+ Profiler.EndSample();
+
+ Profiler.BeginSample("Reading node data");
+ var reader = new LightReader {
+ nodes = nodes,
+ nodePositions = this.positions.AsUnsafeSpan(),
+ nodeWalkable = this.walkableWithErosion.AsUnsafeSpan(),
+ };
+ GridIterationUtilities.ForEachCellIn3DSlice(slice, ref reader);
+ Profiler.EndSample();
+
+ ReadNodeNormals(slice, graphNodeNormals, dependencyTracker);
+ }
+
+ void ReadNodeNormals (Slice3D slice, NativeArray graphNodeNormals, JobDependencyTracker dependencyTracker) {
+ UnityEngine.Assertions.Assert.IsTrue(graphNodeNormals.IsCreated);
+ // Read the normal data from the graphNodeNormals array and copy it to the nodeNormals array.
+ // The nodeArrayBounds may have fewer layers than the readBounds if layers are being added.
+ // This means we can copy only a subset of the normals.
+ // We MemSet the array to zero first to avoid any uninitialized data remaining.
+ // TODO: Do clamping in caller
+ //var clampedReadBounds = new IntBounds(readBounds.min, new int3(readBounds.max.x, math.min(nodeArrayBounds.y, readBounds.max.y), readBounds.max.z));
+ if (dependencyTracker != null) {
+ normals.MemSet(float4.zero).Schedule(dependencyTracker);
+ new JobCopyRectangle {
+ input = graphNodeNormals,
+ output = normals,
+ inputSlice = slice,
+ outputSlice = new Slice3D(bounds, slice.slice),
+ }.Schedule(dependencyTracker);
+ } else {
+ Profiler.BeginSample("ReadNodeNormals");
+ normals.AsUnsafeSpan().FillZeros();
+ JobCopyRectangle.Copy(graphNodeNormals, normals, slice, new Slice3D(bounds, slice.slice));
+ Profiler.EndSample();
+ }
+ }
+
+ public static GridGraphNodeData ReadFromNodes (GridNodeBase[] nodes, Slice3D slice, JobHandle nodesDependsOn, NativeArray graphNodeNormals, Allocator allocator, bool layeredDataLayout, JobDependencyTracker dependencyTracker) {
+ var nodeData = new GridGraphNodeData {
+ allocationMethod = allocator,
+ numNodes = slice.slice.volume,
+ bounds = slice.slice,
+ layeredDataLayout = layeredDataLayout,
+ };
+ nodeData.AllocateBuffers(dependencyTracker);
+
+ // This is a managed type, we need to trick Unity to allow this inside of a job
+ var nodesHandle = System.Runtime.InteropServices.GCHandle.Alloc(nodes);
+
+ var job = new JobReadNodeData {
+ nodesHandle = nodesHandle,
+ nodePositions = nodeData.positions,
+ nodePenalties = nodeData.penalties,
+ nodeTags = nodeData.tags,
+ nodeConnections = nodeData.connections,
+ nodeWalkableWithErosion = nodeData.walkableWithErosion,
+ nodeWalkable = nodeData.walkable,
+ slice = slice,
+ }.ScheduleBatch(nodeData.numNodes, math.max(2000, nodeData.numNodes/16), dependencyTracker, nodesDependsOn);
+
+ dependencyTracker.DeferFree(nodesHandle, job);
+
+ if (graphNodeNormals.IsCreated) nodeData.ReadNodeNormals(slice, graphNodeNormals, dependencyTracker);
+ return nodeData;
+ }
+
+ public GridGraphNodeData ReadFromNodesAndCopy (GridNodeBase[] nodes, Slice3D slice, JobHandle nodesDependsOn, NativeArray graphNodeNormals, bool copyPenaltyAndTags, JobDependencyTracker dependencyTracker) {
+ var newData = GridGraphNodeData.ReadFromNodes(nodes, slice, nodesDependsOn, graphNodeNormals, allocationMethod, layeredDataLayout, dependencyTracker);
+ // Overwrite a rectangle in the center with the data from this object.
+ // In the end we will have newly calculated data in the middle and data read from nodes along the borders
+ newData.CopyFrom(this, copyPenaltyAndTags, dependencyTracker);
+ return newData;
+ }
+
+ public void CopyFrom(GridGraphNodeData other, bool copyPenaltyAndTags, JobDependencyTracker dependencyTracker) => CopyFrom(other, IntBounds.Intersection(bounds, other.bounds), copyPenaltyAndTags, dependencyTracker);
+
+ public void CopyFrom (GridGraphNodeData other, IntBounds bounds, bool copyPenaltyAndTags, JobDependencyTracker dependencyTracker) {
+ var job = new JobCopyBuffers {
+ input = other,
+ output = this,
+ copyPenaltyAndTags = copyPenaltyAndTags,
+ bounds = bounds,
+ };
+ if (dependencyTracker != null) {
+ job.Schedule(dependencyTracker);
+ } else {
+#if UNITY_2022_2_OR_NEWER
+ job.RunByRef();
+#else
+ job.Run();
+#endif
+ }
+ }
+
+ public JobHandle AssignToNodes (GridNodeBase[] nodes, int3 nodeArrayBounds, IntBounds writeMask, uint graphIndex, JobHandle nodesDependsOn, JobDependencyTracker dependencyTracker) {
+ // This is a managed type, we need to trick Unity to allow this inside of a job
+ var nodesHandle = System.Runtime.InteropServices.GCHandle.Alloc(nodes);
+
+ // Assign the data to the nodes (in parallel for performance)
+ // This will also dirty all nodes, but that is a thread-safe operation.
+ var job2 = new JobWriteNodeData {
+ nodesHandle = nodesHandle,
+ graphIndex = graphIndex,
+ nodePositions = positions,
+ nodePenalties = penalties,
+ nodeTags = tags,
+ nodeConnections = connections,
+ nodeWalkableWithErosion = walkableWithErosion,
+ nodeWalkable = walkable,
+ nodeArrayBounds = nodeArrayBounds,
+ dataBounds = bounds,
+ writeMask = writeMask,
+ }.ScheduleBatch(writeMask.volume, math.max(1000, writeMask.volume/16), dependencyTracker, nodesDependsOn);
+
+ dependencyTracker.DeferFree(nodesHandle, job2);
+ return job2;
+ }
+ }
+
+ public struct GridGraphScanData {
+ ///
+ /// Tracks dependencies between jobs to allow parallelism without tediously specifying dependencies manually.
+ /// Always use when scheduling jobs.
+ ///
+ public JobDependencyTracker dependencyTracker;
+
+ /// The up direction of the graph, in world space
+ public Vector3 up;
+
+ /// Transforms graph-space to world space
+ public GraphTransform transform;
+
+ /// Data for all nodes in the graph update that is being calculated
+ public GridGraphNodeData nodes;
+
+ ///
+ /// Bounds of the data arrays.
+ /// Deprecated: Use nodes.bounds or heightHitsBounds depending on if you are using the heightHits array or not
+ ///
+ [System.Obsolete("Use nodes.bounds or heightHitsBounds depending on if you are using the heightHits array or not")]
+ public IntBounds bounds => nodes.bounds;
+
+ ///
+ /// True if the data may have multiple layers.
+ /// For layered data the nodes are laid out as `data[y*width*depth + z*width + x]`.
+ /// For non-layered data the nodes are laid out as `data[z*width + x]` (which is equivalent to the above layout assuming y=0).
+ ///
+ /// Deprecated: Use nodes.layeredDataLayout instead
+ ///
+ [System.Obsolete("Use nodes.layeredDataLayout instead")]
+ public bool layeredDataLayout => nodes.layeredDataLayout;
+
+ ///
+ /// Raycasts hits used for height testing.
+ /// This data is only valid if height testing is enabled, otherwise the array is uninitialized (heightHits.IsCreated will be false).
+ ///
+ /// Data is valid in these passes:
+ /// - BeforeCollision: Valid (if height testing is enabled)
+ /// - BeforeConnections: Valid (if height testing is enabled)
+ /// - AfterConnections: Valid (if height testing is enabled)
+ /// - AfterErosion: Valid (if height testing is enabled)
+ /// - PostProcess: Valid (if height testing is enabled)
+ ///
+ /// Warning: This array does not have the same size as the arrays in . It will usually be slightly smaller. See .
+ ///
+ public NativeArray heightHits;
+
+ ///
+ /// Bounds for the array.
+ ///
+ /// During an update, the scan data may contain more nodes than we are doing height testing for.
+ /// For a few nodes around the update, the data will be read from the existing graph, instead. This is done for performance.
+ /// This means that there may not be any height testing information these nodes.
+ /// However, all nodes that will be written to will always have height testing information.
+ ///
+ public IntBounds heightHitsBounds;
+
+ ///
+ /// Node positions.
+ /// Deprecated: Use instead
+ ///
+ [System.Obsolete("Use nodes.positions instead")]
+ public NativeArray nodePositions => nodes.positions;
+
+ ///
+ /// Node connections.
+ /// Deprecated: Use instead
+ ///
+ [System.Obsolete("Use nodes.connections instead")]
+ public NativeArray nodeConnections => nodes.connections;
+
+ ///
+ /// Node penalties.
+ /// Deprecated: Use instead
+ ///
+ [System.Obsolete("Use nodes.penalties instead")]
+ public NativeArray nodePenalties => nodes.penalties;
+
+ ///
+ /// Node tags.
+ /// Deprecated: Use instead
+ ///
+ [System.Obsolete("Use nodes.tags instead")]
+ public NativeArray nodeTags => nodes.tags;
+
+ ///
+ /// Node normals.
+ /// Deprecated: Use instead
+ ///
+ [System.Obsolete("Use nodes.normals instead")]
+ public NativeArray nodeNormals => nodes.normals;
+
+ ///
+ /// Node walkability.
+ /// Deprecated: Use instead
+ ///
+ [System.Obsolete("Use nodes.walkable instead")]
+ public NativeArray nodeWalkable => nodes.walkable;
+
+ ///
+ /// Node walkability with erosion.
+ /// Deprecated: Use instead
+ ///
+ [System.Obsolete("Use nodes.walkableWithErosion instead")]
+ public NativeArray nodeWalkableWithErosion => nodes.walkableWithErosion;
+
+ public void SetDefaultPenalties (uint initialPenalty) {
+ nodes.penalties.MemSet(initialPenalty).Schedule(dependencyTracker);
+ }
+
+ public void SetDefaultNodePositions (GraphTransform transform) {
+ new JobNodeGridLayout {
+ graphToWorld = transform.matrix,
+ bounds = nodes.bounds,
+ nodePositions = nodes.positions,
+ }.Schedule(dependencyTracker);
+ }
+
+ public JobHandle HeightCheck (GraphCollision collision, int maxHits, IntBounds recalculationBounds, NativeArray outLayerCount, float characterHeight, Allocator allocator) {
+ // For some reason the physics code crashes when allocating raycastCommands with UninitializedMemory, even though I have verified that every
+ // element in the array is set to a well defined value before the physics code gets to it... Mysterious.
+ var cellCount = recalculationBounds.size.x * recalculationBounds.size.z;
+ var raycastCommands = dependencyTracker.NewNativeArray(cellCount, allocator, NativeArrayOptions.ClearMemory);
+
+ heightHits = dependencyTracker.NewNativeArray(cellCount * maxHits, allocator, NativeArrayOptions.ClearMemory);
+ heightHitsBounds = recalculationBounds;
+
+ // Due to floating point inaccuracies we don't want the rays to end *exactly* at the base of the graph
+ // The rays may or may not hit colliders with the exact same y coordinate.
+ // We extend the rays a bit to ensure they always hit
+ const float RayLengthMargin = 0.01f;
+ var prepareJob = new JobPrepareGridRaycast {
+ graphToWorld = transform.matrix,
+ bounds = recalculationBounds,
+ physicsScene = Physics.defaultPhysicsScene,
+ raycastOffset = up * collision.fromHeight,
+ raycastDirection = -up * (collision.fromHeight + RayLengthMargin),
+ raycastMask = collision.heightMask,
+ raycastCommands = raycastCommands,
+ }.Schedule(dependencyTracker);
+
+ if (maxHits > 1) {
+ // Skip this distance between each hit.
+ // It is pretty arbitrarily chosen, but it must be lower than characterHeight.
+ // If it would be set too low then many thin colliders stacked on top of each other could lead to a very large number of hits
+ // that will not lead to any walkable nodes anyway.
+ float minStep = characterHeight * 0.5f;
+ var dependency = new JobRaycastAll(raycastCommands, heightHits, Physics.defaultPhysicsScene, maxHits, allocator, dependencyTracker, minStep).Schedule(prepareJob);
+
+ dependency = new JobMaxHitCount {
+ hits = heightHits,
+ maxHits = maxHits,
+ layerStride = cellCount,
+ maxHitCount = outLayerCount,
+ }.Schedule(dependency);
+
+ return dependency;
+ } else {
+ dependencyTracker.ScheduleBatch(raycastCommands, heightHits, 2048);
+ outLayerCount[0] = 1;
+ return default;
+ }
+ }
+
+ public void CopyHits (IntBounds recalculationBounds) {
+ // Copy the hit points and normals to separate arrays
+ // Ensure the normals for the upper layers are zeroed out.
+ nodes.normals.MemSet(float4.zero).Schedule(dependencyTracker);
+ new JobCopyHits {
+ hits = heightHits,
+ points = nodes.positions,
+ normals = nodes.normals,
+ slice = new Slice3D(nodes.bounds, recalculationBounds),
+ }.Schedule(dependencyTracker);
+ }
+
+ public void CalculateWalkabilityFromHeightData (bool useRaycastNormal, bool unwalkableWhenNoGround, float maxSlope, float characterHeight) {
+ new JobNodeWalkability {
+ useRaycastNormal = useRaycastNormal,
+ unwalkableWhenNoGround = unwalkableWhenNoGround,
+ maxSlope = maxSlope,
+ up = up,
+ nodeNormals = nodes.normals,
+ nodeWalkable = nodes.walkable,
+ nodePositions = nodes.positions.Reinterpret(),
+ characterHeight = characterHeight,
+ layerStride = nodes.bounds.size.x*nodes.bounds.size.z,
+ }.Schedule(dependencyTracker);
+ }
+
+ public IEnumerator CollisionCheck (GraphCollision collision, IntBounds calculationBounds) {
+ if (collision.type == ColliderType.Ray && !collision.use2D) {
+ var collisionCheckResult = dependencyTracker.NewNativeArray(nodes.numNodes, nodes.allocationMethod, NativeArrayOptions.UninitializedMemory);
+ collision.JobCollisionRay(nodes.positions, collisionCheckResult, up, nodes.allocationMethod, dependencyTracker);
+ nodes.walkable.BitwiseAndWith(collisionCheckResult).WithLength(nodes.numNodes).Schedule(dependencyTracker);
+ return null;
+
+// Before Unity 2023.3, these features compile, but they will cause memory corruption in some cases, due to a bug in Unity
+#if UNITY_2022_2_OR_NEWER && UNITY_2023_3_OR_NEWER && UNITY_HAS_FIXED_MEMORY_CORRUPTION_ISSUE
+ } else if (collision.type == ColliderType.Capsule && !collision.use2D) {
+ var collisionCheckResult = dependencyTracker.NewNativeArray(nodes.numNodes, nodes.allocationMethod, NativeArrayOptions.UninitializedMemory);
+ collision.JobCollisionCapsule(nodes.positions, collisionCheckResult, up, nodes.allocationMethod, dependencyTracker);
+ nodes.walkable.BitwiseAndWith(collisionCheckResult).WithLength(nodes.numNodes).Schedule(dependencyTracker);
+ return null;
+ } else if (collision.type == ColliderType.Sphere && !collision.use2D) {
+ var collisionCheckResult = dependencyTracker.NewNativeArray(nodes.numNodes, nodes.allocationMethod, NativeArrayOptions.UninitializedMemory);
+ collision.JobCollisionSphere(nodes.positions, collisionCheckResult, up, nodes.allocationMethod, dependencyTracker);
+ nodes.walkable.BitwiseAndWith(collisionCheckResult).WithLength(nodes.numNodes).Schedule(dependencyTracker);
+ return null;
+#endif
+ } else {
+ // This part can unfortunately not be jobified yet
+ return new JobCheckCollisions {
+ nodePositions = nodes.positions,
+ collisionResult = nodes.walkable,
+ collision = collision,
+ }.ExecuteMainThreadJob(dependencyTracker);
+ }
+ }
+
+ public void Connections (float maxStepHeight, bool maxStepUsesSlope, IntBounds calculationBounds, NumNeighbours neighbours, bool cutCorners, bool use2D, bool useErodedWalkability, float characterHeight) {
+ var job = new JobCalculateGridConnections {
+ maxStepHeight = maxStepHeight,
+ maxStepUsesSlope = maxStepUsesSlope,
+ up = up,
+ bounds = calculationBounds.Offset(-nodes.bounds.min),
+ arrayBounds = nodes.bounds.size,
+ neighbours = neighbours,
+ use2D = use2D,
+ cutCorners = cutCorners,
+ nodeWalkable = (useErodedWalkability ? nodes.walkableWithErosion : nodes.walkable).AsUnsafeSpanNoChecks(),
+ nodePositions = nodes.positions.AsUnsafeSpanNoChecks(),
+ nodeNormals = nodes.normals.AsUnsafeSpanNoChecks(),
+ nodeConnections = nodes.connections.AsUnsafeSpanNoChecks(),
+ characterHeight = characterHeight,
+ layeredDataLayout = nodes.layeredDataLayout,
+ };
+
+ if (dependencyTracker != null) {
+ job.ScheduleBatch(calculationBounds.size.z, 20, dependencyTracker);
+ } else {
+ job.RunBatch(calculationBounds.size.z);
+ }
+
+ // For single layer graphs this will have already been done in the JobCalculateGridConnections job
+ // but for layered grid graphs we need to handle things differently because the data layout is different.
+ // It needs to be done after all axis aligned connections have been calculated.
+ if (nodes.layeredDataLayout) {
+ var job2 = new JobFilterDiagonalConnections {
+ slice = new Slice3D(nodes.bounds, calculationBounds),
+ neighbours = neighbours,
+ cutCorners = cutCorners,
+ nodeConnections = nodes.connections.AsUnsafeSpanNoChecks(),
+ };
+ if (dependencyTracker != null) {
+ job2.ScheduleBatch(calculationBounds.size.z, 20, dependencyTracker);
+ } else {
+ job2.RunBatch(calculationBounds.size.z);
+ }
+ }
+ }
+
+ public void Erosion (NumNeighbours neighbours, int erodeIterations, IntBounds erosionWriteMask, bool erosionUsesTags, int erosionStartTag, int erosionTagsPrecedenceMask) {
+ if (!nodes.layeredDataLayout) {
+ new JobErosion {
+ bounds = nodes.bounds,
+ writeMask = erosionWriteMask,
+ neighbours = neighbours,
+ nodeConnections = nodes.connections,
+ erosion = erodeIterations,
+ nodeWalkable = nodes.walkable,
+ outNodeWalkable = nodes.walkableWithErosion,
+ nodeTags = nodes.tags,
+ erosionUsesTags = erosionUsesTags,
+ erosionStartTag = erosionStartTag,
+ erosionTagsPrecedenceMask = erosionTagsPrecedenceMask,
+ }.Schedule(dependencyTracker);
+ } else {
+ new JobErosion {
+ bounds = nodes.bounds,
+ writeMask = erosionWriteMask,
+ neighbours = neighbours,
+ nodeConnections = nodes.connections,
+ erosion = erodeIterations,
+ nodeWalkable = nodes.walkable,
+ outNodeWalkable = nodes.walkableWithErosion,
+ nodeTags = nodes.tags,
+ erosionUsesTags = erosionUsesTags,
+ erosionStartTag = erosionStartTag,
+ erosionTagsPrecedenceMask = erosionTagsPrecedenceMask,
+ }.Schedule(dependencyTracker);
+ }
+ }
+
+ public void AssignNodeConnections (GridNodeBase[] nodes, int3 nodeArrayBounds, IntBounds writeBounds) {
+ var bounds = this.nodes.bounds;
+ var writeDataOffset = writeBounds.min - bounds.min;
+ var nodeConnections = this.nodes.connections.AsUnsafeReadOnlySpan();
+ for (int y = 0; y < writeBounds.size.y; y++) {
+ var yoffset = (y + writeBounds.min.y)*nodeArrayBounds.x*nodeArrayBounds.z;
+ for (int z = 0; z < writeBounds.size.z; z++) {
+ var zoffset = yoffset + (z + writeBounds.min.z)*nodeArrayBounds.x + writeBounds.min.x;
+ var zoffset2 = (y+writeDataOffset.y)*bounds.size.x*bounds.size.z + (z+writeDataOffset.z)*bounds.size.x + writeDataOffset.x;
+ for (int x = 0; x < writeBounds.size.x; x++) {
+ var node = nodes[zoffset + x];
+ var dataIdx = zoffset2 + x;
+ var conn = nodeConnections[dataIdx];
+
+ if (node == null) continue;
+
+ if (node is LevelGridNode lgnode) {
+ lgnode.SetAllConnectionInternal(conn);
+ } else {
+ var gnode = node as GridNode;
+ gnode.SetAllConnectionInternal((int)conn);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridGraphScanData.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridGraphScanData.cs.meta
new file mode 100644
index 0000000..c108af9
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridGraphScanData.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: cd8d2fda4c637484c806417e77602960
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridIterationUtilities.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridIterationUtilities.cs
new file mode 100644
index 0000000..8d5485b
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridIterationUtilities.cs
@@ -0,0 +1,261 @@
+using Pathfinding.Jobs;
+using Unity.Collections;
+using Unity.Mathematics;
+using UnityEngine.Assertions;
+
+namespace Pathfinding.Graphs.Grid {
+ ///
+ /// Helpers for iterating over grid graph data.
+ ///
+ /// This is a helper for iterating over grid graph data, which is typically stored in an array of size width*layers*depth (x*y*z).
+ /// It is used internally by grid graph jobs, and can also be used by custom grid graph rules.
+ ///
+ /// See: grid-rules-write (view in online documentation for working links)
+ ///
+ public static class GridIterationUtilities {
+ /// Callback struct for
+ public interface ISliceAction {
+ void Execute(uint outerIdx, uint innerIdx);
+ }
+
+ /// Callback struct for
+ public interface ISliceActionWithCoords {
+ void Execute(uint outerIdx, uint innerIdx, int3 innerCoords);
+ }
+
+ /// Callback struct for
+ public interface ICellAction {
+ void Execute(uint idx, int x, int y, int z);
+ }
+
+ ///
+ /// Iterates over a slice of a 3D array.
+ ///
+ /// This is a helper for iterating over grid graph data, which is typically stored in an array of size width*layers*depth (x*y*z).
+ ///
+ /// In burst-compiled code, this will be essentially as fast as writing the loop code yourself. In C#, it is marginally slower than writing the loop code yourself.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Bounds of the slice and the size of the outer array it is relative to.
+ /// Your callback struct. The Execute method on the callback struct will be called for each element in the slice. It will be passed both the index in the slice and the index in the outer array.
+ public static void ForEachCellIn3DSlice(Slice3D slice, ref T action) where T : struct, ISliceAction {
+ var size = slice.slice.size;
+ var(strideX, strideY, strideZ) = slice.outerStrides;
+ var outerOffset = slice.outerStartIndex;
+ uint i = 0;
+ for (int y = 0; y < size.y; y++) {
+ for (int z = 0; z < size.z; z++) {
+ int offset2 = y*strideY + z*strideZ + outerOffset;
+ for (int x = 0; x < size.x; x++, i++) {
+ action.Execute((uint)(offset2 + x), i);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Iterates over a slice of a 3D array.
+ ///
+ /// This is a helper for iterating over grid graph data, which is typically stored in an array of size width*layers*depth (x*y*z).
+ ///
+ /// In burst-compiled code, this will be essentially as fast as writing the loop code yourself. In C#, it is marginally slower than writing the loop code yourself.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Bounds of the slice and the size of the outer array it is relative to.
+ /// Your callback struct. The Execute method on the callback struct will be called for each element in the slice. It will be passed both the index in the slice and the index in the outer array.
+ public static void ForEachCellIn3DSliceWithCoords(Slice3D slice, ref T action) where T : struct, ISliceActionWithCoords {
+ var size = slice.slice.size;
+ var(strideX, strideY, strideZ) = slice.outerStrides;
+ var outerOffset = slice.outerStartIndex;
+ uint i = (uint)(size.x*size.y*size.z) - 1;
+ for (int y = size.y - 1; y >= 0; y--) {
+ for (int z = size.z - 1; z >= 0; z--) {
+ int offset2 = y*strideY + z*strideZ + outerOffset;
+ for (int x = size.x - 1; x >= 0; x--, i--) {
+ action.Execute((uint)(offset2 + x), i, new int3(x, y, z));
+ }
+ }
+ }
+ }
+
+ ///
+ /// Iterates over a 3D array.
+ ///
+ /// This is a helper for iterating over grid graph data, which is typically stored in an array of size width*layers*depth (x*y*z).
+ ///
+ /// In burst-compiled code, this will be essentially as fast as writing the loop code yourself. In C#, it is marginally slower than writing the loop code yourself.
+ ///
+ /// Size of the array.
+ /// Your callback struct. The Execute method on the callback struct will be called for each element in the array. It will be passed the x, y and z coordinates of the element as well as the index in the array.
+ public static void ForEachCellIn3DArray(int3 size, ref T action) where T : struct, ICellAction {
+ uint i = (uint)(size.x*size.y*size.z) - 1;
+ for (int y = size.y - 1; y >= 0; y--) {
+ for (int z = size.z - 1; z >= 0; z--) {
+ for (int x = size.x - 1; x >= 0; x--, i--) {
+ action.Execute(i, x, y, z);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Helper interface for modifying nodes.
+ /// This is used by the function.
+ ///
+ public interface INodeModifier {
+ ///
+ /// Called for every node that is being updated.
+ ///
+ /// See: gridgraphrule-burst (view in online documentation for working links) for example usage.
+ ///
+ /// Index of the node. This is the index in the data arrays for the graph update, not necessarily the index in the graph.
+ /// X coordinate of the node, relative to the updated region.
+ /// Layer (Y) coordinate of the node, relative to the updated region.
+ /// Z coordinate of the node, relative to the updated region.
+ void ModifyNode(int dataIndex, int dataX, int dataLayer, int dataZ);
+ }
+
+ ///
+ /// Iterate through all nodes that exist.
+ ///
+ /// See: grid-rules-write (view in online documentation for working links) for example usage.
+ ///
+ /// Size of the rectangle of the grid graph that is being updated/scanned
+ /// Data for all node normals. This is used to determine if a node exists (important for layered grid graphs).
+ /// The ModifyNode method on the callback struct will be called for each node.
+ public static void ForEachNode(int3 arrayBounds, NativeArray nodeNormals, ref T callback) where T : struct, INodeModifier {
+ Assert.IsTrue(nodeNormals.Length == arrayBounds.x * arrayBounds.y * arrayBounds.z);
+ int i = 0;
+
+ for (int y = 0; y < arrayBounds.y; y++) {
+ for (int z = 0; z < arrayBounds.z; z++) {
+ for (int x = 0; x < arrayBounds.x; x++, i++) {
+ // Check if the node exists at all
+ // This is important for layered grid graphs
+ // A normal is never zero otherwise
+ if (math.any(nodeNormals[i])) {
+ callback.ModifyNode(i, x, y, z);
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Helper interface for modifying node connections.
+ /// This is used by the function.
+ ///
+ public interface IConnectionFilter {
+ ///
+ /// Returns true if the connection should be enabled.
+ ///
+ /// See: gridgraphrule-connection-filter (view in online documentation for working links) for example usage.
+ /// See:
+ ///
+ /// Index of the node for which the connection is being tested. This is the index in the data arrays for the graph update, not necessarily the index in the graph.
+ /// X coordinate of the node for which the connection is being tested, relative to the updated region.
+ /// Layer (Y) coordinate of the node for which the connection is being tested, relative to the updated region.
+ /// Z coordinate of the node for which the connection is being tested, relative to the updated region.
+ /// Direction to the neighbour. See \reflink{GridNode.HasConnectionInDirection}.
+ /// Index of the neighbour node. This is the index in the data arrays for the graph update, not necessarily the index in the graph.
+ bool IsValidConnection(int dataIndex, int dataX, int dataLayer, int dataZ, int direction, int neighbourDataIndex);
+ }
+
+ ///
+ /// Iterate through all enabled connections of all nodes.
+ ///
+ /// See: grid-rules-write (view in online documentation for working links) for example usage.
+ ///
+ /// Sub-rectangle of the grid graph that is being updated/scanned
+ /// Data with all node connections.
+ /// Should be true for layered grid graphs and false otherwise.
+ /// Your callback struct. The IsValidConnection method on the callback struct will be called for each connection. If false is returned, the connection will be disabled.
+ public static void FilterNodeConnections(IntBounds bounds, NativeArray nodeConnections, bool layeredDataLayout, ref T filter) where T : struct, IConnectionFilter {
+ var size = bounds.size;
+ Assert.IsTrue(nodeConnections.Length == size.x * size.y * size.z);
+ unsafe {
+ var neighbourOffsets = stackalloc int[8];
+ for (int i = 0; i < 8; i++) neighbourOffsets[i] = GridGraph.neighbourZOffsets[i] * size.x + GridGraph.neighbourXOffsets[i];
+ var layerStride = size.x * size.z;
+
+ int nodeIndex = 0;
+ for (int y = 0; y < size.y; y++) {
+ for (int z = 0; z < size.z; z++) {
+ for (int x = 0; x < size.x; x++, nodeIndex++) {
+ var conn = nodeConnections[nodeIndex];
+ if (layeredDataLayout) {
+ // Layered grid graph
+ for (int dir = 0; dir < 8; dir++) {
+ var connectionValue = (int)((conn >> LevelGridNode.ConnectionStride*dir) & LevelGridNode.ConnectionMask);
+ if (connectionValue != LevelGridNode.NoConnection && !filter.IsValidConnection(nodeIndex, x, y, z, dir, nodeIndex + neighbourOffsets[dir] + (connectionValue - y)*layerStride)) {
+ conn |= (ulong)LevelGridNode.NoConnection << LevelGridNode.ConnectionStride*dir;
+ }
+ }
+ } else {
+ // Normal grid graph
+ // Iterate through all connections on the node
+ for (int dir = 0; dir < 8; dir++) {
+ if (((int)conn & (1 << dir)) != 0 && !filter.IsValidConnection(nodeIndex, x, y, z, dir, nodeIndex + neighbourOffsets[dir])) {
+ conn &= ~(1UL << dir);
+ }
+ }
+ }
+ nodeConnections[nodeIndex] = conn;
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Returns the data index for a node's neighbour in the given direction.
+ ///
+ /// The bounds, nodeConnections and layeredDataLayout fields can be retrieved from the .data object.
+ ///
+ /// Returns: Null if the node has no connection in that direction. Otherwise the data index for that node is returned.
+ ///
+ /// See: gridgraphrule-connection-filter (view in online documentation for working links) for example usage.
+ ///
+ /// Sub-rectangle of the grid graph that is being updated/scanned
+ /// Data for all node connections
+ /// True if this is a layered grid graph
+ /// X coordinate in the data arrays for the node for which you want to get a neighbour
+ /// Layer (Y) coordinate in the data arrays for the node for which you want to get a neighbour
+ /// Z coordinate in the data arrays for the node for which you want to get a neighbour
+ /// Direction to the neighbour. See \reflink{GridNode.HasConnectionInDirection}.
+ public static int? GetNeighbourDataIndex (IntBounds bounds, NativeArray nodeConnections, bool layeredDataLayout, int dataX, int dataLayer, int dataZ, int direction) {
+ // Find the coordinates of the adjacent node
+ var dx = GridGraph.neighbourXOffsets[direction];
+ var dz = GridGraph.neighbourZOffsets[direction];
+
+ int nx = dataX + dx;
+ int nz = dataZ + dz;
+
+ // The data arrays are laid out row by row
+ const int xstride = 1;
+ var zstride = bounds.size.x;
+ var ystride = bounds.size.x * bounds.size.z;
+
+ var dataIndex = dataLayer * ystride + dataZ * zstride + dataX * xstride;
+ var neighbourDataIndex = nz * zstride + nx * xstride;
+
+ if (layeredDataLayout) {
+ // In a layered grid graph we need to account for nodes in different layers
+ var ny = (nodeConnections[dataIndex] >> LevelGridNode.ConnectionStride*direction) & LevelGridNode.ConnectionMask;
+ if (ny == LevelGridNode.NoConnection) return null;
+
+ // For valid nodeConnections arrays this is not necessary as out of bounds connections are not valid and it will thus be caught above in the 'has connection' check.
+ // But let's be safe in case users do something weird
+ if (nx < 0 || nz < 0 || nx >= bounds.size.x || nz >= bounds.size.z) throw new System.Exception("Node has an invalid connection to a node outside the bounds of the graph");
+
+ neighbourDataIndex += (int)ny * ystride;
+ } else
+ if ((nodeConnections[dataIndex] & (1UL << direction)) == 0) return null;
+
+ if (nx < 0 || nz < 0 || nx >= bounds.size.x || nz >= bounds.size.z) throw new System.Exception("Node has an invalid connection to a node outside the bounds of the graph");
+ return neighbourDataIndex;
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridIterationUtilities.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridIterationUtilities.cs.meta
new file mode 100644
index 0000000..ba19ff9
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/GridIterationUtilities.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0a7d565c3874ce349a83c260182a8b63
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs.meta
new file mode 100644
index 0000000..6cc1028
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ed5b7a8f175d0794db354a0757bb79ec
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobAllocateNodes.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobAllocateNodes.cs
new file mode 100644
index 0000000..d301d44
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobAllocateNodes.cs
@@ -0,0 +1,57 @@
+using Pathfinding.Util;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Mathematics;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ ///
+ /// Allocates and deallocates nodes in a grid graph.
+ ///
+ /// This will inspect every cell in the dataBounds and allocate or deallocate the node depending on if that slot should have a node or not according to the nodeNormals array (pure zeroes means no node).
+ ///
+ /// This is only used for incremental updates of grid graphs.
+ /// The initial layer of the grid graph (which is always filled with nodes) is allocated in the method.
+ ///
+ public struct JobAllocateNodes : IJob {
+ public AstarPath active;
+ [ReadOnly]
+ public NativeArray nodeNormals;
+ public IntBounds dataBounds;
+ public int3 nodeArrayBounds;
+ public GridNodeBase[] nodes;
+ public System.Func newGridNodeDelegate;
+
+ public void Execute () {
+ var size = dataBounds.size;
+
+ // Start at y=1 because all nodes at y=0 are guaranteed to already be allocated (they are always allocated in a layered grid graph).
+ var nodeNormalsSpan = nodeNormals.AsUnsafeReadOnlySpan();
+ for (int y = 1; y < size.y; y++) {
+ for (int z = 0; z < size.z; z++) {
+ var rowOffset = ((y + dataBounds.min.y) * nodeArrayBounds.z + (z + dataBounds.min.z)) * nodeArrayBounds.x + dataBounds.min.x;
+ for (int x = 0; x < size.x; x++) {
+ var nodeIndex = rowOffset + x;
+ var shouldHaveNode = math.any(nodeNormalsSpan[nodeIndex]);
+ var node = nodes[nodeIndex];
+ var hasNode = node != null;
+ if (shouldHaveNode != hasNode) {
+ if (shouldHaveNode) {
+ node = nodes[nodeIndex] = newGridNodeDelegate();
+ active.InitializeNode(node);
+ } else {
+ // Clear custom connections first and clear connections from other nodes to this one
+ node.ClearCustomConnections(true);
+ // Clear grid connections without clearing the connections from other nodes to this one (a bit slow)
+ // Since this is inside a graph update we guarantee that the grid connections will be correct at the end
+ // of the update anyway
+ node.ResetConnectionsInternal();
+ node.Destroy();
+ nodes[nodeIndex] = null;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobAllocateNodes.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobAllocateNodes.cs.meta
new file mode 100644
index 0000000..a2391b4
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobAllocateNodes.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 773a80d74b04a904faae186478d396c4
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCalculateGridConnections.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCalculateGridConnections.cs
new file mode 100644
index 0000000..0a14596
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCalculateGridConnections.cs
@@ -0,0 +1,219 @@
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Collections.LowLevel.Unsafe;
+using Unity.Jobs;
+using Unity.Mathematics;
+using Pathfinding.Jobs;
+using Pathfinding.Util;
+using System.Data;
+using UnityEngine.Assertions;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ ///
+ /// Calculates the grid connections for all nodes.
+ ///
+ /// This is a IJobParallelForBatch job. Calculating the connections in multiple threads is faster,
+ /// but due to hyperthreading (used on most intel processors) the individual threads will become slower.
+ /// It is still worth it though.
+ ///
+ [BurstCompile(FloatMode = FloatMode.Fast, CompileSynchronously = true)]
+ public struct JobCalculateGridConnections : IJobParallelForBatched {
+ public float maxStepHeight;
+ /// Normalized up direction
+ public Vector3 up;
+ public IntBounds bounds;
+ public int3 arrayBounds;
+ public NumNeighbours neighbours;
+ public bool use2D;
+ public bool cutCorners;
+ public bool maxStepUsesSlope;
+ public float characterHeight;
+ public bool layeredDataLayout;
+
+ [ReadOnly]
+ public UnsafeSpan nodeWalkable;
+
+ [ReadOnly]
+ public UnsafeSpan nodeNormals;
+
+ [ReadOnly]
+ public UnsafeSpan nodePositions;
+
+ /// All bitpacked node connections
+ [WriteOnly]
+ public UnsafeSpan nodeConnections;
+
+ public bool allowBoundsChecks => false;
+
+
+ ///
+ /// Check if a connection to node B is valid.
+ /// Node A is assumed to be walkable already
+ ///
+ public static bool IsValidConnection (float4 nodePosA, float4 nodeNormalA, bool nodeWalkableB, float4 nodePosB, float4 nodeNormalB, bool maxStepUsesSlope, float maxStepHeight, float4 up) {
+ if (!nodeWalkableB) return false;
+
+ if (!maxStepUsesSlope) {
+ // Check their differences along the Y coordinate (well, the up direction really. It is not necessarily the Y axis).
+ return math.abs(math.dot(up, nodePosB - nodePosA)) <= maxStepHeight;
+ } else {
+ float4 v = nodePosB - nodePosA;
+ float heightDifference = math.dot(v, up);
+
+ // Check if the step is small enough.
+ // This is a fast path for the common case.
+ if (math.abs(heightDifference) <= maxStepHeight) return true;
+
+ float4 v_flat = (v - heightDifference * up) * 0.5f;
+
+ // Math!
+ // Calculates the approximate offset along the up direction
+ // that the ground will have moved at the midpoint between the
+ // nodes compared to the nodes' center points.
+ float NDotU = math.dot(nodeNormalA, up);
+ float offsetA = -math.dot(nodeNormalA - NDotU * up, v_flat);
+
+ NDotU = math.dot(nodeNormalB, up);
+ float offsetB = math.dot(nodeNormalB - NDotU * up, v_flat);
+
+ // Check the height difference with slopes taken into account.
+ // Note that since we also do the heightDifference check above we will ensure slope offsets do not increase the height difference.
+ // If we allowed this then some connections might not be valid near the start of steep slopes.
+ return math.abs(heightDifference + offsetB - offsetA) <= maxStepHeight;
+ }
+ }
+
+ public void Execute (int start, int count) {
+ if (layeredDataLayout) ExecuteLayered(start, count);
+ else ExecuteFlat(start, count);
+ }
+
+ public void ExecuteFlat (int start, int count) {
+ if (maxStepHeight <= 0 || use2D) maxStepHeight = float.PositiveInfinity;
+
+ float4 up = new float4(this.up.x, this.up.y, this.up.z, 0);
+
+ NativeArray neighbourOffsets = new NativeArray(8, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
+ for (int i = 0; i < 8; i++) neighbourOffsets[i] = GridGraph.neighbourZOffsets[i] * arrayBounds.x + GridGraph.neighbourXOffsets[i];
+ var nodePositions = this.nodePositions.Reinterpret();
+
+ // The loop is parallelized over z coordinates
+ start += bounds.min.z;
+ for (int z = start; z < start + count; z++) {
+ var initialConnections = 0xFF;
+
+ // Disable connections to out-of-bounds nodes
+ // See GridNode.HasConnectionInDirection
+ if (z == 0) initialConnections &= ~((1 << 0) | (1 << 7) | (1 << 4));
+ if (z == arrayBounds.z - 1) initialConnections &= ~((1 << 2) | (1 << 5) | (1 << 6));
+
+ for (int x = bounds.min.x; x < bounds.max.x; x++) {
+ int nodeIndex = z * arrayBounds.x + x;
+ if (!nodeWalkable[nodeIndex]) {
+ nodeConnections[nodeIndex] = 0;
+ continue;
+ }
+
+ // Bitpacked connections
+ // bit 0 is set if connection 0 is enabled
+ // bit 1 is set if connection 1 is enabled etc.
+ int conns = initialConnections;
+
+ // Disable connections to out-of-bounds nodes
+ if (x == 0) conns &= ~((1 << 3) | (1 << 6) | (1 << 7));
+ if (x == arrayBounds.x - 1) conns &= ~((1 << 1) | (1 << 4) | (1 << 5));
+
+ float4 pos = new float4(nodePositions[nodeIndex], 0);
+ float4 normal = nodeNormals[nodeIndex];
+
+ for (int i = 0; i < 8; i++) {
+ int neighbourIndex = nodeIndex + neighbourOffsets[i];
+ if ((conns & (1 << i)) != 0 && !IsValidConnection(pos, normal, nodeWalkable[neighbourIndex], new float4(nodePositions[neighbourIndex], 0), nodeNormals[neighbourIndex], maxStepUsesSlope, maxStepHeight, up)) {
+ // Enable connection i
+ conns &= ~(1 << i);
+ }
+ }
+
+ nodeConnections[nodeIndex] = (ulong)GridNode.FilterDiagonalConnections(conns, neighbours, cutCorners);
+ }
+ }
+ }
+
+ public void ExecuteLayered (int start, int count) {
+ if (maxStepHeight <= 0 || use2D) maxStepHeight = float.PositiveInfinity;
+
+ float4 up = new float4(this.up.x, this.up.y, this.up.z, 0);
+
+ NativeArray neighbourOffsets = new NativeArray(8, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
+ for (int i = 0; i < 8; i++) neighbourOffsets[i] = GridGraph.neighbourZOffsets[i] * arrayBounds.x + GridGraph.neighbourXOffsets[i];
+
+ var layerStride = arrayBounds.z*arrayBounds.x;
+ start += bounds.min.z;
+ for (int y = bounds.min.y; y < bounds.max.y; y++) {
+ // The loop is parallelized over z coordinates
+ for (int z = start; z < start + count; z++) {
+ for (int x = bounds.min.x; x < bounds.max.x; x++) {
+ // Bitpacked connections
+ ulong conns = 0;
+ int nodeIndexXZ = z * arrayBounds.x + x;
+ int nodeIndex = nodeIndexXZ + y * layerStride;
+ float4 pos = new float4(nodePositions[nodeIndex], 0);
+ float4 normal = nodeNormals[nodeIndex];
+
+ if (nodeWalkable[nodeIndex]) {
+ var ourY = math.dot(up, pos);
+
+ float ourHeight;
+ if (y == arrayBounds.y-1 || !math.any(nodeNormals[nodeIndex + layerStride])) {
+ ourHeight = float.PositiveInfinity;
+ } else {
+ var nodeAboveNeighbourPos = new float4(nodePositions[nodeIndex + layerStride], 0);
+ ourHeight = math.max(0, math.dot(up, nodeAboveNeighbourPos) - ourY);
+ }
+
+ for (int i = 0; i < 8; i++) {
+ int nx = x + GridGraph.neighbourXOffsets[i];
+ int nz = z + GridGraph.neighbourZOffsets[i];
+
+ // Check if the new position is inside the grid
+ int conn = LevelGridNode.NoConnection;
+ if (nx >= 0 && nz >= 0 && nx < arrayBounds.x && nz < arrayBounds.z) {
+ int neighbourStartIndex = nodeIndexXZ + neighbourOffsets[i];
+ for (int y2 = 0; y2 < arrayBounds.y; y2++) {
+ var neighbourIndex = neighbourStartIndex + y2 * layerStride;
+ float4 nodePosB = new float4(nodePositions[neighbourIndex], 0);
+ var neighbourY = math.dot(up, nodePosB);
+ // Is there a node above this one
+ float neighbourHeight;
+ if (y2 == arrayBounds.y-1 || !math.any(nodeNormals[neighbourIndex + layerStride])) {
+ neighbourHeight = float.PositiveInfinity;
+ } else {
+ var nodeAboveNeighbourPos = new float4(nodePositions[neighbourIndex + layerStride], 0);
+ neighbourHeight = math.max(0, math.dot(up, nodeAboveNeighbourPos) - neighbourY);
+ }
+
+ float bottom = math.max(neighbourY, ourY);
+ float top = math.min(neighbourY + neighbourHeight, ourY + ourHeight);
+
+ float dist = top-bottom;
+
+ if (dist >= characterHeight && IsValidConnection(pos, normal, nodeWalkable[neighbourIndex], new float4(nodePositions[neighbourIndex], 0), nodeNormals[neighbourIndex], maxStepUsesSlope, maxStepHeight, up)) {
+ conn = y2;
+ }
+ }
+ }
+
+ conns |= (ulong)conn << LevelGridNode.ConnectionStride*i;
+ }
+ } else {
+ conns = LevelGridNode.AllConnectionsMask;
+ }
+
+ nodeConnections[nodeIndex] = conns;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCalculateGridConnections.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCalculateGridConnections.cs.meta
new file mode 100644
index 0000000..089d203
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCalculateGridConnections.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 63dd791a75e95424ea05940b4c155c25
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCheckCollisions.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCheckCollisions.cs
new file mode 100644
index 0000000..71a3d2c
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCheckCollisions.cs
@@ -0,0 +1,33 @@
+using UnityEngine;
+using Unity.Collections;
+using Pathfinding.Jobs;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ ///
+ /// Checks if nodes are obstructed by obstacles or not.
+ ///
+ /// See:
+ ///
+ struct JobCheckCollisions : IJobTimeSliced {
+ [ReadOnly]
+ public NativeArray nodePositions;
+ public NativeArray collisionResult;
+ public GraphCollision collision;
+ int startIndex;
+
+ public void Execute () {
+ Execute(TimeSlice.Infinite);
+ }
+
+ public bool Execute (TimeSlice timeSlice) {
+ for (int i = startIndex; i < nodePositions.Length; i++) {
+ collisionResult[i] = collisionResult[i] && collision.Check(nodePositions[i]);
+ if ((i & 127) == 0 && timeSlice.expired) {
+ startIndex = i + 1;
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCheckCollisions.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCheckCollisions.cs.meta
new file mode 100644
index 0000000..9311878
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCheckCollisions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6977ffeeb891185449eae9f827667558
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobColliderHitsToBooleans.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobColliderHitsToBooleans.cs
new file mode 100644
index 0000000..84ae59e
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobColliderHitsToBooleans.cs
@@ -0,0 +1,28 @@
+#if UNITY_2022_2_OR_NEWER
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ ///
+ /// Fills the output with true or false depending on if the collider hit was a hit.
+ ///
+ /// result[i] = false if hits[i] is a valid hit, otherwise true.
+ ///
+ [BurstCompile]
+ public struct JobColliderHitsToBooleans : IJob {
+ [ReadOnly]
+ public NativeArray hits;
+
+ [WriteOnly]
+ public NativeArray result;
+
+ public void Execute () {
+ for (int i = 0; i < hits.Length; i++) {
+ result[i] = hits[i].instanceID == 0;
+ }
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobColliderHitsToBooleans.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobColliderHitsToBooleans.cs.meta
new file mode 100644
index 0000000..39e8bb7
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobColliderHitsToBooleans.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0e741e2fc3248564998e707ba25fe69c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCopyBuffers.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCopyBuffers.cs
new file mode 100644
index 0000000..690448f
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCopyBuffers.cs
@@ -0,0 +1,50 @@
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Mathematics;
+using Pathfinding.Jobs;
+using UnityEngine.Assertions;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ ///
+ /// Copies 3D arrays with grid data from one shape to another.
+ ///
+ /// Only the data for the nodes that exist in both buffers will be copied.
+ ///
+ /// This essentially is several jobs in one (to avoid scheduling overhead).
+ /// See that job for more documentation.
+ ///
+ [BurstCompile]
+ public struct JobCopyBuffers : IJob {
+ [ReadOnly]
+ [DisableUninitializedReadCheck]
+ public GridGraphNodeData input;
+
+ [WriteOnly]
+ public GridGraphNodeData output;
+ public IntBounds bounds;
+
+ public bool copyPenaltyAndTags;
+
+ public void Execute () {
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ if (!input.bounds.Contains(bounds)) throw new System.ArgumentException("Bounds are outside the source buffer");
+ if (!output.bounds.Contains(bounds)) throw new System.ArgumentException("Bounds are outside the destination buffer");
+#endif
+ var inputSlice = new Slice3D(input.bounds, bounds);
+ var outputSlice = new Slice3D(output.bounds, bounds);
+ // Note: Having a single job that copies all of the buffers avoids a lot of scheduling overhead.
+ // We do miss out on parallelization, however for this job it is not that significant.
+ JobCopyRectangle.Copy(input.positions, output.positions, inputSlice, outputSlice);
+ JobCopyRectangle.Copy(input.normals, output.normals, inputSlice, outputSlice);
+ JobCopyRectangle.Copy(input.connections, output.connections, inputSlice, outputSlice);
+ if (copyPenaltyAndTags) {
+ JobCopyRectangle.Copy(input.penalties, output.penalties, inputSlice, outputSlice);
+ JobCopyRectangle.Copy(input.tags, output.tags, inputSlice, outputSlice);
+ }
+ JobCopyRectangle.Copy(input.walkable, output.walkable, inputSlice, outputSlice);
+ JobCopyRectangle.Copy(input.walkableWithErosion, output.walkableWithErosion, inputSlice, outputSlice);
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCopyBuffers.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCopyBuffers.cs.meta
new file mode 100644
index 0000000..9c60956
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobCopyBuffers.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a3c4dcf30e4497e44a9c7421803ec902
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobErosion.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobErosion.cs
new file mode 100644
index 0000000..5b5ba35
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobErosion.cs
@@ -0,0 +1,190 @@
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Mathematics;
+using Pathfinding.Jobs;
+using UnityEngine.Assertions;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ ///
+ /// Calculates erosion.
+ /// Note that to ensure that connections are completely up to date after updating a node you
+ /// have to calculate the connections for both the changed node and its neighbours.
+ ///
+ /// In a layered grid graph, this will recalculate the connections for all nodes
+ /// in the (x,z) cell (it may have multiple layers of nodes).
+ ///
+ /// See: CalculateConnections(GridNodeBase)
+ ///
+ [BurstCompile]
+ public struct JobErosion : IJob where AdjacencyMapper : GridAdjacencyMapper, new() {
+ public IntBounds bounds;
+ public IntBounds writeMask;
+ public NumNeighbours neighbours;
+ public int erosion;
+ public bool erosionUsesTags;
+ public int erosionStartTag;
+
+ [ReadOnly]
+ public NativeArray nodeConnections;
+
+ [ReadOnly]
+ public NativeArray nodeWalkable;
+
+ [WriteOnly]
+ public NativeArray outNodeWalkable;
+
+ public NativeArray nodeTags;
+ public int erosionTagsPrecedenceMask;
+
+ // Note: the first 3 connections are to nodes with a higher x or z coordinate
+ // The last 3 connections are to nodes with a lower x or z coordinate
+ // This is required for the grassfire transform to work properly
+ // This is a permutation of GridGraph.hexagonNeighbourIndices
+ static readonly int[] hexagonNeighbourIndices = { 1, 2, 5, 0, 3, 7 };
+
+ public void Execute () {
+ var slice = new Slice3D(bounds, bounds);
+ var size = slice.slice.size;
+ slice.AssertMatchesOuter(nodeConnections);
+ slice.AssertMatchesOuter(nodeWalkable);
+ slice.AssertMatchesOuter(outNodeWalkable);
+ slice.AssertMatchesOuter(nodeTags);
+ Assert.IsTrue(bounds.Contains(writeMask));
+
+ var(outerStrideX, outerStrideY, outerStrideZ) = slice.outerStrides;
+ var(innerStrideX, innerStrideY, innerStrideZ) = slice.innerStrides;
+ NativeArray neighbourOffsets = new NativeArray(8, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
+ for (int i = 0; i < 8; i++) neighbourOffsets[i] = GridGraph.neighbourZOffsets[i] * innerStrideZ + GridGraph.neighbourXOffsets[i] * innerStrideX;
+
+ var erosionDistances = new NativeArray(slice.length, Allocator.Temp, NativeArrayOptions.ClearMemory);
+ var adjacencyMapper = new AdjacencyMapper();
+ var layers = adjacencyMapper.LayerCount(slice.slice);
+ var outerOffset = slice.outerStartIndex;
+ if (neighbours == NumNeighbours.Six) {
+ // Use the grassfire transform: https://en.wikipedia.org/wiki/Grassfire_transform extended to hexagonal graphs
+ for (int z = 1; z < size.z - 1; z++) {
+ for (int x = 1; x < size.x - 1; x++) {
+ for (int y = 0; y < layers; y++) {
+ // Note: This is significantly faster than using csum, because burst can optimize it better
+ int outerIndex = z * outerStrideZ + x * outerStrideX + y * outerStrideY + outerOffset;
+ var innerIndexXZ = z * innerStrideZ + x * innerStrideX;
+ int innerIndex = innerIndexXZ + y * innerStrideY;
+ int v = int.MaxValue;
+ for (int i = 3; i < 6; i++) {
+ int connection = hexagonNeighbourIndices[i];
+ if (!adjacencyMapper.HasConnection(outerIndex, connection, nodeConnections)) v = -1;
+ else v = math.min(v, erosionDistances[adjacencyMapper.GetNeighbourIndex(innerIndexXZ, innerIndex, connection, nodeConnections, neighbourOffsets, innerStrideY)]);
+ }
+
+ erosionDistances[innerIndex] = v + 1;
+ }
+ }
+ }
+
+ for (int z = size.z - 2; z > 0; z--) {
+ for (int x = size.x - 2; x > 0; x--) {
+ for (int y = 0; y < layers; y++) {
+ int outerIndex = z * outerStrideZ + x * outerStrideX + y * outerStrideY + outerOffset;
+ var innerIndexXZ = z * innerStrideZ + x * innerStrideX;
+ int innerIndex = innerIndexXZ + y * innerStrideY;
+ int v = int.MaxValue;
+ for (int i = 3; i < 6; i++) {
+ int connection = hexagonNeighbourIndices[i];
+ if (!adjacencyMapper.HasConnection(outerIndex, connection, nodeConnections)) v = -1;
+ else v = math.min(v, erosionDistances[adjacencyMapper.GetNeighbourIndex(innerIndexXZ, innerIndex, connection, nodeConnections, neighbourOffsets, innerStrideY)]);
+ }
+
+ erosionDistances[innerIndex] = math.min(erosionDistances[innerIndex], v + 1);
+ }
+ }
+ }
+ } else {
+ /* Index offset to get neighbour nodes. Added to a node's index to get a neighbour node index.
+ *
+ * \code
+ * Z
+ * |
+ * |
+ *
+ * 6 2 5
+ * \ | /
+ * -- 3 - X - 1 ----- X
+ * / | \
+ * 7 0 4
+ *
+ * |
+ * |
+ * \endcode
+ */
+ const int DirectionDown = 0;
+ const int DirectionRight = 1;
+ const int DirectionUp = 2;
+ const int DirectionLeft = 3;
+
+ // Use the grassfire transform: https://en.wikipedia.org/wiki/Grassfire_transform
+ for (int z = 1; z < size.z - 1; z++) {
+ for (int x = 1; x < size.x - 1; x++) {
+ for (int y = 0; y < layers; y++) {
+ int outerIndex = z * outerStrideZ + x * outerStrideX + y * outerStrideY + outerOffset;
+ var innerIndexXZ = z * innerStrideZ + x * innerStrideX;
+ int innerIndex = innerIndexXZ + y * innerStrideY;
+ var v1 = -1;
+ if (adjacencyMapper.HasConnection(outerIndex, DirectionDown, nodeConnections)) v1 = erosionDistances[adjacencyMapper.GetNeighbourIndex(innerIndexXZ, innerIndex, DirectionDown, nodeConnections, neighbourOffsets, innerStrideY)];
+ var v2 = -1;
+ if (adjacencyMapper.HasConnection(outerIndex, DirectionLeft, nodeConnections)) v2 = erosionDistances[adjacencyMapper.GetNeighbourIndex(innerIndexXZ, innerIndex, DirectionLeft, nodeConnections, neighbourOffsets, innerStrideY)];
+
+ erosionDistances[innerIndex] = math.min(v1, v2) + 1;
+ }
+ }
+ }
+
+ for (int z = size.z - 2; z > 0; z--) {
+ for (int x = size.x - 2; x > 0; x--) {
+ for (int y = 0; y < layers; y++) {
+ int outerIndex = z * outerStrideZ + x * outerStrideX + y * outerStrideY + outerOffset;
+ var innerIndexXZ = z * innerStrideZ + x * innerStrideX;
+ int innerIndex = innerIndexXZ + y * innerStrideY;
+ var v1 = -1;
+ if (adjacencyMapper.HasConnection(outerIndex, DirectionUp, nodeConnections)) v1 = erosionDistances[adjacencyMapper.GetNeighbourIndex(innerIndexXZ, innerIndex, DirectionUp, nodeConnections, neighbourOffsets, innerStrideY)];
+ var v2 = -1;
+ if (adjacencyMapper.HasConnection(outerIndex, DirectionRight, nodeConnections)) v2 = erosionDistances[adjacencyMapper.GetNeighbourIndex(innerIndexXZ, innerIndex, DirectionRight, nodeConnections, neighbourOffsets, innerStrideY)];
+
+ erosionDistances[innerIndex] = math.min(erosionDistances[outerIndex], math.min(v1, v2) + 1);
+ }
+ }
+ }
+ }
+
+ var relativeWriteMask = writeMask.Offset(-bounds.min);
+
+ // Erosion tags are allowed to overwrite the ones the user specifies, as well as the ones that are already reserved for erosion.
+ for (int i = erosionStartTag; i < erosionStartTag + erosion; i++) erosionTagsPrecedenceMask |= 1 << i;
+
+ for (int y = relativeWriteMask.min.y; y < relativeWriteMask.max.y; y++) {
+ for (int z = relativeWriteMask.min.z; z < relativeWriteMask.max.z; z++) {
+ for (int x = relativeWriteMask.min.x; x < relativeWriteMask.max.x; x++) {
+ int outerIndex = x * outerStrideX + y * outerStrideY + z * outerStrideZ + outerOffset;
+ int innerIndex = x * innerStrideX + y * innerStrideY + z * innerStrideZ;
+ if (erosionUsesTags) {
+ var prevTag = nodeTags[outerIndex];
+ outNodeWalkable[outerIndex] = nodeWalkable[outerIndex];
+
+ if (erosionDistances[innerIndex] < erosion) {
+ if (((erosionTagsPrecedenceMask >> prevTag) & 0x1) != 0) {
+ nodeTags[outerIndex] = nodeWalkable[outerIndex] ? math.min(GraphNode.MaxTagIndex, erosionDistances[innerIndex] + erosionStartTag) : 0;
+ }
+ } else if (prevTag >= erosionStartTag && prevTag < erosionStartTag + erosion) {
+ // If the node already had a tag that was reserved for erosion, but it shouldn't have that tag, then we remove it.
+ nodeTags[outerIndex] = 0;
+ }
+ } else {
+ outNodeWalkable[outerIndex] = nodeWalkable[outerIndex] & (erosionDistances[innerIndex] >= erosion);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobErosion.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobErosion.cs.meta
new file mode 100644
index 0000000..09484b2
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobErosion.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 12ba2e44c6c183644a4ad6186f4ab21e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobFilterDiagonalConnections.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobFilterDiagonalConnections.cs
new file mode 100644
index 0000000..6b40a5f
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobFilterDiagonalConnections.cs
@@ -0,0 +1,128 @@
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Mathematics;
+using Pathfinding.Jobs;
+using Pathfinding.Util;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ ///
+ /// Filters out diagonal connections that are not allowed in layered grid graphs.
+ ///
+ /// This is a IJobParallelForBatched job which is parallelelized over the z coordinate of the .
+ ///
+ /// The job will run first, and calculate the connections for all nodes.
+ /// However, for layered grid graphs, the connections for diagonal nodes may be incorrect, and this
+ /// post-processing pass is needed to validate the diagonal connections.
+ ///
+ [BurstCompile]
+ public struct JobFilterDiagonalConnections : IJobParallelForBatched {
+ public Slice3D slice;
+ public NumNeighbours neighbours;
+ public bool cutCorners;
+
+ /// All bitpacked node connections
+ public UnsafeSpan nodeConnections;
+
+ public bool allowBoundsChecks => false;
+
+ public void Execute (int start, int count) {
+ slice.AssertMatchesOuter(nodeConnections);
+
+ // For single layer graphs this will have already been done in the JobCalculateGridConnections job
+ // but for layered grid graphs we need to handle things differently because the data layout is different
+
+ int3 size = slice.outerSize;
+ NativeArray neighbourOffsets = new NativeArray(8, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
+
+ for (int i = 0; i < 8; i++) neighbourOffsets[i] = GridGraph.neighbourZOffsets[i] * size.x + GridGraph.neighbourXOffsets[i];
+
+ ulong hexagonConnectionMask = 0;
+ for (int i = 0; i < GridGraph.hexagonNeighbourIndices.Length; i++) hexagonConnectionMask |= (ulong)LevelGridNode.ConnectionMask << (LevelGridNode.ConnectionStride*GridGraph.hexagonNeighbourIndices[i]);
+
+ int adjacencyThreshold = cutCorners ? 1 : 2;
+ int layerStride = size.x * size.z;
+ start += slice.slice.min.z;
+ for (int y = slice.slice.min.y; y < slice.slice.max.y; y++) {
+ // The loop is parallelized over z coordinates
+ for (int z = start; z < start + count; z++) {
+ for (int x = slice.slice.min.x; x < slice.slice.max.x; x++) {
+ int nodeIndexXZ = z * size.x + x;
+ int nodeIndex = nodeIndexXZ + y * layerStride;
+
+ switch (neighbours) {
+ case NumNeighbours.Four:
+ // Mask out all the diagonal connections
+ nodeConnections[nodeIndex] = nodeConnections[nodeIndex] | LevelGridNode.DiagonalConnectionsMask;
+ break;
+ case NumNeighbours.Eight:
+ var conns = nodeConnections[nodeIndex];
+
+ // Skip node if no connections are enabled already
+ if (conns == LevelGridNode.AllConnectionsMask) continue;
+
+ // When cutCorners is enabled then the diagonal connection is allowed
+ // if at least one axis aligned connection is adjacent to this diagonal.
+ // Otherwise both axis aligned connections must be present.
+ //
+ // X ----- axis2
+ // | \
+ // | \
+ // | \
+ // axis1 diagonal
+ //
+ // Z
+ // |
+ // |
+ //
+ // 6 2 5
+ // \ | /
+ // -- 3 - X - 1 ----- X
+ // / | \
+ // 7 0 4
+ //
+ // |
+ // |
+ //
+ for (int dir = 0; dir < 4; dir++) {
+ int adjacent = 0;
+ var axis1 = (conns >> dir*LevelGridNode.ConnectionStride) & LevelGridNode.ConnectionMask;
+ var axis2 = (conns >> ((dir+1) % 4)*LevelGridNode.ConnectionStride) & LevelGridNode.ConnectionMask;
+ var diagonal = (conns >> (dir+4)*LevelGridNode.ConnectionStride) & LevelGridNode.ConnectionMask;
+
+ // Check if the diagonal connection is present at all.
+ // The JobCalculateGridConnections calculated this.
+ if (diagonal == LevelGridNode.NoConnection) continue;
+
+ if (axis1 != LevelGridNode.NoConnection) {
+ // We also check that the neighbour node is also connected to the diagonal node
+ var neighbourDir = (dir + 1) % 4;
+ var neighbourIndex = nodeIndexXZ + neighbourOffsets[dir] + (int)axis1 * layerStride;
+ if (((nodeConnections[neighbourIndex] >> neighbourDir*LevelGridNode.ConnectionStride) & LevelGridNode.ConnectionMask) == diagonal) {
+ adjacent++;
+ }
+ }
+ if (axis2 != LevelGridNode.NoConnection) {
+ var neighbourDir = dir;
+ var neighbourIndex = nodeIndexXZ + neighbourOffsets[(dir+1)%4] + (int)axis2 * layerStride;
+ if (((nodeConnections[neighbourIndex] >> neighbourDir*LevelGridNode.ConnectionStride) & LevelGridNode.ConnectionMask) == diagonal) {
+ adjacent++;
+ }
+ }
+
+ if (adjacent < adjacencyThreshold) conns |= (ulong)LevelGridNode.NoConnection << (dir + 4)*LevelGridNode.ConnectionStride;
+ }
+ nodeConnections[nodeIndex] = conns;
+ break;
+ case NumNeighbours.Six:
+ // Hexagon layout
+ // Note that for layered nodes NoConnection is all bits set (see LevelGridNode.NoConnection)
+ // So in contrast to the non-layered grid graph we do a bitwise OR here
+ nodeConnections[nodeIndex] = (nodeConnections[nodeIndex] | ~hexagonConnectionMask) & LevelGridNode.AllConnectionsMask;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobFilterDiagonalConnections.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobFilterDiagonalConnections.cs.meta
new file mode 100644
index 0000000..a3d3d9b
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobFilterDiagonalConnections.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e737565f0f6416f4ab1470d4f194ed25
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobMergeRaycastCollisionHits.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobMergeRaycastCollisionHits.cs
new file mode 100644
index 0000000..61707dd
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobMergeRaycastCollisionHits.cs
@@ -0,0 +1,31 @@
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ ///
+ /// Calculates if either of the two input hits actually hit something.
+ ///
+ /// result[i] = true if hit1[i] or hit2[i] is a valid hit.
+ ///
+ /// A valid hit will always have a non-zero normal.
+ ///
+ [BurstCompile]
+ public struct JobMergeRaycastCollisionHits : IJob {
+ [ReadOnly]
+ public NativeArray hit1;
+
+ [ReadOnly]
+ public NativeArray hit2;
+
+ [WriteOnly]
+ public NativeArray result;
+
+ public void Execute () {
+ for (int i = 0; i < hit1.Length; i++) {
+ result[i] = hit1[i].normal == Vector3.zero && hit2[i].normal == Vector3.zero;
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobMergeRaycastCollisionHits.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobMergeRaycastCollisionHits.cs.meta
new file mode 100644
index 0000000..73957ca
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobMergeRaycastCollisionHits.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: be7b697c92431a74a8db8ae716c3f0b6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeGridLayout.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeGridLayout.cs
new file mode 100644
index 0000000..e9bdaf3
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeGridLayout.cs
@@ -0,0 +1,36 @@
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Mathematics;
+using Pathfinding.Jobs;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ ///
+ /// Calculates the default node positions for a grid graph.
+ ///
+ /// The node positions will lie on the base plane of the grid graph.
+ ///
+ /// See:
+ ///
+ [BurstCompile(FloatMode = FloatMode.Fast)]
+ public struct JobNodeGridLayout : IJob, GridIterationUtilities.ICellAction {
+ public Matrix4x4 graphToWorld;
+ public IntBounds bounds;
+
+ [WriteOnly]
+ public NativeArray nodePositions;
+
+ public static Vector3 NodePosition (Matrix4x4 graphToWorld, int x, int z) {
+ return graphToWorld.MultiplyPoint3x4(new Vector3(x + 0.5f, 0, z + 0.5f));
+ }
+
+ public void Execute () {
+ GridIterationUtilities.ForEachCellIn3DArray(bounds.size, ref this);
+ }
+
+ public void Execute (uint innerIndex, int x, int y, int z) {
+ nodePositions[(int)innerIndex] = NodePosition(graphToWorld, x + bounds.min.x, z + bounds.min.z);
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeGridLayout.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeGridLayout.cs.meta
new file mode 100644
index 0000000..4a82431
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeGridLayout.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7dba760f9d6d81e458c5122c0d28b2df
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeWalkability.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeWalkability.cs
new file mode 100644
index 0000000..7629df7
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeWalkability.cs
@@ -0,0 +1,73 @@
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Mathematics;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ /// Calculates for each grid node if it should be walkable or not
+ [BurstCompile(FloatMode = FloatMode.Fast)]
+ public struct JobNodeWalkability : IJob {
+ ///
+ /// If true, use the normal of the raycast hit to check if the ground is flat enough to stand on.
+ ///
+ /// Any nodes with a steeper slope than will be made unwalkable.
+ ///
+ public bool useRaycastNormal;
+ /// Max slope in degrees
+ public float maxSlope;
+ /// Normalized up direction of the graph
+ public Vector3 up;
+ /// If true, nodes will be made unwalkable if no ground was found under them
+ public bool unwalkableWhenNoGround;
+ /// For layered grid graphs, if there's a node above another node closer than this distance, the lower node will be made unwalkable
+ public float characterHeight;
+ /// Number of nodes in each layer
+ public int layerStride;
+
+ [ReadOnly]
+ public NativeArray nodePositions;
+
+ public NativeArray nodeNormals;
+
+ [WriteOnly]
+ public NativeArray nodeWalkable;
+
+ public void Execute () {
+ // Cosinus of the max slope
+ float cosMaxSlopeAngle = math.cos(math.radians(maxSlope));
+ float4 upNative = new float4(up.x, up.y, up.z, 0);
+ float3 upNative3 = upNative.xyz;
+
+ for (int i = 0; i < nodeNormals.Length; i++) {
+ // walkable will be set to false if no ground was found (unless that setting has been disabled)
+ // The normal will only be non-zero if something was hit.
+ bool didHit = math.any(nodeNormals[i]);
+ var walkable = didHit;
+ if (!didHit && !unwalkableWhenNoGround && i < layerStride) {
+ walkable = true;
+ // If there was no hit, but we still want to make the node walkable, then we set the normal to the up direction
+ nodeNormals[i] = upNative;
+ }
+
+ // Check if the node is on a slope steeper than permitted
+ if (walkable && useRaycastNormal && didHit) {
+ // Take the dot product to find out the cosine of the angle it has (faster than Vector3.Angle)
+ float angle = math.dot(nodeNormals[i], upNative);
+
+ // Check if the ground is flat enough to stand on
+ if (angle < cosMaxSlopeAngle) {
+ walkable = false;
+ }
+ }
+
+ // Check if there is a node above this one (layered grid graph only)
+ if (walkable && i + layerStride < nodeNormals.Length && math.any(nodeNormals[i + layerStride])) {
+ walkable = math.dot(upNative3, nodePositions[i + layerStride] - nodePositions[i]) >= characterHeight;
+ }
+
+ nodeWalkable[i] = walkable;
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeWalkability.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeWalkability.cs.meta
new file mode 100644
index 0000000..2a3d418
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobNodeWalkability.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a4ace8275be3d2f4b990df3672acb302
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareCapsuleCommands.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareCapsuleCommands.cs
new file mode 100644
index 0000000..4644787
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareCapsuleCommands.cs
@@ -0,0 +1,45 @@
+#if UNITY_2022_2_OR_NEWER
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Collections.LowLevel.Unsafe;
+using Pathfinding.Util;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ ///
+ /// Prepares a set of capsule commands for collision checking in a grid graph.
+ ///
+ /// See:
+ ///
+ [BurstCompile]
+ public struct JobPrepareCapsuleCommands : IJob {
+ public Vector3 direction;
+ public Vector3 originOffset;
+ public float radius;
+ public LayerMask mask;
+ public PhysicsScene physicsScene;
+
+ [ReadOnly]
+ public NativeArray origins;
+
+ [WriteOnly]
+ public NativeArray commands;
+
+ public void Execute () {
+ var commandSpan = commands.AsUnsafeSpan();
+ // It turns out it is faster to set all commands to the same value using MemCpyReplicate and then patch point0 and point1,
+ // rather than setting each command individually (about 30% faster even).
+ // MemCpy is very fast.
+ var queryParameters = new QueryParameters(mask, false, QueryTriggerInteraction.Ignore, false);
+ commandSpan.Fill(new OverlapCapsuleCommand(physicsScene, Vector3.zero, Vector3.zero, radius, queryParameters));
+
+ for (int i = 0; i < commandSpan.Length; i++) {
+ var origin = origins[i] + originOffset;
+ commandSpan[i].point0 = origin;
+ commandSpan[i].point1 = origin + direction;
+ }
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareCapsuleCommands.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareCapsuleCommands.cs.meta
new file mode 100644
index 0000000..414cc20
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareCapsuleCommands.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: af594897c5b7f094585ba27f8be57d45
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareGridRaycast.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareGridRaycast.cs
new file mode 100644
index 0000000..e05347d
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareGridRaycast.cs
@@ -0,0 +1,61 @@
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Collections.LowLevel.Unsafe;
+using Pathfinding.Util;
+using UnityEngine.Assertions;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ ///
+ /// Prepares a set of raycast commands for a grid graph.
+ ///
+ /// Each ray will start at from the node's position. The end point of the raycast will be the start point + .
+ ///
+ /// See:
+ ///
+ [BurstCompile]
+ public struct JobPrepareGridRaycast : IJob {
+ public Matrix4x4 graphToWorld;
+ public IntBounds bounds;
+ public Vector3 raycastOffset;
+ public Vector3 raycastDirection;
+ public LayerMask raycastMask;
+ public PhysicsScene physicsScene;
+
+ [WriteOnly]
+ public NativeArray raycastCommands;
+
+ public void Execute () {
+ float raycastLength = raycastDirection.magnitude;
+ var size = bounds.size;
+
+ // In particular Unity 2022.2 seems to assert that RaycastCommands use normalized directions
+ var direction = raycastDirection.normalized;
+ var commands = raycastCommands.AsUnsafeSpan();
+
+ Assert.AreEqual(commands.Length, size.x * size.z);
+
+#if UNITY_2022_2_OR_NEWER
+ var queryParameters = new QueryParameters(raycastMask, false, QueryTriggerInteraction.Ignore, false);
+ // This is about 30% faster than setting each command individually. MemCpy is fast.
+ commands.Fill(new RaycastCommand(physicsScene, Vector3.zero, direction, queryParameters, raycastLength));
+#else
+ const int RaycastMaxHits = 1;
+#endif
+
+ for (int z = 0; z < size.z; z++) {
+ int zw = z * size.x;
+ for (int x = 0; x < size.x; x++) {
+ int idx = zw + x;
+ var pos = JobNodeGridLayout.NodePosition(graphToWorld, x + bounds.min.x, z + bounds.min.z);
+#if UNITY_2022_2_OR_NEWER
+ commands[idx].from = pos + raycastOffset;
+#else
+ commands[idx] = new RaycastCommand(pos + raycastOffset, direction, raycastLength, raycastMask, RaycastMaxHits);
+#endif
+ }
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareGridRaycast.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareGridRaycast.cs.meta
new file mode 100644
index 0000000..3800b46
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareGridRaycast.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9f081267224f64d44a99b9962d3d300a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareRaycasts.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareRaycasts.cs
new file mode 100644
index 0000000..8a7fc7a
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareRaycasts.cs
@@ -0,0 +1,51 @@
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Collections.LowLevel.Unsafe;
+using Pathfinding.Util;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ ///
+ /// Prepares a set of raycast commands for a grid graph.
+ ///
+ /// This is very similar to but it uses an array of origin points instead of a grid pattern.
+ ///
+ /// See:
+ ///
+ [BurstCompile]
+ public struct JobPrepareRaycasts : IJob {
+ public Vector3 direction;
+ public Vector3 originOffset;
+ public float distance;
+ public LayerMask mask;
+ public PhysicsScene physicsScene;
+
+ [ReadOnly]
+ public NativeArray origins;
+
+ [WriteOnly]
+ public NativeArray raycastCommands;
+
+ public void Execute () {
+ // In particular Unity 2022.2 seems to assert that RaycastCommands use normalized directions
+ var direction = this.direction.normalized;
+ var commands = raycastCommands.AsUnsafeSpan();
+
+#if UNITY_2022_2_OR_NEWER
+ var queryParameters = new QueryParameters(mask, false, QueryTriggerInteraction.Ignore, false);
+ var defaultCommand = new RaycastCommand(physicsScene, Vector3.zero, direction, queryParameters, distance);
+ // This is about 30% faster than setting each command individually. MemCpy is fast.
+ commands.Fill(defaultCommand);
+#endif
+
+ for (int i = 0; i < raycastCommands.Length; i++) {
+#if UNITY_2022_2_OR_NEWER
+ commands[i].from = origins[i] + originOffset;
+#else
+ raycastCommands[i] = new RaycastCommand(origins[i] + originOffset, direction, distance, mask, 1);
+#endif
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareRaycasts.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareRaycasts.cs.meta
new file mode 100644
index 0000000..c998e6b
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareRaycasts.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 41ad6b54690bf4949a2974c3ca5d2503
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareSphereCommands.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareSphereCommands.cs
new file mode 100644
index 0000000..50562a2
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareSphereCommands.cs
@@ -0,0 +1,42 @@
+#if UNITY_2022_2_OR_NEWER
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Collections.LowLevel.Unsafe;
+using Pathfinding.Util;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ ///
+ /// Prepares a set of sphere commands for collision checking in a grid graph.
+ ///
+ /// See:
+ ///
+ [BurstCompile]
+ public struct JobPrepareSphereCommands : IJob {
+ public Vector3 originOffset;
+ public float radius;
+ public LayerMask mask;
+ public PhysicsScene physicsScene;
+
+ [ReadOnly]
+ public NativeArray origins;
+
+ [WriteOnly]
+ public NativeArray commands;
+
+ public void Execute () {
+ var commandSpan = commands.AsUnsafeSpan();
+ // It turns out it is faster to set all commands to the same value using MemCpyReplicate and then patch point,
+ // rather than setting each command individually.
+ var queryParameters = new QueryParameters(mask, false, QueryTriggerInteraction.Ignore, false);
+ commandSpan.Fill(new OverlapSphereCommand(physicsScene, Vector3.zero, radius, queryParameters));
+
+ for (int i = 0; i < commandSpan.Length; i++) {
+ var origin = origins[i] + originOffset;
+ commandSpan[i].point = origin;
+ }
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareSphereCommands.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareSphereCommands.cs.meta
new file mode 100644
index 0000000..f5fbad1
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobPrepareSphereCommands.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: db154518b4b8b044d946bb6a9770bdbf
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobRaycastAll.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobRaycastAll.cs
new file mode 100644
index 0000000..fd34a1d
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobRaycastAll.cs
@@ -0,0 +1,121 @@
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using UnityEngine;
+using Pathfinding.Jobs;
+using Unity.Mathematics;
+
+namespace Pathfinding.Jobs {
+ public struct JobRaycastAll {
+ int maxHits;
+ public readonly float minStep;
+
+ NativeArray results;
+ NativeArray semiResults;
+ NativeArray commands;
+ public PhysicsScene physicsScene;
+
+ [BurstCompile]
+ private struct JobCreateCommands : IJobParallelFor {
+ public NativeArray commands;
+ [ReadOnly]
+ public NativeArray raycastHits;
+
+ public float minStep;
+ public PhysicsScene physicsScene;
+
+ public void Execute (int index) {
+ var rayHit = raycastHits[index];
+
+ if (rayHit.normal != default(Vector3)) {
+ var previousCommand = commands[index];
+ // Little hack to bypass same collider hit in specific cases
+ var point = rayHit.point + previousCommand.direction.normalized * minStep;
+ var distance = previousCommand.distance - (point - previousCommand.from).magnitude;
+#if UNITY_2022_2_OR_NEWER
+ // TODO: In 2022.2 with the 'hit multiple faces' option, this whole class might be redundant.
+ var queryParameters = new QueryParameters(previousCommand.queryParameters.layerMask, false, QueryTriggerInteraction.Ignore, false);
+ commands[index] = new RaycastCommand(physicsScene, point, previousCommand.direction, queryParameters, distance);
+#else
+ commands[index] = new RaycastCommand(point, previousCommand.direction, distance, previousCommand.layerMask, 1);
+#endif
+ } else {
+#if UNITY_2022_2_OR_NEWER
+ // Note: Using a default RaycastCommand may cause Unity to crash.
+ // This seems to be primarily because it assumes a non-zero direction.
+ commands[index] = new RaycastCommand(physicsScene, Vector3.zero, Vector3.up, new QueryParameters(0, false, QueryTriggerInteraction.Ignore, false), 1);
+#else
+ commands[index] = new RaycastCommand(Vector3.zero, Vector3.up, 1, 0, 1);
+#endif
+ }
+ }
+ }
+
+ [BurstCompile]
+ private struct JobCombineResults : IJob {
+ public int maxHits;
+ [ReadOnly]
+ public NativeArray semiResults;
+ public NativeArray results;
+
+ public void Execute () {
+ int layerStride = semiResults.Length / maxHits;
+
+ for (int i = 0; i < layerStride; i++) {
+ int layerOffset = 0;
+
+ for (int j = maxHits - 1; j >= 0; j--) {
+ if (math.any(semiResults[i + j*layerStride].normal)) {
+ results[i + layerOffset] = semiResults[i + j*layerStride];
+ layerOffset += layerStride;
+ }
+ }
+ }
+ }
+ }
+
+ /// Jobified version of Physics.RaycastNonAlloc.
+ /// Array of commands to perform.
+ /// Array to store results in.
+ /// PhysicsScene to use for the raycasts. Only used in Unity 2022.2 or later.
+ /// Max hits count per command.
+ /// Allocator to use for the results array.
+ /// Tracker to use for dependencies.
+ /// Minimal distance each Raycast should progress.
+ public JobRaycastAll(NativeArray commands, NativeArray results, PhysicsScene physicsScene, int maxHits, Allocator allocator, JobDependencyTracker dependencyTracker, float minStep = 0.0001f) {
+ if (maxHits <= 0) throw new System.ArgumentException("maxHits should be greater than zero");
+ if (results.Length < commands.Length * maxHits) throw new System.ArgumentException("Results array length does not match maxHits count");
+ if (minStep < 0f) throw new System.ArgumentException("minStep should be more or equal to zero");
+
+ this.results = results;
+ this.maxHits = maxHits;
+ this.minStep = minStep;
+ this.commands = commands;
+ this.physicsScene = physicsScene;
+
+ semiResults = dependencyTracker.NewNativeArray(maxHits * commands.Length, allocator);
+ }
+
+ public JobHandle Schedule (JobHandle dependency) {
+ for (int i = 0; i < maxHits; i++) {
+ var semiResultsPart = semiResults.GetSubArray(i*commands.Length, commands.Length);
+ dependency = RaycastCommand.ScheduleBatch(commands, semiResultsPart, 128, dependency);
+ if (i < maxHits - 1) {
+ var filter = new JobCreateCommands {
+ commands = commands,
+ raycastHits = semiResultsPart,
+ minStep = minStep,
+ physicsScene = physicsScene,
+ };
+ dependency = filter.Schedule(commands.Length, 256, dependency);
+ }
+ }
+
+ return new JobCombineResults {
+ semiResults = semiResults,
+ maxHits = maxHits,
+ results = results
+ }.Schedule(dependency);
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobRaycastAll.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobRaycastAll.cs.meta
new file mode 100644
index 0000000..aed1b42
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobRaycastAll.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9f8eb8ba655e2d2dc88958d073ff52f3
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobReadNodeData.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobReadNodeData.cs
new file mode 100644
index 0000000..a94b652
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobReadNodeData.cs
@@ -0,0 +1,93 @@
+using UnityEngine;
+using Unity.Collections;
+using Unity.Mathematics;
+using Pathfinding.Jobs;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ ///
+ /// Reads node data from managed objects into unmanaged arrays.
+ ///
+ /// This is done so that burst jobs can later access this data directly.
+ ///
+ /// Later, data will be written back to the managed objects using the job.
+ ///
+ public struct JobReadNodeData : IJobParallelForBatched {
+ public System.Runtime.InteropServices.GCHandle nodesHandle;
+ public uint graphIndex;
+
+ public Slice3D slice;
+
+ [WriteOnly]
+ public NativeArray nodePositions;
+
+ [WriteOnly]
+ public NativeArray nodePenalties;
+
+ [WriteOnly]
+ public NativeArray nodeTags;
+
+ [WriteOnly]
+ public NativeArray nodeConnections;
+
+ [WriteOnly]
+ public NativeArray nodeWalkableWithErosion;
+
+ [WriteOnly]
+ public NativeArray nodeWalkable;
+
+ public bool allowBoundsChecks => false;
+
+ struct Reader : GridIterationUtilities.ISliceAction {
+ public GridNodeBase[] nodes;
+ public NativeArray nodePositions;
+ public NativeArray nodePenalties;
+ public NativeArray nodeTags;
+ public NativeArray nodeConnections;
+ public NativeArray nodeWalkableWithErosion;
+ public NativeArray nodeWalkable;
+
+ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+ public void Execute (uint outerIdx, uint innerIdx) {
+ var dataIdx = (int)innerIdx;
+ // The data bounds may have more layers than the existing nodes if a new layer is being added.
+ // We can only copy from the nodes that exist.
+ if (outerIdx < nodes.Length) {
+ var node = nodes[outerIdx];
+ if (node != null) {
+ nodePositions[dataIdx] = (Vector3)node.position;
+ nodePenalties[dataIdx] = node.Penalty;
+ nodeTags[dataIdx] = (int)node.Tag;
+ nodeConnections[dataIdx] = node is GridNode gn ? (ulong)gn.GetAllConnectionInternal() : (node as LevelGridNode).gridConnections;
+ nodeWalkableWithErosion[dataIdx] = node.Walkable;
+ nodeWalkable[dataIdx] = node.WalkableErosion;
+ return;
+ }
+ }
+
+ // Fallback in case the node was null (only happens for layered grid graphs),
+ // or if we are adding more layers to the graph, in which case we are outside
+ // the bounds of the nodes array.
+ nodePositions[dataIdx] = Vector3.zero;
+ nodePenalties[dataIdx] = 0;
+ nodeTags[dataIdx] = 0;
+ nodeConnections[dataIdx] = 0;
+ nodeWalkableWithErosion[dataIdx] = false;
+ nodeWalkable[dataIdx] = false;
+ }
+ }
+
+ public void Execute (int startIndex, int count) {
+ var reader = new Reader {
+ // This is a managed type, we need to trick Unity to allow this inside of a job
+ nodes = (GridNodeBase[])nodesHandle.Target,
+ nodePositions = nodePositions,
+ nodePenalties = nodePenalties,
+ nodeTags = nodeTags,
+ nodeConnections = nodeConnections,
+ nodeWalkableWithErosion = nodeWalkableWithErosion,
+ nodeWalkable = nodeWalkable
+ };
+ GridIterationUtilities.ForEachCellIn3DSlice(slice, ref reader);
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobReadNodeData.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobReadNodeData.cs.meta
new file mode 100644
index 0000000..2092692
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobReadNodeData.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 203c976aeb6f3d84caeab084738a53c8
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobWriteNodeData.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobWriteNodeData.cs
new file mode 100644
index 0000000..96a58a8
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobWriteNodeData.cs
@@ -0,0 +1,91 @@
+using UnityEngine;
+using Unity.Collections;
+using Unity.Mathematics;
+using Pathfinding.Jobs;
+using UnityEngine.Assertions;
+
+namespace Pathfinding.Graphs.Grid.Jobs {
+ ///
+ /// Writes node data from unmanaged arrays into managed objects.
+ ///
+ /// This is done after burst jobs have been working on graph data, as they cannot access the managed objects directly.
+ ///
+ /// Earlier, data will have been either calculated from scratch, or read from the managed objects using the job.
+ ///
+ public struct JobWriteNodeData : IJobParallelForBatched {
+ public System.Runtime.InteropServices.GCHandle nodesHandle;
+ public uint graphIndex;
+
+ /// (width, depth) of the array that the refers to
+ public int3 nodeArrayBounds;
+ public IntBounds dataBounds;
+ public IntBounds writeMask;
+
+ [ReadOnly]
+ public NativeArray nodePositions;
+
+ [ReadOnly]
+ public NativeArray nodePenalties;
+
+ [ReadOnly]
+ public NativeArray nodeTags;
+
+ [ReadOnly]
+ public NativeArray nodeConnections;
+
+ [ReadOnly]
+ public NativeArray nodeWalkableWithErosion;
+
+ [ReadOnly]
+ public NativeArray nodeWalkable;
+
+ public bool allowBoundsChecks => false;
+
+ public void Execute (int startIndex, int count) {
+ // This is a managed type, we need to trick Unity to allow this inside of a job
+ var nodes = (GridNodeBase[])nodesHandle.Target;
+
+ var relativeMask = writeMask.Offset(-dataBounds.min);
+
+ // Determinstically convert the indices to rows. It is much easier to process a number of whole rows.
+ var writeSize = writeMask.size;
+ var zstart = startIndex / (writeSize.x*writeSize.y);
+ var zend = (startIndex+count) / (writeSize.x*writeSize.y);
+
+ Assert.IsTrue(zstart >= 0 && zstart <= writeSize.z);
+ Assert.IsTrue(zend >= 0 && zend <= writeSize.z);
+ relativeMask.min.z = writeMask.min.z + zstart - dataBounds.min.z;
+ relativeMask.max.z = writeMask.min.z + zend - dataBounds.min.z;
+
+ var dataSize = dataBounds.size;
+ for (int y = relativeMask.min.y; y < relativeMask.max.y; y++) {
+ for (int z = relativeMask.min.z; z < relativeMask.max.z; z++) {
+ var rowOffset1 = (y*dataSize.z + z)*dataSize.x;
+ var rowOffset2 = (z + dataBounds.min.z)*nodeArrayBounds.x + dataBounds.min.x;
+ var rowOffset3 = (y + dataBounds.min.y)*nodeArrayBounds.z*nodeArrayBounds.x + rowOffset2;
+ for (int x = relativeMask.min.x; x < relativeMask.max.x; x++) {
+ int dataIndex = rowOffset1 + x;
+ int nodeIndex = rowOffset3 + x;
+ var node = nodes[nodeIndex];
+ if (node != null) {
+ node.GraphIndex = graphIndex;
+ node.NodeInGridIndex = rowOffset2 + x;
+ // TODO: Use UnsafeSpan
+ node.position = (Int3)nodePositions[dataIndex];
+ node.Penalty = nodePenalties[dataIndex];
+ node.Tag = (uint)nodeTags[dataIndex];
+ if (node is GridNode gridNode) {
+ gridNode.SetAllConnectionInternal((int)nodeConnections[dataIndex]);
+ } else if (node is LevelGridNode levelGridNode) {
+ levelGridNode.LayerCoordinateInGrid = y + dataBounds.min.y;
+ levelGridNode.SetAllConnectionInternal(nodeConnections[dataIndex]);
+ }
+ node.Walkable = nodeWalkableWithErosion[dataIndex];
+ node.WalkableErosion = nodeWalkable[dataIndex];
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobWriteNodeData.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobWriteNodeData.cs.meta
new file mode 100644
index 0000000..503743b
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Jobs/JobWriteNodeData.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 86fcb7ce1d754024290197052acf39d5
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules.meta
new file mode 100644
index 0000000..ab73ead
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e96df07c8616c534c99a64575f066a3d
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/GridGraphRules.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/GridGraphRules.cs
new file mode 100644
index 0000000..174b7d2
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/GridGraphRules.cs
@@ -0,0 +1,293 @@
+using System.Collections.Generic;
+
+namespace Pathfinding.Graphs.Grid.Rules {
+ using Pathfinding.Serialization;
+ using Pathfinding.Jobs;
+ using Unity.Jobs;
+ using Unity.Collections;
+ using Unity.Mathematics;
+
+ public class CustomGridGraphRuleEditorAttribute : System.Attribute {
+ public System.Type type;
+ public string name;
+ public CustomGridGraphRuleEditorAttribute(System.Type type, string name) {
+ this.type = type;
+ this.name = name;
+ }
+ }
+
+ ///
+ /// Container for all rules in a grid graph.
+ ///
+ ///
+ /// // Get the first grid graph in the scene
+ /// var gridGraph = AstarPath.active.data.gridGraph;
+ ///
+ /// gridGraph.rules.AddRule(new Pathfinding.Graphs.Grid.Rules.RuleAnglePenalty {
+ /// penaltyScale = 10000,
+ /// curve = AnimationCurve.Linear(0, 0, 90, 1),
+ /// });
+ ///
+ ///
+ /// See:
+ /// See: grid-rules (view in online documentation for working links)
+ ///
+ [JsonOptIn]
+ public class GridGraphRules {
+ List >[] jobSystemCallbacks;
+ List >[] mainThreadCallbacks;
+
+ /// List of all rules
+ [JsonMember]
+ List rules = new List();
+
+ long lastHash;
+
+ /// Context for when scanning or updating a graph
+ public class Context {
+ /// Graph which is being scanned or updated
+ public GridGraph graph;
+ /// Data for all the nodes as NativeArrays
+ public GridGraphScanData data;
+ ///
+ /// Tracks dependencies between jobs to allow parallelism without tediously specifying dependencies manually.
+ /// Always use when scheduling jobs.
+ ///
+ public JobDependencyTracker tracker => data.dependencyTracker;
+ }
+
+ public void AddRule (GridGraphRule rule) {
+ rules.Add(rule);
+ lastHash = -1;
+ }
+
+ public void RemoveRule (GridGraphRule rule) {
+ rules.Remove(rule);
+ lastHash = -1;
+ }
+
+ public IReadOnlyList GetRules () {
+ if (rules == null) rules = new List();
+ return rules.AsReadOnly();
+ }
+
+ long Hash () {
+ long hash = 196613;
+
+ for (int i = 0; i < rules.Count; i++) {
+ if (rules[i] != null && rules[i].enabled) hash = hash * 1572869 ^ (long)rules[i].Hash;
+ }
+ return hash;
+ }
+
+ public void RebuildIfNecessary () {
+ var newHash = Hash();
+
+ if (newHash == lastHash && jobSystemCallbacks != null && mainThreadCallbacks != null) return;
+ lastHash = newHash;
+ Rebuild();
+ }
+
+ public void Rebuild () {
+ rules = rules ?? new List();
+ jobSystemCallbacks = jobSystemCallbacks ?? new List >[6];
+ for (int i = 0; i < jobSystemCallbacks.Length; i++) {
+ if (jobSystemCallbacks[i] != null) jobSystemCallbacks[i].Clear();
+ }
+ mainThreadCallbacks = mainThreadCallbacks ?? new List >[6];
+ for (int i = 0; i < mainThreadCallbacks.Length; i++) {
+ if (mainThreadCallbacks[i] != null) mainThreadCallbacks[i].Clear();
+ }
+ for (int i = 0; i < rules.Count; i++) {
+ if (rules[i].enabled) rules[i].Register(this);
+ }
+ }
+
+ public void DisposeUnmanagedData () {
+ if (rules != null) {
+ for (int i = 0; i < rules.Count; i++) {
+ if (rules[i] != null) {
+ rules[i].DisposeUnmanagedData();
+ rules[i].SetDirty();
+ }
+ }
+ }
+ }
+
+ static void CallActions (List > actions, Context context) {
+ if (actions != null) {
+ try {
+ for (int i = 0; i < actions.Count; i++) actions[i](context);
+ } catch (System.Exception e) {
+ UnityEngine.Debug.LogException(e);
+ }
+ }
+ }
+
+ ///
+ /// Executes the rules for the given pass.
+ /// Call handle.Complete on, or wait for, all yielded job handles.
+ ///
+ public IEnumerator ExecuteRule (GridGraphRule.Pass rule, Context context) {
+ if (jobSystemCallbacks == null) Rebuild();
+ CallActions(jobSystemCallbacks[(int)rule], context);
+
+ if (mainThreadCallbacks[(int)rule] != null && mainThreadCallbacks[(int)rule].Count > 0) {
+ if (!context.tracker.forceLinearDependencies) yield return context.tracker.AllWritesDependency;
+ CallActions(mainThreadCallbacks[(int)rule], context);
+ }
+ }
+
+ public void ExecuteRuleMainThread (GridGraphRule.Pass rule, Context context) {
+ if (jobSystemCallbacks[(int)rule] != null && jobSystemCallbacks[(int)rule].Count > 0) throw new System.Exception("A job system pass has been added for the " + rule + " pass. " + rule + " only supports main thread callbacks.");
+ if (context.tracker != null) context.tracker.AllWritesDependency.Complete();
+ CallActions(mainThreadCallbacks[(int)rule], context);
+ }
+
+ ///
+ /// Adds a pass callback that uses the job system.
+ /// This rule should only schedule jobs using the `Context.tracker` dependency tracker. Data is not safe to access directly in the callback
+ ///
+ /// This method should only be called from rules in their Register method.
+ ///
+ public void AddJobSystemPass (GridGraphRule.Pass pass, System.Action action) {
+ var index = (int)pass;
+
+ if (jobSystemCallbacks[index] == null) {
+ jobSystemCallbacks[index] = new List >();
+ }
+ jobSystemCallbacks[index].Add(action);
+ }
+
+ ///
+ /// Adds a pass callback that runs in the main thread.
+ /// The callback may access and modify any data in the context.
+ /// You do not need to schedule jobs in order to access the data.
+ ///
+ /// Warning: Not all data in the Context is valid for every pass. For example you cannot access node connections in the BeforeConnections pass
+ /// since they haven't been calculated yet.
+ ///
+ /// This is a bit slower than since parallelism and the burst compiler cannot be used.
+ /// But if you need to use non-thread-safe APIs or data then this is a good choice.
+ ///
+ /// This method should only be called from rules in their Register method.
+ ///
+ public void AddMainThreadPass (GridGraphRule.Pass pass, System.Action action) {
+ var index = (int)pass;
+
+ if (mainThreadCallbacks[index] == null) {
+ mainThreadCallbacks[index] = new List >();
+ }
+ mainThreadCallbacks[index].Add(action);
+ }
+
+ /// Deprecated: Use AddJobSystemPass or AddMainThreadPass instead
+ [System.Obsolete("Use AddJobSystemPass or AddMainThreadPass instead")]
+ public void Add (GridGraphRule.Pass pass, System.Action action) {
+ AddJobSystemPass(pass, action);
+ }
+ }
+
+ ///
+ /// Custom rule for a grid graph.
+ /// See:
+ /// See: grid-rules (view in online documentation for working links)
+ ///
+ [JsonDynamicType]
+ // Compatibility with old versions
+ [JsonDynamicTypeAlias("Pathfinding.RuleTexture", typeof(RuleTexture))]
+ [JsonDynamicTypeAlias("Pathfinding.RuleAnglePenalty", typeof(RuleAnglePenalty))]
+ [JsonDynamicTypeAlias("Pathfinding.RuleElevationPenalty", typeof(RuleElevationPenalty))]
+ [JsonDynamicTypeAlias("Pathfinding.RulePerLayerModifications", typeof(RulePerLayerModifications))]
+ public abstract class GridGraphRule {
+ /// Only enabled rules are executed
+ [JsonMember]
+ public bool enabled = true;
+ int dirty = 1;
+
+ ///
+ /// Where in the scanning process a rule will be executed.
+ /// Check the documentation for to see which data fields are valid in which passes.
+ ///
+ public enum Pass {
+ ///
+ /// Before the collision testing phase but after height testing.
+ /// This is very early. Most data is not valid by this point.
+ ///
+ /// You can use this if you want to modify the node positions and still have it picked up by the collision testing code.
+ ///
+ BeforeCollision,
+ ///
+ /// Before connections are calculated.
+ /// At this point height testing and collision testing has been done (if they are enabled).
+ ///
+ /// This is the most common pass to use.
+ /// If you are modifying walkability here then connections and erosion will be calculated properly.
+ ///
+ BeforeConnections,
+ ///
+ /// After connections are calculated.
+ ///
+ /// If you are modifying connections directly you should do that in this pass.
+ ///
+ /// Note: If erosion is used then this pass will be executed twice. One time before erosion and one time after erosion
+ /// when the connections are calculated again.
+ ///
+ AfterConnections,
+ ///
+ /// After erosion is calculated but before connections have been recalculated.
+ ///
+ /// If no erosion is used then this pass will not be executed.
+ ///
+ AfterErosion,
+ ///
+ /// After everything else.
+ /// This pass is executed after everything else is done.
+ /// You should not modify walkability in this pass because then the node connections will not be up to date.
+ ///
+ PostProcess,
+ ///
+ /// After the graph update has been applied to the graph.
+ ///
+ /// This pass can only be added as a main-thread pass.
+ ///
+ /// Warning: No native data in the context is valid at this point. It has all been disposed.
+ /// You cannot modify any data in this pass.
+ ///
+ AfterApplied,
+ }
+
+ ///
+ /// Hash of the settings for this rule.
+ /// The method will be called again whenever the hash changes.
+ /// If the hash does not change it is assumed that the method does not need to be called again.
+ ///
+ public virtual int Hash => dirty;
+
+ ///
+ /// Call if you have changed any setting of the rule.
+ /// This will ensure that any cached data the rule uses is rebuilt.
+ /// If you do not do this then any settings changes may not affect the graph when it is rescanned or updated.
+ ///
+ /// The purpose of this method call is to cause the property to change. If your custom rule overrides the Hash property to
+ /// return a hash of some settings, then you do not need to call this method for the changes the hash function already accounts for.
+ ///
+ public virtual void SetDirty () {
+ dirty++;
+ }
+
+ ///
+ /// Called when the rule is removed or the graph is destroyed.
+ /// Use this to e.g. clean up any NativeArrays that the rule uses.
+ ///
+ /// Note: The rule should remain valid after this method has been called.
+ /// However the method is guaranteed to be called before the rule is executed again.
+ ///
+ public virtual void DisposeUnmanagedData () {
+ }
+
+ /// Does preprocessing and adds callbacks to the object
+ public virtual void Register (GridGraphRules rules) {
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/GridGraphRules.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/GridGraphRules.cs.meta
new file mode 100644
index 0000000..a73e50c
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/GridGraphRules.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4d90c9a7bca49464796933f43b5506fc
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleAnglePenalty.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleAnglePenalty.cs
new file mode 100644
index 0000000..1322770
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleAnglePenalty.cs
@@ -0,0 +1,81 @@
+namespace Pathfinding.Graphs.Grid.Rules {
+ using Pathfinding.Jobs;
+ using Unity.Jobs;
+ using Unity.Collections;
+ using Unity.Burst;
+ using UnityEngine;
+ using Unity.Mathematics;
+
+ ///
+ /// Applies penalty based on the slope of the surface below the node.
+ ///
+ /// This is useful if you for example want to discourage agents from walking on steep slopes.
+ ///
+ /// The penalty applied is equivalent to:
+ ///
+ ///
+ /// penalty = curve.evaluate(slope angle in degrees) * penaltyScale
+ ///
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// See: grid-rules (view in online documentation for working links)
+ ///
+ [Pathfinding.Util.Preserve]
+ public class RuleAnglePenalty : GridGraphRule {
+ public float penaltyScale = 10000;
+ public AnimationCurve curve = AnimationCurve.Linear(0, 0, 90, 1);
+ NativeArray angleToPenalty;
+
+ public override void Register (GridGraphRules rules) {
+ if (!angleToPenalty.IsCreated) angleToPenalty = new NativeArray(32, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
+ for (int i = 0; i < angleToPenalty.Length; i++) {
+ angleToPenalty[i] = Mathf.Max(0, curve.Evaluate(90.0f * i / (angleToPenalty.Length - 1)) * penaltyScale);
+ }
+
+ rules.AddJobSystemPass(Pass.BeforeConnections, context => {
+ new JobPenaltyAngle {
+ angleToPenalty = angleToPenalty,
+ up = context.data.up,
+ nodeNormals = context.data.nodes.normals,
+ penalty = context.data.nodes.penalties,
+ }.Schedule(context.tracker);
+ });
+ }
+
+ public override void DisposeUnmanagedData () {
+ if (angleToPenalty.IsCreated) angleToPenalty.Dispose();
+ }
+
+ [BurstCompile(FloatMode = FloatMode.Fast)]
+ public struct JobPenaltyAngle : IJob {
+ public Vector3 up;
+
+ [ReadOnly]
+ public NativeArray angleToPenalty;
+
+ [ReadOnly]
+ public NativeArray nodeNormals;
+
+ public NativeArray penalty;
+
+ public void Execute () {
+ float4 up = new float4(this.up.x, this.up.y, this.up.z, 0);
+
+ for (int i = 0; i < penalty.Length; i++) {
+ float4 normal = nodeNormals[i];
+ if (math.any(normal)) {
+ float angle = math.acos(math.dot(normal, up));
+ // Take the dot product to find out the cosinus of the angle it has
+ // Add penalty based on the angle from a precalculated array
+ float x = angle*(angleToPenalty.Length - 1)/math.PI;
+ int ix = (int)x;
+ float p1 = angleToPenalty[math.max(ix, 0)];
+ float p2 = angleToPenalty[math.min(ix + 1, angleToPenalty.Length - 1)];
+ penalty[i] += (uint)math.lerp(p1, p2, x - ix);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleAnglePenalty.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleAnglePenalty.cs.meta
new file mode 100644
index 0000000..d047088
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleAnglePenalty.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 40d5c4aeb2276457f8fe040e4c5d71fe
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleElevationPenalty.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleElevationPenalty.cs
new file mode 100644
index 0000000..6f62660
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleElevationPenalty.cs
@@ -0,0 +1,76 @@
+namespace Pathfinding.Graphs.Grid.Rules {
+ using Pathfinding.Jobs;
+ using Unity.Jobs;
+ using Unity.Collections;
+ using Unity.Burst;
+ using UnityEngine;
+ using Unity.Mathematics;
+
+ ///
+ /// Applies penalty based on the elevation of the node.
+ ///
+ /// This is useful if you for example want to discourage agents from walking high up in mountain regions.
+ ///
+ /// The penalty applied is equivalent to:
+ ///
+ ///
+ /// penalty = curve.evaluate(Mathf.Clamp01(Mathf.InverseLerp(lower elevation range, upper elevation range, elevation))) * penaltyScale
+ ///
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// See: grid-rules (view in online documentation for working links)
+ ///
+ [Pathfinding.Util.Preserve]
+ public class RuleElevationPenalty : GridGraphRule {
+ public float penaltyScale = 10000;
+ public Vector2 elevationRange = new Vector2(0, 100);
+ public AnimationCurve curve = AnimationCurve.Linear(0, 0, 1, 1);
+ NativeArray elevationToPenalty;
+
+ public override void Register (GridGraphRules rules) {
+ if (!elevationToPenalty.IsCreated) elevationToPenalty = new NativeArray(64, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
+ for (int i = 0; i < elevationToPenalty.Length; i++) {
+ elevationToPenalty[i] = Mathf.Max(0, penaltyScale * curve.Evaluate(i * 1.0f / (elevationToPenalty.Length - 1)));
+ }
+
+ var clampedElevationRange = new Vector2(math.max(0, elevationRange.x), math.max(1, elevationRange.y));
+ rules.AddJobSystemPass(Pass.BeforeConnections, context => {
+ //var elevationRangeScale = Matrix4x4.TRS(new Vector3(0, -clampedElevationRange.x, 0), Quaternion.identity, new Vector3(1, 1/(clampedElevationRange.y - clampedElevationRange.x), 1));
+ var elevationRangeScale = Matrix4x4.Scale(new Vector3(1, 1/(clampedElevationRange.y - clampedElevationRange.x), 1)) * Matrix4x4.Translate(new Vector3(0, -clampedElevationRange.x, 0));
+ new JobElevationPenalty {
+ elevationToPenalty = elevationToPenalty,
+ nodePositions = context.data.nodes.positions,
+ worldToGraph = elevationRangeScale * context.data.transform.matrix.inverse,
+ penalty = context.data.nodes.penalties,
+ }.Schedule(context.tracker);
+ });
+ }
+
+ public override void DisposeUnmanagedData () {
+ if (elevationToPenalty.IsCreated) elevationToPenalty.Dispose();
+ }
+
+ [BurstCompile(FloatMode = FloatMode.Fast)]
+ public struct JobElevationPenalty : IJob {
+ [ReadOnly]
+ public NativeArray elevationToPenalty;
+
+ [ReadOnly]
+ public NativeArray nodePositions;
+
+ public Matrix4x4 worldToGraph;
+ public NativeArray penalty;
+
+ public void Execute () {
+ for (int i = 0; i < penalty.Length; i++) {
+ float y = math.clamp(worldToGraph.MultiplyPoint3x4(nodePositions[i]).y, 0, 1) * (elevationToPenalty.Length - 1);
+ int iy = (int)y;
+ float p1 = elevationToPenalty[iy];
+ float p2 = elevationToPenalty[math.min(iy + 1, elevationToPenalty.Length - 1)];
+ penalty[i] += (uint)math.lerp(p1, p2, y - iy);
+ }
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleElevationPenalty.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleElevationPenalty.cs.meta
new file mode 100644
index 0000000..5878475
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleElevationPenalty.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d2933158d922e49e39a332d795d26d68
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RulePerLayerModifications.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RulePerLayerModifications.cs
new file mode 100644
index 0000000..1684b04
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RulePerLayerModifications.cs
@@ -0,0 +1,79 @@
+using Pathfinding.Jobs;
+
+namespace Pathfinding.Graphs.Grid.Rules {
+ ///
+ /// Modifies nodes based on the layer of the surface under the node.
+ ///
+ /// You can for example make all surfaces with a specific layer make the nodes get a specific tag.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// See: grid-rules (view in online documentation for working links)
+ ///
+ [Pathfinding.Util.Preserve]
+ public class RulePerLayerModifications : GridGraphRule {
+ public PerLayerRule[] layerRules = new PerLayerRule[0];
+ const int SetTagBit = 1 << 30;
+
+ public struct PerLayerRule {
+ /// Layer this rule applies to
+ public int layer;
+ /// The action to apply to matching nodes
+ public RuleAction action;
+ ///
+ /// Tag for the RuleAction.SetTag action.
+ /// Must be between 0 and
+ ///
+ public int tag;
+ }
+
+ public enum RuleAction {
+ /// Sets the tag of all affected nodes to
+ SetTag,
+ /// Makes all affected nodes unwalkable
+ MakeUnwalkable,
+ }
+
+ public override void Register (GridGraphRules rules) {
+ int[] layerToTag = new int[32];
+ bool[] layerToUnwalkable = new bool[32];
+ for (int i = 0; i < layerRules.Length; i++) {
+ var rule = layerRules[i];
+ if (rule.action == RuleAction.SetTag) {
+ layerToTag[rule.layer] = SetTagBit | rule.tag;
+ } else {
+ layerToUnwalkable[rule.layer] = true;
+ }
+ }
+
+ rules.AddMainThreadPass(Pass.BeforeConnections, context => {
+ if (!context.data.heightHits.IsCreated) {
+ UnityEngine.Debug.LogError("RulePerLayerModifications requires height testing to be enabled on the grid graph", context.graph.active);
+ return;
+ }
+
+ var raycastHits = context.data.heightHits;
+ var nodeWalkable = context.data.nodes.walkable;
+ var nodeTags = context.data.nodes.tags;
+ var slice = new Slice3D(context.data.nodes.bounds, context.data.heightHitsBounds);
+ var size = slice.slice.size;
+ for (int y = 0; y < size.y; y++) {
+ for (int z = 0; z < size.z; z++) {
+ var rowOffset = y * size.x * size.z + z * size.x;
+ for (int x = 0; x < size.x; x++) {
+ var innerIndex = rowOffset + x;
+ var outerIndex = slice.InnerCoordinateToOuterIndex(x, y, z);
+ var coll = raycastHits[innerIndex].collider;
+ if (coll != null) {
+ var layer = coll.gameObject.layer;
+ if (layerToUnwalkable[layer]) nodeWalkable[outerIndex] = false;
+ var tag = layerToTag[layer];
+ if ((tag & SetTagBit) != 0) nodeTags[outerIndex] = tag & 0xFF;
+ }
+ }
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RulePerLayerModifications.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RulePerLayerModifications.cs.meta
new file mode 100644
index 0000000..bfa3859
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RulePerLayerModifications.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4869e55551c0e4e1abaaf19bcc3d44a1
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleTexture.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleTexture.cs
new file mode 100644
index 0000000..ddc6a9c
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleTexture.cs
@@ -0,0 +1,181 @@
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Mathematics;
+
+namespace Pathfinding.Graphs.Grid.Rules {
+ using Pathfinding.Jobs;
+
+ ///
+ /// Modifies nodes based on the contents of a texture.
+ ///
+ /// This can be used to "paint" penalties or walkability using an external program such as Photoshop.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// See: grid-rules (view in online documentation for working links)
+ ///
+ [Pathfinding.Util.Preserve]
+ public class RuleTexture : GridGraphRule {
+ public Texture2D texture;
+
+ public ChannelUse[] channels = new ChannelUse[4];
+ public float[] channelScales = { 1000, 1000, 1000, 1000 };
+
+ public ScalingMode scalingMode = ScalingMode.StretchToFitGraph;
+ public float nodesPerPixel = 1;
+
+ NativeArray colors;
+
+ public enum ScalingMode {
+ FixedScale,
+ StretchToFitGraph,
+ }
+
+ public override int Hash {
+ get {
+ var h = base.Hash ^ (texture != null ? (int)texture.updateCount : 0);
+#if UNITY_EDITOR
+ if (texture != null) h ^= (int)texture.imageContentsHash.GetHashCode();
+#endif
+ return h;
+ }
+ }
+
+ public enum ChannelUse {
+ None,
+ /// Penalty goes from 0 to channelScale depending on the channel value
+ Penalty,
+ /// Node Y coordinate goes from 0 to channelScale depending on the channel value
+ Position,
+ /// If channel value is zero the node is made unwalkable, penalty goes from 0 to channelScale depending on the channel value
+ WalkablePenalty,
+ /// If channel value is zero the node is made unwalkable
+ Walkable,
+ }
+
+ public override void Register (GridGraphRules rules) {
+ if (texture == null) return;
+
+ if (!texture.isReadable) {
+ Debug.LogError("Texture for the texture rule on a grid graph is not marked as readable.", texture);
+ return;
+ }
+
+ if (colors.IsCreated) colors.Dispose();
+ colors = new NativeArray(texture.GetPixels32(), Allocator.Persistent).Reinterpret();
+
+ // Make sure this is done outside the delegate, just in case the texture is later resized
+ var textureSize = new int2(texture.width, texture.height);
+
+ float4 channelPenaltiesCombined = float4.zero;
+ bool4 channelDeterminesWalkability = false;
+ float4 channelPositionScalesCombined = float4.zero;
+ for (int i = 0; i < 4; i++) {
+ channelPenaltiesCombined[i] = channels[i] == ChannelUse.Penalty || channels[i] == ChannelUse.WalkablePenalty ? channelScales[i] : 0;
+ channelDeterminesWalkability[i] = channels[i] == ChannelUse.Walkable || channels[i] == ChannelUse.WalkablePenalty;
+ channelPositionScalesCombined[i] = channels[i] == ChannelUse.Position ? channelScales[i] : 0;
+ }
+
+ channelPositionScalesCombined /= 255.0f;
+ channelPenaltiesCombined /= 255.0f;
+
+ if (math.any(channelPositionScalesCombined)) {
+ rules.AddJobSystemPass(Pass.BeforeCollision, context => {
+ new JobTexturePosition {
+ colorData = colors,
+ nodePositions = context.data.nodes.positions,
+ nodeNormals = context.data.nodes.normals,
+ bounds = context.data.nodes.bounds,
+ colorDataSize = textureSize,
+ scale = scalingMode == ScalingMode.FixedScale ? 1.0f/math.max(0.01f, nodesPerPixel) : textureSize / new float2(context.graph.width, context.graph.depth),
+ channelPositionScale = channelPositionScalesCombined,
+ graphToWorld = context.data.transform.matrix,
+ }.Schedule(context.tracker);
+ });
+ }
+
+ rules.AddJobSystemPass(Pass.BeforeConnections, context => {
+ new JobTexturePenalty {
+ colorData = colors,
+ penalty = context.data.nodes.penalties,
+ walkable = context.data.nodes.walkable,
+ nodeNormals = context.data.nodes.normals,
+ bounds = context.data.nodes.bounds,
+ colorDataSize = textureSize,
+ scale = scalingMode == ScalingMode.FixedScale ? 1.0f/math.max(0.01f, nodesPerPixel) : textureSize / new float2(context.graph.width, context.graph.depth),
+ channelPenalties = channelPenaltiesCombined,
+ channelDeterminesWalkability = channelDeterminesWalkability,
+ }.Schedule(context.tracker);
+ });
+ }
+
+ public override void DisposeUnmanagedData () {
+ if (colors.IsCreated) colors.Dispose();
+ }
+
+ [BurstCompile]
+ public struct JobTexturePosition : IJob, GridIterationUtilities.INodeModifier {
+ [ReadOnly]
+ public NativeArray colorData;
+ [WriteOnly]
+ public NativeArray nodePositions;
+ [ReadOnly]
+ public NativeArray nodeNormals;
+
+ public Matrix4x4 graphToWorld;
+ public IntBounds bounds;
+ public int2 colorDataSize;
+ public float2 scale;
+ public float4 channelPositionScale;
+
+ public void ModifyNode (int dataIndex, int dataX, int dataLayer, int dataZ) {
+ var offset = bounds.min.xz;
+ int2 colorPos = math.clamp((int2)math.round((new float2(dataX, dataZ) + offset) * scale), int2.zero, colorDataSize - new int2(1, 1));
+ int colorIndex = colorPos.y*colorDataSize.x + colorPos.x;
+
+ int4 color = new int4((colorData[colorIndex] >> 0) & 0xFF, (colorData[colorIndex] >> 8) & 0xFF, (colorData[colorIndex] >> 16) & 0xFF, (colorData[colorIndex] >> 24) & 0xFF);
+
+ float y = math.dot(channelPositionScale, color);
+
+ nodePositions[dataIndex] = graphToWorld.MultiplyPoint3x4(new Vector3((bounds.min.x + dataX) + 0.5f, y, (bounds.min.z + dataZ) + 0.5f));
+ }
+
+ public void Execute () {
+ GridIterationUtilities.ForEachNode(bounds.size, nodeNormals, ref this);
+ }
+ }
+
+ [BurstCompile]
+ public struct JobTexturePenalty : IJob, GridIterationUtilities.INodeModifier {
+ [ReadOnly]
+ public NativeArray colorData;
+ public NativeArray penalty;
+ public NativeArray walkable;
+ [ReadOnly]
+ public NativeArray nodeNormals;
+
+ public IntBounds bounds;
+ public int2 colorDataSize;
+ public float2 scale;
+ public float4 channelPenalties;
+ public bool4 channelDeterminesWalkability;
+
+ public void ModifyNode (int dataIndex, int dataX, int dataLayer, int dataZ) {
+ var offset = bounds.min.xz;
+ int2 colorPos = math.clamp((int2)math.round((new float2(dataX, dataZ) + offset) * scale), int2.zero, colorDataSize - new int2(1, 1));
+ int colorIndex = colorPos.y*colorDataSize.x + colorPos.x;
+
+ int4 color = new int4((colorData[colorIndex] >> 0) & 0xFF, (colorData[colorIndex] >> 8) & 0xFF, (colorData[colorIndex] >> 16) & 0xFF, (colorData[colorIndex] >> 24) & 0xFF);
+
+ penalty[dataIndex] += (uint)math.dot(channelPenalties, color);
+ walkable[dataIndex] = walkable[dataIndex] & !math.any(channelDeterminesWalkability & (color == 0));
+ }
+
+ public void Execute () {
+ GridIterationUtilities.ForEachNode(bounds.size, nodeNormals, ref this);
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleTexture.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleTexture.cs.meta
new file mode 100644
index 0000000..6b3c4aa
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleTexture.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 42c128143490d447fa6420a4f35fe9bb
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/GridGraph.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/GridGraph.cs
new file mode 100644
index 0000000..ccf5675
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/GridGraph.cs
@@ -0,0 +1,3681 @@
+using System.Collections.Generic;
+using Math = System.Math;
+using UnityEngine;
+using System.Linq;
+using UnityEngine.Profiling;
+
+
+namespace Pathfinding {
+ using Pathfinding.Serialization;
+ using Pathfinding.Util;
+ using Unity.Collections;
+ using Unity.Jobs;
+ using Unity.Mathematics;
+ using Pathfinding.Jobs;
+ using Pathfinding.Graphs.Grid.Jobs;
+ using Unity.Burst;
+ using Pathfinding.Drawing;
+ using Pathfinding.Graphs.Grid;
+ using Pathfinding.Graphs.Grid.Rules;
+ using UnityEngine.Assertions;
+
+ ///
+ /// Generates a grid of nodes.
+ /// [Open online documentation to see images]
+ /// The GridGraph does exactly what the name implies, generates nodes in a grid pattern.
+ ///
+ /// Grid graphs are excellent for when you already have a grid-based world. But they also work well for free-form worlds.
+ ///
+ /// Features:
+ /// - Throw any scene at it, and with minimal configurations you can get a good graph from it.
+ /// - Predictable pattern.
+ /// - Grid graphs work well with penalties and tags.
+ /// - You can update parts of the graph during runtime.
+ /// - Graph updates are fast.
+ /// - Scanning the graph is comparatively fast.
+ /// - Supports linecasting.
+ /// - Supports the funnel modifier.
+ /// - Supports both 2D and 3D physics.
+ /// - Supports isometric and hexagonal node layouts.
+ /// - Can apply penalty and walkability values from a supplied image.
+ /// - Perfect for terrains since it can make nodes walkable or unwalkable depending on the slope.
+ /// - Only supports a single layer, but you can use a if you need more layers.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Inspector
+ ///
+ /// \inspectorField{Shape, inspectorGridMode}
+ /// \inspectorField{2D, is2D}
+ /// \inspectorField{Align to tilemap, AlignToTilemap}
+ /// \inspectorField{Width, width}
+ /// \inspectorField{Depth, depth}
+ /// \inspectorField{Node size, nodeSize}
+ /// \inspectorField{Aspect ratio (isometric/advanced shape), aspectRatio}
+ /// \inspectorField{Isometric angle (isometric/advanced shape), isometricAngle}
+ /// \inspectorField{Center, center}
+ /// \inspectorField{Rotation, rotation}
+ /// \inspectorField{Connections, neighbours}
+ /// \inspectorField{Cut corners, cutCorners}
+ /// \inspectorField{Max step height, maxStepHeight}
+ /// \inspectorField{Account for slopes, maxStepUsesSlope}
+ /// \inspectorField{Max slope, maxSlope}
+ /// \inspectorField{Erosion iterations, erodeIterations}
+ /// \inspectorField{Use 2D physics, collision.use2D}
+ ///
+ /// Collision testing
+ /// \inspectorField{Collider type, collision.type}
+ /// \inspectorField{Diameter, collision.diameter}
+ /// \inspectorField{Height/length, collision.height}
+ /// \inspectorField{Offset, collision.collisionOffset}
+ /// \inspectorField{Obstacle layer mask, collision.mask}
+ /// \inspectorField{Preview, GridGraphEditor.collisionPreviewOpen}
+ ///
+ /// Height testing
+ /// \inspectorField{Ray length, collision.fromHeight}
+ /// \inspectorField{Mask, collision.heightMask}
+ /// \inspectorField{Thick raycast, collision.thickRaycast}
+ /// \inspectorField{Unwalkable when no ground, collision.unwalkableWhenNoGround}
+ ///
+ /// Rules
+ /// Take a look at grid-rules (view in online documentation for working links) for a list of available rules.
+ ///
+ /// Other settings
+ /// \inspectorField{Show surface, showMeshSurface}
+ /// \inspectorField{Show outline, showMeshOutline}
+ /// \inspectorField{Show connections, showNodeConnections}
+ /// \inspectorField{Initial penalty, NavGraph.initialPenalty}
+ ///
+ /// Updating the graph during runtime
+ /// Any graph which implements the IUpdatableGraph interface can be updated during runtime.
+ /// For grid graphs this is a great feature since you can update only a small part of the grid without causing any lag like a complete rescan would.
+ ///
+ /// If you for example just have instantiated an obstacle in the scene and you want to update the grid where that obstacle was instantiated, you can do this:
+ ///
+ /// AstarPath.active.UpdateGraphs (obstacle.collider.bounds);
+ /// Where obstacle is the GameObject you just instantiated.
+ ///
+ /// As you can see, the UpdateGraphs function takes a Bounds parameter and it will send an update call to all updateable graphs.
+ ///
+ /// A grid graph will assume anything could have changed inside that bounding box, and recalculate all nodes that could possibly be affected.
+ /// Thus it may end up updating a few more nodes than just those covered by the bounding box.
+ ///
+ /// See: graph-updates (view in online documentation for working links) for more info about updating graphs during runtime
+ ///
+ /// Hexagonal graphs
+ /// The graph can be configured to work like a hexagon graph with some simple settings. The grid graph has a Shape dropdown.
+ /// If you set it to 'Hexagonal' the graph will behave as a hexagon graph.
+ /// Often you may want to rotate the graph +45 or -45 degrees.
+ /// [Open online documentation to see images]
+ ///
+ /// Note: Snapping to the closest node is not exactly as you would expect in a real hexagon graph,
+ /// but it is close enough that you will likely not notice.
+ ///
+ /// Configure using code
+ ///
+ /// A grid graph can be added and configured completely at runtime via code.
+ ///
+ ///
+ /// // This holds all graph data
+ /// AstarData data = AstarPath.active.data;
+ ///
+ /// // This creates a Grid Graph
+ /// GridGraph gg = data.AddGraph(typeof(GridGraph)) as GridGraph;
+ ///
+ /// // Setup a grid graph with some values
+ /// int width = 50;
+ /// int depth = 50;
+ /// float nodeSize = 1;
+ ///
+ /// gg.center = new Vector3(10, 0, 0);
+ ///
+ /// // Updates internal size from the above values
+ /// gg.SetDimensions(width, depth, nodeSize);
+ ///
+ /// // Scans all graphs
+ /// AstarPath.active.Scan();
+ ///
+ ///
+ /// See: runtime-graphs (view in online documentation for working links)
+ ///
+ /// Tree colliders
+ /// It seems that Unity will only generate tree colliders at runtime when the game is started.
+ /// For this reason, the grid graph will not pick up tree colliders when outside of play mode
+ /// but it will pick them up once the game starts. If it still does not pick them up
+ /// make sure that the trees actually have colliders attached to them and that the tree prefabs are
+ /// in the correct layer (the layer should be included in the 'Collision Testing' mask).
+ ///
+ /// See: for documentation on the 'Height Testing' and 'Collision Testing' sections
+ /// of the grid graph settings.
+ /// See:
+ ///
+ [JsonOptIn]
+ [Pathfinding.Util.Preserve]
+ public class GridGraph : NavGraph, IUpdatableGraph, ITransformedGraph
+ , IRaycastableGraph {
+ protected override void DisposeUnmanagedData () {
+ // Destroy all nodes to make the graph go into an unscanned state
+ DestroyAllNodes();
+
+ // Clean up a reference in a static variable which otherwise should point to this graph forever and stop the GC from collecting it
+ GridNode.ClearGridGraph((int)graphIndex, this);
+
+ // Dispose of native arrays. This is very important to avoid memory leaks!
+ rules.DisposeUnmanagedData();
+ this.nodeData.Dispose();
+ }
+
+ protected override void DestroyAllNodes () {
+ GetNodes(node => {
+ // If the grid data happens to be invalid (e.g we had to abort a graph update while it was running) using 'false' as
+ // the parameter will prevent the Destroy method from potentially throwing IndexOutOfRange exceptions due to trying
+ // to access nodes outside the graph. It is safe to do this because we are destroying all nodes in the graph anyway.
+ // We do however need to clear custom connections in both directions
+ (node as GridNodeBase).ClearCustomConnections(true);
+ node.ClearConnections(false);
+ node.Destroy();
+ });
+ // Important: so that multiple calls to DestroyAllNodes still works
+ nodes = null;
+ }
+
+
+ ///
+ /// Number of layers in the graph.
+ /// For grid graphs this is always 1, for layered grid graphs it can be higher.
+ /// The nodes array has the size width*depth*layerCount.
+ ///
+ public virtual int LayerCount {
+ get => 1;
+ protected set {
+ if (value != 1) throw new System.NotSupportedException("Grid graphs cannot have multiple layers");
+ }
+ }
+
+ public virtual int MaxLayers => 1;
+
+ public override int CountNodes () {
+ return nodes != null ? nodes.Length : 0;
+ }
+
+ public override void GetNodes (System.Action action) {
+ if (nodes == null) return;
+ for (int i = 0; i < nodes.Length; i++) action(nodes[i]);
+ }
+
+ ///
+ /// Determines the layout of the grid graph inspector in the Unity Editor.
+ ///
+ /// A grid graph can be set up as a normal grid, isometric grid or hexagonal grid.
+ /// Each of these modes use a slightly different inspector layout.
+ /// When changing the shape in the inspector, it will automatically set other relevant fields
+ /// to appropriate values. For example, when setting the shape to hexagonal it will automatically set
+ /// the field to Six.
+ ///
+ /// This field is only used in the editor, it has no effect on the rest of the game whatsoever.
+ ///
+ /// If you want to change the grid shape like in the inspector you can use the method.
+ ///
+ [JsonMember]
+ public InspectorGridMode inspectorGridMode = InspectorGridMode.Grid;
+
+ ///
+ /// Determines how the size of each hexagon is set in the inspector.
+ /// For hexagons the normal nodeSize field doesn't really correspond to anything specific on the hexagon's geometry, so this enum is used to give the user the opportunity to adjust more concrete dimensions of the hexagons
+ /// without having to pull out a calculator to calculate all the square roots and complicated conversion factors.
+ ///
+ /// This field is only used in the graph inspector, the field will always use the same internal units.
+ /// If you want to set the node size through code then you can use .
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// See:
+ /// See:
+ /// See:
+ ///
+ [JsonMember]
+ public InspectorGridHexagonNodeSize inspectorHexagonSizeMode = InspectorGridHexagonNodeSize.Width;
+
+ ///
+ /// Width of the grid in nodes.
+ ///
+ /// Grid graphs are typically anywhere from 10-500 nodes wide. But it can go up to 1024 nodes wide by default.
+ /// Consider using a recast graph instead, if you find yourself needing a very high resolution grid.
+ ///
+ /// This value will be clamped to at most 1024 unless ASTAR_LARGER_GRIDS has been enabled in the A* Inspector -> Optimizations tab.
+ ///
+ /// See:
+ /// See: SetDimensions
+ ///
+ public int width;
+
+ ///
+ /// Depth (height) of the grid in nodes.
+ ///
+ /// Grid graphs are typically anywhere from 10-500 nodes wide. But it can go up to 1024 nodes wide by default.
+ /// Consider using a recast graph instead, if you find yourself needing a very high resolution grid.
+ ///
+ /// This value will be clamped to at most 1024 unless ASTAR_LARGER_GRIDS has been enabled in the A* Inspector -> Optimizations tab.
+ ///
+ /// See:
+ /// See: SetDimensions
+ ///
+ public int depth;
+
+ ///
+ /// Scaling of the graph along the X axis.
+ /// This should be used if you want different scales on the X and Y axis of the grid
+ ///
+ /// This option is only visible in the inspector if the graph shape is set to isometric or advanced.
+ ///
+ [JsonMember]
+ public float aspectRatio = 1F;
+
+ ///
+ /// Angle in degrees to use for the isometric projection.
+ /// If you are making a 2D isometric game, you may want to use this parameter to adjust the layout of the graph to match your game.
+ /// This will essentially scale the graph along one of its diagonals to produce something like this:
+ ///
+ /// A perspective view of an isometric graph.
+ /// [Open online documentation to see images]
+ ///
+ /// A top down view of an isometric graph. Note that the graph is entirely 2D, there is no perspective in this image.
+ /// [Open online documentation to see images]
+ ///
+ /// For commonly used values see and .
+ ///
+ /// Usually the angle that you want to use is either 30 degrees (alternatively 90-30 = 60 degrees) or atan(1/sqrt(2)) which is approximately 35.264 degrees (alternatively 90 - 35.264 = 54.736 degrees).
+ /// You might also want to rotate the graph plus or minus 45 degrees around the Y axis to get the oritientation required for your game.
+ ///
+ /// You can read more about it on the wikipedia page linked below.
+ ///
+ /// See: http://en.wikipedia.org/wiki/Isometric_projection
+ /// See: https://en.wikipedia.org/wiki/Isometric_graphics_in_video_games_and_pixel_art
+ /// See: rotation
+ ///
+ /// This option is only visible in the inspector if the graph shape is set to isometric or advanced.
+ ///
+ [JsonMember]
+ public float isometricAngle;
+
+ /// Commonly used value for
+ public static readonly float StandardIsometricAngle = 90-Mathf.Atan(1/Mathf.Sqrt(2))*Mathf.Rad2Deg;
+
+ /// Commonly used value for
+ public static readonly float StandardDimetricAngle = Mathf.Acos(1/2f)*Mathf.Rad2Deg;
+
+ ///
+ /// If true, all edge costs will be set to the same value.
+ /// If false, diagonals will cost more.
+ /// This is useful for a hexagon graph where the diagonals are actually the same length as the
+ /// normal edges (since the graph has been skewed)
+ ///
+ [JsonMember]
+ public bool uniformEdgeCosts;
+
+ ///
+ /// Rotation of the grid in degrees.
+ ///
+ /// The nodes are laid out along the X and Z axes of the rotation.
+ ///
+ /// For a 2D game, the rotation will typically be set to (-90, 270, 90).
+ /// If the graph is aligned with the XY plane, the inspector will automatically switch to 2D mode.
+ ///
+ /// See:
+ ///
+ [JsonMember]
+ public Vector3 rotation;
+
+ ///
+ /// Center point of the grid in world space.
+ ///
+ /// The graph can be positioned anywhere in the world.
+ ///
+ /// See:
+ ///
+ [JsonMember]
+ public Vector3 center;
+
+ /// Size of the grid. Can be negative or smaller than
+ [JsonMember]
+ public Vector2 unclampedSize = new Vector2(10, 10);
+
+ ///
+ /// Size of one node in world units.
+ ///
+ /// For a grid layout, this is the length of the sides of the grid squares.
+ ///
+ /// For a hexagonal layout, this value does not correspond to any specific dimension of the hexagon.
+ /// Instead you can convert it to a dimension on a hexagon using .
+ ///
+ /// See:
+ ///
+ [JsonMember]
+ public float nodeSize = 1;
+
+ /// Settings on how to check for walkability and height
+ [JsonMember]
+ public GraphCollision collision = new GraphCollision();
+
+ ///
+ /// The max y coordinate difference between two nodes to enable a connection.
+ /// Set to 0 to ignore the value.
+ ///
+ /// This affects for example how the graph is generated around ledges and stairs.
+ ///
+ /// See:
+ /// Version: Was previously called maxClimb
+ ///
+ [JsonMember]
+ public float maxStepHeight = 0.4F;
+
+ ///
+ /// The max y coordinate difference between two nodes to enable a connection.
+ /// Deprecated: This field has been renamed to
+ ///
+ [System.Obsolete("This field has been renamed to maxStepHeight")]
+ public float maxClimb {
+ get {
+ return maxStepHeight;
+ }
+ set {
+ maxStepHeight = value;
+ }
+ }
+
+ ///
+ /// Take the slope into account for .
+ ///
+ /// When this is enabled the normals of the terrain will be used to make more accurate estimates of how large the steps are between adjacent nodes.
+ ///
+ /// When this is disabled then calculated step between two nodes is their y coordinate difference. This may be inaccurate, especially at the start of steep slopes.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// In the image below you can see an example of what happens near a ramp.
+ /// In the topmost image the ramp is not connected with the rest of the graph which is obviously not what we want.
+ /// In the middle image an attempt has been made to raise the max step height while keeping disabled. However this causes too many connections to be added.
+ /// The agent should not be able to go up the ramp from the side.
+ /// Finally in the bottommost image the has been restored to the original value but has been enabled. This configuration handles the ramp in a much smarter way.
+ /// Note that all the values in the image are just example values, they may be different for your scene.
+ /// [Open online documentation to see images]
+ ///
+ /// See:
+ ///
+ [JsonMember]
+ public bool maxStepUsesSlope = true;
+
+ /// The max slope in degrees for a node to be walkable.
+ [JsonMember]
+ public float maxSlope = 90;
+
+ ///
+ /// Use heigh raycasting normal for max slope calculation.
+ /// True if is less than 90 degrees.
+ ///
+ protected bool useRaycastNormal { get { return Math.Abs(90-maxSlope) > float.Epsilon; } }
+
+ ///
+ /// Number of times to erode the graph.
+ ///
+ /// The graph can be eroded to add extra margin to obstacles.
+ /// It is very convenient if your graph contains ledges, and where the walkable nodes without erosion are too close to the edge.
+ ///
+ /// Below is an image showing a graph with 0, 1 and 2 erosion iterations:
+ /// [Open online documentation to see images]
+ ///
+ /// Note: A high number of erosion iterations can slow down graph updates during runtime.
+ /// This is because the region that is updated needs to be expanded by the erosion iterations times two to account for possible changes in the border nodes.
+ ///
+ /// See: erosionUseTags
+ ///
+ [JsonMember]
+ public int erodeIterations;
+
+ ///
+ /// Use tags instead of walkability for erosion.
+ /// Tags will be used for erosion instead of marking nodes as unwalkable. The nodes will be marked with tags in an increasing order starting with the tag .
+ /// Debug with the Tags mode to see the effect. With this enabled you can in effect set how close different AIs are allowed to get to walls using the Valid Tags field on the Seeker component.
+ /// [Open online documentation to see images]
+ /// [Open online documentation to see images]
+ /// See: erosionFirstTag
+ ///
+ [JsonMember]
+ public bool erosionUseTags;
+
+ ///
+ /// Tag to start from when using tags for erosion.
+ /// See:
+ /// See:
+ ///
+ [JsonMember]
+ public int erosionFirstTag = 1;
+
+ ///
+ /// Bitmask for which tags can be overwritten by erosion tags.
+ ///
+ /// When is enabled, nodes near unwalkable nodes will be marked with tags.
+ /// However, if these nodes already have tags, you may want the custom tag to take precedence.
+ /// This mask controls which tags are allowed to be replaced by the new erosion tags.
+ ///
+ /// In the image below, erosion has applied tags which have overwritten both the base tag (tag 0) and the custom tag set on the nodes (shown in red).
+ /// [Open online documentation to see images]
+ ///
+ /// In the image below, erosion has applied tags, but it was not allowed to overwrite the custom tag set on the nodes (shown in red).
+ /// [Open online documentation to see images]
+ ///
+ /// See:
+ /// See:
+ /// See: This field is a bit mask. See: bitmasks (view in online documentation for working links)
+ ///
+ [JsonMember]
+ public int erosionTagsPrecedenceMask = -1;
+
+ ///
+ /// Number of neighbours for each node.
+ /// Either four, six, eight connections per node.
+ ///
+ /// Six connections is primarily for hexagonal graphs.
+ ///
+ [JsonMember]
+ public NumNeighbours neighbours = NumNeighbours.Eight;
+
+ ///
+ /// If disabled, will not cut corners on obstacles.
+ /// If this is true, and is set to Eight, obstacle corners are allowed to be cut by a connection.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ [JsonMember]
+ public bool cutCorners = true;
+
+ ///
+ /// Offset for the position when calculating penalty.
+ /// Deprecated: Use the RuleElevationPenalty class instead
+ /// See: penaltyPosition
+ ///
+ [JsonMember]
+ [System.Obsolete("Use the RuleElevationPenalty class instead")]
+ public float penaltyPositionOffset;
+
+ ///
+ /// Use position (y-coordinate) to calculate penalty.
+ /// Deprecated: Use the RuleElevationPenalty class instead
+ ///
+ [JsonMember]
+ [System.Obsolete("Use the RuleElevationPenalty class instead")]
+ public bool penaltyPosition;
+
+ ///
+ /// Scale factor for penalty when calculating from position.
+ /// Deprecated: Use the class instead
+ /// See: penaltyPosition
+ ///
+ [JsonMember]
+ [System.Obsolete("Use the RuleElevationPenalty class instead")]
+ public float penaltyPositionFactor = 1F;
+
+ /// Deprecated: Use the class instead
+ [JsonMember]
+ [System.Obsolete("Use the RuleAnglePenalty class instead")]
+ public bool penaltyAngle;
+
+ ///
+ /// How much penalty is applied depending on the slope of the terrain.
+ /// At a 90 degree slope (not that exactly 90 degree slopes can occur, but almost 90 degree), this penalty is applied.
+ /// At a 45 degree slope, half of this is applied and so on.
+ /// Note that you may require very large values, a value of 1000 is equivalent to the cost of moving 1 world unit.
+ ///
+ /// Deprecated: Use the class instead
+ ///
+ [JsonMember]
+ [System.Obsolete("Use the RuleAnglePenalty class instead")]
+ public float penaltyAngleFactor = 100F;
+
+ ///
+ /// How much extra to penalize very steep angles.
+ ///
+ /// Deprecated: Use the class instead
+ ///
+ [JsonMember]
+ [System.Obsolete("Use the RuleAnglePenalty class instead")]
+ public float penaltyAnglePower = 1;
+
+ ///
+ /// Additional rules to use when scanning the grid graph.
+ ///
+ ///
+ /// // Get the first grid graph in the scene
+ /// var gridGraph = AstarPath.active.data.gridGraph;
+ ///
+ /// gridGraph.rules.AddRule(new Pathfinding.Graphs.Grid.Rules.RuleAnglePenalty {
+ /// penaltyScale = 10000,
+ /// curve = AnimationCurve.Linear(0, 0, 90, 1),
+ /// });
+ ///
+ ///
+ /// See:
+ /// See:
+ ///
+ [JsonMember]
+ public GridGraphRules rules = new GridGraphRules();
+
+ /// Show an outline of the grid nodes in the Unity Editor
+ [JsonMember]
+ public bool showMeshOutline = true;
+
+ /// Show the connections between the grid nodes in the Unity Editor
+ [JsonMember]
+ public bool showNodeConnections;
+
+ /// Show the surface of the graph. Each node will be drawn as a square (unless e.g hexagon graph mode has been enabled).
+ [JsonMember]
+ public bool showMeshSurface = true;
+
+ ///
+ /// Holds settings for using a texture as source for a grid graph.
+ /// Texure data can be used for fine grained control over how the graph will look.
+ /// It can be used for positioning, penalty and walkability control.
+ /// Below is a screenshot of a grid graph with a penalty map applied.
+ /// It has the effect of the AI taking the longer path along the green (low penalty) areas.
+ /// [Open online documentation to see images]
+ /// Color data is got as 0...255 values.
+ ///
+ /// Warning: Can only be used with Unity 3.4 and up
+ ///
+ /// Deprecated: Use the RuleTexture class instead
+ ///
+ [JsonMember]
+ [System.Obsolete("Use the RuleTexture class instead")]
+ public TextureData textureData = new TextureData();
+
+ ///
+ /// Size of the grid. Will always be positive and larger than .
+ /// See:
+ ///
+ public Vector2 size { get; protected set; }
+
+ /* End collision and stuff */
+
+ ///
+ /// Index offset to get neighbour nodes. Added to a node's index to get a neighbour node index.
+ ///
+ ///
+ /// Z
+ /// |
+ /// |
+ ///
+ /// 6 2 5
+ /// \ | /
+ /// -- 3 - X - 1 ----- X
+ /// / | \
+ /// 7 0 4
+ ///
+ /// |
+ /// |
+ ///
+ ///
+ [System.NonSerialized]
+ public readonly int[] neighbourOffsets = new int[8];
+
+ ///
+ /// Costs to neighbour nodes.
+ ///
+ /// See .
+ ///
+ [System.NonSerialized]
+ public readonly uint[] neighbourCosts = new uint[8];
+
+ /// Offsets in the X direction for neighbour nodes. Only 1, 0 or -1
+ public static readonly int[] neighbourXOffsets = { 0, 1, 0, -1, 1, 1, -1, -1 };
+
+ /// Offsets in the Z direction for neighbour nodes. Only 1, 0 or -1
+ public static readonly int[] neighbourZOffsets = { -1, 0, 1, 0, -1, 1, 1, -1 };
+
+ /// Which neighbours are going to be used when =6
+ internal static readonly int[] hexagonNeighbourIndices = { 0, 1, 5, 2, 3, 7 };
+
+ /// Which neighbours are going to be used when =4
+ internal static readonly int[] axisAlignedNeighbourIndices = { 0, 1, 2, 3 };
+
+ /// Which neighbours are going to be used when =8
+ internal static readonly int[] allNeighbourIndices = { 0, 1, 2, 3, 4, 5, 6, 7 };
+
+ ///
+ /// Neighbour direction indices to use depending on how many neighbours each node should have.
+ ///
+ /// The following illustration shows the direction indices for all 8 neighbours,
+ ///
+ /// Z
+ /// |
+ /// |
+ ///
+ /// 6 2 5
+ /// \ | /
+ /// -- 3 - X - 1 ----- X
+ /// / | \
+ /// 7 0 4
+ ///
+ /// |
+ /// |
+ ///
+ ///
+ /// For other neighbour counts, a subset of these will be returned.
+ ///
+ /// These can then be used to index into the , , , and arrays.
+ ///
+ /// See:
+ /// See:
+ ///
+ public static int[] GetNeighbourDirections (NumNeighbours neighbours) {
+ switch (neighbours) {
+ case NumNeighbours.Four:
+ return axisAlignedNeighbourIndices;
+ case NumNeighbours.Six:
+ return hexagonNeighbourIndices;
+ default:
+ return allNeighbourIndices;
+ }
+ }
+
+ ///
+ /// Mask based on hexagonNeighbourIndices.
+ /// This indicates which connections (out of the 8 standard ones) should be enabled for hexagonal graphs.
+ ///
+ ///
+ /// int hexagonConnectionMask = 0;
+ /// for (int i = 0; i < GridGraph.hexagonNeighbourIndices.Length; i++) hexagonConnectionMask |= 1 << GridGraph.hexagonNeighbourIndices[i];
+ ///
+ ///
+ internal const int HexagonConnectionMask = 0b010101111;
+
+ ///
+ /// All nodes in this graph.
+ /// Nodes are laid out row by row.
+ ///
+ /// The first node has grid coordinates X=0, Z=0, the second one X=1, Z=0
+ /// the last one has grid coordinates X=width-1, Z=depth-1.
+ ///
+ ///
+ /// var gg = AstarPath.active.data.gridGraph;
+ /// int x = 5;
+ /// int z = 8;
+ /// GridNodeBase node = gg.nodes[z*gg.width + x];
+ ///
+ ///
+ /// See:
+ /// See:
+ ///
+ public GridNodeBase[] nodes;
+
+ ///
+ /// Internal data for each node.
+ ///
+ /// It also contains some data not stored in the node objects, such as normals for the surface of the graph.
+ /// These normals need to be saved when the option is enabled for graph updates to work.
+ ///
+ protected GridGraphNodeData nodeData;
+
+ internal ref GridGraphNodeData nodeDataRef => ref nodeData;
+
+ ///
+ /// Determines how the graph transforms graph space to world space.
+ /// See:
+ ///
+ public GraphTransform transform { get; private set; } = new GraphTransform(Matrix4x4.identity);
+
+ ///
+ /// Delegate which creates and returns a single instance of the node type for this graph.
+ /// This may be set in the constructor for graphs inheriting from the GridGraph to change the node type of the graph.
+ ///
+ protected System.Func newGridNodeDelegate = () => new GridNode();
+
+ ///
+ /// Get or set if the graph should be in 2D mode.
+ ///
+ /// Note: This is just a convenience property, this property will actually read/modify the of the graph. A rotation aligned with the 2D plane is what determines if the graph is 2D or not.
+ ///
+ /// See: You can also set if the graph should use 2D physics using `this.collision.use2D` ().
+ ///
+ public bool is2D {
+ get {
+ return Quaternion.Euler(this.rotation) * Vector3.up == -Vector3.forward;
+ }
+ set {
+ if (value != is2D) {
+ this.rotation = value ? new Vector3(this.rotation.y - 90, 270, 90) : new Vector3(0, this.rotation.x + 90, 0);
+ }
+ }
+ }
+
+ public override bool isScanned => nodes != null;
+
+ protected virtual GridNodeBase[] AllocateNodesJob (int size, out JobHandle dependency) {
+ var newNodes = new GridNodeBase[size];
+
+ dependency = active.AllocateNodes(newNodes, size, newGridNodeDelegate, 1);
+ return newNodes;
+ }
+
+ /// Used for using a texture as a source for a grid graph.
+ public class TextureData {
+ public bool enabled;
+ public Texture2D source;
+ public float[] factors = new float[3];
+ public ChannelUse[] channels = new ChannelUse[3];
+
+ Color32[] data;
+
+ /// Reads texture data
+ public void Initialize () {
+ if (enabled && source != null) {
+ for (int i = 0; i < channels.Length; i++) {
+ if (channels[i] != ChannelUse.None) {
+ try {
+ data = source.GetPixels32();
+ } catch (UnityException e) {
+ Debug.LogWarning(e.ToString());
+ data = null;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ /// Applies the texture to the node
+ public void Apply (GridNode node, int x, int z) {
+ if (enabled && data != null && x < source.width && z < source.height) {
+ Color32 col = data[z*source.width+x];
+
+ if (channels[0] != ChannelUse.None) {
+ ApplyChannel(node, x, z, col.r, channels[0], factors[0]);
+ }
+
+ if (channels[1] != ChannelUse.None) {
+ ApplyChannel(node, x, z, col.g, channels[1], factors[1]);
+ }
+
+ if (channels[2] != ChannelUse.None) {
+ ApplyChannel(node, x, z, col.b, channels[2], factors[2]);
+ }
+
+ node.WalkableErosion = node.Walkable;
+ }
+ }
+
+ /// Applies a value to the node using the specified ChannelUse
+ void ApplyChannel (GridNode node, int x, int z, int value, ChannelUse channelUse, float factor) {
+ switch (channelUse) {
+ case ChannelUse.Penalty:
+ node.Penalty += (uint)Mathf.RoundToInt(value*factor);
+ break;
+ case ChannelUse.Position:
+ node.position = GridNode.GetGridGraph(node.GraphIndex).GraphPointToWorld(x, z, value);
+ break;
+ case ChannelUse.WalkablePenalty:
+ if (value == 0) {
+ node.Walkable = false;
+ } else {
+ node.Penalty += (uint)Mathf.RoundToInt((value-1)*factor);
+ }
+ break;
+ }
+ }
+
+ public enum ChannelUse {
+ None,
+ Penalty,
+ Position,
+ WalkablePenalty,
+ }
+ }
+
+ public override void RelocateNodes (Matrix4x4 deltaMatrix) {
+ // It just makes a lot more sense to use the other overload and for that case we don't have to serialize the matrix
+ throw new System.Exception("This method cannot be used for Grid Graphs. Please use the other overload of RelocateNodes instead");
+ }
+
+ ///
+ /// Relocate the grid graph using new settings.
+ /// This will move all nodes in the graph to new positions which matches the new settings.
+ ///
+ ///
+ /// // Move the graph to the origin, with no rotation, and with a node size of 1.0
+ /// var gg = AstarPath.active.data.gridGraph;
+ /// gg.RelocateNodes(center: Vector3.zero, rotation: Quaternion.identity, nodeSize: 1.0f);
+ ///
+ ///
+ public void RelocateNodes (Vector3 center, Quaternion rotation, float nodeSize, float aspectRatio = 1, float isometricAngle = 0) {
+ var previousTransform = transform;
+
+ this.center = center;
+ this.rotation = rotation.eulerAngles;
+ this.aspectRatio = aspectRatio;
+ this.isometricAngle = isometricAngle;
+
+ DirtyBounds(bounds);
+ SetDimensions(width, depth, nodeSize);
+
+ GetNodes(node => {
+ var gnode = node as GridNodeBase;
+ var height = previousTransform.InverseTransform((Vector3)node.position).y;
+ node.position = GraphPointToWorld(gnode.XCoordinateInGrid, gnode.ZCoordinateInGrid, height);
+ });
+ DirtyBounds(bounds);
+ }
+
+ ///
+ /// True if the point is inside the bounding box of this graph.
+ ///
+ /// For a graph that uses 2D physics, or if height testing is disabled, then the graph is treated as infinitely tall.
+ /// Otherwise, the height of the graph is determined by .
+ ///
+ /// Note: For an unscanned graph, this will always return false.
+ ///
+ public override bool IsInsideBounds (Vector3 point) {
+ if (this.nodes == null) return false;
+
+ var local = transform.InverseTransform(point);
+ if (!(local.x >= 0 && local.z >= 0 && local.x <= width && local.z <= depth)) return false;
+
+ if (collision.use2D || !collision.heightCheck) return true;
+
+ return local.y >= 0 && local.y <= collision.fromHeight;
+ }
+
+ ///
+ /// World bounding box for the graph.
+ ///
+ /// This always contains the whole graph.
+ ///
+ /// Note: Since this is an axis-aligned bounding box, it may not be particularly tight if the graph is significantly rotated.
+ ///
+ public override Bounds bounds => transform.Transform(new Bounds(new Vector3(width*0.5f, collision.fromHeight*0.5f, depth*0.5f), new Vector3(width, collision.fromHeight, depth)));
+
+ ///
+ /// Transform a point in graph space to world space.
+ /// This will give you the node position for the node at the given x and z coordinate
+ /// if it is at the specified height above the base of the graph.
+ ///
+ public Int3 GraphPointToWorld (int x, int z, float height) {
+ return (Int3)transform.Transform(new Vector3(x+0.5f, height, z+0.5f));
+ }
+
+ ///
+ /// Converts a hexagon dimension to a node size.
+ ///
+ /// A hexagon can be defined using either its diameter, or width, none of which are the same as the used internally to define the size of a single node.
+ ///
+ /// See:
+ ///
+ public static float ConvertHexagonSizeToNodeSize (InspectorGridHexagonNodeSize mode, float value) {
+ if (mode == InspectorGridHexagonNodeSize.Diameter) value *= 1.5f/(float)System.Math.Sqrt(2.0f);
+ else if (mode == InspectorGridHexagonNodeSize.Width) value *= (float)System.Math.Sqrt(3.0f/2.0f);
+ return value;
+ }
+
+ ///
+ /// Converts an internal node size to a hexagon dimension.
+ ///
+ /// A hexagon can be defined using either its diameter, or width, none of which are the same as the used internally to define the size of a single node.
+ ///
+ /// See: ConvertHexagonSizeToNodeSize
+ ///
+ public static float ConvertNodeSizeToHexagonSize (InspectorGridHexagonNodeSize mode, float value) {
+ if (mode == InspectorGridHexagonNodeSize.Diameter) value *= (float)System.Math.Sqrt(2.0f)/1.5f;
+ else if (mode == InspectorGridHexagonNodeSize.Width) value *= (float)System.Math.Sqrt(2.0f/3.0f);
+ return value;
+ }
+
+ public int Width {
+ get {
+ return width;
+ }
+ set {
+ width = value;
+ }
+ }
+ public int Depth {
+ get {
+ return depth;
+ }
+ set {
+ depth = value;
+ }
+ }
+
+ ///
+ /// Default cost of moving one node in a particular direction.
+ ///
+ /// Note: You can only call this after the graph has been scanned. Otherwise it will return zero.
+ ///
+ ///
+ /// Z
+ /// |
+ /// |
+ ///
+ /// 6 2 5
+ /// \ | /
+ /// -- 3 - X - 1 ----- X
+ /// / | \
+ /// 7 0 4
+ ///
+ /// |
+ /// |
+ ///
+ ///
+ public uint GetConnectionCost (int dir) {
+ return neighbourCosts[dir];
+ }
+
+ ///
+ /// Changes the grid shape.
+ /// This is equivalent to changing the 'shape' dropdown in the grid graph inspector.
+ ///
+ /// Calling this method will set , , and
+ /// to appropriate values for that shape.
+ ///
+ /// Note: Setting the shape to does not do anything except set the field.
+ ///
+ /// See:
+ ///
+ public void SetGridShape (InspectorGridMode shape) {
+ switch (shape) {
+ case InspectorGridMode.Grid:
+ isometricAngle = 0;
+ aspectRatio = 1;
+ uniformEdgeCosts = false;
+ if (neighbours == NumNeighbours.Six) neighbours = NumNeighbours.Eight;
+ break;
+ case InspectorGridMode.Hexagonal:
+ isometricAngle = StandardIsometricAngle;
+ aspectRatio = 1;
+ uniformEdgeCosts = true;
+ neighbours = NumNeighbours.Six;
+ break;
+ case InspectorGridMode.IsometricGrid:
+ uniformEdgeCosts = false;
+ if (neighbours == NumNeighbours.Six) neighbours = NumNeighbours.Eight;
+ isometricAngle = StandardIsometricAngle;
+ break;
+ case InspectorGridMode.Advanced:
+ default:
+ break;
+ }
+ inspectorGridMode = shape;
+ }
+
+ ///
+ /// Aligns this grid to a given tilemap or grid layout.
+ ///
+ /// This is very handy if your game uses a tilemap for rendering and you want to make sure the graph is laid out exactly the same.
+ /// Matching grid parameters manually can be quite tricky in some cases.
+ ///
+ /// The inspector will automatically show a button to align to a tilemap if one is detected in the scene.
+ /// If no tilemap is detected, the button be hidden.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// See: tilemaps (view in online documentation for working links)
+ ///
+ public void AlignToTilemap (UnityEngine.GridLayout grid) {
+ var origin = grid.CellToWorld(new Vector3Int(0, 0, 0));
+ var dx = grid.CellToWorld(new Vector3Int(1, 0, 0)) - origin;
+ var dy = grid.CellToWorld(new Vector3Int(0, 1, 0)) - origin;
+
+ switch (grid.cellLayout) {
+ case GridLayout.CellLayout.Rectangle: {
+ var rot = new quaternion(new float3x3(
+ dx.normalized,
+ -Vector3.Cross(dx, dy).normalized,
+ dy.normalized
+ ));
+
+ this.nodeSize = dy.magnitude;
+ this.isometricAngle = 0f;
+ this.aspectRatio = dx.magnitude / this.nodeSize;
+ if (!float.IsFinite(this.aspectRatio)) this.aspectRatio = 1.0f;
+ this.rotation = ((Quaternion)rot).eulerAngles;
+ this.uniformEdgeCosts = false;
+ if (this.neighbours == NumNeighbours.Six) this.neighbours = NumNeighbours.Eight;
+ this.inspectorGridMode = InspectorGridMode.Grid;
+ break;
+ }
+ case GridLayout.CellLayout.Isometric:
+ var d1 = grid.CellToWorld(new Vector3Int(1, 1, 0)) - origin;
+ var d2 = grid.CellToWorld(new Vector3Int(1, -1, 0)) - origin;
+ if (d1.magnitude > d2.magnitude) {
+ Memory.Swap(ref d1, ref d2);
+ }
+ var rot2 = math.mul(new quaternion(new float3x3(
+ d2.normalized,
+ -Vector3.Cross(d2, d1).normalized,
+ d1.normalized
+ )), quaternion.RotateY(-math.PI * 0.25f));
+
+ this.isometricAngle = Mathf.Acos(d1.magnitude / d2.magnitude) * Mathf.Rad2Deg;
+ this.nodeSize = d2.magnitude / Mathf.Sqrt(2.0f);
+ this.rotation = ((Quaternion)rot2).eulerAngles;
+ this.uniformEdgeCosts = false;
+ this.aspectRatio = 1.0f;
+ if (this.neighbours == NumNeighbours.Six) this.neighbours = NumNeighbours.Eight;
+ this.inspectorGridMode = InspectorGridMode.IsometricGrid;
+ break;
+ case GridLayout.CellLayout.Hexagon:
+ // Note: Unity does not use a mathematically perfect hexagonal layout by default. The cells can be squished vertically or horizontally.
+ var d12 = grid.CellToWorld(new Vector3Int(1, 0, 0)) - origin;
+ var d32 = grid.CellToWorld(new Vector3Int(-1, 1, 0)) - origin;
+ this.aspectRatio = (d12.magnitude / Mathf.Sqrt(2f/3f)) / (Vector3.Cross(d12.normalized, d32).magnitude / (1.5f * Mathf.Sqrt(2)/3f));
+ this.nodeSize = GridGraph.ConvertHexagonSizeToNodeSize(InspectorGridHexagonNodeSize.Width, d12.magnitude / aspectRatio);
+
+ var crossAxis = -Vector3.Cross(d12, Vector3.Cross(d12, d32));
+
+ var rot3 = new quaternion(new float3x3(
+ d12.normalized,
+ -Vector3.Cross(d12, crossAxis).normalized,
+ crossAxis.normalized
+ ));
+
+ this.rotation = ((Quaternion)rot3).eulerAngles;
+ this.uniformEdgeCosts = true;
+ this.neighbours = NumNeighbours.Six;
+ this.inspectorGridMode = InspectorGridMode.Hexagonal;
+ break;
+ }
+
+ // Snap center to the closest grid point
+ UpdateTransform();
+ var layoutCellPivotIsCenter = grid.cellLayout == GridLayout.CellLayout.Hexagon;
+ var offset = new Vector3(((width % 2) == 0) != layoutCellPivotIsCenter ? 0 : 0.5f, 0, ((depth % 2) == 0) != layoutCellPivotIsCenter ? 0f : 0.5f);
+ var worldOffset = transform.TransformVector(offset);
+ var centerCell = grid.WorldToCell(center + worldOffset);
+ centerCell.z = 0;
+ center = grid.CellToWorld(centerCell) - worldOffset;
+ if (float.IsNaN(center.x)) center = Vector3.zero;
+ UpdateTransform();
+ }
+
+ ///
+ /// Updates from , and values.
+ /// Also .
+ /// Note: This does not rescan the graph, that must be done with Scan
+ ///
+ /// You should use this method instead of setting the and fields
+ /// as the grid dimensions are not defined by the and variables but by
+ /// the and variables.
+ ///
+ ///
+ /// var gg = AstarPath.active.data.gridGraph;
+ /// var width = 80;
+ /// var depth = 60;
+ /// var nodeSize = 1.0f;
+ ///
+ /// gg.SetDimensions(width, depth, nodeSize);
+ ///
+ /// // Recalculate the graph
+ /// AstarPath.active.Scan();
+ ///
+ ///
+ public void SetDimensions (int width, int depth, float nodeSize) {
+ unclampedSize = new Vector2(width, depth)*nodeSize;
+ this.nodeSize = nodeSize;
+ UpdateTransform();
+ }
+
+ ///
+ /// Updates the field which transforms graph space to world space.
+ /// In graph space all nodes are laid out in the XZ plane with the first node having a corner in the origin.
+ /// One unit in graph space is one node so the first node in the graph is at (0.5,0) the second one at (1.5,0) etc.
+ ///
+ /// This takes the current values of the parameters such as position and rotation into account.
+ /// The transform that was used the last time the graph was scanned is stored in the field.
+ ///
+ /// The field is calculated using this method when the graph is scanned.
+ /// The width, depth variables are also updated based on the field.
+ ///
+ public void UpdateTransform () {
+ CalculateDimensions(out width, out depth, out nodeSize);
+ transform = CalculateTransform();
+ }
+
+ ///
+ /// Returns a new transform which transforms graph space to world space.
+ /// Does not update the field.
+ /// See:
+ ///
+ public GraphTransform CalculateTransform () {
+ CalculateDimensions(out var newWidth, out var newDepth, out var newNodeSize);
+
+ if (this.neighbours == NumNeighbours.Six) {
+ var ax1 = new Vector3(newNodeSize*aspectRatio*Mathf.Sqrt(2f/3f), 0, 0);
+ var ax2 = new Vector3(0, 1, 0);
+ var ax3 = new Vector3(-aspectRatio * newNodeSize * 0.5f * Mathf.Sqrt(2f/3f), 0, newNodeSize * (1.5f * Mathf.Sqrt(2)/3f));
+ var m = new Matrix4x4(
+ (Vector4)ax1,
+ (Vector4)ax2,
+ (Vector4)ax3,
+ new Vector4(0, 0, 0, 1)
+ );
+
+ var boundsMatrix = Matrix4x4.TRS(center, Quaternion.Euler(rotation), Vector3.one) * m;
+
+ // Generate a matrix where Vector3.zero is the corner of the graph instead of the center
+ m = Matrix4x4.TRS(boundsMatrix.MultiplyPoint3x4(-new Vector3(newWidth, 0, newDepth)*0.5F), Quaternion.Euler(rotation), Vector3.one) * m;
+ return new GraphTransform(m);
+ } else {
+ // Generate a matrix which shrinks the graph along the main diagonal
+ var squishFactor = new Vector3(Mathf.Cos(Mathf.Deg2Rad*isometricAngle), 1, 1);
+ var isometricMatrix = Matrix4x4.Scale(new Vector3(newNodeSize*aspectRatio, 1, newNodeSize));
+ var squishAngle = Mathf.Atan2(newNodeSize, newNodeSize*aspectRatio) * Mathf.Rad2Deg;
+ isometricMatrix = Matrix4x4.Rotate(Quaternion.Euler(0, -squishAngle, 0)) * Matrix4x4.Scale(squishFactor) * Matrix4x4.Rotate(Quaternion.Euler(0, squishAngle, 0)) * isometricMatrix;
+
+ // Generate a matrix for the bounds of the graph
+ // This moves a point to the correct offset in the world and the correct rotation and the aspect ratio and isometric angle is taken into account
+ var boundsMatrix = Matrix4x4.TRS(center, Quaternion.Euler(rotation), Vector3.one) * isometricMatrix;
+
+ // Generate a matrix where Vector3.zero is the corner of the graph instead of the center
+ // The unit is nodes here (so (0.5,0,0.5) is the position of the first node and (1.5,0,0.5) is the position of the second node)
+ // 0.5 is added since this is the node center, not its corner. In graph space a node has a size of 1
+ var m = Matrix4x4.TRS(boundsMatrix.MultiplyPoint3x4(-new Vector3(newWidth, 0, newDepth)*0.5F), Quaternion.Euler(rotation), Vector3.one) * isometricMatrix;
+
+ return new GraphTransform(m);
+ }
+ }
+
+ ///
+ /// Calculates the width/depth of the graph from and .
+ /// The node size may be changed due to constraints that the width/depth is not
+ /// allowed to be larger than 1024 (artificial limit).
+ ///
+ void CalculateDimensions (out int width, out int depth, out float nodeSize) {
+ var newSize = unclampedSize;
+
+ // Make sure size is positive
+ newSize.x *= Mathf.Sign(newSize.x);
+ newSize.y *= Mathf.Sign(newSize.y);
+
+#if !ASTAR_LARGER_GRIDS
+ // Clamp the nodeSize so that the graph is never larger than 1024*1024
+ nodeSize = Mathf.Max(this.nodeSize, newSize.x/1024f);
+ nodeSize = Mathf.Max(this.nodeSize, newSize.y/1024f);
+#else
+ nodeSize = Mathf.Max(this.nodeSize, newSize.x/8192f);
+ nodeSize = Mathf.Max(this.nodeSize, newSize.y/8192f);
+#endif
+
+ // Prevent the graph to become smaller than a single node
+ newSize.x = newSize.x < nodeSize ? nodeSize : newSize.x;
+ newSize.y = newSize.y < nodeSize ? nodeSize : newSize.y;
+
+ size = newSize;
+
+ // Calculate the number of nodes along each side
+ width = Mathf.FloorToInt(size.x / nodeSize);
+ depth = Mathf.FloorToInt(size.y / nodeSize);
+
+ // Take care of numerical edge cases
+ if (Mathf.Approximately(size.x / nodeSize, Mathf.CeilToInt(size.x / nodeSize))) {
+ width = Mathf.CeilToInt(size.x / nodeSize);
+ }
+
+ if (Mathf.Approximately(size.y / nodeSize, Mathf.CeilToInt(size.y / nodeSize))) {
+ depth = Mathf.CeilToInt(size.y / nodeSize);
+ }
+ }
+
+ public override float NearestNodeDistanceSqrLowerBound (Vector3 position, NNConstraint constraint) {
+ if (nodes == null || depth*width*LayerCount != nodes.Length) {
+ return float.PositiveInfinity;
+ }
+
+ position = transform.InverseTransform(position);
+
+ float xf = position.x;
+ float zf = position.z;
+ float xc = Mathf.Clamp(xf, 0, width);
+ float zc = Mathf.Clamp(zf, 0, depth);
+
+ // Node y coordinates (in graph space) may range from -inf to +inf theoretically, so we only use the xz distance to calculate the lower bound
+ return (xf-xc)*(xf-xc) + (zf-zc)*(zf-zc);
+ }
+
+ protected virtual GridNodeBase GetNearestFromGraphSpace (Vector3 positionGraphSpace) {
+ if (nodes == null || depth*width != nodes.Length) {
+ return null;
+ }
+
+ float xf = positionGraphSpace.x;
+ float zf = positionGraphSpace.z;
+ int x = Mathf.Clamp((int)xf, 0, width-1);
+ int z = Mathf.Clamp((int)zf, 0, depth-1);
+ return nodes[z*width+x];
+ }
+
+ public override NNInfo GetNearest (Vector3 position, NNConstraint constraint, float maxDistanceSqr) {
+ if (nodes == null || depth*width*LayerCount != nodes.Length) {
+ return NNInfo.Empty;
+ }
+
+ // Position in global space
+ Vector3 globalPosition = position;
+
+ // Position in graph space
+ position = transform.InverseTransform(position);
+
+ // Find the coordinates of the closest node
+ float xf = position.x;
+ float zf = position.z;
+ int x = Mathf.Clamp((int)xf, 0, width-1);
+ int z = Mathf.Clamp((int)zf, 0, depth-1);
+
+ GridNodeBase minNode = null;
+
+ // If set, we use another distance metric instead of the normal euclidean distance.
+ // See constraint.projectionAxis for more info.
+ // Note: The grid graph does not support any projectionAxis other than one parallel to the graph's up axis.
+ // So if the constraint has a projectionAxis, we treat it as if it is transform.up
+ var projectedDistance = constraint != null ? constraint.distanceMetric.isProjectedDistance : false;
+
+ // Search up to this distance
+ float minDistSqr = maxDistanceSqr;
+ var layerCount = LayerCount;
+ var layerStride = width*depth;
+ long yOffset = 0;
+ float yDistanceScale = 0;
+ Int3 up = default;
+ if (projectedDistance) {
+ up = (Int3)transform.WorldUpAtGraphPosition(globalPosition);
+ yOffset = Int3.DotLong((Int3)globalPosition, up);
+ yDistanceScale = constraint.distanceMetric.distanceScaleAlongProjectionDirection * Int3.PrecisionFactor * Int3.PrecisionFactor;
+ }
+
+ // Check the closest cell
+ for (int y = 0; y < layerCount; y++) {
+ var node = nodes[z*width + x + layerStride*y];
+ if (node != null && (constraint == null || constraint.Suitable(node))) {
+ float cost;
+ if (projectedDistance) {
+ var distX = math.clamp(xf, x, x + 1.0f) - xf;
+ var distZ = math.clamp(zf, z, z + 1.0f) - zf;
+ var distSideSqr = nodeSize*nodeSize * (distX*distX + distZ*distZ);
+ var distUp = (Int3.DotLong(node.position, up) - yOffset) * yDistanceScale;
+ cost = Mathf.Sqrt(distSideSqr) + Mathf.Abs(distUp);
+ cost = cost*cost;
+ } else {
+ cost = ((Vector3)node.position-globalPosition).sqrMagnitude;
+ }
+ if (cost <= minDistSqr) {
+ // Minimum distance so far
+ minDistSqr = cost;
+ minNode = node;
+ }
+ }
+ }
+
+ // Search in a square/spiral pattern around the closest cell
+ //
+ // 6
+ // 7 1 5
+ // 8 2 X 0 4
+ // 9 3 .
+ // .
+ //
+ // and so on...
+
+ // Lower bound on the distance to any cell which is not the closest one
+ float distanceToEdgeOfNode = Mathf.Min(Mathf.Min(xf - x, 1.0f - (xf - x)), Mathf.Min(zf - z, 1.0f - (zf - z))) * nodeSize;
+
+ for (int w = 1;; w++) {
+ // Check if the nodes are within distance limit.
+ // This is an optimization to avoid calculating the distance to all nodes.
+ // Since we search in a square pattern, we will have to search up to
+ // sqrt(2) times further away than the closest node we have found so far (or the maximum distance).
+ var distanceThreshold = math.max(0, w-2)*nodeSize + distanceToEdgeOfNode;
+ if (minDistSqr - 0.00001f <= distanceThreshold*distanceThreshold) {
+ break;
+ }
+
+ bool anyInside = false;
+
+ int nx = x + w;
+ int nz = z;
+ int dx = -1;
+ int dz = 1;
+ for (int d = 0; d < 4; d++) {
+ for (int i = 0; i < w; i++) {
+ if (nx >= 0 && nz >= 0 && nx < width && nz < depth) {
+ anyInside = true;
+ var nodeIndex = nx+nz*width;
+ for (int y = 0; y < layerCount; y++) {
+ var node = nodes[nodeIndex + layerStride*y];
+ if (node != null && (constraint == null || constraint.Suitable(node))) {
+ float cost;
+ if (projectedDistance) {
+ var distX = math.clamp(xf, nx, nx + 1.0f) - xf;
+ var distZ = math.clamp(zf, nz, nz + 1.0f) - zf;
+ var distSideSqr = nodeSize*nodeSize * (distX*distX + distZ*distZ);
+ var distUp = (Int3.DotLong(node.position, up) - yOffset) * yDistanceScale;
+ cost = Mathf.Sqrt(distSideSqr) + Mathf.Abs(distUp);
+ cost = cost*cost;
+ } else {
+ cost = ((Vector3)node.position-globalPosition).sqrMagnitude;
+ }
+ if (cost <= minDistSqr) {
+ // Minimum distance so far
+ minDistSqr = cost;
+ minNode = node;
+ }
+ }
+ }
+ }
+ nx += dx;
+ nz += dz;
+ }
+
+ // Rotate direction by 90 degrees counter-clockwise
+ var ndx = -dz;
+ var ndz = dx;
+ dx = ndx;
+ dz = ndz;
+ }
+
+ // No nodes were inside grid bounds
+ // We will not be able to find any more valid nodes
+ // so just break
+ if (!anyInside) break;
+ }
+
+ if (minNode != null) {
+ if (projectedDistance) {
+ // Walk towards the closest cell.
+ // We do this to ensure that if projectedDistance is true, then internal edges in the graph
+ // will *never* be obstructions for the agent.
+ //
+ // For example, if we have two nodes A and B which have different Y coordinates,
+ // and we have an agent (X) which has just stepped out of A and into node B.
+ // Assume that A and B are connected.
+ //
+ // __A__X
+ //
+ // __B__
+ //
+ // In this case, even though A might be closer with DistanceMetric.ClosestAsSeenFromAboveSoft,
+ // we want to return node B because clamping to A would mean clamping along to an obstacle edge
+ // which does not exist (A and B are connected).
+ // This is very important when this is used to clamp the agent to the navmesh,
+ // but it is also generally what you want in other situations as well.
+ while (true) {
+ var dx = x - minNode.XCoordinateInGrid;
+ var dz = z - minNode.ZCoordinateInGrid;
+ if (dx == 0 && dz == 0) break;
+ var d1 = dx > 0 ? 1 : (dx < 0 ? 3 : -1);
+ var d2 = dz > 0 ? 2 : (dz < 0 ? 0 : -1);
+ if (Mathf.Abs(dx) < Mathf.Abs(dz)) Memory.Swap(ref d1, ref d2);
+
+ // Try to walk along d1, if that does not work, try d2
+ var next = minNode.GetNeighbourAlongDirection(d1);
+ if (next != null && (constraint == null || constraint.Suitable(next))) minNode = next;
+ else if (d2 != -1 && (next = minNode.GetNeighbourAlongDirection(d2)) != null && (constraint == null || constraint.Suitable(next))) minNode = next;
+ else break;
+ }
+ }
+
+ // Closest point on the node if the node is treated as a square
+ var nx = minNode.XCoordinateInGrid;
+ var nz = minNode.ZCoordinateInGrid;
+ var closest = transform.Transform(new Vector3(Mathf.Clamp(xf, nx, nx+1f), transform.InverseTransform((Vector3)minNode.position).y, Mathf.Clamp(zf, nz, nz+1f)));
+ // If projectedDistance is enabled, the distance is already accurate.
+ // Otherwise, we need to calculate the distance to the closest point on the node instead of to the center
+ var cost = projectedDistance ? minDistSqr : (closest-globalPosition).sqrMagnitude;
+ return cost <= maxDistanceSqr ? new NNInfo(
+ minNode,
+ closest,
+ cost
+ ) : NNInfo.Empty;
+ } else {
+ return NNInfo.Empty;
+ }
+ }
+
+ ///
+ /// Sets up with the current settings. , , and are set up.
+ /// The cost for a non-diagonal movement between two adjacent nodes is RoundToInt ( * Int3.Precision)
+ /// The cost for a diagonal movement between two adjacent nodes is RoundToInt ( * Sqrt (2) * Int3.Precision)
+ ///
+ public virtual void SetUpOffsetsAndCosts () {
+ // First 4 are for the four directly adjacent nodes the last 4 are for the diagonals
+ neighbourOffsets[0] = -width;
+ neighbourOffsets[1] = 1;
+ neighbourOffsets[2] = width;
+ neighbourOffsets[3] = -1;
+ neighbourOffsets[4] = -width+1;
+ neighbourOffsets[5] = width+1;
+ neighbourOffsets[6] = width-1;
+ neighbourOffsets[7] = -width-1;
+
+ // The width of a single node, and thus also the distance between two adjacent nodes (axis aligned).
+ // For hexagonal graphs the node size is different from the width of a hexaon.
+ float nodeWidth = neighbours == NumNeighbours.Six ? ConvertNodeSizeToHexagonSize(InspectorGridHexagonNodeSize.Width, nodeSize) : nodeSize;
+
+ uint straightCost = (uint)Mathf.RoundToInt(nodeWidth*Int3.Precision);
+
+ // Diagonals normally cost sqrt(2) (approx 1.41) times more
+ uint diagonalCost = uniformEdgeCosts ? straightCost : (uint)Mathf.RoundToInt(nodeWidth*Mathf.Sqrt(2F)*Int3.Precision);
+
+ neighbourCosts[0] = straightCost;
+ neighbourCosts[1] = straightCost;
+ neighbourCosts[2] = straightCost;
+ neighbourCosts[3] = straightCost;
+ neighbourCosts[4] = diagonalCost;
+ neighbourCosts[5] = diagonalCost;
+ neighbourCosts[6] = diagonalCost;
+ neighbourCosts[7] = diagonalCost;
+
+ /* Z
+ * |
+ * |
+ *
+ * 6 2 5
+ * \ | /
+ * -- 3 - X - 1 ----- X
+ * / | \
+ * 7 0 4
+ *
+ * |
+ * |
+ */
+ }
+
+ public enum RecalculationMode {
+ /// Recalculates the nodes from scratch. Used when the graph is first scanned. You should have destroyed all existing nodes before updating the graph with this mode.
+ RecalculateFromScratch,
+ /// Recalculate the minimal number of nodes necessary to guarantee changes inside the graph update's bounding box are taken into account. Some data may be read from the existing nodes
+ RecalculateMinimal,
+ /// Nodes are not recalculated. Used for graph updates which only set node properties
+ NoRecalculation,
+ }
+
+ ///
+ /// Moves the grid by a number of nodes.
+ ///
+ /// This is used by the component to efficiently move the graph.
+ ///
+ /// All nodes that can stay in the same position will stay. The ones that would have fallen off the edge of the graph will wrap around to the other side
+ /// and then be recalculated.
+ ///
+ /// See:
+ ///
+ /// Returns: An async graph update promise. See .
+ ///
+ /// Number of nodes along the graph's X axis to move by.
+ /// Number of nodes along the graph's Z axis to move by.
+ public IGraphUpdatePromise TranslateInDirection(int dx, int dz) => new GridGraphMovePromise(this, dx, dz);
+
+ class GridGraphMovePromise : IGraphUpdatePromise {
+ public GridGraph graph;
+ public int dx;
+ public int dz;
+ IGraphUpdatePromise[] promises;
+ IntRect[] rects;
+ int3 startingSize;
+
+ static void DecomposeInsetsToRectangles (int width, int height, int insetLeft, int insetRight, int insetBottom, int insetTop, IntRect[] output) {
+ output[0] = new IntRect(0, 0, insetLeft - 1, height - 1);
+ output[1] = new IntRect(width - insetRight, 0, width - 1, height - 1);
+ output[2] = new IntRect(insetLeft, 0, width - insetRight - 1, insetBottom - 1);
+ output[3] = new IntRect(insetLeft, height - insetTop - 1, width - insetRight - 1, height - 1);
+ }
+
+ public GridGraphMovePromise(GridGraph graph, int dx, int dz) {
+ this.graph = graph;
+ this.dx = dx;
+ this.dz = dz;
+ var transform = graph.transform * Matrix4x4.Translate(new Vector3(dx, 0, dz));
+
+ // If the graph is moved by more than half its width/depth, then we recalculate the whole graph instead
+ startingSize = new int3(graph.width, graph.LayerCount, graph.depth);
+ if (math.abs(dx) > graph.width/2 || math.abs(dz) > graph.depth/2) {
+ rects = new IntRect[1] {
+ new IntRect(0, 0, graph.width - 1, graph.depth - 1)
+ };
+ } else {
+ // We recalculate nodes within some distance from each side of the (translated) grid.
+ // We must always recalculate at least the nodes along the border, since they may have had
+ // connections to nodes that are now outside the graph.
+ // TODO: This can potentially be optimized to just clearing the out-of-bounds connections
+ // on border nodes, instead of completely recalculating the border nodes.
+ var insetLeft = math.max(1, -dx);
+ var insetRight = math.max(1, dx);
+ var insetBottom = math.max(1, -dz);
+ var insetTop = math.max(1, dz);
+ rects = new IntRect[4];
+ DecomposeInsetsToRectangles(graph.width, graph.depth, insetLeft, insetRight, insetBottom, insetTop, rects);
+ }
+
+ promises = new GridGraphUpdatePromise[rects.Length];
+ var nodes = new GridGraphUpdatePromise.NodesHolder { nodes = graph.nodes };
+ for (int i = 0; i < rects.Length; i++) {
+ var dependencyTracker = ObjectPool.Claim();
+ // TODO: Use the exact rect given, don't expand it using physics checks
+ // We do need to expand the insets using erosion, though.
+ promises[i] = new GridGraphUpdatePromise(
+ graph: graph,
+ transform: transform,
+ nodes: nodes,
+ nodeArrayBounds: startingSize,
+ rect: rects[i],
+ dependencyTracker: dependencyTracker,
+ nodesDependsOn: default,
+ allocationMethod: Allocator.Persistent,
+ recalculationMode: RecalculationMode.RecalculateMinimal,
+ graphUpdateObject: null,
+ ownsJobDependencyTracker: true
+ );
+ }
+ }
+
+ public IEnumerator Prepare () {
+ yield return graph.nodeData.Rotate2D(-dx, -dz, default);
+
+ for (int i = 0; i < promises.Length; i++) {
+ var it = promises[i].Prepare();
+ while (it.MoveNext()) yield return it.Current;
+ }
+ }
+
+ public void Apply (IGraphUpdateContext ctx) {
+ graph.AssertSafeToUpdateGraph();
+ var nodes = graph.nodes;
+ if (!math.all(new int3(graph.width, graph.LayerCount, graph.depth) == startingSize)) throw new System.InvalidOperationException("The graph has been resized since the update was created. This is not allowed.");
+ if (nodes == null || nodes.Length != graph.width * graph.depth * graph.LayerCount) {
+ throw new System.InvalidOperationException("The Grid Graph is not scanned, cannot recalculate connections.");
+ }
+
+ Profiler.BeginSample("Rotating node array");
+ Memory.Rotate3DArray(nodes, startingSize, -dx, -dz);
+ Profiler.EndSample();
+
+ Profiler.BeginSample("Recalculating node indices");
+ // Recalculate the node indices for all nodes that exist before the update
+ for (int y = 0; y < startingSize.y; y++) {
+ var layerOffset = y * startingSize.x * startingSize.z;
+ for (int z = 0; z < startingSize.z; z++) {
+ var rowOffset = z * startingSize.x;
+ for (int x = 0; x < startingSize.x; x++) {
+ var nodeIndexXZ = rowOffset + x;
+ var node = nodes[layerOffset + nodeIndexXZ];
+ if (node != null) node.NodeInGridIndex = nodeIndexXZ;
+ }
+ }
+ }
+ Profiler.EndSample();
+
+ Profiler.BeginSample("Clearing custom connections");
+ var layers = graph.LayerCount;
+ for (int i = 0; i < rects.Length; i++) {
+ var r = rects[i];
+ for (int y = 0; y < layers; y++) {
+ var layerOffset = y * graph.width * graph.depth;
+ for (int z = r.ymin; z <= r.ymax; z++) {
+ var rowOffset = z * graph.width + layerOffset;
+ for (int x = r.xmin; x <= r.xmax; x++) {
+ var node = nodes[rowOffset + x];
+ if (node != null) {
+ // Clear connections on all nodes that are wrapped and placed on the other side of the graph.
+ // This is both to clear any custom connections (which do not really make sense after moving the node)
+ // and to prevent possible exceptions when the node will later (possibly) be destroyed because it was
+ // not needed anymore (only for layered grid graphs).
+ node.ClearCustomConnections(true);
+ }
+ }
+ }
+ }
+ }
+ Profiler.EndSample();
+ for (int i = 0; i < promises.Length; i++) {
+ promises[i].Apply(ctx);
+ }
+ // Move the center (this is in world units, so we need to convert it back from graph space)
+ graph.center += graph.transform.TransformVector(new Vector3(dx, 0, dz));
+ graph.UpdateTransform();
+
+ if (promises.Length > 0) graph.rules.ExecuteRuleMainThread(GridGraphRule.Pass.AfterApplied, (promises[0] as GridGraphUpdatePromise).context);
+ }
+ }
+
+ class GridGraphUpdatePromise : IGraphUpdatePromise {
+ /// Reference to a nodes array to allow multiple serial updates to have a common reference to the nodes
+ public class NodesHolder {
+ public GridNodeBase[] nodes;
+ }
+ public GridGraph graph;
+ public NodesHolder nodes;
+ public JobDependencyTracker dependencyTracker;
+ public int3 nodeArrayBounds;
+ public IntRect rect;
+ public JobHandle nodesDependsOn;
+ public Allocator allocationMethod;
+ public RecalculationMode recalculationMode;
+ public GraphUpdateObject graphUpdateObject;
+ IntBounds writeMaskBounds;
+ internal GridGraphRules.Context context;
+ bool emptyUpdate;
+ IntBounds readBounds;
+ IntBounds fullRecalculationBounds;
+ public bool ownsJobDependencyTracker = false;
+ GraphTransform transform;
+
+ public int CostEstimate => fullRecalculationBounds.volume;
+
+ public GridGraphUpdatePromise(GridGraph graph, GraphTransform transform, NodesHolder nodes, int3 nodeArrayBounds, IntRect rect, JobDependencyTracker dependencyTracker, JobHandle nodesDependsOn, Allocator allocationMethod, RecalculationMode recalculationMode, GraphUpdateObject graphUpdateObject, bool ownsJobDependencyTracker) {
+ this.graph = graph;
+ this.transform = transform;
+ this.nodes = nodes;
+ this.nodeArrayBounds = nodeArrayBounds;
+ this.dependencyTracker = dependencyTracker;
+ this.nodesDependsOn = nodesDependsOn;
+ this.allocationMethod = allocationMethod;
+ this.recalculationMode = recalculationMode;
+ this.graphUpdateObject = graphUpdateObject;
+ this.ownsJobDependencyTracker = ownsJobDependencyTracker;
+ CalculateRectangles(graph, rect, out this.rect, out var fullRecalculationRect, out var writeMaskRect, out var readRect);
+
+ if (recalculationMode == RecalculationMode.RecalculateFromScratch) {
+ // If we are not allowed to read from the graph, we need to recalculate everything that we would otherwise just have read from the graph
+ fullRecalculationRect = readRect;
+ }
+
+ // Check if there is anything to do. The bounds may not even overlap the graph.
+ // Note that writeMaskRect may overlap the graph even though fullRecalculationRect is invalid.
+ // We ignore that case however since any changes we might write can only be caused by a node that is actually recalculated.
+ if (!fullRecalculationRect.IsValid()) {
+ emptyUpdate = true;
+ }
+
+ // Note that IntRects are defined with inclusive (min,max) coordinates while IntBounds use an exclusive upper bounds.
+ readBounds = new IntBounds(readRect.xmin, 0, readRect.ymin, readRect.xmax + 1, nodeArrayBounds.y, readRect.ymax + 1);
+ fullRecalculationBounds = new IntBounds(fullRecalculationRect.xmin, 0, fullRecalculationRect.ymin, fullRecalculationRect.xmax + 1, nodeArrayBounds.y, fullRecalculationRect.ymax + 1);
+ writeMaskBounds = new IntBounds(writeMaskRect.xmin, 0, writeMaskRect.ymin, writeMaskRect.xmax + 1, nodeArrayBounds.y, writeMaskRect.ymax + 1);
+
+ // If recalculating a very small number of nodes, then disable dependency tracking and just run jobs one after the other.
+ // This is faster since dependency tracking has some overhead
+ if (ownsJobDependencyTracker) dependencyTracker.SetLinearDependencies(CostEstimate < 500);
+ }
+
+ /// Calculates the rectangles used for different purposes during a graph update.
+ /// The graph
+ /// The rectangle to update. Anything inside this rectangle may have changed (which may affect nodes outside this rectangle as well).
+ /// The original rectangle passed to the update method, clamped to the grid.
+ /// The rectangle of nodes which will be recalculated from scratch.
+ /// The rectangle of nodes which will have their results written back to the graph.
+ /// The rectangle of nodes which we need to read from in order to recalculate all nodes in writeMaskRect correctly.
+ public static void CalculateRectangles (GridGraph graph, IntRect rect, out IntRect originalRect, out IntRect fullRecalculationRect, out IntRect writeMaskRect, out IntRect readRect) {
+ fullRecalculationRect = rect;
+ var collision = graph.collision;
+ if (collision.collisionCheck && collision.type != ColliderType.Ray) fullRecalculationRect = fullRecalculationRect.Expand(Mathf.FloorToInt(collision.diameter * 0.5f + 0.5f));
+
+ // Rectangle of nodes which will have their results written back to the node class objects.
+ // Due to erosion a bit more of the graph may be affected by the updates in the fullRecalculationBounds.
+ writeMaskRect = fullRecalculationRect.Expand(graph.erodeIterations + 1);
+
+ // Rectangle of nodes which we need to read from in order to recalculate all nodes in writeMaskRect correctly.
+ // Due to how erosion works we need to recalculate erosion in an even larger region to make sure we
+ // get the correct result inside the writeMask
+ readRect = writeMaskRect.Expand(graph.erodeIterations + 1);
+
+ // Clamp to the grid dimensions
+ var gridRect = new IntRect(0, 0, graph.width - 1, graph.depth - 1);
+ readRect = IntRect.Intersection(readRect, gridRect);
+ fullRecalculationRect = IntRect.Intersection(fullRecalculationRect, gridRect);
+ writeMaskRect = IntRect.Intersection(writeMaskRect, gridRect);
+ originalRect = IntRect.Intersection(rect, gridRect);
+ }
+
+
+ public IEnumerator Prepare () {
+ if (emptyUpdate) yield break;
+
+ var collision = graph.collision;
+ var rules = graph.rules;
+
+ if (recalculationMode != RecalculationMode.RecalculateFromScratch) {
+ // In case a previous graph update has changed the number of layers in the graph
+ writeMaskBounds.max.y = fullRecalculationBounds.max.y = readBounds.max.y = graph.nodeData.bounds.max.y;
+ }
+
+ // We never reduce the number of layers in an existing graph.
+ // Unless we are scanning the graph (not doing an update).
+ var minLayers = recalculationMode == RecalculationMode.RecalculateFromScratch ? 1 : fullRecalculationBounds.max.y;
+
+ if (recalculationMode == RecalculationMode.RecalculateMinimal && readBounds == fullRecalculationBounds) {
+ // There is no point reading from the graph since we are recalculating all those nodes anyway.
+ // This happens if an update is done to the whole graph.
+ // Skipping the read can improve performance quite a lot for that kind of updates.
+ // This is purely an optimization and should not change the result.
+ recalculationMode = RecalculationMode.RecalculateFromScratch;
+ }
+
+#if ASTAR_DEBUG
+ var debugMatrix = graph.transform.matrix;
+ // using (Draw.WithDuration(1)) {
+ using (Draw.WithLineWidth(2)) {
+ using (Draw.WithMatrix(debugMatrix)) {
+ Draw.xz.WireRectangle(Rect.MinMaxRect(fullRecalculationBounds.min.x, fullRecalculationBounds.min.z, fullRecalculationBounds.max.x, fullRecalculationBounds.max.z), Color.yellow);
+ }
+ using (Draw.WithMatrix(debugMatrix * Matrix4x4.Translate(Vector3.up*0.1f))) {
+ Draw.xz.WireRectangle(Rect.MinMaxRect(writeMaskBounds.min.x, writeMaskBounds.min.z, writeMaskBounds.max.x, writeMaskBounds.max.z), Color.magenta);
+ Draw.xz.WireRectangle(Rect.MinMaxRect(readBounds.min.x, readBounds.min.z, readBounds.max.x, readBounds.max.z), Color.blue);
+ Draw.xz.WireRectangle((Rect)rect, Color.green);
+ }
+ }
+#endif
+
+ var layeredDataLayout = graph is LayerGridGraph;
+ float characterHeight = graph is LayerGridGraph lg ? lg.characterHeight : float.PositiveInfinity;
+
+ context = new GridGraphRules.Context {
+ graph = graph,
+ data = new GridGraphScanData {
+ dependencyTracker = dependencyTracker,
+ transform = transform,
+ up = transform.TransformVector(Vector3.up).normalized,
+ }
+ };
+
+ if (recalculationMode == RecalculationMode.RecalculateFromScratch || recalculationMode == RecalculationMode.RecalculateMinimal) {
+ var heightCheck = collision.heightCheck && !collision.use2D;
+ if (heightCheck) {
+ var layerCount = dependencyTracker.NewNativeArray(1, allocationMethod, NativeArrayOptions.UninitializedMemory);
+ yield return context.data.HeightCheck(collision, graph.MaxLayers, fullRecalculationBounds, layerCount, characterHeight, allocationMethod);
+ // The size of the buffers depend on the height check for layered grid graphs since the number of layers might change.
+ // Never reduce the layer count of the graph.
+ // Unless we are recalculating the whole graph: in that case we don't care about the existing layers.
+ // For (not layered) grid graphs this is always 1.
+ var layers = Mathf.Max(minLayers, layerCount[0]);
+ readBounds.max.y = fullRecalculationBounds.max.y = writeMaskBounds.max.y = layers;
+ context.data.heightHitsBounds.max.y = layerCount[0];
+ context.data.nodes = new GridGraphNodeData {
+ bounds = fullRecalculationBounds,
+ numNodes = fullRecalculationBounds.volume,
+ layeredDataLayout = layeredDataLayout,
+ allocationMethod = allocationMethod,
+ };
+ context.data.nodes.AllocateBuffers(dependencyTracker);
+
+ // Set the positions to be used if the height check ray didn't hit anything
+ context.data.SetDefaultNodePositions(transform);
+ context.data.CopyHits(context.data.heightHitsBounds);
+ context.data.CalculateWalkabilityFromHeightData(graph.useRaycastNormal, collision.unwalkableWhenNoGround, graph.maxSlope, characterHeight);
+ } else {
+ context.data.nodes = new GridGraphNodeData {
+ bounds = fullRecalculationBounds,
+ numNodes = fullRecalculationBounds.volume,
+ layeredDataLayout = layeredDataLayout,
+ allocationMethod = allocationMethod,
+ };
+ context.data.nodes.AllocateBuffers(dependencyTracker);
+ context.data.SetDefaultNodePositions(transform);
+ // Mark all nodes as walkable to begin with
+ context.data.nodes.walkable.MemSet(true).Schedule(dependencyTracker);
+ // Set the normals to point straight up
+ context.data.nodes.normals.MemSet(new float4(context.data.up.x, context.data.up.y, context.data.up.z, 0)).Schedule(dependencyTracker);
+ }
+
+ context.data.SetDefaultPenalties(graph.initialPenalty);
+
+ // Kick off jobs early while we prepare the rest of them
+ JobHandle.ScheduleBatchedJobs();
+
+ rules.RebuildIfNecessary();
+
+ {
+ // Here we execute some rules and possibly wait for some dependencies to complete.
+ // If main thread rules are used then we need to wait for all previous jobs to complete before the rule is actually executed.
+ var wait = rules.ExecuteRule(GridGraphRule.Pass.BeforeCollision, context);
+ while (wait.MoveNext()) yield return wait.Current;
+ }
+
+ if (collision.collisionCheck) {
+ context.tracker.timeSlice = TimeSlice.MillisFromNow(1);
+ var wait = context.data.CollisionCheck(collision, fullRecalculationBounds);
+ while (wait != null && wait.MoveNext()) {
+ yield return wait.Current;
+ context.tracker.timeSlice = TimeSlice.MillisFromNow(2);
+ }
+ }
+
+ {
+ var wait = rules.ExecuteRule(GridGraphRule.Pass.BeforeConnections, context);
+ while (wait.MoveNext()) yield return wait.Current;
+ }
+
+ if (recalculationMode == RecalculationMode.RecalculateMinimal) {
+ // context.data.nodes = context.data.nodes.ReadFromNodesAndCopy(nodes, new Slice3D(nodeArrayBounds, readBounds), nodesDependsOn, graph.nodeData.normals, graphUpdateObject != null ? graphUpdateObject.resetPenaltyOnPhysics : true, dependencyTracker);
+ var newNodes = new GridGraphNodeData {
+ bounds = readBounds,
+ numNodes = readBounds.volume,
+ layeredDataLayout = layeredDataLayout,
+ allocationMethod = allocationMethod,
+ };
+ newNodes.AllocateBuffers(dependencyTracker);
+ // If our layer count is increased, then some nodes may end up with uninitialized normals if we didn't do this memset
+ newNodes.normals.MemSet(float4.zero).Schedule(dependencyTracker);
+ newNodes.walkable.MemSet(false).Schedule(dependencyTracker);
+ newNodes.walkableWithErosion.MemSet(false).Schedule(dependencyTracker);
+ newNodes.CopyFrom(graph.nodeData, true, dependencyTracker);
+ newNodes.CopyFrom(context.data.nodes, graphUpdateObject != null ? graphUpdateObject.resetPenaltyOnPhysics : true, dependencyTracker);
+ context.data.nodes = newNodes;
+ }
+ } else {
+ // If we are not allowed to recalculate the graph then we read all the necessary info from the existing nodes
+ // context.data.nodes = GridGraphNodeData.ReadFromNodes(nodes, new Slice3D(nodeArrayBounds, readBounds), nodesDependsOn, graph.nodeData.normals, allocationMethod, context.data.nodes.layeredDataLayout, dependencyTracker);
+
+ context.data.nodes = new GridGraphNodeData {
+ bounds = readBounds,
+ numNodes = readBounds.volume,
+ layeredDataLayout = layeredDataLayout,
+ allocationMethod = allocationMethod,
+ };
+ UnityEngine.Assertions.Assert.IsTrue(graph.nodeData.bounds.Contains(context.data.nodes.bounds));
+ context.data.nodes.AllocateBuffers(dependencyTracker);
+ context.data.nodes.CopyFrom(graph.nodeData, true, dependencyTracker);
+ }
+
+ if (graphUpdateObject != null) {
+ // The GraphUpdateObject has an empty implementation of WillUpdateNode,
+ // so we only need to call it if we are dealing with a subclass of GraphUpdateObject.
+ // The WillUpdateNode method will be deprecated in the future.
+ if (graphUpdateObject.GetType() != typeof(GraphUpdateObject)) {
+ // Mark nodes that might be changed
+ var nodes = this.nodes.nodes;
+ for (int y = writeMaskBounds.min.y; y < writeMaskBounds.max.y; y++) {
+ for (int z = writeMaskBounds.min.z; z < writeMaskBounds.max.z; z++) {
+ var rowOffset = y*nodeArrayBounds.x*nodeArrayBounds.z + z*nodeArrayBounds.x;
+ for (int x = writeMaskBounds.min.x; x < writeMaskBounds.max.x; x++) {
+ graphUpdateObject.WillUpdateNode(nodes[rowOffset + x]);
+ }
+ }
+ }
+ }
+
+ var updateRect = rect;
+ if (updateRect.IsValid()) {
+ // Note that IntRects are defined with inclusive (min,max) coordinates while IntBounds use exclusive upper bounds.
+ var updateBounds = new IntBounds(updateRect.xmin, 0, updateRect.ymin, updateRect.xmax + 1, context.data.nodes.layers, updateRect.ymax + 1).Offset(-context.data.nodes.bounds.min);
+ var nodeIndices = dependencyTracker.NewNativeArray(updateBounds.volume, context.data.nodes.allocationMethod, NativeArrayOptions.ClearMemory);
+ int i = 0;
+ var dataBoundsSize = context.data.nodes.bounds.size;
+ for (int y = updateBounds.min.y; y < updateBounds.max.y; y++) {
+ for (int z = updateBounds.min.z; z < updateBounds.max.z; z++) {
+ var rowOffset = y*dataBoundsSize.x*dataBoundsSize.z + z*dataBoundsSize.x;
+ for (int x = updateBounds.min.x; x < updateBounds.max.x; x++) {
+ nodeIndices[i++] = rowOffset + x;
+ }
+ }
+ }
+ graphUpdateObject.ApplyJob(new GraphUpdateObject.GraphUpdateData {
+ nodePositions = context.data.nodes.positions,
+ nodePenalties = context.data.nodes.penalties,
+ nodeWalkable = context.data.nodes.walkable,
+ nodeTags = context.data.nodes.tags,
+ nodeIndices = nodeIndices,
+ }, dependencyTracker);
+ }
+ }
+
+ // Calculate the connections between nodes and also erode the graph
+ context.data.Connections(graph.maxStepHeight, graph.maxStepUsesSlope, context.data.nodes.bounds, graph.neighbours, graph.cutCorners, collision.use2D, false, characterHeight);
+ {
+ var wait = rules.ExecuteRule(GridGraphRule.Pass.AfterConnections, context);
+ while (wait.MoveNext()) yield return wait.Current;
+ }
+
+ if (graph.erodeIterations > 0) {
+ context.data.Erosion(graph.neighbours, graph.erodeIterations, writeMaskBounds, graph.erosionUseTags, graph.erosionFirstTag, graph.erosionTagsPrecedenceMask);
+ {
+ var wait = rules.ExecuteRule(GridGraphRule.Pass.AfterErosion, context);
+ while (wait.MoveNext()) yield return wait.Current;
+ }
+
+ // After erosion is done we need to recalculate the node connections
+ context.data.Connections(graph.maxStepHeight, graph.maxStepUsesSlope, context.data.nodes.bounds, graph.neighbours, graph.cutCorners, collision.use2D, true, characterHeight);
+ {
+ var wait = rules.ExecuteRule(GridGraphRule.Pass.AfterConnections, context);
+ while (wait.MoveNext()) yield return wait.Current;
+ }
+ } else {
+ // If erosion is disabled we can just copy nodeWalkable to nodeWalkableWithErosion
+ // TODO: Can we just do an assignment of the whole array?
+ context.data.nodes.walkable.CopyToJob(context.data.nodes.walkableWithErosion).Schedule(dependencyTracker);
+ }
+
+ {
+ var wait = rules.ExecuteRule(GridGraphRule.Pass.PostProcess, context);
+ while (wait.MoveNext()) yield return wait.Current;
+ }
+
+ // Make the graph's buffers be tracked by the dependency tracker,
+ // so that they can be disposed automatically, unless we persist them.
+ graph.nodeData.TrackBuffers(dependencyTracker);
+
+ if (recalculationMode == RecalculationMode.RecalculateFromScratch) {
+ UnityEngine.Assertions.Assert.AreEqual(Allocator.Persistent, context.data.nodes.allocationMethod);
+ graph.nodeData = context.data.nodes;
+ } else {
+ // Copy node data back to the graph's buffer
+ graph.nodeData.ResizeLayerCount(context.data.nodes.layers, dependencyTracker);
+ graph.nodeData.CopyFrom(context.data.nodes, writeMaskBounds, true, dependencyTracker);
+ }
+
+ graph.nodeData.PersistBuffers(dependencyTracker);
+
+ // We need to wait for the nodes array to be fully initialized before trying to resize it or reading from it
+ yield return nodesDependsOn;
+
+ yield return dependencyTracker.AllWritesDependency;
+
+ dependencyTracker.ClearMemory();
+ }
+
+ public void Apply (IGraphUpdateContext ctx) {
+ graph.AssertSafeToUpdateGraph();
+ if (emptyUpdate) {
+ Dispose();
+ return;
+ }
+
+ var destroyPreviousNodes = nodes.nodes != graph.nodes;
+ // For layered grid graphs, we may need to allocate more nodes for the upper layers
+ if (context.data.nodes.layers > 1) {
+ nodeArrayBounds.y = context.data.nodes.layers;
+ var newNodeCount = nodeArrayBounds.x*nodeArrayBounds.y*nodeArrayBounds.z;
+ // Resize the nodes array.
+ // We reference it via a shared reference, so that if any other updates will run after this one,
+ // they will see the resized nodes array immediately.
+ Memory.Realloc(ref nodes.nodes, newNodeCount);
+
+ // This job needs to be executed on the main thread
+ // TODO: Do we need writeMaskBounds to prevent allocating nodes outside the permitted region?
+ new JobAllocateNodes {
+ active = graph.active,
+ nodeNormals = graph.nodeData.normals,
+ dataBounds = context.data.nodes.bounds,
+ nodeArrayBounds = nodeArrayBounds,
+ nodes = nodes.nodes,
+ newGridNodeDelegate = graph.newGridNodeDelegate,
+ }.Execute();
+ }
+
+ var assignToNodesJob = graph.nodeData.AssignToNodes(this.nodes.nodes, nodeArrayBounds, writeMaskBounds, graph.graphIndex, default, dependencyTracker);
+ assignToNodesJob.Complete();
+
+ // Destroy the old nodes (if any) and assign the new nodes as an atomic operation from the main thread's perspective
+ if (nodes.nodes != graph.nodes) {
+ if (destroyPreviousNodes) {
+ graph.DestroyAllNodes();
+ }
+ graph.nodes = nodes.nodes;
+ graph.LayerCount = context.data.nodes.layers;
+ }
+
+ // Recalculate off mesh links in the affected area
+ ctx.DirtyBounds(graph.GetBoundsFromRect(new IntRect(writeMaskBounds.min.x, writeMaskBounds.min.z, writeMaskBounds.max.x - 1, writeMaskBounds.max.z - 1)));
+ Dispose();
+ }
+
+ public void Dispose () {
+ if (ownsJobDependencyTracker) {
+ ObjectPool.Release(ref dependencyTracker);
+ if (context != null) context.data.dependencyTracker = null;
+ }
+ }
+ }
+
+ protected override IGraphUpdatePromise ScanInternal (bool async) {
+ if (nodeSize <= 0) {
+ return null;
+ }
+
+ // Make sure the matrix is up to date
+ UpdateTransform();
+
+#if !ASTAR_LARGER_GRIDS
+ if (width > 1024 || depth > 1024) {
+ Debug.LogError("One of the grid's sides is longer than 1024 nodes");
+ return null;
+ }
+#endif
+
+ SetUpOffsetsAndCosts();
+
+ // Set a global reference to this graph so that nodes can find it
+ GridNode.SetGridGraph((int)graphIndex, this);
+
+ // Create and initialize the collision class
+ collision ??= new GraphCollision();
+ collision.Initialize(transform, nodeSize);
+
+
+ // Used to allocate buffers for jobs
+ var dependencyTracker = ObjectPool.Claim();
+
+ // Create all nodes
+ var newNodes = AllocateNodesJob(width * depth, out var allocateNodesJob);
+
+ // TODO: Set time slicing in dependency tracker
+ return new GridGraphUpdatePromise(
+ graph: this,
+ transform: transform,
+ nodes: new GridGraphUpdatePromise.NodesHolder { nodes = newNodes },
+ nodeArrayBounds: new int3(width, 1, depth),
+ rect: new IntRect(0, 0, width - 1, depth - 1),
+ dependencyTracker: dependencyTracker,
+ nodesDependsOn: allocateNodesJob,
+ allocationMethod: Allocator.Persistent,
+ recalculationMode: RecalculationMode.RecalculateFromScratch,
+ graphUpdateObject: null,
+ ownsJobDependencyTracker: true
+ );
+ }
+
+ ///
+ /// Set walkability for multiple nodes at once.
+ ///
+ /// If you are calculating your graph's walkability in some custom way, you can use this method to copy that data to the graph.
+ /// In most cases you'll not use this method, but instead build your world with colliders and such, and then scan the graph.
+ ///
+ /// Note: Any other graph updates may overwrite this data.
+ ///
+ ///
+ /// AstarPath.active.AddWorkItem(() => {
+ /// var grid = AstarPath.active.data.gridGraph;
+ /// // Mark all nodes in a 10x10 square, in the top-left corner of the graph, as unwalkable.
+ /// grid.SetWalkability(new bool[10*10], new IntRect(0, 0, 9, 9));
+ /// });
+ ///
+ ///
+ /// See: grid-rules (view in online documentation for working links) for an alternative way of modifying the graph's walkability. It is more flexible and robust, but requires a bit more code.
+ ///
+ public void SetWalkability (bool[] walkability, IntRect rect) {
+ AssertSafeToUpdateGraph();
+ var gridRect = new IntRect(0, 0, width - 1, depth - 1);
+ if (!gridRect.Contains(rect)) throw new System.ArgumentException("Rect (" + rect + ") must be within the graph bounds (" + gridRect + ")");
+ if (walkability.Length != rect.Width*rect.Height) throw new System.ArgumentException("Array must have the same length as rect.Width*rect.Height");
+ if (LayerCount != 1) throw new System.InvalidOperationException("This method only works in single-layered grid graphs.");
+
+ for (int z = 0; z < rect.Height; z++) {
+ var offset = (z + rect.ymin) * width + rect.xmin;
+ for (int x = 0; x < rect.Width; x++) {
+ var w = walkability[z * rect.Width + x];
+ nodes[offset + x].WalkableErosion = w;
+ nodes[offset + x].Walkable = w;
+ }
+ }
+
+ // Recalculate connections for all affected nodes and their neighbours
+ RecalculateConnectionsInRegion(rect.Expand(1));
+ }
+
+ ///
+ /// Recalculates node connections for all nodes in grid graph.
+ ///
+ /// This is used if you have manually changed the walkability, or other parameters, of some grid nodes, and you need their connections to be recalculated.
+ /// If you are changing the connections themselves, you should use the and functions instead.
+ ///
+ /// Typically you do not change walkability manually. Instead you can use for example a .
+ ///
+ /// Note: This will not take into account any grid graph rules that modify connections. So if you have any of those added to the grid graph, you probably want to do a regular graph update instead.
+ ///
+ /// See: graph-updates (view in online documentation for working links)
+ /// See:
+ /// See:
+ ///
+ public void RecalculateAllConnections () {
+ RecalculateConnectionsInRegion(new IntRect(0, 0, width - 1, depth - 1));
+ }
+
+ ///
+ /// Recalculates node connections for all nodes in a given region of the grid.
+ ///
+ /// This is used if you have manually changed the walkability, or other parameters, of some grid nodes, and you need their connections to be recalculated.
+ /// If you are changing the connections themselves, you should use the and functions instead.
+ ///
+ /// Typically you do not change walkability manually. Instead you can use for example a .
+ ///
+ /// Warning: This method has some constant overhead, so if you are making several changes to the graph, it is best to batch these updates and only make a single call to this method.
+ ///
+ /// Note: This will not take into account any grid graph rules that modify connections. So if you have any of those added to the grid graph, you probably want to do a regular graph update instead.
+ ///
+ /// See: graph-updates (view in online documentation for working links)
+ /// See:
+ /// See:
+ ///
+ public void RecalculateConnectionsInRegion (IntRect recalculateRect) {
+ AssertSafeToUpdateGraph();
+ if (nodes == null || nodes.Length != width * depth * LayerCount) {
+ throw new System.InvalidOperationException("The Grid Graph is not scanned, cannot recalculate connections.");
+ }
+ Assert.AreEqual(new int3(width, LayerCount, depth), nodeData.bounds.size);
+
+ var gridRect = new IntRect(0, 0, width - 1, depth - 1);
+ var writeRect = IntRect.Intersection(recalculateRect, gridRect);
+
+ // Skip recalculation if the rectangle is outside the graph
+ if (!writeRect.IsValid()) return;
+
+ var dependencyTracker = ObjectPool.Claim();
+ // We need to read node data from the rectangle, and a 1 node border around it in order to be able to calculate connections
+ // inside the rectangle properly.
+ var readRect = IntRect.Intersection(writeRect.Expand(1), gridRect);
+ var readBounds = new IntBounds(readRect.xmin, 0, readRect.ymin, readRect.xmax + 1, LayerCount, readRect.ymax + 1);
+ if (readBounds.volume < 200) dependencyTracker.SetLinearDependencies(true);
+
+ var layeredDataLayout = this is LayerGridGraph;
+ var data = new GridGraphScanData {
+ dependencyTracker = dependencyTracker,
+ // We can use the temp allocator here because everything will be done before this method returns.
+ // Unity will normally not let us use these allocations in jobs (presumably because it cannot guarantee that the job will complete before the end of the frame),
+ // but we will trick it using the UnsafeSpan struct. This is safe because we know that the job will complete before this method returns.
+ nodes = GridGraphNodeData.ReadFromNodes(nodes, new Slice3D(nodeData.bounds, readBounds), default, nodeData.normals, Allocator.TempJob, layeredDataLayout, dependencyTracker),
+ transform = transform,
+ up = transform.WorldUpAtGraphPosition(Vector3.zero),
+ };
+ float characterHeight = this is LayerGridGraph lg ? lg.characterHeight : float.PositiveInfinity;
+
+ var writeBounds = new IntBounds(writeRect.xmin, 0, writeRect.ymin, writeRect.xmax + 1, LayerCount, writeRect.ymax + 1);
+ data.Connections(maxStepHeight, maxStepUsesSlope, writeBounds, neighbours, cutCorners, collision.use2D, true, characterHeight);
+ this.nodeData.CopyFrom(data.nodes, writeBounds, true, dependencyTracker);
+ dependencyTracker.AllWritesDependency.Complete();
+ Profiler.BeginSample("Write connections");
+ data.AssignNodeConnections(nodes, new int3(width, LayerCount, depth), writeBounds);
+ Profiler.EndSample();
+ ObjectPool.Release(ref dependencyTracker);
+
+ // Recalculate off mesh links in the affected area
+ active.DirtyBounds(GetBoundsFromRect(writeRect));
+ }
+
+ ///
+ /// Calculates the grid connections for a cell as well as its neighbours.
+ /// This is a useful utility function if you want to modify the walkability of a single node in the graph.
+ ///
+ ///
+ /// AstarPath.active.AddWorkItem(ctx => {
+ /// var grid = AstarPath.active.data.gridGraph;
+ /// int x = 5;
+ /// int z = 7;
+ ///
+ /// // Mark a single node as unwalkable
+ /// grid.GetNode(x, z).Walkable = false;
+ ///
+ /// // Recalculate the connections for that node as well as its neighbours
+ /// grid.CalculateConnectionsForCellAndNeighbours(x, z);
+ /// });
+ ///
+ ///
+ /// Warning: If you are recalculating connections for a lot of nodes at the same time, use instead, since that will be much faster.
+ ///
+ public void CalculateConnectionsForCellAndNeighbours (int x, int z) {
+ RecalculateConnectionsInRegion(new IntRect(x - 1, z - 1, x + 1, z + 1));
+ }
+
+ ///
+ /// Calculates the grid connections for a single node.
+ /// Convenience function, it's slightly faster to use CalculateConnections(int,int)
+ /// but that will only show when calculating for a large number of nodes.
+ /// This function will also work for both grid graphs and layered grid graphs.
+ ///
+ /// Deprecated: This method is very slow since 4.3.80. Use or instead to batch connection recalculations.
+ ///
+ [System.Obsolete("This method is very slow since 4.3.80. Use RecalculateConnectionsInRegion or RecalculateAllConnections instead to batch connection recalculations.")]
+ public virtual void CalculateConnections (GridNodeBase node) {
+ int index = node.NodeInGridIndex;
+ int x = index % width;
+ int z = index / width;
+
+ CalculateConnections(x, z);
+ }
+
+ ///
+ /// Calculates the grid connections for a single node.
+ /// Note that to ensure that connections are completely up to date after updating a node you
+ /// have to calculate the connections for both the changed node and its neighbours.
+ ///
+ /// In a layered grid graph, this will recalculate the connections for all nodes
+ /// in the (x,z) cell (it may have multiple layers of nodes).
+ ///
+ /// See: CalculateConnections(GridNodeBase)
+ ///
+ /// Deprecated: This method is very slow since 4.3.80. Use instead to batch connection recalculations.
+ ///
+ [System.Obsolete("This method is very slow since 4.3.80. Use RecalculateConnectionsInRegion instead to batch connection recalculations.")]
+ public virtual void CalculateConnections (int x, int z) {
+ RecalculateConnectionsInRegion(new IntRect(x, z, x, z));
+ }
+
+ public override void OnDrawGizmos (DrawingData gizmos, bool drawNodes, RedrawScope redrawScope) {
+ using (var helper = GraphGizmoHelper.GetSingleFrameGizmoHelper(gizmos, active, redrawScope)) {
+ // The width and depth fields might not be up to date, so recalculate
+ // them from the #unclampedSize field
+ int w, d;
+ float s;
+ CalculateDimensions(out w, out d, out s);
+ var bounds = new Bounds();
+ bounds.SetMinMax(Vector3.zero, new Vector3(w, 0, d));
+ using (helper.builder.WithMatrix(CalculateTransform().matrix)) {
+ helper.builder.WireBox(bounds, Color.white);
+
+ int nodeCount = nodes != null ? nodes.Length : -1;
+
+ if (drawNodes && width*depth*LayerCount != nodeCount) {
+ var color = new Color(1, 1, 1, 0.2f);
+ helper.builder.WireGrid(new float3(w*0.5f, 0, d*0.5f), Quaternion.identity, new int2(w, d), new float2(w, d), color);
+ }
+ }
+ }
+
+ if (!drawNodes) {
+ return;
+ }
+
+ // Loop through chunks of size chunkWidth*chunkWidth and create a gizmo mesh for each of those chunks.
+ // This is done because rebuilding the gizmo mesh (such as when using Unity Gizmos) every frame is pretty slow
+ // for large graphs. However just checking if any mesh needs to be updated is relatively fast. So we just store
+ // a hash together with the mesh and rebuild the mesh when necessary.
+ const int chunkWidth = 32;
+ GridNodeBase[] allNodes = ArrayPool.Claim(chunkWidth*chunkWidth*LayerCount);
+ for (int cx = width/chunkWidth; cx >= 0; cx--) {
+ for (int cz = depth/chunkWidth; cz >= 0; cz--) {
+ Profiler.BeginSample("Hash");
+ var allNodesCount = GetNodesInRegion(new IntRect(cx*chunkWidth, cz*chunkWidth, (cx+1)*chunkWidth - 1, (cz+1)*chunkWidth - 1), allNodes);
+ var hasher = new NodeHasher(active);
+ hasher.Add(showMeshOutline);
+ hasher.Add(showMeshSurface);
+ hasher.Add(showNodeConnections);
+ for (int i = 0; i < allNodesCount; i++) {
+ hasher.HashNode(allNodes[i]);
+ }
+ Profiler.EndSample();
+
+ if (!gizmos.Draw(hasher, redrawScope)) {
+ Profiler.BeginSample("Rebuild Retained Gizmo Chunk");
+ using (var helper = GraphGizmoHelper.GetGizmoHelper(gizmos, active, hasher, redrawScope)) {
+ if (showNodeConnections) {
+ for (int i = 0; i < allNodesCount; i++) {
+ // Don't bother drawing unwalkable nodes
+ if (allNodes[i].Walkable) {
+ helper.DrawConnections(allNodes[i]);
+ }
+ }
+ }
+ if (showMeshSurface || showMeshOutline) CreateNavmeshSurfaceVisualization(allNodes, allNodesCount, helper);
+ }
+ Profiler.EndSample();
+ }
+ }
+ }
+ ArrayPool.Release(ref allNodes);
+
+ if (active.showUnwalkableNodes) DrawUnwalkableNodes(gizmos, nodeSize * 0.3f, redrawScope);
+ }
+
+ ///
+ /// Draw the surface as well as an outline of the grid graph.
+ /// The nodes will be drawn as squares (or hexagons when using = Six).
+ ///
+ void CreateNavmeshSurfaceVisualization (GridNodeBase[] nodes, int nodeCount, GraphGizmoHelper helper) {
+ // Count the number of nodes that we will render
+ int walkable = 0;
+
+ for (int i = 0; i < nodeCount; i++) {
+ if (nodes[i].Walkable) walkable++;
+ }
+
+ var neighbourIndices = neighbours == NumNeighbours.Six ? hexagonNeighbourIndices : new [] { 0, 1, 2, 3 };
+ var offsetMultiplier = neighbours == NumNeighbours.Six ? 0.333333f : 0.5f;
+
+ // 2 for a square-ish grid, 4 for a hexagonal grid.
+ var trianglesPerNode = neighbourIndices.Length-2;
+ var verticesPerNode = 3*trianglesPerNode;
+
+ // Get arrays that have room for all vertices/colors (the array might be larger)
+ var vertices = ArrayPool.Claim(walkable*verticesPerNode);
+ var colors = ArrayPool.Claim(walkable*verticesPerNode);
+ int baseIndex = 0;
+
+ for (int i = 0; i < nodeCount; i++) {
+ var node = nodes[i];
+ if (!node.Walkable) continue;
+
+ var nodeColor = helper.NodeColor(node);
+ // Don't bother drawing transparent nodes
+ if (nodeColor.a <= 0.001f) continue;
+
+ for (int dIndex = 0; dIndex < neighbourIndices.Length; dIndex++) {
+ // For neighbours != Six
+ // n2 -- n3
+ // | |
+ // n -- n1
+ //
+ // n = this node
+ var d = neighbourIndices[dIndex];
+ var nextD = neighbourIndices[(dIndex + 1) % neighbourIndices.Length];
+ GridNodeBase n1, n2, n3 = null;
+ n1 = node.GetNeighbourAlongDirection(d);
+ if (n1 != null && neighbours != NumNeighbours.Six) {
+ n3 = n1.GetNeighbourAlongDirection(nextD);
+ }
+
+ n2 = node.GetNeighbourAlongDirection(nextD);
+ if (n2 != null && n3 == null && neighbours != NumNeighbours.Six) {
+ n3 = n2.GetNeighbourAlongDirection(d);
+ }
+
+ // Position in graph space of the vertex
+ Vector3 p = new Vector3(node.XCoordinateInGrid + 0.5f, 0, node.ZCoordinateInGrid + 0.5f);
+ // Offset along diagonal to get the correct XZ coordinates
+ p.x += (neighbourXOffsets[d] + neighbourXOffsets[nextD]) * offsetMultiplier;
+ p.z += (neighbourZOffsets[d] + neighbourZOffsets[nextD]) * offsetMultiplier;
+
+ // Interpolate the y coordinate of the vertex so that the mesh will be seamless (except in some very rare edge cases)
+ p.y += transform.InverseTransform((Vector3)node.position).y;
+ if (n1 != null) p.y += transform.InverseTransform((Vector3)n1.position).y;
+ if (n2 != null) p.y += transform.InverseTransform((Vector3)n2.position).y;
+ if (n3 != null) p.y += transform.InverseTransform((Vector3)n3.position).y;
+ p.y /= (1f + (n1 != null ? 1f : 0f) + (n2 != null ? 1f : 0f) + (n3 != null ? 1f : 0f));
+
+ // Convert the point from graph space to world space
+ // This handles things like rotations, scale other transformations
+ p = transform.Transform(p);
+ vertices[baseIndex + dIndex] = p;
+ }
+
+ if (neighbours == NumNeighbours.Six) {
+ // Form the two middle triangles
+ vertices[baseIndex + 6] = vertices[baseIndex + 0];
+ vertices[baseIndex + 7] = vertices[baseIndex + 2];
+ vertices[baseIndex + 8] = vertices[baseIndex + 3];
+
+ vertices[baseIndex + 9] = vertices[baseIndex + 0];
+ vertices[baseIndex + 10] = vertices[baseIndex + 3];
+ vertices[baseIndex + 11] = vertices[baseIndex + 5];
+ } else {
+ // Form the last triangle
+ vertices[baseIndex + 4] = vertices[baseIndex + 0];
+ vertices[baseIndex + 5] = vertices[baseIndex + 2];
+ }
+
+ // Set all colors for the node
+ for (int j = 0; j < verticesPerNode; j++) {
+ colors[baseIndex + j] = nodeColor;
+ }
+
+ // Draw the outline of the node
+ for (int j = 0; j < neighbourIndices.Length; j++) {
+ var other = node.GetNeighbourAlongDirection(neighbourIndices[(j+1) % neighbourIndices.Length]);
+ // Just a tie breaker to make sure we don't draw the line twice.
+ // Using NodeInGridIndex instead of NodeIndex to make the gizmos deterministic for a given grid layout.
+ // This is important because if the graph would be re-scanned and only a small part of it would change
+ // then most chunks would be cached by the gizmo system, but the node indices may have changed and
+ // if NodeIndex was used then we might get incorrect gizmos at the borders between chunks.
+ if (other == null || (showMeshOutline && node.NodeInGridIndex < other.NodeInGridIndex)) {
+ helper.builder.Line(vertices[baseIndex + j], vertices[baseIndex + (j+1) % neighbourIndices.Length], other == null ? Color.black : nodeColor);
+ }
+ }
+
+ baseIndex += verticesPerNode;
+ }
+
+ if (showMeshSurface) helper.DrawTriangles(vertices, colors, baseIndex*trianglesPerNode/verticesPerNode);
+
+ ArrayPool.Release(ref vertices);
+ ArrayPool.Release(ref colors);
+ }
+
+ ///
+ /// Bounding box in world space which encapsulates all nodes in the given rectangle.
+ ///
+ /// The bounding box will cover all nodes' surfaces completely. Not just their centers.
+ ///
+ /// Note: The bounding box may not be particularly tight if the graph is not axis-aligned.
+ ///
+ /// See:
+ ///
+ /// Which nodes to consider. Will be clamped to the grid's bounds. If the rectangle is outside the graph, an empty bounds will be returned.
+ public Bounds GetBoundsFromRect (IntRect rect) {
+ rect = IntRect.Intersection(rect, new IntRect(0, 0, width-1, depth-1));
+ if (!rect.IsValid()) return new Bounds();
+ return transform.Transform(new Bounds(
+ new Vector3(rect.xmin + rect.xmax, collision.fromHeight, rect.ymin + rect.ymax) * 0.5f,
+ // Note: We add +1 to the width and height to make the bounding box cover the nodes' surfaces completely, instead
+ // of just their centers.
+ new Vector3(rect.Width + 1, collision.fromHeight, rect.Height + 1)
+ ));
+ }
+
+ ///
+ /// A rect that contains all nodes that the bounds could touch.
+ /// This correctly handles rotated graphs and other transformations.
+ /// The returned rect is guaranteed to not extend outside the graph bounds.
+ ///
+ /// Note: The rect may contain nodes that are not contained in the bounding box since the bounding box is aligned to the world, and the rect is aligned to the grid (which may be rotated).
+ ///
+ /// See:
+ /// See:
+ ///
+ public IntRect GetRectFromBounds (Bounds bounds) {
+ // Take the bounds and transform it using the matrix
+ // Then convert that to a rectangle which contains
+ // all nodes that might be inside the bounds
+
+ bounds = transform.InverseTransform(bounds);
+ Vector3 min = bounds.min;
+ Vector3 max = bounds.max;
+
+ // Allow the bounds to extend a tiny amount into adjacent nodes.
+ // This is mostly to avoid requiring a much larger update region if a user
+ // passes a bounding box exactly (plus/minus floating point errors) covering
+ // a set of nodes.
+ const float MARGIN = 0.01f;
+
+ int minX = Mathf.FloorToInt(min.x+MARGIN);
+ int maxX = Mathf.FloorToInt(max.x-MARGIN);
+
+ int minZ = Mathf.FloorToInt(min.z+MARGIN);
+ int maxZ = Mathf.FloorToInt(max.z-MARGIN);
+
+ var originalRect = new IntRect(minX, minZ, maxX, maxZ);
+
+ // Rect which covers the whole grid
+ var gridRect = new IntRect(0, 0, width-1, depth-1);
+
+ // Clamp the rect to the grid
+ return IntRect.Intersection(originalRect, gridRect);
+ }
+
+ ///
+ /// All nodes inside the bounding box.
+ /// Note: Be nice to the garbage collector and pool the list when you are done with it (optional)
+ /// See: Pathfinding.Util.ListPool
+ ///
+ /// See: GetNodesInRegion(GraphUpdateShape)
+ ///
+ public List GetNodesInRegion (Bounds bounds) {
+ return GetNodesInRegion(bounds, null);
+ }
+
+ ///
+ /// All nodes inside the shape.
+ /// Note: Be nice to the garbage collector and pool the list when you are done with it (optional)
+ /// See: Pathfinding.Util.ListPool
+ ///
+ /// See: GetNodesInRegion(Bounds)
+ ///
+ public List GetNodesInRegion (GraphUpdateShape shape) {
+ return GetNodesInRegion(shape.GetBounds(), shape);
+ }
+
+ ///
+ /// All nodes inside the shape or if null, the bounding box.
+ /// If a shape is supplied, it is assumed to be contained inside the bounding box.
+ /// See: GraphUpdateShape.GetBounds
+ ///
+ protected virtual List GetNodesInRegion (Bounds bounds, GraphUpdateShape shape) {
+ var rect = GetRectFromBounds(bounds);
+
+ if (nodes == null || !rect.IsValid() || nodes.Length != width*depth*LayerCount) {
+ return ListPool.Claim();
+ }
+
+ // Get a buffer we can use
+ var inArea = ListPool.Claim(rect.Width*rect.Height);
+ var rw = rect.Width;
+
+ // Loop through all nodes in the rectangle
+ for (int y = 0; y < LayerCount; y++) {
+ for (int z = rect.ymin; z <= rect.ymax; z++) {
+ var offset = y*width*depth + z*width + rect.xmin;
+ for (int x = 0; x < rw; x++) {
+ var node = nodes[offset + x];
+ if (node == null) continue;
+
+ // If it is contained in the bounds (and optionally the shape)
+ // then add it to the buffer
+ var pos = (Vector3)node.position;
+ if (bounds.Contains(pos) && (shape == null || shape.Contains(pos))) {
+ inArea.Add(node);
+ }
+ }
+ }
+ }
+
+ return inArea;
+ }
+
+ ///
+ /// Get all nodes in a rectangle.
+ ///
+ /// See:
+ ///
+ /// Region in which to return nodes. It will be clamped to the grid.
+ public List GetNodesInRegion (IntRect rect) {
+ // Clamp the rect to the grid
+ // Rect which covers the whole grid
+ var gridRect = new IntRect(0, 0, width-1, depth-1);
+
+ rect = IntRect.Intersection(rect, gridRect);
+
+ if (nodes == null || !rect.IsValid() || nodes.Length != width*depth*LayerCount) return ListPool.Claim(0);
+
+ // Get a buffer we can use
+ var inArea = ListPool.Claim(rect.Width*rect.Height);
+ var rw = rect.Width;
+
+ for (int y = 0; y < LayerCount; y++) {
+ for (int z = rect.ymin; z <= rect.ymax; z++) {
+ var offset = y*width*depth + z*width + rect.xmin;
+ for (int x = 0; x < rw; x++) {
+ var node = nodes[offset + x];
+ if (node != null) inArea.Add(node);
+ }
+ }
+ }
+
+ return inArea;
+ }
+
+ ///
+ /// Get all nodes in a rectangle.
+ /// Returns: The number of nodes written to the buffer.
+ ///
+ /// Note: This method is much faster than GetNodesInRegion(IntRect) which returns a list because this method can make use of the highly optimized
+ /// System.Array.Copy method.
+ ///
+ /// See:
+ ///
+ /// Region in which to return nodes. It will be clamped to the grid.
+ /// Buffer in which the nodes will be stored. Should be at least as large as the number of nodes that can exist in that region.
+ public virtual int GetNodesInRegion (IntRect rect, GridNodeBase[] buffer) {
+ // Clamp the rect to the grid
+ // Rect which covers the whole grid
+ var gridRect = new IntRect(0, 0, width-1, depth-1);
+
+ rect = IntRect.Intersection(rect, gridRect);
+
+ if (nodes == null || !rect.IsValid() || nodes.Length != width*depth) return 0;
+
+ if (buffer.Length < rect.Width*rect.Height) throw new System.ArgumentException("Buffer is too small");
+
+ int counter = 0;
+ for (int z = rect.ymin; z <= rect.ymax; z++, counter += rect.Width) {
+ System.Array.Copy(nodes, z*Width + rect.xmin, buffer, counter, rect.Width);
+ }
+
+ return counter;
+ }
+
+ ///
+ /// Node in the specified cell.
+ /// Returns null if the coordinate is outside the grid.
+ ///
+ ///
+ /// var gg = AstarPath.active.data.gridGraph;
+ /// int x = 5;
+ /// int z = 8;
+ /// GridNodeBase node = gg.GetNode(x, z);
+ ///
+ ///
+ /// If you know the coordinate is inside the grid and you are looking to maximize performance then you
+ /// can look up the node in the internal array directly which is slightly faster.
+ /// See:
+ ///
+ public virtual GridNodeBase GetNode (int x, int z) {
+ if (x < 0 || z < 0 || x >= width || z >= depth) return null;
+ return nodes[x + z*width];
+ }
+
+ class CombinedGridGraphUpdatePromise : IGraphUpdatePromise {
+ List promises;
+
+ public CombinedGridGraphUpdatePromise(GridGraph graph, List graphUpdates) {
+ promises = ListPool.Claim();
+ var nodesHolder = new GridGraphUpdatePromise.NodesHolder { nodes = graph.nodes };
+
+ for (int i = 0; i < graphUpdates.Count; i++) {
+ var graphUpdate = graphUpdates[i];
+ var promise = new GridGraphUpdatePromise(
+ graph: graph,
+ transform: graph.transform,
+ nodes: nodesHolder,
+ nodeArrayBounds: new int3(graph.width, graph.LayerCount, graph.depth),
+ rect: graph.GetRectFromBounds(graphUpdate.bounds),
+ dependencyTracker: ObjectPool.Claim(),
+ nodesDependsOn: default,
+ allocationMethod: Allocator.Persistent,
+ recalculationMode: graphUpdate.updatePhysics ? RecalculationMode.RecalculateMinimal : RecalculationMode.NoRecalculation,
+ graphUpdateObject: graphUpdate,
+ ownsJobDependencyTracker: true
+ );
+ promises.Add(promise);
+ }
+ }
+
+ public IEnumerator Prepare () {
+ for (int i = 0; i < promises.Count; i++) {
+ var it = promises[i].Prepare();
+ while (it.MoveNext()) yield return it.Current;
+ }
+ }
+
+ public void Apply (IGraphUpdateContext ctx) {
+ for (int i = 0; i < promises.Count; i++) {
+ promises[i].Apply(ctx);
+ }
+ ListPool.Release(ref promises);
+ }
+ }
+
+ /// Internal function to update the graph
+ IGraphUpdatePromise IUpdatableGraph.ScheduleGraphUpdates (List graphUpdates) {
+ if (!isScanned || nodes.Length != width*depth*LayerCount) {
+ Debug.LogWarning("The Grid Graph is not scanned, cannot update graph");
+ return null;
+ }
+
+ collision.Initialize(transform, nodeSize);
+ return new CombinedGridGraphUpdatePromise(this, graphUpdates);
+ }
+
+ class GridGraphSnapshot : IGraphSnapshot {
+ internal GridGraphNodeData nodes;
+ internal GridGraph graph;
+
+ public void Dispose () {
+ nodes.Dispose();
+ }
+
+ public void Restore (IGraphUpdateContext ctx) {
+ graph.AssertSafeToUpdateGraph();
+ if (!graph.isScanned) return;
+
+ if (!graph.nodeData.bounds.Contains(nodes.bounds)) {
+ Debug.LogError("Cannot restore snapshot because the graph dimensions have changed since the snapshot was taken");
+ return;
+ }
+
+ var dependencyTracker = ObjectPool.Claim();
+ graph.nodeData.CopyFrom(nodes, true, dependencyTracker);
+
+ var assignToNodesJob = nodes.AssignToNodes(graph.nodes, graph.nodeData.bounds.size, nodes.bounds, graph.graphIndex, new JobHandle(), dependencyTracker);
+ assignToNodesJob.Complete();
+ dependencyTracker.AllWritesDependency.Complete();
+ ObjectPool.Release(ref dependencyTracker);
+
+ // Recalculate off mesh links in the affected area
+ ctx.DirtyBounds(graph.GetBoundsFromRect(new IntRect(nodes.bounds.min.x, nodes.bounds.min.z, nodes.bounds.max.x - 1, nodes.bounds.max.z - 1)));
+ }
+ }
+
+ public override IGraphSnapshot Snapshot (Bounds bounds) {
+ if (active.isScanning || active.IsAnyWorkItemInProgress) {
+ throw new System.InvalidOperationException("Trying to capture a grid graph snapshot while inside a work item. This is not supported, as the graphs may be in an inconsistent state.");
+ }
+
+ if (!isScanned || nodes.Length != width*depth*LayerCount) return null;
+
+ GridGraphUpdatePromise.CalculateRectangles(this, GetRectFromBounds(bounds), out var _, out var _, out var writeMaskRect, out var _);
+ if (!writeMaskRect.IsValid()) return null;
+
+ var nodeBounds = new IntBounds(writeMaskRect.xmin, 0, writeMaskRect.ymin, writeMaskRect.xmax + 1, LayerCount, writeMaskRect.ymax + 1);
+ var snapshotData = new GridGraphNodeData {
+ allocationMethod = Allocator.Persistent,
+ bounds = nodeBounds,
+ numNodes = nodeBounds.volume,
+ };
+ snapshotData.AllocateBuffers(null);
+ snapshotData.CopyFrom(this.nodeData, true, null);
+ return new GridGraphSnapshot {
+ nodes = snapshotData,
+ graph = this,
+ };
+ }
+
+ ///
+ /// Returns if there is an obstacle between from and to on the graph.
+ /// This is not the same as Physics.Linecast, this function traverses the graph and looks for collisions.
+ ///
+ ///
+ /// var gg = AstarPath.active.data.gridGraph;
+ /// bool anyObstaclesInTheWay = gg.Linecast(transform.position, enemy.position);
+ ///
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Edge cases are handled as follows:
+ /// - Shared edges and corners between walkable and unwalkable nodes are treated as walkable (so for example if the linecast just touches a corner of an unwalkable node, this is allowed).
+ /// - If the linecast starts outside the graph, a hit is returned at from.
+ /// - If the linecast starts inside the graph, but the end is outside of it, a hit is returned at the point where it exits the graph (unless there are any other hits before that).
+ ///
+ public bool Linecast (Vector3 from, Vector3 to) {
+ GraphHitInfo hit;
+
+ return Linecast(from, to, out hit);
+ }
+
+ ///
+ /// Returns if there is an obstacle between from and to on the graph.
+ ///
+ /// This is not the same as Physics.Linecast, this function traverses the graph and looks for collisions.
+ ///
+ ///
+ /// var gg = AstarPath.active.data.gridGraph;
+ /// bool anyObstaclesInTheWay = gg.Linecast(transform.position, enemy.position);
+ ///
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Deprecated: The hint parameter is deprecated
+ ///
+ /// Point to linecast from
+ /// Point to linecast to
+ /// This parameter is deprecated. It will be ignored.
+ [System.Obsolete("The hint parameter is deprecated")]
+ public bool Linecast (Vector3 from, Vector3 to, GraphNode hint) {
+ GraphHitInfo hit;
+
+ return Linecast(from, to, hint, out hit);
+ }
+
+ ///
+ /// Returns if there is an obstacle between from and to on the graph.
+ ///
+ /// This is not the same as Physics.Linecast, this function traverses the graph and looks for collisions.
+ ///
+ ///
+ /// var gg = AstarPath.active.data.gridGraph;
+ /// bool anyObstaclesInTheWay = gg.Linecast(transform.position, enemy.position);
+ ///
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Deprecated: The hint parameter is deprecated
+ ///
+ /// Point to linecast from
+ /// Point to linecast to
+ /// Contains info on what was hit, see GraphHitInfo
+ /// This parameter is deprecated. It will be ignored.
+ [System.Obsolete("The hint parameter is deprecated")]
+ public bool Linecast (Vector3 from, Vector3 to, GraphNode hint, out GraphHitInfo hit) {
+ return Linecast(from, to, hint, out hit, null);
+ }
+
+ /// Magnitude of the cross product a x b
+ protected static long CrossMagnitude (int2 a, int2 b) {
+ return (long)a.x*b.y - (long)b.x*a.y;
+ }
+
+ ///
+ /// Clips a line segment in graph space to the graph bounds.
+ /// That is (0,0,0) is the bottom left corner of the graph and (width,0,depth) is the top right corner.
+ /// The first node is placed at (0.5,y,0.5). One unit distance is the same as nodeSize.
+ ///
+ /// Returns false if the line segment does not intersect the graph at all.
+ ///
+ protected bool ClipLineSegmentToBounds (Vector3 a, Vector3 b, out Vector3 outA, out Vector3 outB) {
+ // If the start or end points are outside
+ // the graph then clamping is needed
+ if (a.x < 0 || a.z < 0 || a.x > width || a.z > depth ||
+ b.x < 0 || b.z < 0 || b.x > width || b.z > depth) {
+ // Boundary of the grid
+ var p1 = new Vector3(0, 0, 0);
+ var p2 = new Vector3(0, 0, depth);
+ var p3 = new Vector3(width, 0, depth);
+ var p4 = new Vector3(width, 0, 0);
+
+ int intersectCount = 0;
+
+ bool intersect;
+ Vector3 intersection;
+
+ intersection = VectorMath.SegmentIntersectionPointXZ(a, b, p1, p2, out intersect);
+
+ if (intersect) {
+ intersectCount++;
+ if (!VectorMath.RightOrColinearXZ(p1, p2, a)) {
+ a = intersection;
+ } else {
+ b = intersection;
+ }
+ }
+ intersection = VectorMath.SegmentIntersectionPointXZ(a, b, p2, p3, out intersect);
+
+ if (intersect) {
+ intersectCount++;
+ if (!VectorMath.RightOrColinearXZ(p2, p3, a)) {
+ a = intersection;
+ } else {
+ b = intersection;
+ }
+ }
+ intersection = VectorMath.SegmentIntersectionPointXZ(a, b, p3, p4, out intersect);
+
+ if (intersect) {
+ intersectCount++;
+ if (!VectorMath.RightOrColinearXZ(p3, p4, a)) {
+ a = intersection;
+ } else {
+ b = intersection;
+ }
+ }
+ intersection = VectorMath.SegmentIntersectionPointXZ(a, b, p4, p1, out intersect);
+
+ if (intersect) {
+ intersectCount++;
+ if (!VectorMath.RightOrColinearXZ(p4, p1, a)) {
+ a = intersection;
+ } else {
+ b = intersection;
+ }
+ }
+
+ if (intersectCount == 0) {
+ // The line does not intersect with the grid
+ outA = Vector3.zero;
+ outB = Vector3.zero;
+ return false;
+ }
+ }
+
+ outA = a;
+ outB = b;
+ return true;
+ }
+
+ ///
+ /// Returns if there is an obstacle between from and to on the graph.
+ ///
+ /// This is not the same as Physics.Linecast, this function traverses the graph and looks for collisions.
+ ///
+ ///
+ /// var gg = AstarPath.active.data.gridGraph;
+ /// bool anyObstaclesInTheWay = gg.Linecast(transform.position, enemy.position);
+ ///
+ ///
+ /// Deprecated: The hint parameter is deprecated
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Point to linecast from
+ /// Point to linecast to
+ /// Contains info on what was hit, see GraphHitInfo
+ /// This parameter is deprecated. It will be ignored.
+ /// If a list is passed, then it will be filled with all nodes the linecast traverses
+ /// If not null then the delegate will be called for each node and if it returns false the node will be treated as unwalkable and a hit will be returned.
+ /// Note that unwalkable nodes are always treated as unwalkable regardless of what this filter returns.
+ [System.Obsolete("The hint parameter is deprecated")]
+ public bool Linecast (Vector3 from, Vector3 to, GraphNode hint, out GraphHitInfo hit, List trace, System.Func filter = null) {
+ return Linecast(from, to, out hit, trace, filter);
+ }
+
+ ///
+ /// Returns if there is an obstacle between from and to on the graph.
+ ///
+ /// This is not the same as Physics.Linecast, this function traverses the graph and looks for collisions.
+ ///
+ /// Edge cases are handled as follows:
+ /// - Shared edges and corners between walkable and unwalkable nodes are treated as walkable (so for example if the linecast just touches a corner of an unwalkable node, this is allowed).
+ /// - If the linecast starts outside the graph, a hit is returned at from.
+ /// - If the linecast starts inside the graph, but the end is outside of it, a hit is returned at the point where it exits the graph (unless there are any other hits before that).
+ ///
+ ///
+ /// var gg = AstarPath.active.data.gridGraph;
+ /// bool anyObstaclesInTheWay = gg.Linecast(transform.position, enemy.position);
+ ///
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Point to linecast from
+ /// Point to linecast to
+ /// Contains info on what was hit, see \reflink{GraphHitInfo}.
+ /// If a list is passed, then it will be filled with all nodes the linecast traverses
+ /// If not null then the delegate will be called for each node and if it returns false the node will be treated as unwalkable and a hit will be returned.
+ /// Note that unwalkable nodes are always treated as unwalkable regardless of what this filter returns.
+ public bool Linecast (Vector3 from, Vector3 to, out GraphHitInfo hit, List trace = null, System.Func filter = null) {
+ var res = Linecast(from, to, out GridHitInfo gridHit, trace, filter);
+ hit = new GraphHitInfo {
+ origin = from,
+ node = gridHit.node,
+ };
+ if (res) {
+ // Hit obstacle
+ // We know from what direction we moved in
+ // so we can calculate the line which we hit
+ var ndir = gridHit.direction;
+ if (ndir == -1 || gridHit.node == null) {
+ // We didn't really hit a wall. Possibly the start node was unwalkable or we ended up at the right cell, but wrong floor (layered grid graphs only)
+ hit.point = gridHit.node == null || !gridHit.node.Walkable || (filter != null && !filter(gridHit.node)) ? from : to;
+ if (gridHit.node != null) hit.point = gridHit.node.ProjectOnSurface(hit.point);
+ hit.tangentOrigin = Vector3.zero;
+ hit.tangent = Vector3.zero;
+ } else {
+ Vector3 fromInGraphSpace = transform.InverseTransform(from);
+ Vector3 toInGraphSpace = transform.InverseTransform(to);
+
+ // Throw away components we don't care about (y)
+ // Also subtract 0.5 because nodes have an offset of 0.5 (first node is at (0.5,0.5) not at (0,0))
+ // And it's just more convenient to remove that term here.
+ // The variable names #from and #to are unfortunately already taken, so let's use start and end.
+ var fromInGraphSpace2D = new Vector2(fromInGraphSpace.x - 0.5f, fromInGraphSpace.z - 0.5f);
+ var toInGraphSpace2D = new Vector2(toInGraphSpace.x - 0.5f, toInGraphSpace.z - 0.5f);
+
+ // Current direction and current direction ±90 degrees
+ var d1 = new Vector2(neighbourXOffsets[ndir], neighbourZOffsets[ndir]);
+ var d2 = new Vector2(neighbourXOffsets[(ndir-1+4) & 0x3], neighbourZOffsets[(ndir-1+4) & 0x3]);
+ Vector2 lineDirection = new Vector2(neighbourXOffsets[(ndir+1) & 0x3], neighbourZOffsets[(ndir+1) & 0x3]);
+ var p = new Vector2(gridHit.node.XCoordinateInGrid, gridHit.node.ZCoordinateInGrid);
+ Vector2 lineOrigin = p + (d1 + d2) * 0.5f;
+
+ // Find the intersection
+ var intersection = VectorMath.LineIntersectionPoint(lineOrigin, lineOrigin+lineDirection, fromInGraphSpace2D, toInGraphSpace2D);
+
+ var currentNodePositionInGraphSpace = transform.InverseTransform((Vector3)gridHit.node.position);
+
+ // The intersection is in graph space (with an offset of 0.5) so we need to transform it to world space
+ var intersection3D = new Vector3(intersection.x + 0.5f, currentNodePositionInGraphSpace.y, intersection.y + 0.5f);
+ var lineOrigin3D = new Vector3(lineOrigin.x + 0.5f, currentNodePositionInGraphSpace.y, lineOrigin.y + 0.5f);
+
+ hit.point = transform.Transform(intersection3D);
+ hit.tangentOrigin = transform.Transform(lineOrigin3D);
+ hit.tangent = transform.TransformVector(new Vector3(lineDirection.x, 0, lineDirection.y));
+ }
+ } else {
+ hit.point = to;
+ }
+ return res;
+ }
+
+ ///
+ /// Returns if there is an obstacle between from and to on the graph.
+ ///
+ /// This function is different from the other Linecast functions since it snaps the start and end positions to the centers of the closest nodes on the graph.
+ /// This is not the same as Physics.Linecast, this function traverses the graph and looks for collisions.
+ ///
+ /// Version: Since 3.6.8 this method uses the same implementation as the other linecast methods so there is no performance boost to using it.
+ /// Version: In 3.6.8 this method was rewritten and that fixed a large number of bugs.
+ /// Previously it had not always followed the line exactly as it should have
+ /// and the hit output was not very accurate
+ /// (for example the hit point was just the node position instead of a point on the edge which was hit).
+ ///
+ /// Deprecated: Use instead.
+ ///
+ /// Point to linecast from.
+ /// Point to linecast to.
+ /// Contains info on what was hit, see GraphHitInfo.
+ /// This parameter is deprecated. It will be ignored.
+ [System.Obsolete("Use Linecast instead")]
+ public bool SnappedLinecast (Vector3 from, Vector3 to, GraphNode hint, out GraphHitInfo hit) {
+ return Linecast(
+ (Vector3)GetNearest(from, null).node.position,
+ (Vector3)GetNearest(to, null).node.position,
+ hint,
+ out hit
+ );
+ }
+
+ ///
+ /// Returns if there is an obstacle between the two nodes on the graph.
+ ///
+ /// This method is very similar to the other Linecast methods however it is a bit faster
+ /// due to not having to look up which node is closest to a particular input point.
+ ///
+ ///
+ /// var gg = AstarPath.active.data.gridGraph;
+ /// var node1 = gg.GetNode(2, 3);
+ /// var node2 = gg.GetNode(5, 7);
+ /// bool anyObstaclesInTheWay = gg.Linecast(node1, node2);
+ ///
+ ///
+ /// Node to start from.
+ /// Node to try to reach using a straight line.
+ /// If not null then the delegate will be called for each node and if it returns false the node will be treated as unwalkable and a hit will be returned.
+ /// Note that unwalkable nodes are always treated as unwalkable regardless of what this filter returns.
+ public bool Linecast (GridNodeBase fromNode, GridNodeBase toNode, System.Func filter = null) {
+ var nodeCenter = new int2(FixedPrecisionScale/2, FixedPrecisionScale/2);
+ return Linecast(fromNode, nodeCenter, toNode, nodeCenter, out GridHitInfo hit, null, filter);
+ }
+
+ ///
+ /// Returns if there is an obstacle between from and to on the graph.
+ ///
+ /// This is not the same as Physics.Linecast, this function traverses the graph and looks for collisions.
+ ///
+ /// Note: This overload outputs a hit of type instead of . It's a bit faster to calculate this output
+ /// and it can be useful for some grid-specific algorithms.
+ ///
+ /// Edge cases are handled as follows:
+ /// - Shared edges and corners between walkable and unwalkable nodes are treated as walkable (so for example if the linecast just touches a corner of an unwalkable node, this is allowed).
+ /// - If the linecast starts outside the graph, a hit is returned at from.
+ /// - If the linecast starts inside the graph, but the end is outside of it, a hit is returned at the point where it exits the graph (unless there are any other hits before that).
+ ///
+ ///
+ /// var gg = AstarPath.active.data.gridGraph;
+ /// bool anyObstaclesInTheWay = gg.Linecast(transform.position, enemy.position);
+ ///
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Point to linecast from
+ /// Point to linecast to
+ /// Contains info on what was hit, see \reflink{GridHitInfo}
+ /// If a list is passed, then it will be filled with all nodes the linecast traverses
+ /// If not null then the delegate will be called for each node and if it returns false the node will be treated as unwalkable and a hit will be returned.
+ /// Note that unwalkable nodes are always treated as unwalkable regardless of what this filter returns.
+ public bool Linecast (Vector3 from, Vector3 to, out GridHitInfo hit, List trace = null, System.Func filter = null) {
+ Vector3 fromInGraphSpace = transform.InverseTransform(from);
+ Vector3 toInGraphSpace = transform.InverseTransform(to);
+
+ // Clip the line so that the start and end points are on the graph
+ if (!ClipLineSegmentToBounds(fromInGraphSpace, toInGraphSpace, out var fromInGraphSpaceClipped, out var toInGraphSpaceClipped)) {
+ // Line does not intersect the graph
+ // So there are no obstacles we can hit
+ hit = new GridHitInfo {
+ node = null,
+ direction = -1,
+ };
+ return false;
+ }
+
+ // From is outside the graph, but #to is inside.
+ if ((fromInGraphSpace - fromInGraphSpaceClipped).sqrMagnitude > 0.001f*0.001f) {
+ hit = new GridHitInfo {
+ node = null,
+ direction = -1,
+ };
+ return true;
+ }
+ bool toIsOutsideGraph = (toInGraphSpace - toInGraphSpaceClipped).sqrMagnitude > 0.001f*0.001f;
+
+ // Find the closest nodes to the start and end on the part of the segment which is on the graph
+ var startNode = GetNearestFromGraphSpace(fromInGraphSpaceClipped);
+ var endNode = GetNearestFromGraphSpace(toInGraphSpaceClipped);
+ if (startNode == null || endNode == null) {
+ hit = new GridHitInfo {
+ node = null,
+ direction = -1,
+ };
+ return false;
+ }
+
+ return Linecast(
+ startNode, new Vector2(fromInGraphSpaceClipped.x - startNode.XCoordinateInGrid, fromInGraphSpaceClipped.z - startNode.ZCoordinateInGrid),
+ endNode, new Vector2(toInGraphSpaceClipped.x - endNode.XCoordinateInGrid, toInGraphSpaceClipped.z - endNode.ZCoordinateInGrid),
+ out hit,
+ trace,
+ filter,
+ toIsOutsideGraph
+ );
+ }
+
+ ///
+ /// Scaling used for the coordinates in the Linecast methods that take normalized points using integer coordinates.
+ ///
+ /// To convert from world space, each coordinate is multiplied by this factor and then rounded to the nearest integer.
+ ///
+ /// Typically you do not need to use this constant yourself, instead use the Linecast overloads that do not take integer coordinates.
+ ///
+ public const int FixedPrecisionScale = 1024;
+
+ ///
+ /// Returns if there is an obstacle between the two nodes on the graph.
+ ///
+ /// This method is very similar to the other Linecast methods but it gives some extra control, in particular when the start/end points are at node corners instead of inside nodes.
+ ///
+ /// Shared edges and corners between walkable and unwalkable nodes are treated as walkable.
+ /// So for example if the linecast just touches a corner of an unwalkable node, this is allowed.
+ ///
+ /// Node to start from.
+ /// Where in the start node to start. This is a normalized value so each component must be in the range 0 to 1 (inclusive).
+ /// Node to try to reach using a straight line.
+ /// Where in the end node to end. This is a normalized value so each component must be in the range 0 to 1 (inclusive).
+ /// Contains info on what was hit, see \reflink{GridHitInfo}
+ /// If a list is passed, then it will be filled with all nodes the linecast traverses
+ /// If not null then the delegate will be called for each node and if it returns false the node will be treated as unwalkable and a hit will be returned.
+ /// Note that unwalkable nodes are always treated as unwalkable regardless of what this filter returns.
+ /// If true, the linecast will continue past the end point in the same direction until it hits something.
+ public bool Linecast (GridNodeBase fromNode, Vector2 normalizedFromPoint, GridNodeBase toNode, Vector2 normalizedToPoint, out GridHitInfo hit, List trace = null, System.Func filter = null, bool continuePastEnd = false) {
+ var fixedNormalizedFromPoint = new int2((int)Mathf.Round(normalizedFromPoint.x*FixedPrecisionScale), (int)Mathf.Round(normalizedFromPoint.y*FixedPrecisionScale));
+ var fixedNormalizedToPoint = new int2((int)Mathf.Round(normalizedToPoint.x*FixedPrecisionScale), (int)Mathf.Round(normalizedToPoint.y*FixedPrecisionScale));
+
+ return Linecast(fromNode, fixedNormalizedFromPoint, toNode, fixedNormalizedToPoint, out hit, trace, filter, continuePastEnd);
+ }
+
+ ///
+ /// Returns if there is an obstacle between the two nodes on the graph.
+ /// Like but takes normalized points as fixed precision points normalized between 0 and FixedPrecisionScale instead of between 0 and 1.
+ ///
+ public bool Linecast (GridNodeBase fromNode, int2 fixedNormalizedFromPoint, GridNodeBase toNode, int2 fixedNormalizedToPoint, out GridHitInfo hit, List trace = null, System.Func filter = null, bool continuePastEnd = false) {
+ /*
+ * Briefly, the algorithm used in this function can be described as:
+ * 1. Determine the two axis aligned directions which will bring us closer to the target.
+ * 2. In each step, check which direction out of those two that the linecast exits the current node from.
+ * 3. Try to move in that direction if possible. If the linecast exits the current node through a corner, then moving along either direction is allowed.
+ * 4. If that's not possible, and the line exits the current node at a corner, then try to move to the other side of line to the other row/column.
+ * 5. If we still couldn't move anywhere, report a hit.
+ * 6. Go back to step 2.
+ *
+ * Sadly the implementation is complicated by numerous edge cases, while trying to keep everything highly performant.
+ * I've tried to document them as best I could.
+ *
+ * TODO: Maybe this could be rewritten such that instead of only being positioned at one node at a time,
+ * we could be inside up to two nodes at the same time (which share either an edge or a corner).
+ * This divergence would be done when the linecast line goes through a corner or right in the middle between two nodes.
+ * This could potentially remove a bunch of edge cases.
+ */
+ if (fixedNormalizedFromPoint.x < 0 || fixedNormalizedFromPoint.x > FixedPrecisionScale) throw new System.ArgumentOutOfRangeException(nameof(fixedNormalizedFromPoint), "must be between 0 and 1024");
+ if (fixedNormalizedToPoint.x < 0 || fixedNormalizedToPoint.x > FixedPrecisionScale) throw new System.ArgumentOutOfRangeException(nameof(fixedNormalizedToPoint), "must be between 0 and 1024");
+
+ if (fromNode == null) throw new System.ArgumentNullException(nameof(fromNode));
+ if (toNode == null) throw new System.ArgumentNullException(nameof(toNode));
+
+ // Use the filter
+ if ((filter != null && !filter(fromNode)) || !fromNode.Walkable) {
+ hit = new GridHitInfo {
+ node = fromNode,
+ direction = -1,
+ };
+ return true;
+ }
+
+ if (fromNode == toNode) {
+ // Fast path, we don't have to do anything
+ hit = new GridHitInfo {
+ node = fromNode,
+ direction = -1,
+ };
+ if (trace != null) trace.Add(fromNode);
+ return false;
+ }
+
+ var fromGridCoords = new int2(fromNode.XCoordinateInGrid, fromNode.ZCoordinateInGrid);
+ var toGridCoords = new int2(toNode.XCoordinateInGrid, toNode.ZCoordinateInGrid);
+
+ var fixedFrom = new int2(fromGridCoords.x*FixedPrecisionScale, fromGridCoords.y*FixedPrecisionScale) + fixedNormalizedFromPoint;
+ var fixedTo = new int2(toGridCoords.x*FixedPrecisionScale, toGridCoords.y*FixedPrecisionScale) + fixedNormalizedToPoint;
+ var dir = fixedTo - fixedFrom;
+
+ int remainingSteps = System.Math.Abs(fromGridCoords.x - toGridCoords.x) + System.Math.Abs(fromGridCoords.y - toGridCoords.y);
+ if (continuePastEnd) remainingSteps = int.MaxValue;
+
+ // If the from and to points are identical, but we start and end on different nodes, then dir will be zero
+ // and the direction calculations below will get a bit messsed up.
+ // So instead we don't take any steps at all, there's some code right at the end of this function which will
+ // look around the corner and find the target node anyway.
+ if (math.all(fixedFrom == fixedTo)) remainingSteps = 0;
+
+ /* Y/Z
+ * |
+ * quadrant | quadrant
+ * 1 0
+ * 2
+ * |
+ * ---- 3 - X - 1 ----- X
+ * |
+ * 0
+ * quadrant quadrant
+ * 2 | 3
+ * |
+ */
+
+ // Calculate the quadrant index as shown in the diagram above (the axes are part of the quadrants after them in the counter clockwise direction)
+ int quadrant = 0;
+
+ // The linecast line may be axis aligned, but we might still need to move to the side one step.
+ // Like in the following two cases (starting at node S at corner X and ending at node T at corner P).
+ // ┌─┬─┬─┬─┐ ┌─┬─┬─┬─┐
+ // │S│ │ │ │ │S│ │#│T│
+ // ├─X===P─┤ ├─X===P─┤
+ // │ │ │ │T│ │ │ │ │ │
+ // └─┴─┴─┴─┘ └─┴─┴─┴─┘
+ //
+ // We make sure that we will always be able to move to the side of the line the target is on, if we happen to be on the wrong side of the line.
+ var dirBiased = dir;
+ if (dirBiased.x == 0) dirBiased.x = System.Math.Sign(FixedPrecisionScale/2 - fixedNormalizedToPoint.x);
+ if (dirBiased.y == 0) dirBiased.y = System.Math.Sign(FixedPrecisionScale/2 - fixedNormalizedToPoint.y);
+
+ if (dirBiased.x <= 0 && dirBiased.y > 0) quadrant = 1;
+ else if (dirBiased.x < 0 && dirBiased.y <= 0) quadrant = 2;
+ else if (dirBiased.x >= 0 && dirBiased.y < 0) quadrant = 3;
+
+ // This will be (1,2) for quadrant 0 and (2,3) for quadrant 1 etc.
+ // & 0x3 is just the same thing as % 4 but it is faster
+ // This is the direction which moves further to the right of the segment (when looking from the start)
+ int directionToReduceError = (quadrant + 1) & 0x3;
+ // This is the direction which moves further to the left of the segment (when looking from the start)
+ int directionToIncreaseError = (quadrant + 2) & 0x3;
+
+ // All errors used in this function are proportional to the signed distance.
+ // They have a common multiplier which is dir.magnitude, but dividing away that would be very slow.
+ // Note that almost all errors are multiplied by 2. It might seem like this could be optimized away,
+ // but it cannot. The reason is that later when we use primaryDirectionError we only walk *half* a normal step.
+ // But we don't want to use division, so instead we multiply all other errors by 2.
+ //
+ // How much further we move away from (or towards) the line when walking along the primary direction (e.g up and right or down and left).
+ long primaryDirectionError = CrossMagnitude(dir,
+ new int2(
+ neighbourXOffsets[directionToIncreaseError]+neighbourXOffsets[directionToReduceError],
+ neighbourZOffsets[directionToIncreaseError]+neighbourZOffsets[directionToReduceError]
+ )
+ );
+
+ // Conceptually we start with error 0 at 'fixedFrom' (i.e. precisely on the line).
+ // Imagine walking from fixedFrom to the center of the starting node.
+ // This will change our "error" (signed distance to the line) correspondingly.
+ int2 offset = new int2(FixedPrecisionScale/2, FixedPrecisionScale/2) - fixedNormalizedFromPoint;
+
+ // Signed distance from the line (or at least a value proportional to that)
+ long error = CrossMagnitude(dir, offset) * 2 / FixedPrecisionScale;
+
+ // Walking one step along the X axis will increase (or decrease) our error by this amount.
+ // This is equivalent to a cross product of dir with the x axis: CrossMagnitude(dir, new int2(1, 0)) * 2
+ long xerror = -dir.y * 2;
+ // Walking one step along the Z axis will increase our error by this amount
+ long zerror = dir.x * 2;
+
+ // When we move across a diagonal it can sometimes be important which side of the diagonal we prioritize.
+ //
+ // ┌───┬───┐
+ // │ │ S │
+ //=======P─C
+ // │ │ T │
+ // └───┴───┘
+ //
+ // Assume we are at node S and our target is node T at point P (it lies precisely between S and T).
+ // Note that the linecast line (illustrated as ===) comes from the left. This means that this case will be detected as a diagonal move (because corner C lies on the line).
+ // In this case we can walk either to the right from S or downwards. However walking to the right would mean that we end up in the wrong node (not the T node).
+ // Therefore we make sure that, if possible, we are on the same side of the linecast line as the center of the target node is.
+ int symmetryBreakingDirection1 = directionToIncreaseError;
+ int symmetryBreakingDirection2 = directionToReduceError;
+
+ var fixedCenterOfToNode = new int2(toGridCoords.x*FixedPrecisionScale, toGridCoords.y*FixedPrecisionScale) + new int2(FixedPrecisionScale/2, FixedPrecisionScale/2);
+ long targetNodeError = CrossMagnitude(dir, fixedCenterOfToNode - fixedFrom);
+ if (targetNodeError < 0) {
+ symmetryBreakingDirection1 = directionToReduceError;
+ symmetryBreakingDirection2 = directionToIncreaseError;
+ }
+
+ GridNodeBase prevNode = null;
+ GridNodeBase preventBacktrackingTo = null;
+
+ for (; remainingSteps > 0; remainingSteps--) {
+ if (trace != null) trace.Add(fromNode);
+
+ // How does the error change we take one half step in the primary direction.
+ // The point which this represents is a corner of the current node.
+ // Depending on which side of this point the line is (when seen from the center of the current node)
+ // we know which direction we should walk from the node.
+ // Since the error is just a signed distance, checking the side is equivalent to checking if its positive or negative.
+ var nerror = error + primaryDirectionError;
+
+ int ndir;
+ GridNodeBase nextNode;
+
+ if (nerror == 0) {
+ // This would be a diagonal move. But we don't allow those for simplicity (we can just as well just take it in two axis aligned steps).
+ // In this case we are free to choose which direction to move.
+ // If one direction is blocked, we choose the other one.
+ ndir = symmetryBreakingDirection1;
+ nextNode = fromNode.GetNeighbourAlongDirection(ndir);
+ if ((filter != null && nextNode != null && !filter(nextNode)) || nextNode == prevNode) nextNode = null;
+
+ if (nextNode == null) {
+ // Try the other one too...
+ ndir = symmetryBreakingDirection2;
+ nextNode = fromNode.GetNeighbourAlongDirection(ndir);
+ if ((filter != null && nextNode != null && !filter(nextNode)) || nextNode == prevNode) nextNode = null;
+ }
+ } else {
+ // This is the happy-path of the linecast. We just move in the direction of the line.
+ // Check if we need to reduce or increase the error (we want to keep it near zero)
+ // and pick the appropriate direction to move in
+ ndir = nerror < 0 ? directionToIncreaseError : directionToReduceError;
+ nextNode = fromNode.GetNeighbourAlongDirection(ndir);
+
+ // Use the filter
+ if ((filter != null && nextNode != null && !filter(nextNode)) || nextNode == prevNode) nextNode = null;
+ }
+
+ // If we cannot move forward from this node, we might still be able to by side-stepping.
+ // This is a case that we need to handle if the linecast line exits this node at a corner.
+ //
+ // Assume we start at node S (at corner X) and linecast to node T (corner P)
+ // The linecast goes exactly between two rows of nodes.
+ // The code will start by going down one row, but then after a few nodes it hits an obstacle (when it's in node A).
+ // We don't want to report a hit here because the linecast only touches the edge of the obstacle, which is allowed.
+ // Instead we try to move to the node on the other side of the line (node B).
+ // The shared corner C lies exactly on the line, and we can detect that to figure out which neighbor we should move to.
+ //
+ // ┌───────┬───────┬───────┬───────┐
+ // │ │ B │ │ │
+ // │ S │ ┌───┼───────┼───┐ │
+ // │ │ │ │ │ │ │ │
+ // X===│=======│===C=======P───┼───┤
+ // │ │ │ │ │#######│ │ │
+ // │ └───┼───┘ │#######│ ▼ │
+ // │ │ A │#######│ T │
+ // └───────┴───────┴───────┴───────┘
+ //
+ // After we have done this maneuver it is important that in the next step we don't try to move back to the node we came from.
+ // We keep track of this using the prevNode variable.
+ //
+ if (nextNode == null) {
+ // Loop over the two corners of the side of the node that we hit
+ for (int i = -1; i <= 1; i += 2) {
+ var d = (ndir + i + 4) & 0x3;
+ if (error + xerror/2 * (neighbourXOffsets[ndir] + neighbourXOffsets[d]) + zerror/2 * (neighbourZOffsets[ndir]+neighbourZOffsets[d]) == 0) {
+ // The line touches this corner precisely
+ // Try to side-step in that direction.
+
+ nextNode = fromNode.GetNeighbourAlongDirection(d);
+ if ((filter != null && nextNode != null && !filter(nextNode)) || nextNode == prevNode || nextNode == preventBacktrackingTo) nextNode = null;
+
+ if (nextNode != null) {
+ // This side-stepping might add 1 additional step to the path, or not. It's hard to say.
+ // We add 1 because the for loop will decrement remainingSteps after this iteration ends.
+ remainingSteps = 1 + System.Math.Abs(nextNode.XCoordinateInGrid - toGridCoords.x) + System.Math.Abs(nextNode.ZCoordinateInGrid - toGridCoords.y);
+ ndir = d;
+ prevNode = fromNode;
+ preventBacktrackingTo = nextNode;
+ }
+ break;
+ }
+ }
+
+ // If we still have not found the next node yet, then we have hit an obstacle
+ if (nextNode == null) {
+ hit = new GridHitInfo {
+ node = fromNode,
+ direction = ndir,
+ };
+ return true;
+ }
+ }
+
+ // Calculate how large our error will be after moving along the given direction
+ error += xerror * neighbourXOffsets[ndir] + zerror * neighbourZOffsets[ndir];
+ fromNode = nextNode;
+ }
+
+ hit = new GridHitInfo {
+ node = fromNode,
+ direction = -1,
+ };
+
+ if (fromNode != toNode) {
+ // When the destination is on a corner it is sometimes possible that we end up in the wrong node.
+ //
+ // ┌───┬───┐
+ // │ S │ │
+ // ├───P───┤
+ // │ T │ │
+ // └───┴───┘
+ //
+ // Assume we are at node S and our target is node T at point P (i.e. normalizedToPoint = (1,1) so it is in the corner of the node).
+ // In this case we can walk either to the right from S or downwards. However walking to the right would mean that we end up in the wrong node (not the T node).
+ //
+ // Similarly, if the connection from S to T was blocked for some reason (but both S and T are walkable), then we would definitely end up to the right of S, not in T.
+ //
+ // Therefore we check if the destination is a corner, and if so, try to reach all 4 nodes around that corner to see if any one of those is the destination.
+ var dirToDestination = fixedTo - (new int2(fromNode.XCoordinateInGrid, fromNode.ZCoordinateInGrid)*FixedPrecisionScale + new int2(FixedPrecisionScale/2, FixedPrecisionScale/2));
+
+ // Check if the destination is a corner of this node
+ if (math.all(math.abs(dirToDestination) == new int2(FixedPrecisionScale/2, FixedPrecisionScale/2))) {
+ var delta = dirToDestination*2/FixedPrecisionScale;
+ // Figure out which directions will move us towards the target node.
+ // We first try to move around the corner P in the counter-clockwise direction.
+ // And if that fails, we try to move in the clockwise direction.
+ // ┌───────┬───────┐
+ // │ │ │
+ // │ ccw◄─┼───S │
+ // │ │ │ │
+ // ├───────P───┼───┤
+ // │ │ ▼ │
+ // │ T │ cw │
+ // │ │ │
+ // └───────┴───────┘
+ var counterClockwiseDirection = -1;
+ for (int i = 0; i < 4; i++) {
+ // Exactly one direction will satisfy this. It's kinda annnoying to calculate analytically.
+ if (neighbourXOffsets[i]+neighbourXOffsets[(i+1)&0x3] == delta.x && neighbourZOffsets[i] + neighbourZOffsets[(i+1)&0x3] == delta.y) {
+ counterClockwiseDirection = i;
+ break;
+ }
+ }
+
+ int traceLength = trace != null ? trace.Count : 0;
+ int d = counterClockwiseDirection;
+ var node = fromNode;
+ for (int i = 0; i < 3 && node != toNode; i++) {
+ if (trace != null) trace.Add(node);
+ node = node.GetNeighbourAlongDirection(d);
+ if (node == null || (filter != null && !filter(node))) {
+ node = null;
+ break;
+ }
+ d = (d + 1) & 0x3;
+ }
+
+ if (node != toNode) {
+ if (trace != null) trace.RemoveRange(traceLength, trace.Count - traceLength);
+ node = fromNode;
+ // Try the clockwise direction instead
+ d = (counterClockwiseDirection + 1) & 0x3;
+ for (int i = 0; i < 3 && node != toNode; i++) {
+ if (trace != null) trace.Add(node);
+ node = node.GetNeighbourAlongDirection(d);
+ if (node == null || (filter != null && !filter(node))) {
+ node = null;
+ break;
+ }
+ d = (d - 1 + 4) & 0x3;
+ }
+
+ if (node != toNode && trace != null) {
+ trace.RemoveRange(traceLength, trace.Count - traceLength);
+ }
+ }
+
+ fromNode = node;
+ }
+ }
+
+ if (trace != null) trace.Add(fromNode);
+ return fromNode != toNode;
+ }
+
+ protected override void SerializeExtraInfo (GraphSerializationContext ctx) {
+ if (nodes == null) {
+ ctx.writer.Write(-1);
+ return;
+ }
+
+ ctx.writer.Write(nodes.Length);
+
+ for (int i = 0; i < nodes.Length; i++) {
+ nodes[i].SerializeNode(ctx);
+ }
+
+ SerializeNodeSurfaceNormals(ctx);
+ }
+
+ protected override void DeserializeExtraInfo (GraphSerializationContext ctx) {
+ int count = ctx.reader.ReadInt32();
+
+ if (count == -1) {
+ nodes = null;
+ return;
+ }
+
+ nodes = new GridNode[count];
+
+ for (int i = 0; i < nodes.Length; i++) {
+ nodes[i] = newGridNodeDelegate();
+ active.InitializeNode(nodes[i]);
+ nodes[i].DeserializeNode(ctx);
+ }
+ DeserializeNativeData(ctx, ctx.meta.version >= AstarSerializer.V4_3_6);
+ }
+
+ protected void DeserializeNativeData (GraphSerializationContext ctx, bool normalsSerialized) {
+ UpdateTransform();
+ var tracker = ObjectPool.Claim();
+ bool layeredDataLayout = this is LayerGridGraph;
+ var nodeArraySize = new int3(width, LayerCount, depth);
+ nodeData = GridGraphNodeData.ReadFromNodes(nodes, new Slice3D(nodeArraySize, new IntBounds(0, nodeArraySize)), default, default, Allocator.Persistent, layeredDataLayout, tracker);
+ nodeData.PersistBuffers(tracker);
+ DeserializeNodeSurfaceNormals(ctx, nodes, !normalsSerialized);
+ tracker.AllWritesDependency.Complete();
+ ObjectPool.Release(ref tracker);
+ }
+
+ protected void SerializeNodeSurfaceNormals (GraphSerializationContext ctx) {
+ var normals = nodeData.normals.AsUnsafeReadOnlySpan();
+ for (int i = 0; i < nodes.Length; i++) {
+ ctx.SerializeVector3(new Vector3(normals[i].x, normals[i].y, normals[i].z));
+ }
+ }
+
+ protected void DeserializeNodeSurfaceNormals (GraphSerializationContext ctx, GridNodeBase[] nodes, bool ignoreForCompatibility) {
+ if (nodeData.normals.IsCreated) nodeData.normals.Dispose();
+ nodeData.normals = new NativeArray(nodes.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
+ if (ignoreForCompatibility) {
+ // For backwards compatibility with older versions that do not have the information stored.
+ // For most of these versions the #maxStepUsesSlope field will be deserialized to false anyway, so this array will not have any effect.
+ for (int i = 0; i < nodes.Length; i++) {
+ // If a node is null (can only happen for layered grid graphs) then the normal must be set to zero.
+ // Otherwise we set it to a "reasonable" up direction.
+ nodeData.normals[i] = nodes[i] != null ? new float4(0, 1, 0, 0) : float4.zero;
+ }
+ } else {
+ for (int i = 0; i < nodes.Length; i++) {
+ var v = ctx.DeserializeVector3();
+ nodeData.normals[i] = new float4(v.x, v.y, v.z, 0);
+ }
+ }
+ }
+
+ void HandleBackwardsCompatibility (GraphSerializationContext ctx) {
+ // For compatibility
+ if (ctx.meta.version <= AstarSerializer.V4_3_2) maxStepUsesSlope = false;
+
+#pragma warning disable CS0618 // Type or member is obsolete
+ if (penaltyPosition) {
+ penaltyPosition = false;
+ // Can't convert it exactly. So assume there are no nodes with an elevation greater than 1000
+ rules.AddRule(new RuleElevationPenalty {
+ penaltyScale = Int3.Precision * penaltyPositionFactor * 1000.0f,
+ elevationRange = new Vector2(-penaltyPositionOffset/Int3.Precision, -penaltyPositionOffset/Int3.Precision + 1000),
+ curve = AnimationCurve.Linear(0, 0, 1, 1),
+ });
+ }
+
+ if (penaltyAngle) {
+ penaltyAngle = false;
+
+ // Approximate the legacy behavior with an animation curve
+ var curve = AnimationCurve.Linear(0, 0, 1, 1);
+ var keys = new Keyframe[7];
+ for (int i = 0; i < keys.Length; i++) {
+ var angle = Mathf.PI*0.5f*i/(keys.Length-1);
+ var penalty = (1F-Mathf.Pow(Mathf.Cos(angle), penaltyAnglePower))*penaltyAngleFactor;
+ var key = new Keyframe(Mathf.Rad2Deg * angle, penalty);
+ keys[i] = key;
+ }
+ var maxPenalty = keys.Max(k => k.value);
+ if (maxPenalty > 0) for (int i = 0; i < keys.Length; i++) keys[i].value /= maxPenalty;
+ curve.keys = keys;
+ for (int i = 0; i < keys.Length; i++) {
+ curve.SmoothTangents(i, 0.5f);
+ }
+
+ rules.AddRule(new RuleAnglePenalty {
+ penaltyScale = maxPenalty,
+ curve = curve,
+ });
+ }
+
+ if (textureData.enabled) {
+ textureData.enabled = false;
+ var channelScales = textureData.factors.Select(x => x/255.0f).ToList();
+ while (channelScales.Count < 4) channelScales.Add(1000);
+ var channels = textureData.channels.Cast().ToList();
+ while (channels.Count < 4) channels.Add(RuleTexture.ChannelUse.None);
+
+ rules.AddRule(new RuleTexture {
+ texture = textureData.source,
+ channels = channels.ToArray(),
+ channelScales = channelScales.ToArray(),
+ scalingMode = RuleTexture.ScalingMode.FixedScale,
+ nodesPerPixel = 1.0f,
+ });
+ }
+#pragma warning restore CS0618 // Type or member is obsolete
+ }
+
+ protected override void PostDeserialization (GraphSerializationContext ctx) {
+ HandleBackwardsCompatibility(ctx);
+
+ UpdateTransform();
+ SetUpOffsetsAndCosts();
+ GridNode.SetGridGraph((int)graphIndex, this);
+
+ // Deserialize all nodes
+ if (nodes == null || nodes.Length == 0) return;
+
+ if (width*depth != nodes.Length) {
+ Debug.LogError("Node data did not match with bounds data. Probably a change to the bounds/width/depth data was made after scanning the graph just prior to saving it. Nodes will be discarded");
+ nodes = new GridNodeBase[0];
+ return;
+ }
+
+ for (int z = 0; z < depth; z++) {
+ for (int x = 0; x < width; x++) {
+ var node = nodes[z*width+x];
+
+ if (node == null) {
+ Debug.LogError("Deserialization Error : Couldn't cast the node to the appropriate type - GridGenerator");
+ return;
+ }
+
+ node.NodeInGridIndex = z*width+x;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Number of neighbours for a single grid node.
+ /// Since: The 'Six' item was added in 3.6.1
+ ///
+ public enum NumNeighbours {
+ Four,
+ Eight,
+ Six
+ }
+
+ /// Information about a linecast hit on a grid graph
+ public struct GridHitInfo {
+ ///
+ /// The node which contained the edge that was hit.
+ /// This may be null in case no particular edge was hit.
+ ///
+ public GridNodeBase node;
+ ///
+ /// Direction from the node to the edge that was hit.
+ /// This will be in the range of 0 to 4 (exclusive) or -1 if no particular edge was hit.
+ ///
+ /// See:
+ ///
+ public int direction;
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/GridGraph.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/GridGraph.cs.meta
new file mode 100644
index 0000000..e7eeee2
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/GridGraph.cs.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: d5d28978e568e40429b2981fab3e380e
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/LayerGridGraph.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/LayerGridGraph.cs
new file mode 100644
index 0000000..c629224
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/LayerGridGraph.cs
@@ -0,0 +1,246 @@
+#if !ASTAR_NO_GRID_GRAPH
+using UnityEngine;
+using System.Collections.Generic;
+using Pathfinding.Serialization;
+using Pathfinding.Graphs.Grid;
+
+namespace Pathfinding {
+ ///
+ /// Grid Graph, supports layered worlds.
+ /// [Open online documentation to see images]
+ /// The GridGraph is great in many ways, reliable, easily configured and updatable during runtime.
+ /// But it lacks support for worlds which have multiple layers, such as a building with multiple floors.
+ /// That's where this graph type comes in. It supports basically the same stuff as the grid graph, but also multiple layers.
+ /// It uses a bit more memory than a regular grid graph, but is otherwise equivalent.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Note: The graph supports 16 layers by default, but it can be increased to 256 by enabling the ASTAR_LEVELGRIDNODE_MORE_LAYERS option in the A* Inspector -> Settings -> Optimizations tab.
+ ///
+ /// See:
+ ///
+ [Pathfinding.Util.Preserve]
+ public class LayerGridGraph : GridGraph, IUpdatableGraph {
+ // This function will be called when this graph is destroyed
+ protected override void DisposeUnmanagedData () {
+ base.DisposeUnmanagedData();
+
+ // Clean up a reference in a static variable which otherwise should point to this graph forever and stop the GC from collecting it
+ LevelGridNode.ClearGridGraph((int)graphIndex, this);
+ }
+
+ public LayerGridGraph () {
+ newGridNodeDelegate = () => new LevelGridNode();
+ }
+
+ protected override GridNodeBase[] AllocateNodesJob (int size, out Unity.Jobs.JobHandle dependency) {
+ var newNodes = new LevelGridNode[size];
+
+ dependency = active.AllocateNodes(newNodes, size, newGridNodeDelegate, 1);
+ return newNodes;
+ }
+
+ ///
+ /// Number of layers.
+ /// Warning: Do not modify this variable
+ ///
+ [JsonMember]
+ internal int layerCount;
+
+ /// Nodes with a short distance to the node above it will be set unwalkable
+ [JsonMember]
+ public float characterHeight = 0.4F;
+
+ internal int lastScannedWidth;
+ internal int lastScannedDepth;
+
+ public override int LayerCount {
+ get => layerCount;
+ protected set => layerCount = value;
+ }
+
+ public override int MaxLayers => LevelGridNode.MaxLayerCount;
+
+ public override int CountNodes () {
+ if (nodes == null) return 0;
+
+ int counter = 0;
+ for (int i = 0; i < nodes.Length; i++) {
+ if (nodes[i] != null) counter++;
+ }
+ return counter;
+ }
+
+ public override void GetNodes (System.Action action) {
+ if (nodes == null) return;
+
+ for (int i = 0; i < nodes.Length; i++) {
+ if (nodes[i] != null) action(nodes[i]);
+ }
+ }
+
+ ///
+ /// Get all nodes in a rectangle.
+ /// Returns: The number of nodes written to the buffer.
+ ///
+ /// Region in which to return nodes. It will be clamped to the grid.
+ /// Buffer in which the nodes will be stored. Should be at least as large as the number of nodes that can exist in that region.
+ public override int GetNodesInRegion (IntRect rect, GridNodeBase[] buffer) {
+ // Clamp the rect to the grid
+ // Rect which covers the whole grid
+ var gridRect = new IntRect(0, 0, width-1, depth-1);
+
+ rect = IntRect.Intersection(rect, gridRect);
+
+ if (nodes == null || !rect.IsValid() || nodes.Length != width*depth*layerCount) return 0;
+
+ int counter = 0;
+ try {
+ for (int l = 0; l < layerCount; l++) {
+ var lwd = l * Width * Depth;
+ for (int z = rect.ymin; z <= rect.ymax; z++) {
+ var offset = lwd + z*Width;
+ for (int x = rect.xmin; x <= rect.xmax; x++) {
+ var node = nodes[offset + x];
+ if (node != null) {
+ buffer[counter] = node;
+ counter++;
+ }
+ }
+ }
+ }
+ } catch (System.IndexOutOfRangeException) {
+ // Catch the exception which 'buffer[counter] = node' would throw if the buffer was too small
+ throw new System.ArgumentException("Buffer is too small");
+ }
+
+ return counter;
+ }
+
+ ///
+ /// Node in the specified cell.
+ /// Returns null if the coordinate is outside the grid.
+ ///
+ /// If you know the coordinate is inside the grid and you are looking to maximize performance then you
+ /// can look up the node in the internal array directly which is slightly faster.
+ /// See:
+ ///
+ public GridNodeBase GetNode (int x, int z, int layer) {
+ if (x < 0 || z < 0 || x >= width || z >= depth || layer < 0 || layer >= layerCount) return null;
+ return nodes[x + z*width + layer*width*depth];
+ }
+
+ protected override IGraphUpdatePromise ScanInternal (bool async) {
+ LevelGridNode.SetGridGraph((int)graphIndex, this);
+ layerCount = 0;
+ lastScannedWidth = width;
+ lastScannedDepth = depth;
+ return base.ScanInternal(async);
+ }
+
+ protected override GridNodeBase GetNearestFromGraphSpace (Vector3 positionGraphSpace) {
+ if (nodes == null || depth*width*layerCount != nodes.Length) {
+ return null;
+ }
+
+ float xf = positionGraphSpace.x;
+ float zf = positionGraphSpace.z;
+ int x = Mathf.Clamp((int)xf, 0, width-1);
+ int z = Mathf.Clamp((int)zf, 0, depth-1);
+ var worldPos = transform.Transform(positionGraphSpace);
+ return GetNearestNode(worldPos, x, z, null);
+ }
+
+ private GridNodeBase GetNearestNode (Vector3 position, int x, int z, NNConstraint constraint) {
+ int index = width*z+x;
+ float minDist = float.PositiveInfinity;
+ GridNodeBase minNode = null;
+
+ for (int i = 0; i < layerCount; i++) {
+ var node = nodes[index + width*depth*i];
+ if (node != null) {
+ float dist = ((Vector3)node.position - position).sqrMagnitude;
+ if (dist < minDist && (constraint == null || constraint.Suitable(node))) {
+ minDist = dist;
+ minNode = node;
+ }
+ }
+ }
+ return minNode;
+ }
+
+ protected override void SerializeExtraInfo (GraphSerializationContext ctx) {
+ if (nodes == null) {
+ ctx.writer.Write(-1);
+ return;
+ }
+
+ ctx.writer.Write(nodes.Length);
+
+ for (int i = 0; i < nodes.Length; i++) {
+ if (nodes[i] == null) {
+ ctx.writer.Write(-1);
+ } else {
+ ctx.writer.Write(0);
+ nodes[i].SerializeNode(ctx);
+ }
+ }
+
+ SerializeNodeSurfaceNormals(ctx);
+ }
+
+ protected override void DeserializeExtraInfo (GraphSerializationContext ctx) {
+ int count = ctx.reader.ReadInt32();
+
+ if (count == -1) {
+ nodes = null;
+ return;
+ }
+
+ nodes = new LevelGridNode[count];
+ for (int i = 0; i < nodes.Length; i++) {
+ if (ctx.reader.ReadInt32() != -1) {
+ nodes[i] = newGridNodeDelegate();
+ active.InitializeNode(nodes[i]);
+ nodes[i].DeserializeNode(ctx);
+ } else {
+ nodes[i] = null;
+ }
+ }
+ DeserializeNativeData(ctx, ctx.meta.version >= AstarSerializer.V4_3_37);
+ }
+
+ protected override void PostDeserialization (GraphSerializationContext ctx) {
+ UpdateTransform();
+ lastScannedWidth = width;
+ lastScannedDepth = depth;
+
+ SetUpOffsetsAndCosts();
+ LevelGridNode.SetGridGraph((int)graphIndex, this);
+
+ if (nodes == null || nodes.Length == 0) return;
+
+ if (width*depth*layerCount != nodes.Length) {
+ Debug.LogError("Node data did not match with bounds data. Probably a change to the bounds/width/depth data was made after scanning the graph, just prior to saving it. Nodes will be discarded");
+ nodes = new GridNodeBase[0];
+ return;
+ }
+
+ for (int i = 0; i < layerCount; i++) {
+ for (int z = 0; z < depth; z++) {
+ for (int x = 0; x < width; x++) {
+ LevelGridNode node = nodes[z*width+x + width*depth*i] as LevelGridNode;
+
+ if (node == null) {
+ continue;
+ }
+
+ node.NodeInGridIndex = z*width+x;
+ node.LayerCoordinateInGrid = i;
+ }
+ }
+ }
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/LayerGridGraph.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/LayerGridGraph.cs.meta
new file mode 100644
index 0000000..d897588
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/LayerGridGraph.cs.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: d6ea17a678e4042de89cdfa01860ad8a
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/LinkGraph.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/LinkGraph.cs
new file mode 100644
index 0000000..7212135
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/LinkGraph.cs
@@ -0,0 +1,167 @@
+using UnityEngine;
+using System.Collections.Generic;
+using Pathfinding.Serialization;
+using Pathfinding.Util;
+using UnityEngine.Assertions;
+
+namespace Pathfinding {
+ using System;
+ using Pathfinding.Drawing;
+ using Unity.Jobs;
+
+ ///
+ /// Graph for off-mesh links.
+ ///
+ /// This is an internal graph type which is used to store off-mesh links.
+ /// An off-mesh link between two nodes A and B is represented as: A <--> N1 <--> N2 <--> B
.
+ /// where N1 and N2 are two special nodes added to this graph at the exact start and endpoints of the link.
+ ///
+ /// This graph is not persistent. So it will never be saved to disk and a new one will be created each time the game starts.
+ ///
+ /// It is also not possible to query for the nearest node in this graph. The method will always return an empty result.
+ /// This is by design, as all pathfinding should start on the navmesh, not on an off-mesh link.
+ ///
+ /// See:
+ /// See:
+ ///
+ [JsonOptIn]
+ [Pathfinding.Util.Preserve]
+ public class LinkGraph : NavGraph {
+ LinkNode[] nodes = new LinkNode[0];
+ int nodeCount;
+
+ public override bool isScanned => true;
+
+ public override bool persistent => false;
+
+ public override bool showInInspector => false;
+
+ public override int CountNodes() => nodeCount;
+
+ protected override void DestroyAllNodes () {
+ base.DestroyAllNodes();
+ nodes = new LinkNode[0];
+ nodeCount = 0;
+ }
+
+ public override void GetNodes (Action action) {
+ if (nodes == null) return;
+ for (int i = 0; i < nodeCount; i++) action(nodes[i]);
+ }
+
+ internal LinkNode AddNode () {
+ AssertSafeToUpdateGraph();
+ if (nodeCount >= nodes.Length) {
+ Memory.Realloc(ref nodes, Mathf.Max(16, nodeCount * 2));
+ }
+ nodeCount++;
+ return nodes[nodeCount-1] = new LinkNode(active) {
+ nodeInGraphIndex = nodeCount - 1,
+ GraphIndex = graphIndex,
+ Walkable = true,
+ };
+ }
+
+ internal void RemoveNode (LinkNode node) {
+ if (nodes[node.nodeInGraphIndex] != node) throw new ArgumentException("Node is not in this graph");
+ // Remove and swap with the last node
+ nodeCount--;
+ nodes[node.nodeInGraphIndex] = nodes[nodeCount];
+ nodes[node.nodeInGraphIndex].nodeInGraphIndex = node.nodeInGraphIndex;
+ nodes[nodeCount] = null;
+ node.Destroy();
+ }
+
+ public override float NearestNodeDistanceSqrLowerBound(Vector3 position, NNConstraint constraint = null) => float.PositiveInfinity;
+
+ ///
+ /// It's not possible to query for the nearest node in a link graph.
+ /// This method will always return an empty result.
+ ///
+ public override NNInfo GetNearest(Vector3 position, NNConstraint constraint, float maxDistanceSqr) => default;
+
+ public override void OnDrawGizmos (DrawingData gizmos, bool drawNodes, RedrawScope redrawScope) {
+ // We rely on the link components themselves to draw the links
+
+ // TODO
+ base.OnDrawGizmos(gizmos, drawNodes, redrawScope);
+ }
+
+ class LinkGraphUpdatePromise : IGraphUpdatePromise {
+ public LinkGraph graph;
+
+ public void Apply (IGraphUpdateContext ctx) {
+ // Destroy all previous nodes (if any)
+ graph.DestroyAllNodes();
+ }
+
+ public IEnumerator Prepare() => null;
+ }
+
+ protected override IGraphUpdatePromise ScanInternal () => new LinkGraphUpdatePromise { graph = this };
+ }
+
+ public class LinkNode : PointNode {
+ public OffMeshLinks.OffMeshLinkSource linkSource;
+ public OffMeshLinks.OffMeshLinkConcrete linkConcrete;
+ public int nodeInGraphIndex;
+
+ public LinkNode() { }
+ public LinkNode(AstarPath active) : base(active) {}
+
+ public override void RemovePartialConnection (GraphNode node) {
+ linkConcrete.staleConnections = true;
+ // Mark the link as dirty so that it will be recalculated.
+ // Ensure that this does not immediately schedule an update.
+ // Nodes should only be updated during work items and while graphs are scanned,
+ // and in those cases node links will be refreshed anyway.
+ // However, this can also trigger when the AstarPath component is being destroyed,
+ // or when a graph is removed. In those cases, we don't want to schedule an update.
+ AstarPath.active.offMeshLinks.DirtyNoSchedule(linkSource);
+ base.RemovePartialConnection(node);
+ }
+
+ public override void Open (Path path, uint pathNodeIndex, uint gScore) {
+ // Note: Not calling path.OpenCandidateConnectionsToEndNode here, because link nodes should never be the end node of a path
+
+ if (connections == null) return;
+
+ var pathHandler = (path as IPathInternals).PathHandler;
+ var pn = pathHandler.pathNodes[pathNodeIndex];
+ // Check if our parent node was also a link node by checking if it is in the same graph as this node.
+ // If it is, we are allowed to connect to non-link nodes.
+ // Otherwise, we are at the start of the link and we must only connect to other link nodes.
+ // This is to avoid the path going to a link node, and then going directly back to a non-link node
+ // without actually traversing the link. It would technically be a valid path,
+ // but it causes confusion for other scripts that look for off-mesh links in the path.
+ // TODO: Store the other link node as a field to be able to do a more robust check here?
+ var isEndOfLink = !pathHandler.IsTemporaryNode(pn.parentIndex) && pathHandler.GetNode(pn.parentIndex).GraphIndex == GraphIndex;
+ var canTraverseNonLinkNodes = isEndOfLink;
+
+ for (int i = 0; i < connections.Length; i++) {
+ GraphNode other = connections[i].node;
+
+ if (canTraverseNonLinkNodes == (other.GraphIndex != GraphIndex) && path.CanTraverse(this, other)) {
+ if (other is PointNode) {
+ path.OpenCandidateConnection(pathNodeIndex, other.NodeIndex, gScore, connections[i].cost, 0, other.position);
+ } else {
+ // When connecting to a non-link node, use a special function to open the connection.
+ // The typical case for this is that we are at the end of an off-mesh link and we are connecting to a navmesh node.
+ // In that case, this node's position is in the interior of the navmesh node. We let the navmesh node decide how
+ // that should be handled.
+ other.OpenAtPoint(path, pathNodeIndex, position, gScore);
+ }
+ }
+ }
+ }
+
+ public override void OpenAtPoint (Path path, uint pathNodeIndex, Int3 pos, uint gScore) {
+ if (path.CanTraverse(this)) {
+ // Note: Not calling path.OpenCandidateConnectionsToEndNode here, because link nodes should never be the end node of a path
+
+ var cost = (uint)(pos - this.position).costMagnitude;
+ path.OpenCandidateConnection(pathNodeIndex, NodeIndex, gScore, cost, 0, position);
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/LinkGraph.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/LinkGraph.cs.meta
new file mode 100644
index 0000000..b15371b
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/LinkGraph.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ea4fb279f02f6c74aaf36b080fa54d28
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/NavGraph.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/NavGraph.cs
new file mode 100644
index 0000000..7be91ca
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/NavGraph.cs
@@ -0,0 +1,469 @@
+using UnityEngine;
+using System.Collections.Generic;
+using Pathfinding.Util;
+using Pathfinding.Serialization;
+using Unity.Collections;
+
+namespace Pathfinding {
+ using Pathfinding.Drawing;
+
+ ///
+ /// Exposes internal methods for graphs.
+ /// This is used to hide methods that should not be used by any user code
+ /// but still have to be 'public' or 'internal' (which is pretty much the same as 'public'
+ /// as this library is distributed with source code).
+ ///
+ /// Hiding the internal methods cleans up the documentation and IntelliSense suggestions.
+ ///
+ public interface IGraphInternals {
+ string SerializedEditorSettings { get; set; }
+ void OnDestroy();
+ void DisposeUnmanagedData();
+ void DestroyAllNodes();
+ IGraphUpdatePromise ScanInternal(bool async);
+ void SerializeExtraInfo(GraphSerializationContext ctx);
+ void DeserializeExtraInfo(GraphSerializationContext ctx);
+ void PostDeserialization(GraphSerializationContext ctx);
+ }
+
+ /// Base class for all graphs
+ public abstract class NavGraph : IGraphInternals {
+ /// Reference to the AstarPath object in the scene
+ public AstarPath active;
+
+ ///
+ /// Used as an ID of the graph, considered to be unique.
+ /// Note: This is Pathfinding.Util.Guid not System.Guid. A replacement for System.Guid was coded for better compatibility with iOS
+ ///
+ [JsonMember]
+ public Guid guid;
+
+ ///
+ /// Default penalty to apply to all nodes.
+ ///
+ /// See: graph-updates (view in online documentation for working links)
+ /// See:
+ /// See: tags (view in online documentation for working links)
+ ///
+ [JsonMember]
+ public uint initialPenalty;
+
+ /// Is the graph open in the editor
+ [JsonMember]
+ public bool open;
+
+ /// Index of the graph, used for identification purposes
+ public uint graphIndex;
+
+ ///
+ /// Name of the graph.
+ /// Can be set in the unity editor
+ ///
+ [JsonMember]
+ public string name;
+
+ ///
+ /// Enable to draw gizmos in the Unity scene view.
+ /// In the inspector this value corresponds to the state of
+ /// the 'eye' icon in the top left corner of every graph inspector.
+ ///
+ [JsonMember]
+ public bool drawGizmos = true;
+
+ ///
+ /// Used in the editor to check if the info screen is open.
+ /// Should be inside UNITY_EDITOR only \ but just in case anyone tries to serialize a NavGraph instance using Unity, I have left it like this as it would otherwise cause a crash when building.
+ /// Version 3.0.8.1 was released because of this bug only
+ ///
+ [JsonMember]
+ public bool infoScreenOpen;
+
+ /// Used in the Unity editor to store serialized settings for graph inspectors
+ [JsonMember]
+ string serializedEditorSettings;
+
+
+ /// True if the graph exists, false if it has been destroyed
+ internal bool exists => active != null;
+
+ ///
+ /// True if the graph has been scanned and contains nodes.
+ ///
+ /// Graphs are typically scanned when the game starts, but they can also be scanned manually.
+ ///
+ /// If a graph has not been scanned, it does not contain any nodes and it not possible to use it for pathfinding.
+ ///
+ /// See:
+ ///
+ public abstract bool isScanned { get; }
+
+ ///
+ /// True if the graph will be included when serializing graph data.
+ ///
+ /// If false, the graph will be ignored when saving graph data.
+ ///
+ /// Most graphs are persistent, but the is not persistent because links are always re-created from components at runtime.
+ ///
+ public virtual bool persistent => true;
+
+ ///
+ /// True if the graph should be visible in the editor.
+ ///
+ /// False is used for some internal graph types that users don't have to worry about.
+ ///
+ public virtual bool showInInspector => true;
+
+ ///
+ /// World bounding box for the graph.
+ ///
+ /// This always contains the whole graph.
+ ///
+ /// Note: Since this an axis aligned bounding box, it may not be particularly tight if the graph is rotated.
+ ///
+ /// It is ok for a graph type to return an infinitely large bounding box, but this may make some operations less efficient.
+ /// The point graph will always return an infinitely large bounding box.
+ ///
+ public virtual Bounds bounds => new Bounds(Vector3.zero, Vector3.positiveInfinity);
+
+ ///
+ /// Number of nodes in the graph.
+ /// Note that this is, unless the graph type has overriden it, an O(n) operation.
+ ///
+ /// This is an O(1) operation for grid graphs and point graphs.
+ /// For layered grid graphs it is an O(n) operation.
+ ///
+ public virtual int CountNodes () {
+ int count = 0;
+
+ GetNodes(_ => count++);
+ return count;
+ }
+
+ /// Calls a delegate with all nodes in the graph until the delegate returns false
+ public void GetNodes (System.Func action) {
+ bool cont = true;
+
+ GetNodes(node => {
+ if (cont) cont &= action(node);
+ });
+ }
+
+ ///
+ /// Calls a delegate with all nodes in the graph.
+ /// This is the primary way of iterating through all nodes in a graph.
+ ///
+ /// Do not change the graph structure inside the delegate.
+ ///
+ ///
+ /// var gg = AstarPath.active.data.gridGraph;
+ ///
+ /// gg.GetNodes(node => {
+ /// // Here is a node
+ /// Debug.Log("I found a node at position " + (Vector3)node.position);
+ /// });
+ ///
+ ///
+ /// If you want to store all nodes in a list you can do this
+ ///
+ ///
+ /// var gg = AstarPath.active.data.gridGraph;
+ ///
+ /// List nodes = new List();
+ ///
+ /// gg.GetNodes((System.Action)nodes.Add);
+ ///
+ ///
+ /// See:
+ ///
+ public abstract void GetNodes(System.Action action);
+
+ ///
+ /// True if the point is inside the bounding box of this graph.
+ ///
+ /// This method may be able to use a tighter (non-axis aligned) bounding box than using the one returned by .
+ ///
+ /// It is valid for a graph to return true for all points in the world.
+ /// In particular the PointGraph will always return true, since it has no limits on its bounding box.
+ ///
+ public virtual bool IsInsideBounds(Vector3 point) => true;
+
+ ///
+ /// Throws an exception if it is not safe to update internal graph data right now.
+ ///
+ /// It is safe to update graphs when graphs are being scanned, or inside a work item.
+ /// In other cases pathfinding could be running at the same time, which would not appreciate graph data changing under its feet.
+ ///
+ /// See:
+ ///
+ protected void AssertSafeToUpdateGraph () {
+ if (!active.IsAnyWorkItemInProgress && !active.isScanning) {
+ throw new System.Exception("Trying to update graphs when it is not safe to do so. Graph updates must be done inside a work item or when a graph is being scanned. See AstarPath.AddWorkItem");
+ }
+ }
+
+ ///
+ /// Notifies the system that changes have been made inside these bounds.
+ ///
+ /// This should be called by graphs whenever they are changed.
+ /// It will cause off-mesh links to be updated, and it will also ensure events are callled.
+ ///
+ /// The bounding box should cover the surface of all nodes that have been updated.
+ /// It is fine to use a larger bounding box than necessary (even an infinite one), though this may be slower, since more off-mesh links need to be recalculated.
+ /// You can even use an infinitely large bounding box if you don't want to bother calculating a more accurate one.
+ /// You can also call this multiple times to dirty multiple bounding boxes.
+ ///
+ /// When scanning the graph, this is called automatically - with the value from the property - for all graphs after the scanning has finished.
+ ///
+ /// Note: If possible, it is recommended to use or instead of this method.
+ /// They currently do the same thing, but that may change in future versions.
+ ///
+ protected void DirtyBounds(Bounds bounds) => active.DirtyBounds(bounds);
+
+ ///
+ /// Moves the nodes in this graph.
+ /// Multiplies all node positions by deltaMatrix.
+ ///
+ /// For example if you want to move all your nodes in e.g a point graph 10 units along the X axis from the initial position
+ ///
+ /// var graph = AstarPath.data.pointGraph;
+ /// var m = Matrix4x4.TRS (new Vector3(10,0,0), Quaternion.identity, Vector3.one);
+ /// graph.RelocateNodes (m);
+ ///
+ ///
+ /// Note: For grid graphs, navmesh graphs and recast graphs it is recommended to
+ /// use their custom overloads of the RelocateNodes method which take parameters
+ /// for e.g center and nodeSize (and additional parameters) instead since
+ /// they are both easier to use and are less likely to mess up pathfinding.
+ ///
+ /// Warning: This method is lossy for PointGraphs, so calling it many times may
+ /// cause node positions to lose precision. For example if you set the scale
+ /// to 0 in one call then all nodes will be scaled/moved to the same point and
+ /// you will not be able to recover their original positions. The same thing
+ /// happens for other - less extreme - values as well, but to a lesser degree.
+ ///
+ public virtual void RelocateNodes (Matrix4x4 deltaMatrix) {
+ GetNodes(node => node.position = ((Int3)deltaMatrix.MultiplyPoint((Vector3)node.position)));
+ }
+
+ ///
+ /// Lower bound on the squared distance from the given point to the closest node in this graph.
+ ///
+ /// This is used to speed up searching for the closest node when there is more than one graph in the scene,
+ /// by checking the graphs in order of increasing estimated distance to the point.
+ ///
+ /// Implementors may return 0 at all times if it is hard to come up with a good lower bound.
+ /// It is more important that this function is fast than that it is accurate.
+ ///
+ /// The position to check from
+ /// A constraint on which nodes are valid. This may or may not be used by the function. You may pass null if you consider all nodes valid.
+ public virtual float NearestNodeDistanceSqrLowerBound (Vector3 position, NNConstraint constraint = null) {
+ // If the graph doesn't provide a way to calculate a lower bound, just return 0, since that is always a valid lower bound
+ return 0;
+ }
+
+ ///
+ /// Returns the nearest node to a position using the specified NNConstraint.
+ ///
+ /// The returned will contain both the closest node, and the closest point on the surface of that node.
+ /// The distance is measured to the closest point on the surface of the node.
+ ///
+ /// See: You can use instead, if you want to check all graphs.
+ ///
+ /// Version: Before 4.3.63, this method would not use the NNConstraint in all cases.
+ ///
+ /// The position to try to find the closest node to.
+ /// Used to limit which nodes are considered acceptable.
+ /// You may, for example, only want to consider walkable nodes.
+ /// If null, all nodes will be considered acceptable.
+ public NNInfo GetNearest (Vector3 position, NNConstraint constraint = null) {
+ var maxDistanceSqr = constraint == null || constraint.constrainDistance ? active.maxNearestNodeDistanceSqr : float.PositiveInfinity;
+ return GetNearest(position, constraint, maxDistanceSqr);
+ }
+
+ ///
+ /// Nearest node to a position using the specified NNConstraint.
+ ///
+ /// The returned will contain both the closest node, and the closest point on the surface of that node.
+ /// The distance is measured to the closest point on the surface of the node.
+ ///
+ /// See: You can use instead, if you want to check all graphs.
+ ///
+ /// The position to try to find the closest node to.
+ /// Used to limit which nodes are considered acceptable.
+ /// You may, for example, only want to consider walkable nodes.
+ /// If null, all nodes will be considered acceptable.
+ /// The maximum squared distance from the position to the node.
+ /// If the node is further away than this, the function will return an empty NNInfo.
+ /// You may pass infinity if you do not want to limit the distance.
+ public virtual NNInfo GetNearest (Vector3 position, NNConstraint constraint, float maxDistanceSqr) {
+ // This is a default implementation and it is pretty slow
+ // Graphs usually override this to provide faster and more specialised implementations
+
+ float minDistSqr = maxDistanceSqr;
+ GraphNode minNode = null;
+
+ // Loop through all nodes and find the closest suitable node
+ GetNodes(node => {
+ float dist = (position-(Vector3)node.position).sqrMagnitude;
+
+ if (dist < minDistSqr && (constraint == null || constraint.Suitable(node))) {
+ minDistSqr = dist;
+ minNode = node;
+ }
+ });
+
+ return minNode != null ? new NNInfo(minNode, (Vector3)minNode.position, minDistSqr) : NNInfo.Empty;
+ }
+
+ ///
+ /// Returns the nearest node to a position using the specified NNConstraint.
+ /// Deprecated: Use GetNearest instead
+ ///
+ [System.Obsolete("Use GetNearest instead")]
+ public NNInfo GetNearestForce (Vector3 position, NNConstraint constraint) {
+ return GetNearest(position, constraint);
+ }
+
+ ///
+ /// Function for cleaning up references.
+ /// This will be called on the same time as OnDisable on the gameObject which the AstarPath script is attached to (remember, not in the editor).
+ /// Use for any cleanup code such as cleaning up static variables which otherwise might prevent resources from being collected.
+ /// Use by creating a function overriding this one in a graph class, but always call base.OnDestroy () in that function.
+ /// All nodes should be destroyed in this function otherwise a memory leak will arise.
+ ///
+ protected virtual void OnDestroy () {
+ DestroyAllNodes();
+ DisposeUnmanagedData();
+ }
+
+ ///
+ /// Cleans up any unmanaged data that the graph has.
+ /// Note: The graph has to stay valid after this. However it need not be in a scanned state.
+ ///
+ protected virtual void DisposeUnmanagedData () {
+ }
+
+ ///
+ /// Destroys all nodes in the graph.
+ /// Warning: This is an internal method. Unless you have a very good reason, you should probably not call it.
+ ///
+ protected virtual void DestroyAllNodes () {
+ GetNodes(node => node.Destroy());
+ }
+
+ ///
+ /// Captures a snapshot of a part of the graph, to allow restoring it later.
+ ///
+ /// See: for more details
+ ///
+ /// If this graph type does not support taking snapshots, or if the bounding box does not intersect with the graph, this method returns null.
+ ///
+ public virtual IGraphSnapshot Snapshot(Bounds bounds) => null;
+
+ ///
+ /// Scan the graph.
+ ///
+ /// Consider using AstarPath.Scan() instead since this function only scans this graph, and if you are using multiple graphs
+ /// with connections between them, then it is better to scan all graphs at once.
+ ///
+ public void Scan () {
+ active.Scan(this);
+ }
+
+ ///
+ /// Internal method to scan the graph.
+ ///
+ /// Deprecated: You should use ScanInternal(bool) instead.
+ ///
+ protected virtual IGraphUpdatePromise ScanInternal () {
+ throw new System.NotImplementedException();
+ }
+
+ ///
+ /// Internal method to scan the graph.
+ /// Override this function to implement custom scanning logic.
+ ///
+ protected virtual IGraphUpdatePromise ScanInternal (bool async) {
+ return ScanInternal();
+ }
+
+ ///
+ /// Serializes graph type specific node data.
+ /// This function can be overriden to serialize extra node information (or graph information for that matter)
+ /// which cannot be serialized using the standard serialization.
+ /// Serialize the data in any way you want and return a byte array.
+ /// When loading, the exact same byte array will be passed to the DeserializeExtraInfo function.
+ /// These functions will only be called if node serialization is enabled.
+ ///
+ protected virtual void SerializeExtraInfo (GraphSerializationContext ctx) {
+ }
+
+ ///
+ /// Deserializes graph type specific node data.
+ /// See: SerializeExtraInfo
+ ///
+ protected virtual void DeserializeExtraInfo (GraphSerializationContext ctx) {
+ }
+
+ ///
+ /// Called after all deserialization has been done for all graphs.
+ /// Can be used to set up more graph data which is not serialized
+ ///
+ protected virtual void PostDeserialization (GraphSerializationContext ctx) {
+ }
+
+ /// Draw gizmos for the graph
+ public virtual void OnDrawGizmos (DrawingData gizmos, bool drawNodes, RedrawScope redrawScope) {
+ if (!drawNodes) {
+ return;
+ }
+
+ // This is a relatively slow default implementation.
+ // subclasses of the base graph class may override
+ // this method to draw gizmos in a more optimized way
+
+ var hasher = new NodeHasher(active);
+ GetNodes(node => hasher.HashNode(node));
+
+ // Update the gizmo mesh if necessary
+ if (!gizmos.Draw(hasher, redrawScope)) {
+ using (var helper = GraphGizmoHelper.GetGizmoHelper(gizmos, active, hasher, redrawScope)) {
+ GetNodes((System.Action)helper.DrawConnections);
+ }
+ }
+
+ if (active.showUnwalkableNodes) DrawUnwalkableNodes(gizmos, active.unwalkableNodeDebugSize, redrawScope);
+ }
+
+ protected void DrawUnwalkableNodes (DrawingData gizmos, float size, RedrawScope redrawScope) {
+ var hasher = DrawingData.Hasher.Create(this);
+
+ GetNodes(node => {
+ hasher.Add(node.Walkable);
+ if (!node.Walkable) hasher.Add(node.position);
+ });
+
+ if (!gizmos.Draw(hasher, redrawScope)) {
+ using (var builder = gizmos.GetBuilder(hasher)) {
+ using (builder.WithColor(AstarColor.UnwalkableNode)) {
+ GetNodes(node => {
+ if (!node.Walkable) builder.SolidBox((Vector3)node.position, new Unity.Mathematics.float3(size, size, size));
+ });
+ }
+ }
+ }
+ }
+
+ #region IGraphInternals implementation
+ string IGraphInternals.SerializedEditorSettings { get { return serializedEditorSettings; } set { serializedEditorSettings = value; } }
+ void IGraphInternals.OnDestroy() => OnDestroy();
+ void IGraphInternals.DisposeUnmanagedData() => DisposeUnmanagedData();
+ void IGraphInternals.DestroyAllNodes() => DestroyAllNodes();
+ IGraphUpdatePromise IGraphInternals.ScanInternal(bool async) => ScanInternal(async);
+ void IGraphInternals.SerializeExtraInfo(GraphSerializationContext ctx) => SerializeExtraInfo(ctx);
+ void IGraphInternals.DeserializeExtraInfo(GraphSerializationContext ctx) => DeserializeExtraInfo(ctx);
+ void IGraphInternals.PostDeserialization(GraphSerializationContext ctx) => PostDeserialization(ctx);
+
+ #endregion
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/NavGraph.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/NavGraph.cs.meta
new file mode 100644
index 0000000..dda38a3
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/NavGraph.cs.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: c7636164485c04efe8fad73ab1ee985f
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/NavMeshGraph.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/NavMeshGraph.cs
new file mode 100644
index 0000000..8f9cc04
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/NavMeshGraph.cs
@@ -0,0 +1,372 @@
+using UnityEngine;
+using System.Collections.Generic;
+
+namespace Pathfinding {
+ using UnityEngine.Profiling;
+ using Pathfinding.Util;
+ using Pathfinding.Serialization;
+ using Unity.Collections;
+ using Unity.Jobs;
+ using Pathfinding.Graphs.Navmesh.Jobs;
+ using Pathfinding.Graphs.Navmesh;
+ using Unity.Mathematics;
+
+ public interface INavmesh {
+ void GetNodes(System.Action del);
+ }
+
+ ///
+ /// Generates graphs based on navmeshes.
+ /// [Open online documentation to see images]
+ ///
+ /// Navmeshes are meshes in which each triangle defines a walkable area.
+ /// These are great because the AI can get so much more information on how it can walk.
+ /// Polygons instead of points mean that the can produce really nice looking paths, and the graphs are also really fast to search
+ /// and have a low memory footprint because fewer nodes are usually needed to describe the same area compared to grid graphs.
+ ///
+ /// The navmesh graph requires that you create a navmesh manually. The package also has support for generating navmeshes automatically using the .
+ ///
+ /// For a tutorial on how to configure a navmesh graph, take a look at getstarted2 (view in online documentation for working links).
+ ///
+ /// [Open online documentation to see images]
+ /// [Open online documentation to see images]
+ ///
+ /// See: Pathfinding.RecastGraph
+ ///
+ [JsonOptIn]
+ [Pathfinding.Util.Preserve]
+ public class NavMeshGraph : NavmeshBase, IUpdatableGraph {
+ /// Mesh to construct navmesh from
+ [JsonMember]
+ public Mesh sourceMesh;
+
+ /// Offset in world space
+ [JsonMember]
+ public Vector3 offset;
+
+ /// Rotation in degrees
+ [JsonMember]
+ public Vector3 rotation;
+
+ /// Scale of the graph
+ [JsonMember]
+ public float scale = 1;
+
+ ///
+ /// Determines how normals are calculated.
+ /// Disable for spherical graphs or other complicated surfaces that allow the agents to e.g walk on walls or ceilings.
+ ///
+ /// By default the normals of the mesh will be flipped so that they point as much as possible in the upwards direction.
+ /// The normals are important when connecting adjacent nodes. Two adjacent nodes will only be connected if they are oriented the same way.
+ /// This is particularly important if you have a navmesh on the walls or even on the ceiling of a room. Or if you are trying to make a spherical navmesh.
+ /// If you do one of those things then you should set disable this setting and make sure the normals in your source mesh are properly set.
+ ///
+ /// If you for example take a look at the image below. In the upper case then the nodes on the bottom half of the
+ /// mesh haven't been connected with the nodes on the upper half because the normals on the lower half will have been
+ /// modified to point inwards (as that is the direction that makes them face upwards the most) while the normals on
+ /// the upper half point outwards. This causes the nodes to not connect properly along the seam. When this option
+ /// is set to false instead the nodes are connected properly as in the original mesh all normals point outwards.
+ /// [Open online documentation to see images]
+ ///
+ /// The default value of this field is true to reduce the risk for errors in the common case. If a mesh is supplied that
+ /// has all normals pointing downwards and this option is false, then some methods like will not work correctly
+ /// as they assume that the normals point upwards. For a more complicated surface like a spherical graph those methods make no sense anyway
+ /// as there is no clear definition of what it means to be "inside" a triangle when there is no clear up direction.
+ ///
+ [JsonMember]
+ public bool recalculateNormals = true;
+
+ ///
+ /// Cached bounding box minimum of .
+ /// This is important when the graph has been saved to a file and is later loaded again, but the original mesh does not exist anymore (or has been moved).
+ /// In that case we still need to be able to find the bounding box since the method uses it.
+ ///
+ [JsonMember]
+ Vector3 cachedSourceMeshBoundsMin;
+
+ ///
+ /// Radius to use when expanding navmesh cuts.
+ ///
+ /// See:
+ ///
+ [JsonMember]
+ public float navmeshCuttingCharacterRadius = 0.5f;
+
+ public override float NavmeshCuttingCharacterRadius => navmeshCuttingCharacterRadius;
+
+ public override bool RecalculateNormals => recalculateNormals;
+
+ public override float TileWorldSizeX => forcedBoundsSize.x;
+
+ public override float TileWorldSizeZ => forcedBoundsSize.z;
+
+ // Tiles are not supported, so this is irrelevant
+ public override float MaxTileConnectionEdgeDistance => 0f;
+
+ ///
+ /// True if the point is inside the bounding box of this graph.
+ ///
+ /// Warning: If your input mesh is entirely flat, the bounding box will also end up entirely flat (with a height of zero), this will make this function return false for almost all points, unless they are at exactly the right y-coordinate.
+ ///
+ /// Note: For an unscanned graph, this will always return false.
+ ///
+ public override bool IsInsideBounds (Vector3 point) {
+ if (this.tiles == null || this.tiles.Length == 0 || sourceMesh == null) return false;
+
+ var local = transform.InverseTransform(point);
+ var size = sourceMesh.bounds.size*scale;
+
+ // Allow a small margin
+ const float EPS = 0.0001f;
+
+ return local.x >= -EPS && local.y >= -EPS && local.z >= -EPS && local.x <= size.x + EPS && local.y <= size.y + EPS && local.z <= size.z + EPS;
+ }
+
+ ///
+ /// World bounding box for the graph.
+ ///
+ /// This always contains the whole graph.
+ ///
+ /// Note: Since this is an axis-aligned bounding box, it may not be particularly tight if the graph is significantly rotated.
+ ///
+ /// If no mesh has been assigned, this will return a zero sized bounding box at the origin.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ public override Bounds bounds {
+ get {
+ if (sourceMesh == null) return default;
+ var m = (float4x4)CalculateTransform().matrix;
+ var b = new ToWorldMatrix(new float3x3(m.c0.xyz, m.c1.xyz, m.c2.xyz)).ToWorld(new Bounds(Vector3.zero, sourceMesh.bounds.size * scale));
+ return b;
+ }
+ }
+
+ public override GraphTransform CalculateTransform () {
+ return new GraphTransform(Matrix4x4.TRS(offset, Quaternion.Euler(rotation), Vector3.one) * Matrix4x4.TRS(sourceMesh != null ? sourceMesh.bounds.min * scale : cachedSourceMeshBoundsMin * scale, Quaternion.identity, Vector3.one));
+ }
+
+ class NavMeshGraphUpdatePromise : IGraphUpdatePromise {
+ public NavMeshGraph graph;
+ public List graphUpdates;
+
+ public void Apply (IGraphUpdateContext ctx) {
+ for (int i = 0; i < graphUpdates.Count; i++) {
+ var graphUpdate = graphUpdates[i];
+ UpdateArea(graphUpdate, graph);
+ // TODO: Not strictly accurate, since the update may affect node that have a surface that extends
+ // outside of the bounds.
+ ctx.DirtyBounds(graphUpdate.bounds);
+ }
+ }
+ }
+
+ IGraphUpdatePromise IUpdatableGraph.ScheduleGraphUpdates (List graphUpdates) => new NavMeshGraphUpdatePromise { graph = this, graphUpdates = graphUpdates };
+
+ public static void UpdateArea (GraphUpdateObject o, INavmeshHolder graph) {
+ Bounds bounds = graph.transform.InverseTransform(o.bounds);
+
+ // Bounding rectangle with integer coordinates
+ var irect = new IntRect(
+ Mathf.FloorToInt(bounds.min.x*Int3.Precision),
+ Mathf.FloorToInt(bounds.min.z*Int3.Precision),
+ Mathf.CeilToInt(bounds.max.x*Int3.Precision),
+ Mathf.CeilToInt(bounds.max.z*Int3.Precision)
+ );
+
+ // Corners of the bounding rectangle
+ var a = new Int3(irect.xmin, 0, irect.ymin);
+ var b = new Int3(irect.xmin, 0, irect.ymax);
+ var c = new Int3(irect.xmax, 0, irect.ymin);
+ var d = new Int3(irect.xmax, 0, irect.ymax);
+
+ var ymin = ((Int3)bounds.min).y;
+ var ymax = ((Int3)bounds.max).y;
+
+ // Loop through all nodes and check if they intersect the bounding box
+ graph.GetNodes(_node => {
+ var node = _node as TriangleMeshNode;
+
+ bool inside = false;
+
+ int allLeft = 0;
+ int allRight = 0;
+ int allTop = 0;
+ int allBottom = 0;
+
+ // Check bounding box rect in XZ plane
+ for (int v = 0; v < 3; v++) {
+ Int3 p = node.GetVertexInGraphSpace(v);
+
+ if (irect.Contains(p.x, p.z)) {
+ inside = true;
+ break;
+ }
+
+ if (p.x < irect.xmin) allLeft++;
+ if (p.x > irect.xmax) allRight++;
+ if (p.z < irect.ymin) allTop++;
+ if (p.z > irect.ymax) allBottom++;
+ }
+
+ if (!inside && (allLeft == 3 || allRight == 3 || allTop == 3 || allBottom == 3)) {
+ return;
+ }
+
+ // Check if the polygon edges intersect the bounding rect
+ for (int v = 0; v < 3; v++) {
+ int v2 = v > 1 ? 0 : v+1;
+
+ Int3 vert1 = node.GetVertexInGraphSpace(v);
+ Int3 vert2 = node.GetVertexInGraphSpace(v2);
+
+ if (VectorMath.SegmentsIntersectXZ(a, b, vert1, vert2)) { inside = true; break; }
+ if (VectorMath.SegmentsIntersectXZ(a, c, vert1, vert2)) { inside = true; break; }
+ if (VectorMath.SegmentsIntersectXZ(c, d, vert1, vert2)) { inside = true; break; }
+ if (VectorMath.SegmentsIntersectXZ(d, b, vert1, vert2)) { inside = true; break; }
+ }
+
+ // Check if the node contains any corner of the bounding rect
+ if (inside || node.ContainsPointInGraphSpace(a) || node.ContainsPointInGraphSpace(b) || node.ContainsPointInGraphSpace(c) || node.ContainsPointInGraphSpace(d)) {
+ inside = true;
+ }
+
+ if (!inside) {
+ return;
+ }
+
+ int allAbove = 0;
+ int allBelow = 0;
+
+ // Check y coordinate
+ for (int v = 0; v < 3; v++) {
+ Int3 p = node.GetVertexInGraphSpace(v);
+ if (p.y < ymin) allBelow++;
+ if (p.y > ymax) allAbove++;
+ }
+
+ // Polygon is either completely above the bounding box or completely below it
+ if (allBelow == 3 || allAbove == 3) return;
+
+ // Triangle is inside the bounding box!
+ // Update it!
+ o.WillUpdateNode(node);
+ o.Apply(node);
+ });
+ }
+
+ class NavMeshGraphScanPromise : IGraphUpdatePromise {
+ public NavMeshGraph graph;
+ bool emptyGraph;
+ GraphTransform transform;
+ NavmeshTile[] tiles;
+ Vector3 forcedBoundsSize;
+ IntRect tileRect;
+
+ public IEnumerator Prepare () {
+ var sourceMesh = graph.sourceMesh;
+ graph.cachedSourceMeshBoundsMin = sourceMesh != null ? sourceMesh.bounds.min : Vector3.zero;
+ transform = graph.CalculateTransform();
+
+ if (sourceMesh == null) {
+ emptyGraph = true;
+ yield break;
+ }
+
+ if (!sourceMesh.isReadable) {
+ Debug.LogError("The source mesh " + sourceMesh.name + " is not readable. Enable Read/Write in the mesh's import settings.", sourceMesh);
+ emptyGraph = true;
+ yield break;
+ }
+
+ Profiler.BeginSample("GetMeshData");
+ var meshDatas = Mesh.AcquireReadOnlyMeshData(sourceMesh);
+ MeshUtility.GetMeshData(meshDatas, 0, out var vertices, out var indices);
+ meshDatas.Dispose();
+ Profiler.EndSample();
+
+ // Convert the vertices to graph space
+ // so that the minimum of the bounding box of the mesh is at the origin
+ // (the vertices will later be transformed to world space)
+ var meshToGraphSpace = Matrix4x4.TRS(-sourceMesh.bounds.min * graph.scale, Quaternion.identity, Vector3.one * graph.scale);
+
+ var promise = JobBuildTileMeshFromVertices.Schedule(vertices, indices, meshToGraphSpace, graph.RecalculateNormals);
+ forcedBoundsSize = sourceMesh.bounds.size * graph.scale;
+ tileRect = new IntRect(0, 0, 0, 0);
+ tiles = new NavmeshTile[tileRect.Area];
+ var tilesGCHandle = System.Runtime.InteropServices.GCHandle.Alloc(tiles);
+ var tileWorldSize = new Vector2(forcedBoundsSize.x, forcedBoundsSize.z);
+ var tileNodeConnections = new NativeArray(tiles.Length, Allocator.Persistent);
+ var calculateConnectionsJob = new JobCalculateTriangleConnections {
+ tileMeshes = promise.GetValue().tiles,
+ nodeConnections = tileNodeConnections,
+ }.Schedule(promise.handle);
+ var createTilesJob = new JobCreateTiles {
+ tileMeshes = promise.GetValue().tiles,
+ tiles = tilesGCHandle,
+ tileRect = tileRect,
+ graphTileCount = new Int2(tileRect.Width, tileRect.Height),
+ graphIndex = graph.graphIndex,
+ initialPenalty = graph.initialPenalty,
+ recalculateNormals = graph.recalculateNormals,
+ graphToWorldSpace = transform.matrix,
+ tileWorldSize = tileWorldSize,
+ }.Schedule(promise.handle);
+ var applyConnectionsJob = new JobWriteNodeConnections {
+ tiles = tilesGCHandle,
+ nodeConnections = tileNodeConnections,
+ }.Schedule(JobHandle.CombineDependencies(createTilesJob, calculateConnectionsJob));
+
+ yield return applyConnectionsJob;
+
+ var navmeshOutput = promise.Complete();
+ // This has already been used in the createTilesJob
+ navmeshOutput.Dispose();
+ tileNodeConnections.Dispose();
+
+ vertices.Dispose();
+ indices.Dispose();
+
+ tilesGCHandle.Free();
+ }
+
+ public void Apply (IGraphUpdateContext ctx) {
+ if (emptyGraph) {
+ graph.forcedBoundsSize = Vector3.zero;
+ graph.transform = transform;
+ graph.tileZCount = graph.tileXCount = 1;
+ TriangleMeshNode.SetNavmeshHolder(AstarPath.active.data.GetGraphIndex(graph), graph);
+ graph.FillWithEmptyTiles();
+ return;
+ }
+
+ // Destroy all previous nodes (if any)
+ graph.DestroyAllNodes();
+
+ // Initialize all nodes that were created in the jobs
+ for (int j = 0; j < tiles.Length; j++) AstarPath.active.InitializeNodes(tiles[j].nodes);
+
+ // Assign all data as one atomic operation (from the viewpoint of the main thread)
+ graph.forcedBoundsSize = forcedBoundsSize;
+ graph.transform = transform;
+ graph.tileXCount = tileRect.Width;
+ graph.tileZCount = tileRect.Height;
+ graph.tiles = tiles;
+ TriangleMeshNode.SetNavmeshHolder(graph.active.data.GetGraphIndex(graph), graph);
+
+ // Signal that tiles have been recalculated to the navmesh cutting system.
+ graph.navmeshUpdateData.OnRecalculatedTiles(tiles);
+ if (graph.OnRecalculatedTiles != null) graph.OnRecalculatedTiles(tiles.Clone() as NavmeshTile[]);
+ }
+ }
+
+ protected override IGraphUpdatePromise ScanInternal (bool async) => new NavMeshGraphScanPromise { graph = this };
+
+ protected override void PostDeserialization (GraphSerializationContext ctx) {
+ if (ctx.meta.version < AstarSerializer.V4_3_74) {
+ this.navmeshCuttingCharacterRadius = 0;
+ }
+ base.PostDeserialization(ctx);
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/NavMeshGraph.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/NavMeshGraph.cs.meta
new file mode 100644
index 0000000..1b6d4ad
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/NavMeshGraph.cs.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: ffae751ca240c466185a168f4a9836cb
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh.meta
new file mode 100644
index 0000000..0baa22d
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 85b3765d770e3e34ab31b2c0b95043f5
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/AABBTree.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/AABBTree.cs
new file mode 100644
index 0000000..5794204
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/AABBTree.cs
@@ -0,0 +1,381 @@
+// #define VALIDATE_AABB_TREE
+using UnityEngine;
+using System.Collections.Generic;
+using Pathfinding.Util;
+using Unity.Mathematics;
+using UnityEngine.Assertions;
+
+namespace Pathfinding.Graphs.Navmesh {
+ ///
+ /// Axis Aligned Bounding Box Tree.
+ ///
+ /// Holds a bounding box tree with arbitrary data.
+ ///
+ /// The tree self-balances itself regularly when nodes are added.
+ ///
+ public class AABBTree {
+ Node[] nodes = new Node[0];
+ int root = NoNode;
+ readonly Stack