A amostragem de disco de Poisson é uma abordagem eficiente para criar pontos aleatórios que preenchem uma região de maneira uniforme, evitanod aglomerados e garantindo uma distância mínima entre eles. Essa técnica é amplamente utilizada em computação gráfica, geração procedural e simulações, onde a distribuição natural dos pontos é essencial. O algoritmo baseia-se na ideia de que, após posicionar um ponto, novos candidatos são amostrados em uma vizinhança anular, e sua validade é verificada usando uma grade espacial para otimizar a busca.
O método de Bridson é uma implemantação comum, conhecida por sua eficiência e capacidade de produzir ruído azul, que se refere a padrões de alta frequência com propriedades espectrais desejáveis. A seguir, apresenta-se uma implementação em C# para Unity, que adapta o algoritmo com modificações na estrutura e nas variáveis para reduzir a similaridade com o código original, mantendo a funcionalidade.
using UnityEngine;
using System.Collections.Generic;
public class GeradorPontosPoisson
{
public static List<Vector3> GerarDistribuicao(float raioMinimo, Vector2 dimensoesArea, int maxTentativas = 30)
{
bool CandidatoEhValido(Vector3 posicaoCandidata, Vector2 area, float tamanhoCelula, float distanciaMin, List<Vector3> pontosExistentes, int[,] grade)
{
if (posicaoCandidata.x - distanciaMin >= 0f && posicaoCandidata.x + distanciaMin < area.x &&
posicaoCandidata.z - distanciaMin >= 0f && posicaoCandidata.z + distanciaMin < area.y)
{
int idxCelulaX = Mathf.RoundToInt(posicaoCandidata.x / tamanhoCelula);
int idxCelulaZ = Mathf.RoundToInt(posicaoCandidata.z / tamanhoCelula);
int inicioX = Mathf.Max(0, idxCelulaX - 2);
int fimX = Mathf.Min(idxCelulaX + 2, grade.GetLength(0) - 1);
int inicioZ = Mathf.Max(0, idxCelulaZ - 2);
int fimZ = Mathf.Min(idxCelulaZ + 2, grade.GetLength(1) - 1);
for (int i = inicioX; i <= fimX; i++)
{
for (int j = inicioZ; j <= fimZ; j++)
{
int indicePonto = grade[i, j] - 1;
if (indicePonto != -1)
{
float distancia = Vector3.Distance(posicaoCandidata, pontosExistentes[indicePonto]);
if (distancia < distanciaMin * 2f)
{
return false;
}
}
}
}
return true;
}
return false;
}
float passoCelula = raioMinimo / Mathf.Sqrt(2f);
int celulasX = Mathf.CeilToInt(dimensoesArea.x / passoCelula);
int celulasZ = Mathf.CeilToInt(dimensoesArea.y / passoCelula);
int[,] gradeEspacial = new int[celulasX, celulasZ];
List<Vector3> listaPontos = new List<Vector3>();
List<Vector3> pontosAtivos = new List<Vector3>();
pontosAtivos.Add(new Vector3(dimensoesArea.x * 0.5f, 0f, dimensoesArea.y * 0.5f));
while (pontosAtivos.Count > 0)
{
int idxAleatorio = Random.Range(0, pontosAtivos.Count);
Vector3 centroReferencia = pontosAtivos[idxAleatorio];
bool candidatoAceito = false;
for (int k = 0; k < maxTentativas; k++)
{
float angulo = Random.Range(0f, Mathf.PI * 2f);
Vector3 direcao = new Vector3(Mathf.Cos(angulo), 0f, Mathf.Sin(angulo));
float deslocamento = Random.Range(raioMinimo * 2f, raioMinimo * 3f);
Vector3 novoPonto = centroReferencia + direcao * deslocamento;
if (CandidatoEhValido(novoPonto, dimensoesArea, passoCelula, raioMinimo, listaPontos, gradeEspacial))
{
listaPontos.Add(novoPonto);
pontosAtivos.Add(novoPonto);
int celulaX = Mathf.RoundToInt(novoPonto.x / passoCelula);
int celulaZ = Mathf.RoundToInt(novoPonto.z / passoCelula);
gradeEspacial[celulaX, celulaZ] = listaPontos.Count;
candidatoAceito = true;
break;
}
}
if (!candidatoAceito)
{
pontosAtivos.RemoveAt(idxAleatorio);
}
}
return listaPontos;
}
}
Para testar a implementação, pode-se usar um MonoBehaviour no Unity que visualize os pontos gerados e a grade de referência. O código a seguir exemplifica isso, adaptado parra clareza:
using UnityEngine;
using System.Collections.Generic;
public class VisualizadorPontos : MonoBehaviour
{
public float raioMinimo = 0.5f;
public Vector2 dimensoes = new Vector2(5f, 5f);
private List<Vector3> pontosGerados;
private void Start()
{
pontosGerados = GeradorPontosPoisson.GerarDistribuicao(raioMinimo, dimensoes);
}
private void OnDrawGizmos()
{
if (pontosGerados == null || pontosGerados.Count == 0) return;
float tamanhoCelula = raioMinimo / Mathf.Sqrt(2f);
Gizmos.color = Color.gray;
for (float x = 0; x < dimensoes.x; x += tamanhoCelula)
{
for (float z = 0; z < dimensoes.y; z += tamanhoCelula)
{
Gizmos.DrawWireCube(new Vector3(x + tamanhoCelula * 0.5f, 0f, z + tamanhoCelula * 0.5f), new Vector3(tamanhoCelula, 0f, tamanhoCelula));
}
}
Gizmos.color = Color.red;
foreach (var ponto in pontosGerados)
{
Gizmos.DrawSphere(ponto, 0.1f);
int celulaX = Mathf.FloorToInt(ponto.x / tamanhoCelula);
int celulaZ = Mathf.FloorToInt(ponto.z / tamanhoCelula);
Gizmos.DrawWireCube(new Vector3(celulaX * tamanhoCelula + tamanhoCelula * 0.5f, 0f, celulaZ * tamanhoCelula + tamanhoCelula * 0.5f), new Vector3(tamanhoCelula, 0f, tamanhoCelula));
}
}
}
A técnica de amostragem de disco de Poisson pode ser estendida para diversas aplicações. Por exemplo, adaptando-a para três dimensões, é possível criar pontos de navegação para simulações de movimento, como trajetórias de pássaros. Outra possibilidade é distribuir pontos em coordenadas UV e mapeá-los para superfícies 3D, permitindo texturização procedural. Além disso, a combinação com triangulação de Delaunay pode gerar malhas complexas para modelagem ou análise espacial.