Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 40 additions & 5 deletions Runtime/NativeFlowField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics;
using Unity.Entities;
using Debug = UnityEngine.Debug;
using System.Linq;

namespace FlowFieldAI
{
Expand Down Expand Up @@ -99,6 +100,8 @@ private static class ShaderProperties
public static readonly int Width = Shader.PropertyToID("Width");
public static readonly int Height = Shader.PropertyToID("Height");
public static readonly int DiagonalMovement = Shader.PropertyToID("DiagonalMovement");
public static readonly int UseTravelCosts = Shader.PropertyToID("UseTravelCosts");
public static readonly int TravelCosts = Shader.PropertyToID("TravelCosts");
public static readonly int InputCosts = Shader.PropertyToID("InputCosts");
public static readonly int OutputCosts = Shader.PropertyToID("OutputCosts");
public static readonly int OutputHeatMap = Shader.PropertyToID("OutputHeatMap");
Expand All @@ -108,6 +111,9 @@ private static class ShaderProperties
// ─────────────────────────────────────────────────────────────
// Private Fields
// ─────────────────────────────────────────────────────────────
private ComputeBuffer travelCostsBuffer;
private readonly bool useTravelCosts;

private readonly ComputeShader integrationComputeShader;
private readonly int integrationComputeShaderKernel;
private ComputeBuffer integrationFrontBuffer;
Expand Down Expand Up @@ -148,7 +154,7 @@ private static class ShaderProperties
/// <param name="generateHeatMap">If true, allocates an additional render texture to visualize distance propagation.</param>
/// <param name="minBuffers">Minimum number of buffers to keep pooled.</param>
/// <param name="maxBuffers">Maximum number of buffers allowed for async dispatching.</param>
public NativeFlowField(int width, int height, bool generateHeatMap=false, int minBuffers=2, int maxBuffers=5)
public NativeFlowField(int width, int height, bool generateHeatMap=false, bool useTravelCosts=false, int minBuffers=2, int maxBuffers=5)
{
if (minBuffers < 2)
{
Expand All @@ -165,6 +171,9 @@ public NativeFlowField(int width, int height, bool generateHeatMap=false, int mi

this.generateHeatMap = generateHeatMap;

travelCostsBuffer = new ComputeBuffer(Length, sizeof(float));
this.useTravelCosts = useTravelCosts;

integrationComputeShader = Resources.Load<ComputeShader>("GenerateIntegrationField");
integrationComputeShaderKernel = integrationComputeShader.FindKernel("GenerateIntegrationField");

Expand Down Expand Up @@ -204,6 +213,7 @@ public NativeFlowField(int width, int height, bool generateHeatMap=false, int mi
/// </summary>
public void Dispose()
{
travelCostsBuffer.Dispose();
integrationFrontBuffer.Dispose();
integrationBackBuffer.Dispose();
flowFieldBuffer.Dispose();
Expand All @@ -223,6 +233,18 @@ public void Dispose()
/// <returns>An async GPU readback request that completes when the field is ready.</returns>
public AsyncGPUReadbackRequest Bake(NativeArray<float> inputField) => Bake(inputField, BakeOptions.Default);

/// <summary>
/// Starts a new asynchronous bake using the specified bake options and 0-initialized travel costs.
/// The returned request can be used to track GPU readback completion.
/// </summary>
/// <param name="inputField">
/// A native array representing the input field. Use <see cref="ObstacleCell"/> and <see cref="FreeCell"/> for special cells,
/// and numerical values for target weights.
/// </param>
/// <param name="bakeOptions">Configuration options for this bake operation.</param>
/// <returns>An async GPU readback request that completes when the field is ready.</returns>
public AsyncGPUReadbackRequest Bake(NativeArray<float> inputField, BakeOptions bakeOptions) => Bake(inputField, new NativeArray<float>(), bakeOptions);

/// <summary>
/// Starts a new asynchronous bake using the specified bake options.
/// The returned request can be used to track GPU readback completion.
Expand All @@ -231,13 +253,18 @@ public void Dispose()
/// A native array representing the input field. Use <see cref="ObstacleCell"/> and <see cref="FreeCell"/> for special cells,
/// and numerical values for target weights.
/// </param>
/// <param name="travelCosts">
/// A native array representing the travel costs of each cell in the input field.
/// These costs are added to the minimum weight calculation of each target cell at the same index.
/// </param>
/// <param name="bakeOptions">Configuration options for this bake operation.</param>
/// <returns>An async GPU readback request that completes when the field is ready.</returns>
public AsyncGPUReadbackRequest Bake(NativeArray<float> inputField, BakeOptions bakeOptions)
public AsyncGPUReadbackRequest Bake(NativeArray<float> inputField, NativeArray<float> travelCosts, BakeOptions bakeOptions)
{
bakeTimer.Restart();

ValidateMatrixDimensions(inputField);
if (useTravelCosts) ValidateMatrixDimensions(travelCosts);

if (bufferPool.IsExhausted)
{
Expand All @@ -246,7 +273,7 @@ public AsyncGPUReadbackRequest Bake(NativeArray<float> inputField, BakeOptions b
}

// Initialize buffers
var iterationsThisFrame = InitializeBake(inputField, bakeOptions);
var iterationsThisFrame = InitializeBake(inputField, travelCosts, bakeOptions);

// Generate integration field
PerformIntegration(iterationsThisFrame);
Expand Down Expand Up @@ -284,7 +311,7 @@ public AsyncGPUReadbackRequest Bake(NativeArray<float> inputField, BakeOptions b
// ─────────────────────────────────────────────────────────────
// Private Methods
// ─────────────────────────────────────────────────────────────
private int InitializeBake(NativeArray<float> inputField, BakeOptions bakeOptions)
private int InitializeBake(NativeArray<float> inputField, NativeArray<float> travelCostsField, BakeOptions bakeOptions)
{
// Initialize graphics command buffer
commandBuffer.Clear();
Expand All @@ -308,10 +335,12 @@ private int InitializeBake(NativeArray<float> inputField, BakeOptions bakeOption
? Mathf.Min(iterationsRemaining, bakeOptions.IterationsPerFrame)
: Mathf.Min(iterationsRemaining, bakeOptions.Iterations);

// Initialize compute buffer with input field
// Initialize compute buffer with input field
if (bakeContext.CurrentIteration == 0 && initializeNewBake)
{
commandBuffer.SetBufferData(integrationFrontBuffer, inputField);
if (useTravelCosts)
commandBuffer.SetBufferData(travelCostsBuffer, travelCostsField);
}

return iterationsThisFrame;
Expand All @@ -322,6 +351,10 @@ private void PerformIntegration(int iterationsThisFrame)
commandBuffer.SetComputeIntParam(integrationComputeShader, ShaderProperties.Width, Width);
commandBuffer.SetComputeIntParam(integrationComputeShader, ShaderProperties.Height, Height);
commandBuffer.SetComputeIntParam(integrationComputeShader, ShaderProperties.DiagonalMovement, bakeContext.Options.DiagonalMovement ? 1 : 0);
commandBuffer.SetComputeIntParam(integrationComputeShader, ShaderProperties.UseTravelCosts, useTravelCosts ? 1 : 0);

// Assign travel cost buffer
commandBuffer.SetComputeBufferParam(integrationComputeShader, integrationComputeShaderKernel, ShaderProperties.TravelCosts, travelCostsBuffer);

for (var i = 0; i < iterationsThisFrame; i++)
{
Expand Down Expand Up @@ -356,8 +389,10 @@ private void GenerateFlowField()
commandBuffer.SetComputeIntParam(generateFlowFieldComputeShader, ShaderProperties.Width, Width);
commandBuffer.SetComputeIntParam(generateFlowFieldComputeShader, ShaderProperties.Height, Height);
commandBuffer.SetComputeIntParam(generateFlowFieldComputeShader, ShaderProperties.DiagonalMovement, bakeContext.Options.DiagonalMovement ? 1 : 0);
commandBuffer.SetComputeIntParam(generateFlowFieldComputeShader, ShaderProperties.UseTravelCosts, useTravelCosts ? 1 : 0);
commandBuffer.SetComputeBufferParam(generateFlowFieldComputeShader, generateFlowFieldComputeShaderKernel, ShaderProperties.InputCosts, integrationFrontBuffer);
commandBuffer.SetComputeBufferParam(generateFlowFieldComputeShader, generateFlowFieldComputeShaderKernel, ShaderProperties.OutputFlowField, flowFieldBuffer);
commandBuffer.SetComputeBufferParam(generateFlowFieldComputeShader, generateFlowFieldComputeShaderKernel, ShaderProperties.TravelCosts, travelCostsBuffer);
commandBuffer.DispatchCompute(generateFlowFieldComputeShader, generateFlowFieldComputeShaderKernel, threadGroupsX, threadGroupsY, 1);

Graphics.ExecuteCommandBufferAsync(commandBuffer, bakeContext.Options.ComputeQueueType);
Expand Down
7 changes: 6 additions & 1 deletion Runtime/Resources/GenerateFlowField.compute
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
uint Width;
uint Height;
int DiagonalMovement;
int UseTravelCosts;
StructuredBuffer<float> TravelCosts;
StructuredBuffer<float> InputCosts;
RWStructuredBuffer<int> OutputFlowField;

Expand Down Expand Up @@ -35,7 +37,10 @@ int getFlow(uint x, uint y)
if (InputCosts[flatten(x, y + offsets[i].y, Width)] >= FLT_MAX-1) continue;
}
uint neighborIndex = flatten(nx, ny, Width);
float neighborCost = InputCosts[neighborIndex];
float neighborCost;

if (UseTravelCosts) neighborCost = InputCosts[neighborIndex] + TravelCosts[neighborIndex]; // Add travel cost
else neighborCost = InputCosts[neighborIndex];

if (neighborCost < FLT_MAX && neighborCost > FLT_MIN)
{
Expand Down
13 changes: 10 additions & 3 deletions Runtime/Resources/GenerateIntegrationField.compute
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
uint Width;
uint Height;
int DiagonalMovement;
int UseTravelCosts;
StructuredBuffer<float> TravelCosts;
StructuredBuffer<float> InputCosts;
RWStructuredBuffer<float> OutputCosts;

Expand All @@ -29,11 +31,16 @@ float getMinCost(uint x, uint y)
if (isDiagonal)
{
// Both orthogonal must be free
if (InputCosts[flatten(x + offsets[i].x, y, Width)] >= FLT_MAX-1) continue;
if (InputCosts[flatten(x, y + offsets[i].y, Width)] >= FLT_MAX-1) continue;
if (InputCosts[flatten(x + offsets[i].x, y, Width)] >= FLT_MAX - 1)
continue;
if (InputCosts[flatten(x, y + offsets[i].y, Width)] >= FLT_MAX - 1)
continue;
}
uint neighborIndex = flatten(nx, ny, Width);
float neighborCost = InputCosts[neighborIndex];

float neighborCost;
if (UseTravelCosts) neighborCost = InputCosts[neighborIndex] + TravelCosts[neighborIndex]; // Add travel cost
else neighborCost = InputCosts[neighborIndex];

if (neighborCost < FLT_MAX && neighborCost > FLT_MIN)
{
Expand Down