Newer
Older
Hierarchical-Task-Network-Unity-3D / Assets / Scripts / Environment / PoisonDiscSampling.cs
using System.Collections.Generic;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;

public static class PoissonDiscSamplingJobSystem
{
    [BurstCompile]
    public struct PoissonJob : IJob
    {
        public float radius;
        public float2 regionSize;
        public int numSamplesBeforeRejection;
        public Unity.Mathematics.Random rng;

        public NativeList<float2> points;
        public NativeArray<int> grid;
        public int2 gridSize;
        public float cellSize;

        public void Execute()
        {
            NativeList<float2> spawnPoints = new NativeList<float2>(Allocator.Temp);
            float2 center = regionSize / 2f;
            spawnPoints.Add(center);
            points.Add(center);
            int2 cellPos = (int2)(center / cellSize);
            grid[cellPos.y * gridSize.x + cellPos.x] = points.Length;

            while (spawnPoints.Length > 0)
            {
                int spawnIndex = rng.NextInt(spawnPoints.Length);
                float2 spawnCentre = spawnPoints[spawnIndex];
                bool accepted = false;

                for (int i = 0; i < numSamplesBeforeRejection; i++)
                {
                    float angle = rng.NextFloat(0, math.PI * 2);
                    float2 dir = new float2(math.sin(angle), math.cos(angle));
                    float2 candidate = spawnCentre + dir * rng.NextFloat(radius, 2 * radius);

                    if (IsValid(candidate))
                    {
                        points.Add(candidate);
                        spawnPoints.Add(candidate);
                        int2 gridCoord = (int2)(candidate / cellSize);
                        grid[gridCoord.y * gridSize.x + gridCoord.x] = points.Length;
                        accepted = true;
                        break;
                    }
                }

                if (!accepted)
                    spawnPoints.RemoveAtSwapBack(spawnIndex);
            }

            spawnPoints.Dispose();
        }

        bool IsValid(float2 candidate)
        {
            if (candidate.x < 0 || candidate.x >= regionSize.x || candidate.y < 0 || candidate.y >= regionSize.y)
                return false;

            int2 cell = (int2)(candidate / cellSize);
            int2 searchStart = math.max(0, cell - 2);
            int2 searchEnd = math.min(cell + 2, gridSize - 1);

            for (int x = searchStart.x; x <= searchEnd.x; x++)
            {
                for (int y = searchStart.y; y <= searchEnd.y; y++)
                {
                    int index = grid[y * gridSize.x + x] - 1;
                    if (index >= 0)
                    {
                        float2 point = points[index];
                        if (math.distancesq(candidate, point) < radius * radius)
                            return false;
                    }
                }
            }

            return true;
        }
    }

    public static List<Vector2> GeneratePoints(float radius, Vector2 regionSize, int numSamples = 30)
    {
        float cellSize = radius / Mathf.Sqrt(2);
        int2 gridSize = new int2(
            Mathf.CeilToInt(regionSize.x / cellSize),
            Mathf.CeilToInt(regionSize.y / cellSize)
        );

        NativeArray<int> grid = new NativeArray<int>(gridSize.x * gridSize.y, Allocator.TempJob);
        NativeList<float2> points = new NativeList<float2>(Allocator.TempJob);

        var job = new PoissonJob
        {
            radius = radius,
            regionSize = regionSize,
            numSamplesBeforeRejection = numSamples,
            rng = new Unity.Mathematics.Random((uint)UnityEngine.Random.Range(1, int.MaxValue)),
            grid = grid,
            points = points,
            gridSize = gridSize,
            cellSize = cellSize
        };

        job.Run();

        List<Vector2> finalPoints = new List<Vector2>(points.Length);
        for (int i = 0; i < points.Length; i++)
        {
            finalPoints.Add(points[i]);
        }

        grid.Dispose();
        points.Dispose();

        return finalPoints;
    }
}