private async Task BuildNodeGraph(float regionRadius, float bodyRadius, float toleranceRadius, CancellationToken cancellation)
        {
            this.regionRadius = Mathf.Max(regionRadius, 0);
            this.bodyRadius   = Mathf.Max(bodyRadius, 0);
            cancellation.ThrowIfCancellationRequested();

            // Build stencil.
            var stencil = await VisionStencil.BuildAsync(regionRadius, bodyRadius, toleranceRadius);

            cancellation.ThrowIfCancellationRequested();

            // Build vision edges.
            int visionMaxOffset = Mathf.FloorToInt(regionRadius);
            var tasks           = new List <Task>();

            foreach (var node in nodes)
            {
                if (node is null)
                {
                    continue;
                }
                var currentNode = node;
                var task        = Task.Run(() => {
                    // See all nodes around current one; add edge in graph for every visible node.
                    foreach (var destinationPosition in Enumerables.InSegment2(
                                 currentNode.Position - visionMaxOffset * Vector2Int.one, currentNode.Position + visionMaxOffset * Vector2Int.one))
                    {
                        cancellation.ThrowIfCancellationRequested();
                        if (!Enumerables.IsIndex(destinationPosition, nodes))
                        {
                            continue;
                        }
                        var visionPath = stencil.GetVisionPath(currentNode.Position, destinationPosition);
                        if (visionPath is null)
                        {
                            continue;
                        }
                        bool visible = true;
                        foreach (var passedPosition in visionPath)
                        {
                            var passedNode = nodes[passedPosition.x, passedPosition.y];
                            if (passedNode is null)
                            {
                                visible = false;
                                break;
                            }
                        }
                        if (visible)
                        {
                            var destinationNode = nodes[destinationPosition.x, destinationPosition.y];
                            currentNode.AddVisible(destinationNode);
                        }
                    }
                });
                tasks.Add(task);
                cancellation.ThrowIfCancellationRequested();
            }
            await Task.WhenAll(tasks);

            cancellation.ThrowIfCancellationRequested();

            // Build reach edges.
            tasks.Clear();
            foreach (var node in nodes)
            {
                if (node is null)
                {
                    continue;
                }
                var currentNode = node;
                var task        = Task.Run(() => {
                    // Try reach all nodes around current one; add edge in graph for every reachable node.
                    var reachable = DijkstraDense.FindAllReachable(currentNode.AsVision, regionRadius);
                    cancellation.ThrowIfCancellationRequested();
                    foreach (var nodeAndDistance in reachable)
                    {
                        var reachedPosition = nodeAndDistance.Key.Position;
                        var reachedNode     = nodes[reachedPosition.x, reachedPosition.y];
                        currentNode.AddReachable(reachedNode, nodeAndDistance.Value);
                    }
                    cancellation.ThrowIfCancellationRequested();
                });
                tasks.Add(task);
                cancellation.ThrowIfCancellationRequested();
            }
            await Task.WhenAll(tasks);

            cancellation.ThrowIfCancellationRequested();
        }