Pre Release
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GridCasting", "GridCasting\GridCasting.csproj", "{D26523E3-132B-42EB-891C-45EBF8B98C65}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IgdrasilMath", "..\IgdrasilEngine\IgdrasilMath\IgdrasilMath.csproj", "{10E6C5FC-990E-429E-9635-E4F639FF6B9D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GuidingTurtle", "..\IgdrasilEngine\GuidingTurtle\GuidingTurtle.csproj", "{2575A7D5-2936-4823-A0B4-A466790D70B5}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IgdrasilEngine", "IgdrasilEngine", "{41458A8F-715B-405E-84E2-140989D50977}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{D26523E3-132B-42EB-891C-45EBF8B98C65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D26523E3-132B-42EB-891C-45EBF8B98C65}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D26523E3-132B-42EB-891C-45EBF8B98C65}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D26523E3-132B-42EB-891C-45EBF8B98C65}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{10E6C5FC-990E-429E-9635-E4F639FF6B9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{10E6C5FC-990E-429E-9635-E4F639FF6B9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{10E6C5FC-990E-429E-9635-E4F639FF6B9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{10E6C5FC-990E-429E-9635-E4F639FF6B9D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2575A7D5-2936-4823-A0B4-A466790D70B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2575A7D5-2936-4823-A0B4-A466790D70B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2575A7D5-2936-4823-A0B4-A466790D70B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2575A7D5-2936-4823-A0B4-A466790D70B5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{2575A7D5-2936-4823-A0B4-A466790D70B5} = {41458A8F-715B-405E-84E2-140989D50977}
|
||||
{10E6C5FC-990E-429E-9635-E4F639FF6B9D} = {41458A8F-715B-405E-84E2-140989D50977}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
using Path = GridCasting.Models.Path;
|
||||
|
||||
namespace GridCasting.Executor;
|
||||
|
||||
public class CommandContext
|
||||
{
|
||||
public Path Command { get; }
|
||||
public IDictionary<string, object> Environment { get; }
|
||||
public Stack<object> Stack { get; }
|
||||
|
||||
internal CommandContext(Path command, IDictionary<string, object> environment, Stack<object> stack)
|
||||
{
|
||||
Command = command;
|
||||
Environment = environment;
|
||||
Stack = stack;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Path = GridCasting.Models.Path;
|
||||
|
||||
namespace GridCasting.Executor;
|
||||
|
||||
public interface ICommand
|
||||
{
|
||||
void Execute(CommandContext context);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
namespace GridCasting.Executor;
|
||||
|
||||
public interface IEnvironmentResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads and returns a collection of key-value pairs representing environment variables.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A collection of key-value pairs where the key is a string representing the variable name
|
||||
/// and the value is the associated object.
|
||||
/// </returns>
|
||||
public IEnumerable<KeyValuePair<string, object>> OnLoad();
|
||||
|
||||
/// <summary>
|
||||
/// Unloads and performs necessary cleanup operations for the current environment,
|
||||
/// ensuring resources are released and any outstanding tasks are handled appropriately.
|
||||
/// </summary>
|
||||
public void OnUnload();
|
||||
|
||||
/// <summary>
|
||||
/// Resets the environment variable identified by the specified key to its default value.
|
||||
/// </summary>
|
||||
/// <param name="key">The string key of the environment variable to reset.</param>
|
||||
/// <returns>
|
||||
/// The default value of the environment variable as an object if a reset value is defined;
|
||||
/// otherwise, null.
|
||||
/// </returns>
|
||||
public object? OnReset(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the value of the specified environment variable to a new value.
|
||||
/// </summary>
|
||||
/// <param name="key">The string key representing the name of the environment variable to update.</param>
|
||||
/// <param name="value">The new value to assign to the specified environment variable.</param>
|
||||
public void OnChange(string key, object value);
|
||||
|
||||
/// <summary>
|
||||
/// An event that is triggered whenever an environment variable is updated.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The event provides the key of the updated variable and its new value. This is useful for monitoring
|
||||
/// changes to environment variables in real-time and ensuring the associated logic reacts accordingly.
|
||||
/// </remarks>
|
||||
/// <event>
|
||||
/// Subscribing to this event allows external components to respond dynamically to updates
|
||||
/// in the environment configuration.
|
||||
/// </event>
|
||||
public event Action<string, object> OnUpdate;
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
using GridCasting.Utils;
|
||||
using Path = GridCasting.Models.Path;
|
||||
|
||||
namespace GridCasting.Executor;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a class that handles the execution of commands associated with specific path patterns.
|
||||
/// Manages environment variables through a listenable dictionary and integrates capabilities for
|
||||
/// reacting to changes within these variables.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The <see cref="PathExecutor"/> class is designed to facilitate the execution of path-related commands
|
||||
/// while allowing dynamic management of environment-specific variables. It supports the registration of
|
||||
/// environment resolvers, maintains a collection of commands mapped to path patterns, and provides methods
|
||||
/// for interacting with and executing commands.
|
||||
/// </remarks>
|
||||
public class PathExecutor
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores the internal mapping of commands to their corresponding path patterns.
|
||||
/// This data structure associates commands, which implement the <see cref="ICommand"/> interface,
|
||||
/// with specific path patterns and optionally supports pattern families for extended matching capabilities.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The field leverages a <see cref="Trie{TNode, TValue}"/> to efficiently manage
|
||||
/// hierarchical patterns and improve lookup performance during command execution.
|
||||
/// This is an internal implementation detail and is used to match and retrieve
|
||||
/// relevant commands based on the incoming path during execution.
|
||||
/// </remarks>
|
||||
private readonly Trie<int, ICommand> _commands = new();
|
||||
|
||||
/// <summary>
|
||||
/// Maintains a collection of key-value pairs representing environment-specific variables and their associated values.
|
||||
/// This dictionary acts as the primary storage for environment-related data used during path execution and command processing.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The dictionary is updated and accessed through various methods in the <see cref="PathExecutor"/> class,
|
||||
/// often in conjunction with environment resolvers that implement the <see cref="IEnvironmentResolver"/> interface.
|
||||
/// Additionally, changes to this dictionary are synchronized with the listenable wrapper <see cref="ListenableDictionary{TKey, TValue}"/>
|
||||
/// to trigger appropriate events such as updates, removals, and clear operations.
|
||||
/// </remarks>
|
||||
private readonly Dictionary<string, object> _environmentVariables = new();
|
||||
|
||||
/// <summary>
|
||||
/// Represents a listenable dictionary that wraps environment variables
|
||||
/// with the ability to react to updates, removals, and clearing of its contents.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This field is a <see cref="ListenableDictionary{TKey, TValue}"/> object
|
||||
/// that allows event-driven responses when changes occur in the underlying
|
||||
/// environment variables. It builds upon a base dictionary to manage key-value pairs,
|
||||
/// while invoking relevant event handlers for observed modifications.
|
||||
/// </remarks>
|
||||
private readonly ListenableDictionary<string, object> _environmentVariablesListenable;
|
||||
|
||||
/// <summary>
|
||||
/// Maintains a mapping between environment variable keys and their corresponding environment resolvers.
|
||||
/// This dictionary enables association of specific environment variable keys with implementations
|
||||
/// of the <see cref="IEnvironmentResolver"/> interface to facilitate dynamic updates, load, and unload operations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This mapping is used to handle the assignment and resolution of environment variables within the
|
||||
/// execution context. Each environment variable key is associated with an instance of an
|
||||
/// <see cref="IEnvironmentResolver"/>, which provides the logic for loading, updating, and
|
||||
/// unloading environment variables. Changes to the variable keys trigger relevant resolver events for
|
||||
/// synchronized state management.
|
||||
/// </remarks>
|
||||
private readonly Dictionary<string, IEnvironmentResolver> _environmentResolverMap = new();
|
||||
|
||||
/// <summary>
|
||||
/// Represents the execution stack used internally by the <see cref="PathExecutor"/>.
|
||||
/// This stack manages objects relevant to the execution context of commands,
|
||||
/// facilitating state tracking and enabling command chaining during execution flows.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The stack operates as a first-in-last-out (FILO) data structure, where elements
|
||||
/// added during command execution are processed in reverse order of addition.
|
||||
/// It is primarily used to maintain the execution context and intermediate
|
||||
/// results during command processing within the PathExecutor.
|
||||
/// </remarks>
|
||||
private readonly Stack<object> _stack = new();
|
||||
|
||||
/// <summary>
|
||||
/// Handles the execution of commands associated with specific path patterns and manages environment variables
|
||||
/// with the ability to listen and react to changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class integrates a listenable dictionary for environment variables, enabling event-driven responses to updates,
|
||||
/// removals, or full-clears of its contents. It acts as a mediator for resolving context and executing commands
|
||||
/// based on the given path patterns.
|
||||
/// </remarks>
|
||||
public PathExecutor()
|
||||
{
|
||||
_environmentVariablesListenable = new ListenableDictionary<string, object>(_environmentVariables);
|
||||
_environmentVariablesListenable.OnUpdate += OnUpdate;
|
||||
_environmentVariablesListenable.OnRemove += OnRemove;
|
||||
_environmentVariablesListenable.OnClear += OnClear;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of context resolvers added to the PathExecutor.
|
||||
/// These context resolvers are responsible for handling specific execution contexts
|
||||
/// during the execution of commands.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The property returns a read-only list of <see cref="IEnvironmentResolver"/> instances
|
||||
/// that have been registered using the <see cref="AddEnvironmentResolver"/>.
|
||||
/// </remarks>
|
||||
public IEnumerable<IEnvironmentResolver> Resolvers => _environmentResolverMap.Values;
|
||||
|
||||
/// <summary>
|
||||
/// Adds an environment resolver and integrates its associated variables and update events into the PathExecutor.
|
||||
/// </summary>
|
||||
/// <param name="resolver">
|
||||
/// The environment resolver to be added. This resolver provides a collection of environment variables
|
||||
/// on load and triggers update events when its state changes.
|
||||
/// </param>
|
||||
public void AddEnvironmentResolver(IEnvironmentResolver resolver)
|
||||
{
|
||||
foreach (var (key, value) in resolver.OnLoad())
|
||||
{
|
||||
_environmentVariables.Add(key, value);
|
||||
_environmentResolverMap.Add(key, resolver);
|
||||
}
|
||||
resolver.OnUpdate += OnResolverUpdate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an environment resolver and clears all associated environment variables
|
||||
/// from the resolver's mapped context.
|
||||
/// </summary>
|
||||
/// <param name="resolver">The environment resolver to be removed. It will be unregistered
|
||||
/// from its update events, and its context will be cleaned up.</param>
|
||||
public void RemoveContextResolver(IEnvironmentResolver resolver)
|
||||
{
|
||||
// Unmap all environment variables
|
||||
resolver.OnUpdate -= OnResolverUpdate;
|
||||
_environmentResolverMap.Where(x => x.Value == resolver).ToList()
|
||||
.ForEach(x =>
|
||||
{
|
||||
_environmentResolverMap.Remove(x.Key);
|
||||
_environmentVariables.Remove(x.Key);
|
||||
});
|
||||
resolver.OnUnload();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a command to be executed with an associated path pattern and optional pattern family configuration.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to add. Must implement the <see cref="ICommand"/> interface.</param>
|
||||
/// <param name="pattern">The path object used to define the execution pattern for the command.</param>
|
||||
/// <param name="patternFamily">A boolean flag indicating whether the path pattern should be treated as a family of patterns. Defaults to false.</param>
|
||||
public void AddCommand(ICommand command, Path pattern, bool patternFamily = false) =>
|
||||
_commands.Set(pattern.ToArray(), patternFamily, command);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Clears all objects from the execution stack of the PathExecutor.
|
||||
/// This operation resets the stack to its initial state, removing any previously added elements.
|
||||
/// </summary>
|
||||
public void ResetStack() => _stack.Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Executes the command associated with the provided path.
|
||||
/// If a matching command is found, it is executed within the provided execution context.
|
||||
/// </summary>
|
||||
/// <param name="path">The path object defining the start node and directions for the execution.</param>
|
||||
/// <returns>
|
||||
/// Returns <c>true</c> if a matching command for the specified path is found and executed successfully;
|
||||
/// otherwise, returns <c>false</c>.
|
||||
/// </returns>
|
||||
public bool Execute(Path path)
|
||||
{
|
||||
if (!_commands.TryGetValue(path.ToArray(), out var command)) return false;
|
||||
command.Execute(new CommandContext(
|
||||
path,
|
||||
_environmentVariablesListenable,
|
||||
_stack
|
||||
));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles updates to environment variables by notifying the relevant environment resolver about the change.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the environment variable that has been updated.</param>
|
||||
/// <param name="value">The new value associated with the updated key.</param>
|
||||
private void OnUpdate(string key, object value)
|
||||
{
|
||||
if (!_environmentResolverMap.TryGetValue(key, out var resolver)) return;
|
||||
resolver.OnChange(key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the removal of a specific environment variable from the listenable dictionary and restores it to its default value
|
||||
/// if defined by the associated resolver.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the environment variable to be removed and potentially reset.</param>
|
||||
private void OnRemove(string key)
|
||||
{
|
||||
if (!_environmentResolverMap.TryGetValue(key, out var resolver)) return;
|
||||
var defaultValue = resolver.OnReset(key);
|
||||
if (defaultValue != null) _environmentVariables[key] = defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets environment variables by invoking the reset logic for each associated environment resolver
|
||||
/// and re-populating the variables with their default values if provided.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is triggered when the `OnClear` event is raised from the `ListenableDictionary`.
|
||||
/// It iterates through the configured environment resolvers, calls their reset method for each key,
|
||||
/// and updates the environment variables with reset defaults if applicable.
|
||||
/// </remarks>
|
||||
private void OnClear()
|
||||
{
|
||||
foreach (var (key, resolver) in _environmentResolverMap)
|
||||
{
|
||||
var defaultValue = resolver.OnReset(key);
|
||||
if (defaultValue != null) _environmentVariables[key] = defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the value of an environment variable in the internal storage when a corresponding change is triggered
|
||||
/// by an environment resolver.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the environment variable being updated.</param>
|
||||
/// <param name="value">The new value for the specified environment variable.</param>
|
||||
private void OnResolverUpdate(string key, object value) => _environmentVariables[key] = value;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using GridCasting.Executor;
|
||||
using GridCasting.Models.GridGraph;
|
||||
using GridCasting.Transform;
|
||||
using IgdrasilEngine.Engine.Math.Vectors;
|
||||
|
||||
namespace GridCasting;
|
||||
|
||||
public class GridCasting(
|
||||
PathExecutor executor,
|
||||
GridGraph graph,
|
||||
float sensitivity,
|
||||
params IEnumerable<IPathTransform> transforms)
|
||||
{
|
||||
private readonly GridResolver _resolver = new(graph, sensitivity);
|
||||
private readonly PathExecutor _executor = executor;
|
||||
private readonly IEnumerable<IPathTransform> _transforms = transforms;
|
||||
|
||||
public void Execute(FVector2[] positions)
|
||||
{
|
||||
var path = _resolver.GetPath(positions);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\IgdrasilEngine\IgdrasilMath\IgdrasilMath.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Transform\PathTransforms\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace GridCasting.Models.Grid;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a grid structure that serves as a core abstraction for spatially organizing and managing elements
|
||||
/// in a grid-based layout or system. Provides foundational functionality to integrate with and support operations
|
||||
/// like grid graph generation, position resolution, and pathfinding using other related classes.
|
||||
/// </summary>
|
||||
public class Grid : IEnumerable<GridNode>
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a collection of <see cref="GridNode"/> instances within a grid structure.
|
||||
/// The <c>Nodes</c> variable holds the individual grid elements that collectively
|
||||
/// form the grid, allowing for operations such as traversal, manipulation, or querying
|
||||
/// of the underlying grid layout.
|
||||
/// </summary>
|
||||
public List<GridNode> Nodes { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Provides indexed access to the grid nodes within the grid structure
|
||||
/// by returning the element at the specified position in the node collection.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the grid node in the collection.</param>
|
||||
/// <returns>Returns the <see cref="GridNode"/> instance located at the specified index.</returns>
|
||||
public GridNode this[int index] => Nodes[index];
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the collection of grid nodes
|
||||
/// within the current grid structure.
|
||||
/// </summary>
|
||||
/// <returns>An enumerator for iterating through the <see cref="GridNode"/> elements in the grid.</returns>
|
||||
public IEnumerator<GridNode> GetEnumerator() => Nodes.GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the collection of grid nodes
|
||||
/// within the current grid structure.
|
||||
/// </summary>
|
||||
/// <returns>An enumerator for iterating through the <see cref="GridNode"/> elements in the grid.</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using IgdrasilEngine.Engine.Math.Vectors;
|
||||
|
||||
namespace GridCasting.Models.Grid;
|
||||
|
||||
public class GridNode(FVector2 position)
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the position of the GridNode in a two-dimensional space.
|
||||
/// This position is defined using an instance of the FVector2 struct,
|
||||
/// which encapsulates the X and Y coordinates as floating-point values.
|
||||
/// </summary>
|
||||
public FVector2 Position { get; } = position;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a collection of neighboring GridNode instances that are connected to the current GridNode.
|
||||
/// The connections define the relationships or pathways between this node and others in the grid structure.
|
||||
/// </summary>
|
||||
public List<GridNode?> Connections { get; } = [];
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
using System.Collections;
|
||||
|
||||
namespace GridCasting.Models.GridGraph;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a graph structure designed for grid-based systems. The GridGraph class serves as a foundational component for
|
||||
/// creating, managing, and transforming grids, paths, and related operations in systems that require spatial organization.
|
||||
/// </summary>
|
||||
public class GridGraph : IEnumerable<GridGraphNode>
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the collection of grid nodes within the grid graph.
|
||||
/// Each node symbolizes a distinct element or point in the graph.
|
||||
/// This list enables navigation and structural definition of the graph.
|
||||
/// </summary>
|
||||
public List<GridGraphNode> Nodes { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Provides an indexer to access a specific grid graph node from the collection
|
||||
/// by its index. The indexer allows direct retrieval of a node based on its
|
||||
/// position in the node list.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the node to retrieve.</param>
|
||||
/// <returns>The grid graph node at the specified index in the collection.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown when the specified index is outside the bounds of the node collection.
|
||||
/// </exception>
|
||||
public GridGraphNode this[int index] => Nodes[index];
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the collection of nodes
|
||||
/// in the grid graph.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An enumerator that can be used to iterate through the nodes of the grid graph.
|
||||
/// </returns>
|
||||
public IEnumerator<GridGraphNode> GetEnumerator() => Nodes.GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the collection of nodes
|
||||
/// in the grid graph.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An enumerator that can be used to iterate through the nodes of the grid graph.
|
||||
/// </returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace GridCasting.Models.GridGraph;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an edge in a grid system, connecting two nodes and including metadata about its geometry.
|
||||
/// </summary>
|
||||
public class GridGraphEdge(GridGraphNode nodeA, GridGraphNode nodeB, float angle, float length)
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the starting node of the grid edge.
|
||||
/// </summary>
|
||||
public GridGraphNode NodeA { get; } = nodeA;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the ending node of the grid edge.
|
||||
/// </summary>
|
||||
public GridGraphNode NodeB { get; } = nodeB;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the angle of the grid edge in degrees.
|
||||
/// </summary>
|
||||
public float Angle { get; } = angle;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the length of the grid edge.
|
||||
/// </summary>
|
||||
public float Length { get; } = length;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace GridCasting.Models.GridGraph;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a node in a grid system. Each node may be connected to other nodes
|
||||
/// in the grid via grid edges, which define relationships and structural topology.
|
||||
/// </summary>
|
||||
public class GridGraphNode
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of edges connected to the grid node, representing its relationships
|
||||
/// with adjacent nodes. Each edge contains information about the connected nodes
|
||||
/// and additional metadata such as length and angle.
|
||||
/// </summary>
|
||||
public List<GridGraphEdge> Edges { get; } = [];
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace GridCasting.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a path structure consisting of a start node and a series of directions.
|
||||
/// The path is iterable, allowing iteration over the start node followed by the direction sequence.
|
||||
/// </summary>
|
||||
public struct Path(int startNode, int[] directions) : IEnumerable<int>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the starting node of the path. This represents the initial point
|
||||
/// in the sequence before any directions are taken.
|
||||
/// </summary>
|
||||
public int StartNode { get; } = startNode;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sequence of directions within the path. Each direction
|
||||
/// represents a step or movement from the initial starting node.
|
||||
/// </summary>
|
||||
public int[] Directions { get; } = directions;
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the path, starting with the StartNode followed by the sequence of Directions.
|
||||
/// </summary>
|
||||
/// <returns>An enumerator that iterates through the elements of the path.</returns>
|
||||
public IEnumerator<int> GetEnumerator()
|
||||
{
|
||||
yield return StartNode;
|
||||
foreach (var direction in Directions)
|
||||
yield return direction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the path, starting with the StartNode followed by the sequence of Directions.
|
||||
/// </summary>
|
||||
/// <returns>An enumerator that iterates through the elements of the path.</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
using GridCasting.Models.Grid;
|
||||
using GridCasting.Models.GridGraph;
|
||||
using GridCasting.Utils.BVH;
|
||||
using IgdrasilEngine.Engine.Math.Boxes;
|
||||
using IgdrasilEngine.Engine.Math.Vectors;
|
||||
using IgdrasilEngine.Engine.Utils;
|
||||
using Path = GridCasting.Models.Path;
|
||||
|
||||
namespace GridCasting.Transform;
|
||||
|
||||
/// <summary>
|
||||
/// Provides functionality for resolving, validating, and manipulating grid-based structures,
|
||||
/// as well as transforming those structures into paths or other representations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The <c>GridResolver</c> class enables operations on grid graphs by integrating a set of transforms and ensuring
|
||||
/// that grid structures are consistent and functional.
|
||||
/// </remarks>
|
||||
public class GridResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the underlying grid graph structure used by the <c>GridResolver</c> for processing grid-related sequences and operations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The <c>_graph</c> field is an instance of <see cref="GridGraph"/>, which serves as the core data structure to store grid nodes and their relationships.
|
||||
/// It is used for graph traversal, pathfinding, range-based queries, and various graph-related manipulations within the <c>GridResolver</c> class.
|
||||
/// </remarks>
|
||||
private readonly GridGraph _graph;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the sensitivity threshold for operations within the <c>GridResolver</c>, impacting the resolution or precision
|
||||
/// of grid-based calculations, transformations, and spatial queries.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The <c>_sensitivity</c> field is primarily used to control tolerance levels in various methods, such as determining
|
||||
/// proximity, distance checks, and bounding region adjustments. It directly affects the accuracy and granularity of
|
||||
/// operations such as pathfinding and spatial queries within grid structures handled by the <c>GridResolver</c>.
|
||||
/// </remarks>
|
||||
private readonly float _sensitivity;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a 2D Bounding Volume Hierarchy (BVH) structure for efficiently storing and querying grid graph points
|
||||
/// used within the <c>GridResolver</c> class for spatial operations such as nearest-neighbor searches.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The <c>_bvh</c> field is an instance of <see cref="PointBVH2D{T}"/>, specifically of type <see cref="GridGraphPoint"/>.
|
||||
/// It facilitates spatial organization and query optimizations, enabling efficient handling of operations within the grid,
|
||||
/// such as pathfinding, point lookups, and range-based searches.
|
||||
/// This structure is dynamically built when the <c>GridResolver</c> is initialized based on the nodes of the associated grid graph.
|
||||
/// </remarks>
|
||||
private readonly PointBVH2D<GridGraphPoint> _bvh = new();
|
||||
|
||||
private readonly FBox2 _graphAABB;
|
||||
|
||||
/// <summary>
|
||||
/// Provides functionality for resolving, validating, and manipulating grid-based structures,
|
||||
/// as well as transforming those structures into paths or other representations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The <c>GridResolver</c> class enables operations on grid graphs by integrating a set of transforms and ensuring
|
||||
/// that grid structures are consistent and functional.
|
||||
/// </remarks>
|
||||
public GridResolver(GridGraph graph, float sensitivity)
|
||||
{
|
||||
_graph = graph;
|
||||
_sensitivity = sensitivity;
|
||||
var aabb = new FBox2(FVector2.Zero, FVector2.Zero);
|
||||
HashSet<GridGraphNode> completedNodes = [];
|
||||
TraverseGraph(_graph,
|
||||
point =>
|
||||
{
|
||||
aabb = FBox2.Union(aabb, point.TreePosition);
|
||||
completedNodes.Add(point.Node);
|
||||
_bvh.Add(point);
|
||||
return point;
|
||||
},
|
||||
(_, to) => completedNodes.Contains(to.Node) ? TraversalResult.SkipNode : TraversalResult.EnqueueNode
|
||||
);
|
||||
_graphAABB = aabb;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a grid graph from a given grid structure, converting the grid's nodes into a graph format,
|
||||
/// which enables advanced graph-based operations and transformations.
|
||||
/// </summary>
|
||||
/// <param name="graph">The source grid structure containing nodes to be converted into a graph representation.</param>
|
||||
/// <returns>
|
||||
/// A new instance of <c>GridGraph</c> representing the grid in a graph format if the conversion is successful;
|
||||
/// otherwise, returns null if the grid cannot be converted.
|
||||
/// </returns>
|
||||
public static GridGraph? CreateGridGraph(Grid graph)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the structural integrity of a given grid graph by ensuring that each node maintains
|
||||
/// consistent positional relationships and no node is assigned multiple positions.
|
||||
/// </summary>
|
||||
/// <param name="graph">The grid graph to be verified.</param>
|
||||
/// <returns>
|
||||
/// A boolean value indicating whether the grid graph is valid. Returns true if the graph is structurally consistent,
|
||||
/// and false if inconsistencies, such as multiple positions for the same node, are detected.
|
||||
/// </returns>
|
||||
public static bool VerifyGridGraph(GridGraph graph)
|
||||
{
|
||||
Dictionary<GridGraphNode, FVector2> nodes = new();
|
||||
return TraverseGraph(
|
||||
graph,
|
||||
point =>
|
||||
{
|
||||
nodes.Add(point.Node, point.TreePosition);
|
||||
return point;
|
||||
},
|
||||
(_, to) => nodes.TryGetValue(to.Node, out var existingPosition)
|
||||
? FVector2.Distance(to.TreePosition, existingPosition) > 1e-5
|
||||
? TraversalResult.AbortTraversal // Multiple positions for same node
|
||||
: TraversalResult.SkipNode // Already traversed
|
||||
: TraversalResult.EnqueueNode // New node
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a grid structure within a specified range by traversing a grid graph.
|
||||
/// It adds nodes and their corresponding connections based on the positions defined
|
||||
/// in the graph and the constraints of the given range.
|
||||
/// </summary>
|
||||
/// <param name="range">The rectangular range within which the grid structure is generated.</param>
|
||||
/// <returns>
|
||||
/// A new <see cref="Grid"/> instance containing nodes and connections that exist within
|
||||
/// the specified range in the grid graph.
|
||||
/// </returns>
|
||||
public Grid GenerateGrid(FBox2 range)
|
||||
{
|
||||
Dictionary<FVector2, GridNode> points = [];
|
||||
Grid grid = new();
|
||||
TraverseGraph(
|
||||
_graph,
|
||||
point => {
|
||||
if (points.TryGetValue(point.TreePosition, out var node)) return node;
|
||||
node = new GridNode(point.TreePosition);
|
||||
grid.Nodes.Add(node);
|
||||
return node;
|
||||
},
|
||||
(from, to) =>
|
||||
{
|
||||
if (!range.ContainsInclusive(to.TreePosition))
|
||||
{
|
||||
from.Connections.Add(null);
|
||||
return TraversalResult.SkipNode;
|
||||
}
|
||||
if (points.TryGetValue(to.TreePosition, out var node))
|
||||
{
|
||||
from.Connections.Add(node);
|
||||
return TraversalResult.SkipNode;
|
||||
}
|
||||
node = new GridNode(to.TreePosition);
|
||||
points.Add(node.Position, node);
|
||||
grid.Nodes.Add(node);
|
||||
from.Connections.Add(node);
|
||||
return TraversalResult.EnqueueNode;
|
||||
}
|
||||
);
|
||||
return grid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all grid positions from the specified range within the grid graph.
|
||||
/// Ensures that only unique positions are included and limits results to the defined range.
|
||||
/// </summary>
|
||||
/// <param name="range">The bounding box that defines the area within which grid positions are collected.</param>
|
||||
/// <returns>
|
||||
/// An array of FVector2 objects representing the unique grid positions contained within the specified range.
|
||||
/// </returns>
|
||||
public FVector2[] GetGridPositions(FBox2 range)
|
||||
{
|
||||
HashSet<FVector2> points = [];
|
||||
TraverseGraph(
|
||||
_graph,
|
||||
point => {
|
||||
points.Add(point.TreePosition);
|
||||
return point;
|
||||
},
|
||||
(_, to) => !range.ContainsInclusive(to.TreePosition) || points.Contains(to.TreePosition)
|
||||
? TraversalResult.SkipNode
|
||||
: TraversalResult.EnqueueNode
|
||||
);
|
||||
return points.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a navigable path through a grid graph based on a sequence of positions.
|
||||
/// Validates the possibility of connecting the provided positions within the grid graph.
|
||||
/// </summary>
|
||||
/// <param name="positions">An array of positional vectors specifying the targeted path in the grid graph.</param>
|
||||
/// <returns>
|
||||
/// An instance of the <c>Path</c> struct if a valid path exists connecting the given positions,
|
||||
/// otherwise returns null if the path cannot be constructed due to inconsistencies or breaks.
|
||||
/// </returns>
|
||||
public Path? GetPath(FVector2[] positions)
|
||||
{
|
||||
var point = GetNearestPoint(positions[0]);
|
||||
if (point == null) return null;
|
||||
var directions = new int[positions.Length - 1];
|
||||
var node = point.Node;
|
||||
var position = point.TreePosition;
|
||||
for (var i = 1; i < positions.Length; i++)
|
||||
{
|
||||
var updated = false;
|
||||
for (var j = 0; j < node.Edges.Count; j++)
|
||||
{
|
||||
var newPos = position + new FVector2(
|
||||
node.Edges[j].Length * MathF.Cos(node.Edges[j].Angle),
|
||||
node.Edges[j].Length * MathF.Sin(node.Edges[j].Angle)
|
||||
);
|
||||
if (FVector2.Distance(positions[i], newPos) > _sensitivity) continue;
|
||||
updated = true;
|
||||
directions[i - 1] = j;
|
||||
position = newPos;
|
||||
node = node.Edges[j].NodeA == node ? node.Edges[j].NodeB : node.Edges[j].NodeA;
|
||||
break;
|
||||
}
|
||||
if (!updated) return null; // Path doesn't exist
|
||||
}
|
||||
return new Path(_graph.IndexOf(node), directions);
|
||||
}
|
||||
|
||||
private GridGraphPoint? GetNearestPoint(FVector2 position)
|
||||
{
|
||||
var points = _bvh.FindNearest(position, _sensitivity);
|
||||
switch (points.Count)
|
||||
{
|
||||
case 1:
|
||||
return points[0];
|
||||
case > 1:
|
||||
return null;
|
||||
}
|
||||
var completedPoints = new HashSet<GridGraphPoint>();
|
||||
PriorityQueue<GridGraphPoint, float> queue = new();
|
||||
var threshold = _graphAABB.Size.Length;
|
||||
var min = new GridGraphPoint(new FVector2(0, 0), _graph.Nodes[0]);
|
||||
var minDistance = FVector2.Distance(position, min.TreePosition);
|
||||
queue.Enqueue(min, minDistance);
|
||||
while (queue.TryDequeue(out var point, out var priority))
|
||||
{
|
||||
if (priority < _sensitivity) break;
|
||||
if (!completedPoints.Add(point)) continue;
|
||||
if (_bvh.FindNearest(point.TreePosition, 1e-5f).Count == 0) _bvh.Add(point);
|
||||
if (priority < minDistance)
|
||||
{
|
||||
minDistance = priority;
|
||||
min = point;
|
||||
}
|
||||
else if (priority > minDistance + threshold) break;
|
||||
|
||||
foreach (var edge in point.Node.Edges)
|
||||
{
|
||||
var nextNode = edge.NodeA == point.Node ? edge.NodeB : edge.NodeA;
|
||||
var dir = new FVector2(
|
||||
MathF.Cos(edge.Angle),
|
||||
MathF.Sin(edge.Angle)
|
||||
);
|
||||
var nextPosition = point.TreePosition + edge.Length * dir;
|
||||
var nextPoint = new GridGraphPoint(nextPosition, nextNode);
|
||||
var dirToTarget = (position - point.TreePosition).Normalized;
|
||||
if (FVector2.Dot(dirToTarget, dir) < -0.3f) continue;
|
||||
queue.Enqueue(nextPoint, FVector2.Distance(nextPoint.TreePosition, position));
|
||||
}
|
||||
}
|
||||
|
||||
return minDistance < _sensitivity ? min : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Traverses the specified grid graph using a breadth-first approach. The traversal process is controlled
|
||||
/// through a combination of a point update function and a traversal callback to determine the next course of action
|
||||
/// for each node being evaluated.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type parameter determined by the result of the update function, which is forwarded to the traversal callback.</typeparam>
|
||||
/// <param name="graph">The grid graph to traverse.</param>
|
||||
/// <param name="update">A function that processes each grid point during traversal and returns a value of type <typeparamref name="T"/>.</param>
|
||||
/// <param name="callback">
|
||||
/// A function that decides the traversal behavior for each edge of the graph, using the result of the update function
|
||||
/// and the next grid point being evaluated. The callback returns a <see cref="TraversalResult"/> to either enqueue the node, skip it, or abort the traversal.
|
||||
/// </param>
|
||||
/// <param name="start">The starting point of the traversal. If not provided, the first node in the graph is used.</param>
|
||||
/// <returns>
|
||||
/// A boolean value indicating the success of the traversal. Returns true if the entire graph was processed without
|
||||
/// encountering a condition to abort traversal, and false otherwise.
|
||||
/// </returns>
|
||||
private static bool TraverseGraph<T>(GridGraph graph, Func<GridGraphPoint, T> update,
|
||||
Func<T, GridGraphPoint, TraversalResult> callback, GridGraphPoint? start = null)
|
||||
{
|
||||
Queue<GridGraphPoint> queue = new();
|
||||
queue.Enqueue(start ?? new GridGraphPoint(new FVector2(0, 0), graph.Nodes[0]));
|
||||
while (queue.TryDequeue(out var point))
|
||||
{
|
||||
var pointRepresentation = update(point);
|
||||
foreach (var edge in point.Node.Edges)
|
||||
{
|
||||
var nextNode = edge.NodeA == point.Node ? edge.NodeB : edge.NodeA;
|
||||
var nextPosition = point.TreePosition + new FVector2(
|
||||
edge.Length * MathF.Cos(edge.Angle),
|
||||
edge.Length * MathF.Sin(edge.Angle)
|
||||
);
|
||||
var nextPoint = new GridGraphPoint(nextPosition, nextNode);
|
||||
switch (callback(pointRepresentation, nextPoint))
|
||||
{
|
||||
case TraversalResult.AbortTraversal:
|
||||
return false;
|
||||
case TraversalResult.SkipNode:
|
||||
break;
|
||||
case TraversalResult.EnqueueNode:
|
||||
default:
|
||||
queue.Enqueue(nextPoint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents the result of a traversal operation in a grid graph.
|
||||
/// </summary>
|
||||
private enum TraversalResult
|
||||
{
|
||||
EnqueueNode,
|
||||
SkipNode,
|
||||
AbortTraversal
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents a point in a grid graph, used within a 2D BVH (Bounding Volume Hierarchy) tree structure.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The <c>GridGraphPoint</c> class is used to store and manage nodes of a grid graph, allowing for spatial operations
|
||||
/// such as nearest neighbor searches in 2D space. It inherits from <c>PointBVH2DTransform</c>, enabling integration
|
||||
/// with BVH for fast spatial queries.
|
||||
/// </remarks>
|
||||
private class GridGraphPoint(FVector2 position, GridGraphNode node) : PointBVH2DTransform<GridGraphPoint>
|
||||
{
|
||||
public override FVector2 TreePosition { get; protected set; } = position;
|
||||
public GridGraphNode Node { get; } = node;
|
||||
|
||||
private static FVector2 Quantize(FVector2 p)
|
||||
{
|
||||
const float eps = 0.001f;
|
||||
return new FVector2(
|
||||
MathF.Round(p.X / eps) * eps,
|
||||
MathF.Round(p.Y / eps) * eps
|
||||
);
|
||||
}
|
||||
public override int GetHashCode() => HashCode.Combine(Node, Quantize(TreePosition));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using GridCasting.Models.Grid;
|
||||
using GridCasting.Models.GridGraph;
|
||||
using Path = GridCasting.Models.Path;
|
||||
|
||||
namespace GridCasting.Transform;
|
||||
|
||||
/// <summary>
|
||||
/// Defines methods to transform and reverse-transform paths based on a grid graph.
|
||||
/// </summary>
|
||||
public interface IPathTransform
|
||||
{
|
||||
/// <summary>
|
||||
/// Transforms the given path based on the specified grid graph.
|
||||
/// </summary>
|
||||
/// <param name="graph">The grid graph on which the transformation is based.</param>
|
||||
/// <param name="path">The path to be transformed.</param>
|
||||
/// <returns>A transformed path as per the grid graph rules.</returns>
|
||||
public Path Transform(GridGraph graph, Path path);
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the transformation applied to the provided path based on the specified grid graph.
|
||||
/// </summary>
|
||||
/// <param name="graph">The grid graph used to reverse the transformation.</param>
|
||||
/// <param name="path">The path to be reversed.</param>
|
||||
/// <returns>A path that has the reverse transformation applied based on the grid graph rules.</returns>
|
||||
public Path Reverse(GridGraph graph, Path path);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using IgdrasilEngine.Engine.Math.Boxes;
|
||||
using IgdrasilEngine.Engine.Math.Vectors;
|
||||
|
||||
namespace GridCasting.Utils.BVH.Point;
|
||||
/// <summary>
|
||||
/// Интерфейс для чтения BVH дерева точек в 2D пространстве
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Тип точек в дереве BVH.</typeparam>
|
||||
public interface IReadOnlyPointBVH2D<T> where T : PointBVH2DTransform<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Глубина BVH дерева.
|
||||
/// </summary>
|
||||
/// <returns>Глубина дерева.</returns>
|
||||
public uint Depth();
|
||||
/// <summary>
|
||||
/// Находит все точки в пределах заданного радиуса от указанной позиции.
|
||||
/// </summary>
|
||||
/// <param name="position">Позиция для поиска ближайших точек.</param>
|
||||
/// <param name="radius">Радиус поиска.</param>
|
||||
/// <returns>Список точек, найденных в пределах радиуса.</returns>
|
||||
public List<T> FindNearest(FVector2 position, float radius);
|
||||
/// <summary>
|
||||
/// Находит все точки в пределах заданного радиуса от указанной позиции и добавляет их в предоставленный список.
|
||||
/// </summary>
|
||||
/// <param name="position">Позиция для поиска ближайших точек.</param>
|
||||
/// <param name="radius">Радиус поиска.</param>
|
||||
/// <param name="result">Список для добавления найденных точек.</param>
|
||||
public void FindNearest(FVector2 position, float radius, List<T> result);
|
||||
/// <summary>
|
||||
/// Получает граничный прямоугольник, охватывающий все точки в BVH дереве.
|
||||
/// </summary>
|
||||
/// <returns>Граничный прямоугольник.</returns>
|
||||
public FBox2 GetBoundaryBox();
|
||||
}
|
||||
@@ -0,0 +1,702 @@
|
||||
using GridCasting.Utils.BVH.Point;
|
||||
using IgdrasilEngine.Engine.Math.Boxes;
|
||||
using IgdrasilEngine.Engine.Math.Vectors;
|
||||
|
||||
namespace GridCasting.Utils.BVH;
|
||||
|
||||
/// <summary>
|
||||
/// Дерево BVH для точек в 2D пространстве.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Тип точек в дереве BVH.</typeparam>
|
||||
public class PointBVH2D<T> : IReadOnlyPointBVH2D<T> where T : PointBVH2DTransform<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Стек для обхода дерева BVH.
|
||||
/// </summary>
|
||||
private readonly Stack<Node> _stack = new();
|
||||
/// <summary>
|
||||
/// Корневой узел BVH дерева.
|
||||
/// </summary>
|
||||
private readonly Branch _root;
|
||||
/// <summary>
|
||||
/// Инициализирует новый экземпляр дерева BVH для точек в 2D пространстве.
|
||||
/// </summary>
|
||||
public PointBVH2D()
|
||||
{
|
||||
_root = new Branch(null, default!);
|
||||
_root.Root = _root;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Глубина BVH дерева.
|
||||
/// </summary>
|
||||
/// <returns>Глубина дерева.</returns>
|
||||
public uint Depth() => _root.Depth();
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет точку в BVH дерево.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для добавления.</param>
|
||||
public void Add(T value)
|
||||
{
|
||||
lock (_root)
|
||||
{
|
||||
_root.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Оптимизированно добавляет точку в BVH дерево.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для добавления.</param>
|
||||
public void OptimizedAdd(T value)
|
||||
{
|
||||
lock (_root)
|
||||
{
|
||||
_root.OptimizedAdd(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет точку из BVH дерева.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для удаления.</param>
|
||||
public void Remove(T value)
|
||||
{
|
||||
lock (_root)
|
||||
{
|
||||
_root.Remove(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Находит все точки в пределах заданного радиуса от указанной позиции и добавляет их в предоставленный список.
|
||||
/// </summary>
|
||||
/// <param name="position">Позиция для поиска ближайших точек.</param>
|
||||
/// <param name="radius">Радиус поиска.</param>
|
||||
/// <param name="result">Список для добавления найденных точек.</param>
|
||||
public void FindNearest(FVector2 position, float radius, List<T> result)
|
||||
{
|
||||
lock (_root)
|
||||
{
|
||||
_root.FindNearestFwd(position, radius, result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Находит все точки в пределах заданного радиуса от указанной позиции.
|
||||
/// </summary>
|
||||
/// <param name="position">Позиция для поиска ближайших точек.</param>
|
||||
/// <param name="radius">Радиус поиска.</param>
|
||||
/// <returns>Список найденных точек.</returns>
|
||||
public List<T> FindNearest(FVector2 position, float radius)
|
||||
{
|
||||
var result = new List<T>();
|
||||
FindNearest(position, radius, result);
|
||||
return result;
|
||||
}
|
||||
/// <summary>
|
||||
/// Очищает BVH дерево, удаляя все точки.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
lock (_root)
|
||||
{
|
||||
_root.Left = null;
|
||||
_root.Right = null;
|
||||
_root.AABB = new FBox2(FVector2.Zero, FVector2.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает граничный прямоугольник, охватывающий все точки в BVH дереве.
|
||||
/// </summary>
|
||||
/// <returns>Граничный прямоугольник.</returns>
|
||||
public FBox2 GetBoundaryBox() => _root.AABB;
|
||||
|
||||
/// <summary>
|
||||
/// Базовый класс для узлов BVH дерева.
|
||||
/// </summary>
|
||||
public abstract class Node
|
||||
{
|
||||
/// <summary>
|
||||
/// Корень BVH дерева.
|
||||
/// </summary>
|
||||
protected internal Branch Root;
|
||||
/// <summary>
|
||||
/// Родительский узел BVH дерева.
|
||||
/// </summary>
|
||||
protected internal Branch? Parent;
|
||||
/// <summary>
|
||||
/// Ограничивающий прямоугольник узла BVH дерева.
|
||||
/// </summary>
|
||||
protected internal FBox2 AABB;
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует новый экземпляр узла BVH дерева.
|
||||
/// </summary>
|
||||
/// <param name="aabb">Ограничивающий прямоугольник узла.</param>
|
||||
/// <param name="parent">Родительский узел.</param>
|
||||
/// <param name="root">Корень дерева.</param>
|
||||
protected Node(FBox2 aabb, Branch? parent, Branch root)
|
||||
{
|
||||
AABB = aabb;
|
||||
Parent = parent;
|
||||
Root = root;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Глубина BVH дерева.
|
||||
/// </summary>
|
||||
/// <returns>Глубина дерева.</returns>
|
||||
public abstract uint Depth();
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет точку в BVH дерево.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для добавления.</param>
|
||||
public abstract void Add(T value);
|
||||
/// <summary>
|
||||
/// Добавляет точку в BVH дерево.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для добавления.</param>
|
||||
/// <param name="stack">Стек узлов для оптимизации добавления.</param>
|
||||
public abstract void Add(T value, Stack<Node> stack);
|
||||
/// <summary>
|
||||
/// Оптимизированно добавляет точку в BVH дерево.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для добавления.</param>
|
||||
public abstract void OptimizedAdd(T value);
|
||||
/// <summary>
|
||||
/// Оптимизированно добавляет точку в BVH дерево.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для добавления.</param>
|
||||
/// <param name="stack">Стек узлов для оптимизации добавления.</param>
|
||||
public abstract void OptimizedAdd(T value, Stack<Node> stack);
|
||||
/// <summary>
|
||||
/// Удаляет точку из BVH дерева.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для удаления.</param>
|
||||
public abstract void Remove(T value);
|
||||
/// <summary>
|
||||
/// Удаляет точку из BVH дерева.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для удаления.</param>
|
||||
/// <param name="stack">Стек узлов для оптимизации удаления.</param>
|
||||
public abstract void Remove(T value, Stack<Node> stack);
|
||||
/// <summary>
|
||||
/// Находит все точки в пределах заданного радиуса от указанной позиции.
|
||||
/// </summary>
|
||||
/// <param name="position">Позиция для поиска ближайших точек.</param>
|
||||
/// <param name="radius">Радиус поиска.</param>
|
||||
/// <param name="result">Список для хранения найденных точек.</param>
|
||||
public abstract void FindNearestFwd(FVector2 position, float radius, List<T> result);
|
||||
/// <summary>
|
||||
/// Находит все точки в пределах заданного радиуса от указанной позиции.
|
||||
/// </summary>
|
||||
/// <param name="position">Позиция для поиска ближайших точек.</param>
|
||||
/// <param name="radius">Радиус поиска.</param>
|
||||
/// <param name="result">Список для хранения найденных точек.</param>
|
||||
/// <param name="stack">Стек узлов для оптимизации поиска.</param>
|
||||
public abstract void FindNearestFwd(FVector2 position, float radius, List<T> result, Stack<Node> stack);
|
||||
/// <summary>
|
||||
/// Находит все точки в пределах заданного радиуса от указанной позиции, обходя дерево вверх.
|
||||
/// </summary>
|
||||
/// <param name="position">Позиция для поиска ближайших точек.</param>
|
||||
/// <param name="radius">Радиус поиска.</param>
|
||||
/// <param name="result">Список для хранения найденных точек.</param>
|
||||
public void FindNearestBwd(FVector2 position, float radius, List<T> result)
|
||||
{
|
||||
lock (Root)
|
||||
{
|
||||
if (Parent == null) return;
|
||||
if (Parent.Left == this)
|
||||
Parent.Right?.FindNearestFwd(position, radius, result);
|
||||
else
|
||||
Parent.Left?.FindNearestFwd(position, radius, result);
|
||||
Parent.FindNearestBwd(position, radius, result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет текущий узел из BVH дерева.
|
||||
/// </summary>
|
||||
protected void RemoveCurrentNode()
|
||||
{
|
||||
if (Parent == null) return;
|
||||
if (Parent.Left == this)
|
||||
{
|
||||
Parent.Left = null;
|
||||
if (Parent.Right == null)
|
||||
Parent.RemoveCurrentNode();
|
||||
else
|
||||
Parent.Replace(Parent.Right);
|
||||
}
|
||||
else
|
||||
{
|
||||
Parent.Right = null;
|
||||
if (Parent.Left == null)
|
||||
Parent.RemoveCurrentNode();
|
||||
else
|
||||
Parent.Replace(Parent.Left);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Заменяет текущий узел на указанный узел в BVH дереве
|
||||
/// </summary>
|
||||
/// <param name="node">Узел для замены.</param>
|
||||
protected void Replace(Node node)
|
||||
{
|
||||
if (Parent == null) return;
|
||||
if (Parent.Left == this)
|
||||
Parent.Left = node;
|
||||
else
|
||||
Parent.Right = node;
|
||||
node.Parent = Parent;
|
||||
Parent.UpdateAABB();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ветвь BVH дерева.
|
||||
/// </summary>
|
||||
public class Branch : Node
|
||||
{
|
||||
/// <summary>
|
||||
/// Левый дочерний узел ветви BVH дерева.
|
||||
/// </summary>
|
||||
protected internal Node? Left;
|
||||
/// <summary>
|
||||
/// Правый дочерний узел ветви BVH дерева.
|
||||
/// </summary>
|
||||
protected internal Node? Right;
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует новый экземпляр ветви BVH дерева.
|
||||
/// </summary>
|
||||
/// <param name="parent">Родительская ветвь.</param>
|
||||
/// <param name="root">Корневая ветвь.</param>
|
||||
public Branch(Branch? parent, Branch root) : base(new FBox2(FVector2.Zero, FVector2.Zero), parent, root)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Глубина BVH дерева.
|
||||
/// </summary>
|
||||
/// <returns>Глубина дерева.</returns>
|
||||
public override uint Depth()
|
||||
{
|
||||
var left = Left?.Depth() ?? 0;
|
||||
var right = Right?.Depth() ?? 0;
|
||||
return System.Math.Max(left, right) + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет точку в BVH дерево.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для добавления.</param>
|
||||
public override void Add(T value)
|
||||
{
|
||||
var left = Left == null ? 0 : FBox2.Distance(Left.AABB, value.TreePosition);
|
||||
var right = Right == null ? 0 : FBox2.Distance(Right.AABB, value.TreePosition);
|
||||
|
||||
if (left < right)
|
||||
{
|
||||
if (Left == null)
|
||||
Left = new Leaf(this, Root, value);
|
||||
else Left.Add(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Right == null)
|
||||
Right = new Leaf(this, Root, value);
|
||||
else Right.Add(value);
|
||||
}
|
||||
|
||||
UpdateAABB();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет точку в BVH дерево.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для добавления.</param>
|
||||
/// <param name="stack">Стек узлов для обхода.</param>
|
||||
public override void Add(T value, Stack<Node> stack)
|
||||
{
|
||||
var left = Left == null ? 0 : FBox2.Distance(Left.AABB, value.TreePosition);
|
||||
var right = Right == null ? 0 : FBox2.Distance(Right.AABB, value.TreePosition);
|
||||
|
||||
if (left < right)
|
||||
{
|
||||
if (Left == null)
|
||||
Left = new Leaf(this, Root, value);
|
||||
else stack.Push(Left);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Right == null)
|
||||
Right = new Leaf(this, Root, value);
|
||||
else stack.Push(Right);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет точку в BVH дерево.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для добавления.</param>
|
||||
public override void OptimizedAdd(T value)
|
||||
{
|
||||
var left = Left == null ? 0 : FBox2.Distance(Left.AABB, value.TreePosition);
|
||||
var right = Right == null ? 0 : FBox2.Distance(Right.AABB, value.TreePosition);
|
||||
|
||||
if (left < right)
|
||||
{
|
||||
if (Left == null)
|
||||
{
|
||||
Left = new Leaf(this, Root, value);
|
||||
Balance();
|
||||
UpdateAABB();
|
||||
}
|
||||
else if (left <= 0)
|
||||
Left.OptimizedAdd(value);
|
||||
else
|
||||
{
|
||||
var node = new Branch(this, Root);
|
||||
node.Left = new Leaf(node, Root, value);
|
||||
node.Right = Left;
|
||||
Left.Parent = node;
|
||||
Left = node;
|
||||
node.Balance();
|
||||
node.UpdateAABB();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Right == null)
|
||||
{
|
||||
Right = new Leaf(this, Root, value);
|
||||
Balance();
|
||||
UpdateAABB();
|
||||
}
|
||||
else if (right <= 0)
|
||||
Right.OptimizedAdd(value);
|
||||
else
|
||||
{
|
||||
var node = new Branch(this, Root);
|
||||
node.Left = new Leaf(node, Root, value);
|
||||
node.Right = Right;
|
||||
Right.Parent = node;
|
||||
Right = node;
|
||||
node.Balance();
|
||||
node.UpdateAABB();
|
||||
}
|
||||
}
|
||||
|
||||
UpdateAABB();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет точку в BVH дерево.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для добавления.</param>
|
||||
/// <param name="stack">Стек узлов для обхода.</param>
|
||||
public override void OptimizedAdd(T value, Stack<Node> stack)
|
||||
{
|
||||
var left = Left == null ? 0 : FBox2.Distance(Left.AABB, value.TreePosition);
|
||||
var right = Right == null ? 0 : FBox2.Distance(Right.AABB, value.TreePosition);
|
||||
|
||||
if (left < right)
|
||||
{
|
||||
if (Left == null)
|
||||
Left = new Leaf(this, Root, value);
|
||||
else if (left <= 0)
|
||||
stack.Push(Left);
|
||||
else
|
||||
{
|
||||
var node = new Branch(Left.Parent, Root);
|
||||
node.Left = new Leaf(node, Root, value);
|
||||
node.Right = Left;
|
||||
Left.Parent = node;
|
||||
Left = node;
|
||||
node.Balance();
|
||||
node.UpdateAABB();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Right == null)
|
||||
Right = new Leaf(this, Root, value);
|
||||
else if (right <= 0)
|
||||
stack.Push(Right);
|
||||
else
|
||||
{
|
||||
var node = new Branch(Right.Parent, Root);
|
||||
node.Left = new Leaf(node, Root, value);
|
||||
node.Right = Right;
|
||||
Right.Parent = node;
|
||||
Right = node;
|
||||
node.Balance();
|
||||
node.UpdateAABB();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Балансирует BVH дерево, удаляя пустые узлы.
|
||||
/// </summary>
|
||||
private void Balance() => RemoveHoles();
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет пустые узлы из BVH дерева.
|
||||
/// </summary>
|
||||
private void RemoveHoles()
|
||||
{
|
||||
if (Left == null)
|
||||
{
|
||||
if (Right == null)
|
||||
RemoveCurrentNode();
|
||||
else
|
||||
{
|
||||
if (Right is Branch br)
|
||||
br.RemoveHoles();
|
||||
Replace(Right);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (Right == null)
|
||||
{
|
||||
if (Left == null)
|
||||
RemoveCurrentNode();
|
||||
else
|
||||
{
|
||||
if (Left is Branch br)
|
||||
br.RemoveHoles();
|
||||
Replace(Left);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет точку из BVH дерева.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для удаления.</param>
|
||||
public override void Remove(T value)
|
||||
{
|
||||
|
||||
Left?.Remove(value);
|
||||
Right?.Remove(value);
|
||||
if (Left == null && Right == null)
|
||||
RemoveCurrentNode();
|
||||
else UpdateAABB();
|
||||
}
|
||||
/// <summary>
|
||||
/// Удаляет точку из BVH дерева.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для удаления.</param>
|
||||
/// <param name="stack">Стек узлов для обхода.</param>
|
||||
public override void Remove(T value, Stack<Node> stack)
|
||||
{
|
||||
if (Left != null) stack.Push(Left);
|
||||
if (Right != null) stack.Push(Right);
|
||||
}
|
||||
/// <summary>
|
||||
/// Находит все точки в пределах заданного радиуса от указанной позиции.
|
||||
/// </summary>
|
||||
/// <param name="position">Позиция центра поиска.</param>
|
||||
/// <param name="radius">Радиус поиска.</param>
|
||||
/// <param name="result">Список для хранения найденных точек.</param>
|
||||
public override void FindNearestFwd(FVector2 position, float radius, List<T> result)
|
||||
{
|
||||
if (!FBox2.SphereIntersection(AABB, position, radius)) return;
|
||||
Left?.FindNearestFwd(position, radius, result);
|
||||
Right?.FindNearestFwd(position, radius, result);
|
||||
}
|
||||
/// <summary>
|
||||
/// Находит все точки в пределах заданного радиуса от указанной позиции.
|
||||
/// </summary>
|
||||
/// <param name="position">Позиция центра поиска.</param>
|
||||
/// <param name="radius">Радиус поиска.</param>
|
||||
/// <param name="result">Список для хранения найденных точек.</param>
|
||||
/// <param name="stack">Стек узлов для обхода.</param>
|
||||
public override void FindNearestFwd(FVector2 position, float radius, List<T> result, Stack<Node> stack)
|
||||
{
|
||||
if (!FBox2.SphereIntersection(AABB, position, radius)) return;
|
||||
if (Left != null) stack.Push(Left);
|
||||
if (Right != null) stack.Push(Right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновляет ограничивающий прямоугольник узла BVH дерева.
|
||||
/// </summary>
|
||||
protected internal void UpdateAABB()
|
||||
{
|
||||
if (Left == null && Right == null) return;
|
||||
var left = Left?.AABB ?? Right!.AABB;
|
||||
var right = Right?.AABB ?? Left!.AABB;
|
||||
AABB = FBox2.Union(left, right);
|
||||
}
|
||||
/// <summary>
|
||||
/// Перемещает точку в BVH дереве.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для перемещения.</param>
|
||||
public void Relocate(T value)
|
||||
{
|
||||
if (Parent == null)
|
||||
{
|
||||
OptimizedAdd(value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Left == null && Right == null)
|
||||
RemoveCurrentNode();
|
||||
else Parent.UpdateAABB();
|
||||
|
||||
if (Parent.AABB.ContainsInclusive(value.TreePosition))
|
||||
{
|
||||
Parent.OptimizedAdd(value);
|
||||
return;
|
||||
}
|
||||
|
||||
Parent.Relocate(value);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Лист BVH дерева.
|
||||
/// </summary>
|
||||
public class Leaf : Node
|
||||
{
|
||||
/// <summary>
|
||||
/// Значение точки, хранящейся в листе BVH дерева.
|
||||
/// </summary>
|
||||
private readonly T _value;
|
||||
/// <summary>
|
||||
/// Инициализирует новый экземпляр листа BVH дерева.
|
||||
/// </summary>
|
||||
/// <param name="parent">Родительский узел.</param>
|
||||
/// <param name="root">Корень дерева.</param>
|
||||
/// <param name="value">Значение точки.</param>
|
||||
public Leaf(Branch? parent, Branch root, T value) : base(new FBox2(value.TreePosition, value.TreePosition),
|
||||
parent, root)
|
||||
{
|
||||
_value = value;
|
||||
_value.Location = this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Глубина BVH дерева.
|
||||
/// </summary>
|
||||
/// <returns>Глубина дерева.</returns>
|
||||
public override uint Depth() => 1;
|
||||
/// <summary>
|
||||
/// Добавляет точку в BVH дерево.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для добавления.</param>
|
||||
public override void Add(T value) => OptimizedAdd(value);
|
||||
/// <summary>
|
||||
/// Добавляет точку в BVH дерево.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для добавления.</param>
|
||||
/// <param name="stack">Стек узлов.</param>
|
||||
public override void Add(T value, Stack<Node> stack) => OptimizedAdd(value);
|
||||
/// <summary>
|
||||
/// Перемещает точку в BVH дереве.
|
||||
/// </summary>
|
||||
public void Relocate()
|
||||
{
|
||||
lock (Root)
|
||||
{
|
||||
// if (Parent == null)
|
||||
// {
|
||||
// OptimizedAdd(_value);
|
||||
// return;
|
||||
// }
|
||||
Root.Remove(_value);
|
||||
Root.OptimizedAdd(_value);
|
||||
// Parent.UpdateAABB();
|
||||
//
|
||||
// if (Parent.AABB.ContainsInclusive(_value.TreePosition))
|
||||
// {
|
||||
// Parent.OptimizedAdd(_value);
|
||||
// return;
|
||||
// }
|
||||
// Parent.Relocate(_value);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Оптимизированно добавляет точку в BVH дерево.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для добавления.</param>
|
||||
public override void OptimizedAdd(T value)
|
||||
{
|
||||
if (Parent == null) return;
|
||||
if (Parent.Left == this)
|
||||
{
|
||||
var node = new Branch(Parent, Root);
|
||||
node.Left = new Leaf(node, Root, value);
|
||||
node.Right = this;
|
||||
Parent.Left = node;
|
||||
Parent = node;
|
||||
node.UpdateAABB();
|
||||
}
|
||||
else
|
||||
{
|
||||
var node = new Branch(Parent, Root);
|
||||
node.Left = new Leaf(node, Root, value);
|
||||
node.Right = this;
|
||||
Parent.Right = node;
|
||||
Parent = node;
|
||||
node.UpdateAABB();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Оптимизированно добавляет точку в BVH дерево.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для добавления.</param>
|
||||
/// <param name="stack">Стек узлов.</param>
|
||||
public override void OptimizedAdd(T value, Stack<Node> stack) => OptimizedAdd(value);
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет точку из BVH дерева.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для удаления.</param>
|
||||
public override void Remove(T value)
|
||||
{
|
||||
if (!_value.Equals(value)) return;
|
||||
RemoveCurrentNode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет точку из BVH дерева.
|
||||
/// </summary>
|
||||
/// <param name="value">Точка для удаления.</param>
|
||||
/// <param name="stack">Стек узлов.</param>
|
||||
public override void Remove(T value, Stack<Node> stack) => Remove(value);
|
||||
|
||||
/// <summary>
|
||||
/// Находит все точки в пределах заданного радиуса от указанной позиции.
|
||||
/// </summary>
|
||||
/// <param name="position">Позиция для поиска.</param>
|
||||
/// <param name="radius">Радиус поиска.</param>
|
||||
/// <param name="result">Список для хранения найденных точек.</param>
|
||||
public override void FindNearestFwd(FVector2 position, float radius, List<T> result)
|
||||
{
|
||||
if (FVector2.Distance(position, _value.TreePosition) > radius) return;
|
||||
result.Add(_value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Находит все точки в пределах заданного радиуса от указанной позиции.
|
||||
/// </summary>
|
||||
/// <param name="position">Позиция для поиска.</param>
|
||||
/// <param name="radius">Радиус поиска.</param>
|
||||
/// <param name="result">Список для хранения найденных точек.</param>
|
||||
/// <param name="stack">Стек узлов.</param>
|
||||
public override void FindNearestFwd(FVector2 position, float radius, List<T> result, Stack<Node> stack) => FindNearestFwd(position, radius, result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using GridCasting.Utils.BVH.Point;
|
||||
using IgdrasilEngine.Engine.Math.Vectors;
|
||||
|
||||
namespace GridCasting.Utils.BVH;
|
||||
|
||||
/// <summary>
|
||||
/// Базовый класс для точек, хранящихся в BVH дереве в 2D пространстве.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Тип точек в дереве BVH.</typeparam>
|
||||
public abstract class PointBVH2DTransform<T> where T : PointBVH2DTransform<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Позиция точки в дереве BVH.
|
||||
/// </summary>
|
||||
public abstract FVector2 TreePosition { get; protected set; }
|
||||
/// <summary>
|
||||
/// Лист BVH дерева, в котором находится эта точка.
|
||||
/// </summary>
|
||||
public PointBVH2D<T>.Leaf? Location { get; protected internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Находит все точки в пределах заданного радиуса от позиции этой точки и добавляет их в предоставленный список.
|
||||
/// </summary>
|
||||
/// <param name="radius">Радиус поиска.</param>
|
||||
/// <param name="result">Список для добавления найденных точек.</param>
|
||||
public void FindNearest(float radius, ref List<T> result) => Location?.FindNearestBwd(TreePosition, radius, result);
|
||||
|
||||
/// <summary>
|
||||
/// Находит все точки в пределах заданного радиуса от позиции этой точки.
|
||||
/// </summary>
|
||||
/// <param name="radius">Радиус поиска.</param>
|
||||
/// <returns>Список найденных точек.</returns>
|
||||
public List<T> FindNearest(float radius)
|
||||
{
|
||||
var result = new List<T>();
|
||||
FindNearest(radius, ref result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace GridCasting.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// ListenableDictionary is a wrapper around a standard dictionary that extends its functionality
|
||||
/// by providing event-based notifications for certain operations such as adding, removing, or updating entries.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of keys maintained in the dictionary.</typeparam>
|
||||
/// <typeparam name="TValue">The type of values maintained in the dictionary.</typeparam>
|
||||
public class ListenableDictionary<TKey, TValue>(IDictionary<TKey, TValue> baseDictionary) : IDictionary<TKey, TValue>
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an event triggered when the <see cref="ListenableDictionary{TKey, TValue}"/> is cleared using the <c>Clear</c> method.
|
||||
/// Subscribing to this event allows monitoring actions where all dictionary entries are removed at once.
|
||||
/// </summary>
|
||||
public event Action? OnClear;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an event triggered when an entry is removed from the <see cref="ListenableDictionary{TKey, TValue}"/>
|
||||
/// using the <c>Remove</c> method. Subscribing to this event allows monitoring the removal of specific keys from the dictionary.
|
||||
/// </summary>
|
||||
public event Action<TKey>? OnRemove;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an event triggered whenever an entry in the <see cref="ListenableDictionary{TKey, TValue}"/> is updated or replaced.
|
||||
/// Subscribing to this event allows monitoring changes to existing key-value pairs within the dictionary,
|
||||
/// providing the updated key and corresponding value.
|
||||
/// </summary>
|
||||
public event Action<TKey, TValue>? OnUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the ListenableDictionary.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An enumerator for the entries in the dictionary.
|
||||
/// </returns>
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => baseDictionary.GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the ListenableDictionary as a non-generic collection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An enumerator for the entries in the dictionary as a non-generic IEnumerable.
|
||||
/// </returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Removes all entries from the ListenableDictionary.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This operation clears the underlying dictionary and triggers the <see cref="OnClear"/> event if any subscribers are registered.
|
||||
/// </remarks>
|
||||
public void Clear()
|
||||
{
|
||||
baseDictionary.Clear();
|
||||
OnClear?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of key-value pairs contained within the <see cref="ListenableDictionary{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// An integer representing the count of elements currently stored in the dictionary.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// This property retrieves the count directly from the underlying base dictionary.
|
||||
/// The count updates dynamically as items are added or removed.
|
||||
/// </remarks>
|
||||
public int Count => baseDictionary.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the <see cref="ListenableDictionary{TKey, TValue}"/> is read-only.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A read-only dictionary does not allow adding, removing, or modifying its elements.
|
||||
/// This property reflects the underlying dictionary's read-only status.
|
||||
/// </remarks>
|
||||
public bool IsReadOnly => baseDictionary.IsReadOnly;
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified key and value to the ListenableDictionary.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the element to add to the dictionary.</param>
|
||||
/// <param name="value">The value of the element to add to the dictionary.</param>
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
baseDictionary.Add(key, value);
|
||||
OnUpdate?.Invoke(key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the ListenableDictionary contains the specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to locate in the dictionary.</param>
|
||||
/// <returns>
|
||||
/// true if the ListenableDictionary contains an element with the specified key; otherwise, false.
|
||||
/// </returns>
|
||||
public bool ContainsKey(TKey key) => baseDictionary.ContainsKey(key);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the value with the specified key from the ListenableDictionary.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the element to remove from the dictionary.</param>
|
||||
/// <returns>
|
||||
/// true if the element is successfully removed; otherwise, false.
|
||||
/// This method also returns false if the key was not found in the dictionary.
|
||||
/// </returns>
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
var result = baseDictionary.Remove(key);
|
||||
if (result) OnRemove?.Invoke(key);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the value associated with the specified key from the ListenableDictionary.
|
||||
/// </summary>
|
||||
/// <param name="key">The key whose value to retrieve.</param>
|
||||
/// <param name="value">When this method returns, contains the value associated with the specified key,
|
||||
/// if the key is found; otherwise, the default value for the type of the value parameter.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the ListenableDictionary contains an element with the specified key; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) =>
|
||||
baseDictionary.TryGetValue(key, out value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value associated with the specified key in the ListenableDictionary.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the value to get or set.</param>
|
||||
/// <value>
|
||||
/// The value associated with the specified key. If the key does not exist, a get operation throws a KeyNotFoundException.
|
||||
/// A set operation will update the value and trigger the <see cref="OnUpdate"/> event.
|
||||
/// </value>
|
||||
/// <exception cref="KeyNotFoundException">Thrown when attempting to get a value for a key that does not exist.</exception>
|
||||
/// <remarks>
|
||||
/// Setting a value will replace the existing value if the key is already present, and notify subscribers via the <see cref="OnUpdate"/> event.
|
||||
/// </remarks>
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get => baseDictionary[key];
|
||||
set
|
||||
{
|
||||
baseDictionary[key] = value;
|
||||
OnUpdate?.Invoke(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection containing the keys in the <see cref="ListenableDictionary{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The returned collection reflects the current state of the dictionary and provides a way to iterate through all the keys.
|
||||
/// </remarks>
|
||||
public ICollection<TKey> Keys => baseDictionary.Keys;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection containing the values in the <see cref="ListenableDictionary{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The returned collection directly reflects the values present in the underlying dictionary at the time of access.
|
||||
/// Changes to the ListenableDictionary will be reflected in this collection.
|
||||
/// </remarks>
|
||||
public ICollection<TValue> Values => baseDictionary.Values;
|
||||
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) => baseDictionary.Contains(item);
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int index) => baseDictionary.CopyTo(array, index);
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
if (!baseDictionary.TryGetValue(item.Key, out var value)) return false;
|
||||
if (!EqualityComparer<TValue>.Default.Equals(value, item.Value)) return false;
|
||||
Remove(item.Key);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace GridCasting.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a generic prefix tree (Trie) data structure, which maps keys
|
||||
/// composed of sequences of <typeparamref name="TKey"/> into values of <typeparamref name="TValue"/>.
|
||||
/// This data structure allows hierarchical storage and efficient retrieval of values associated
|
||||
/// with a sequence (path) of keys.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">
|
||||
/// Specifies the type of the key elements that make up the paths. Must be a non-nullable type.
|
||||
/// </typeparam>
|
||||
/// <typeparam name="TValue">
|
||||
/// Specifies the type of the values that are stored in the trie.
|
||||
/// </typeparam>
|
||||
/// <remarks>
|
||||
/// - The trie supports key sequences of arbitrary length.
|
||||
/// - Child nodes are stored internally in a dictionary for efficient access.
|
||||
/// - Enables operations such as setting, retrieving, and removing values at specific paths.
|
||||
/// - Implements lazy creation for child nodes during insertion.
|
||||
/// - Allows for weak leaf and overwrite behavior during the modification of its structure.
|
||||
/// </remarks>
|
||||
public class Trie<TKey, TValue> where TKey : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the stored value of the current node in the trie. This value
|
||||
/// is assigned when a key-path is associated with a specific value in the trie.
|
||||
/// It can be null if no value has been set for this node.
|
||||
/// </summary>
|
||||
private TValue? _value;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the current node in the trie serves as a "weak leaf," which means
|
||||
/// it is treated as a terminus for key-path lookup but may represent a non-terminal node in the trie structure.
|
||||
/// When this flag is set to true, it allows the trie to consider this node as a valid endpoint for key-path retrieval, even if it has child nodes.
|
||||
/// This is useful for scenarios where certain paths should be considered complete and retrievable, regardless of whether they have further branches in the trie.
|
||||
/// </summary>
|
||||
private bool _isWeakLeaf;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the collection of child nodes for the current node in the trie.
|
||||
/// Each child node is associated with a key and serves as the next level in the trie structure.
|
||||
/// This dictionary enables the hierarchical representation of key-paths.
|
||||
/// </summary>
|
||||
private readonly Dictionary<TKey, Trie<TKey, TValue>> _children = new();
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to the value associated with a specific key path in the trie.
|
||||
/// If the key path exists, the corresponding value is returned; otherwise, a
|
||||
/// KeyNotFoundException is thrown. The indexer facilitates value retrieval
|
||||
/// using a sequence of keys, aligning with trie-based key-path structures.
|
||||
/// </summary>
|
||||
/// <param name="path">An array of keys representing the path to a value in the trie.</param>
|
||||
/// <returns>The value associated with the specified key path.</returns>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the specified key path does not exist in the trie.
|
||||
/// </exception>
|
||||
public TValue this[TKey[] path] => TryGetValue(path, out var value)
|
||||
? value
|
||||
: throw new KeyNotFoundException($"The given path was not present in the trie: [{string.Join(", ", path)}]");
|
||||
|
||||
/// <summary>
|
||||
/// Sets a value in the trie at the specified key path. If the key path does not exist, it will be created.
|
||||
/// </summary>
|
||||
/// <param name="path">The key path where the value should be set. Each element in the array represents a level in the trie.</param>
|
||||
/// <param name="weakLeaf">Indicates whether the node at the specified key path should be marked as a weak leaf.</param>
|
||||
/// <param name="value">The value to set at the specified key path.</param>
|
||||
/// <param name="rewrite">Indicates whether the value should be overwritten if a value already exists at the specified key path. Defaults to true.</param>
|
||||
/// <returns>Returns true if the value was successfully set, otherwise false.</returns>
|
||||
public bool Set(TKey[] path, bool weakLeaf, TValue value, bool rewrite = true)
|
||||
{
|
||||
if (path.Length == 0)
|
||||
{
|
||||
if (rewrite || _value == null)
|
||||
{
|
||||
_value = value;
|
||||
_isWeakLeaf = weakLeaf;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!_children.TryGetValue(path[0], out var child))
|
||||
_children[path[0]] = child = new Trie<TKey, TValue>();
|
||||
return child.Set(path[1..], weakLeaf, value, rewrite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the value associated with the specified key path in the trie.
|
||||
/// If the specified path exists and has a corresponding value, the value will be removed.
|
||||
/// Intermediate nodes may also be adjusted if they become empty after the removal.
|
||||
/// </summary>
|
||||
/// <param name="path">The key path where the value should be removed. Each element in the array represents a level in the trie.</param>
|
||||
/// <param name="weakLeafDrop">Indicates whether to drop all children of a weak leaf node when it is removed. Defaults to true.</param>
|
||||
/// <returns>Returns true if the value was successfully removed, otherwise false.</returns>
|
||||
public bool Remove(TKey[] path, bool weakLeafDrop = true)
|
||||
{
|
||||
if (path.Length == 0 || _isWeakLeaf)
|
||||
{
|
||||
if (_value == null) return false;
|
||||
_value = default;
|
||||
// If this node is a weak leaf, we can drop all children as well, since they are not reachable anymore.
|
||||
if (_isWeakLeaf && weakLeafDrop)_children.Clear();
|
||||
_isWeakLeaf = false;
|
||||
return true;
|
||||
}
|
||||
if (!_children.TryGetValue(path[0], out var child)) return false;
|
||||
var childRemoved = child.Remove(path[1..]);
|
||||
// Fold branch if empty
|
||||
if (child._children.Count == 0)
|
||||
_children.Remove(path[0]);
|
||||
return childRemoved;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve a value from the trie at the specified key path.
|
||||
/// </summary>
|
||||
/// <param name="path">The key path to search within the trie. Each element represents a level in the trie.</param>
|
||||
/// <param name="value">When this method returns, contains the value associated with the specified key path, if the key path is found; otherwise, contains the default value for the type of the value parameter. This parameter is passed uninitialized.</param>
|
||||
/// <returns>Returns true if a value is found at the specified key path; otherwise, false.</returns>
|
||||
public bool TryGetValue(TKey[] path, [NotNullWhen(true)] out TValue? value)
|
||||
{
|
||||
if (path.Length == 0 || _isWeakLeaf)
|
||||
{
|
||||
value = _value;
|
||||
return value != null;
|
||||
}
|
||||
if (_children.TryGetValue(path[0], out var child))
|
||||
return child.TryGetValue(path[1..], out value);
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "8.0.0",
|
||||
"rollForward": "latestMinor",
|
||||
"allowPrerelease": false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user