• 泊松盘采样(Poisson Disk Sampling)生成均匀随机点


    当需要生成随机点且要求随机点自然均匀的分布时,使用泊松盘采样就较为适合。

    但该方法与统计学上的概念关联不大,这个只相当于点在面积上服从泊松分布,

    而实现这个结果有很多做法。

    最终效果:

     圆形为含半径的点,圆形的中心代表生成点

    B站有一个不错的搬运教程(Bridson方法):

    https://www.bilibili.com/video/BV1KV411x7LM

    另外Bridson文章里说蓝噪声(BlueNoise)也基于此方法生成

    我做了些修改,代码如下:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public static class PoissonDiscSampling
    {
        public static List<Vector3> GeneratePoints(float radius, Vector2 sampleRegionSize, int numSamplesBeforeRejection = 32)
        {
            bool IsValid(Vector3 candidate, Vector2 sampleRegionSize, float cellSize, float radius, List<Vector3> points, int[,] grid)
            {
                if (candidate.x - radius >= 0f && candidate.x + radius < sampleRegionSize.x && candidate.z - radius >= 0f && candidate.z + radius < sampleRegionSize.y)
                {
                    int cellX = Mathf.RoundToInt(candidate.x / cellSize);
                    int cellZ = Mathf.RoundToInt(candidate.z / cellSize);
                    int searchStartX = Mathf.Max(0, cellX - 3);
                    int searchEndX = Mathf.Min(cellX + 3, grid.GetLength(0) - 1);
                    int searchStartZ = Mathf.Max(0, cellZ - 3);
                    int searchEndZ = Mathf.Min(cellZ + 3, grid.GetLength(1) - 1);
                    //如果要检测其它格子内的球,需要遍历周围6个格子
    
                    for (int x = searchStartX; x <= searchEndX; x++)
                    {
                        for (int z = searchStartZ; z <= searchEndZ; z++)
                        {
                            int pointIndex = grid[x, z] - 1;//存长度不存索引,取时减1,0就变成了-1,不需要初始化数组了
                            if (pointIndex != -1)
                            {
                                float dst = (candidate - points[pointIndex]).magnitude;
                                if (dst < radius * 2f)
                                {
                                    return false;
                                }
                            }
                        }
                    }
    
                    return true;
                }
    
                return false;
            }
    
            float cellSize = radius / Mathf.Sqrt(2);
    
            int[,] grid = new int[Mathf.CeilToInt(sampleRegionSize.x / cellSize), Mathf.CeilToInt(sampleRegionSize.y / cellSize)];
            List<Vector3> points = new List<Vector3>();
            List<Vector3> spawnPoints = new List<Vector3>();
    
            spawnPoints.Add(new Vector3(sampleRegionSize.x / 2f, 0f, sampleRegionSize.y / 2f));
            while (spawnPoints.Count > 0)
            {
                int spawnIndex = Random.Range(0, spawnPoints.Count);
                Vector3 spawnCenter = spawnPoints[spawnIndex];
    
                bool candidateAccepted = false;
                for (int i = 0; i < numSamplesBeforeRejection; i++)
                {
                    float angle = Random.value * Mathf.PI * 2f;
                    Vector3 dir = new Vector3(Mathf.Sin(angle), 0f, Mathf.Cos(angle));
                    Vector3 candidate = spawnCenter + dir * Random.Range(2f, 3f) * radius;
    
                    if (IsValid(candidate, sampleRegionSize, cellSize, radius, points, grid))
                    {
                        points.Add(candidate);
                        spawnPoints.Add(candidate);
                        grid[Mathf.RoundToInt(candidate.x / cellSize), Mathf.RoundToInt(candidate.z / cellSize)] = points.Count;
                        candidateAccepted = true;
                        break;
                    }
                }
                if (!candidateAccepted)
                {
                    spawnPoints.RemoveAt(spawnIndex);
                }
            }
    
            return points;
        }
    }

    测试代码如下:

    using System.Collections.Generic;
    using UnityEngine;
    
    public class Test : MonoBehaviour
    {
        public float radius = 0.3f;
        public Vector2 sampleRegionSize = new Vector2(3f, 3f);
        private List<Vector3> mPoints;
    
    
        private void OnEnable()
        {
            mPoints = PossonDiscSampling.GeneratePoints(radius, sampleRegionSize);
        }
    
        private void OnDrawGizmos()
        {
            if (mPoints == null) return;
    
            float cellSize = radius / Mathf.Sqrt(2);
    
            Color cacheColor = Gizmos.color;
            Gizmos.color = new Color(0.5f, 0.5f, 0.5f, 0.5f);
            for (float x = 0; x < sampleRegionSize.x; x += cellSize)
            {
                for (float z = 0; z < sampleRegionSize.y; z += cellSize)
                    Gizmos.DrawWireCube(new Vector3(x, 0f, z), new Vector3(cellSize, 0f, cellSize));
            }//生成对应采样点的调试方格
            Gizmos.color = cacheColor;
    
            for (int i = 0; i < mPoints.Count; i++)
            {
                Vector3 vector = mPoints[i];
    
                Gizmos.DrawWireSphere(vector, radius);
    
                int x = Mathf.FloorToInt(vector.x / cellSize);
                int z = Mathf.FloorToInt(vector.z / cellSize);
    
                Gizmos.DrawWireCube(new Vector3(x, 0f, z) * cellSize, new Vector3(cellSize, 0f, cellSize));
                //生成当前点所属方格
            }
        }
    }

    对于该做法还可以做如下应用层面的扩展:

    1.扩展到3D空间,一些鸟的移动轨迹可以直接用这个做路点寻路

    2.可以尝试在UV上分布,然后映射到3D空间

    3.可以基于这个做三角剖分(https://www.cnblogs.com/hont/p/15310157.html)

  • 相关阅读:
    25个Apache性能优化技巧推荐 新风宇宙
    九个PHP很有用的功能 新风宇宙
    ubuntu nginx的安装 新风宇宙
    .net 下对winapi的调用
    jquery选中单选框、复选框、下拉框
    中国标准书号校验码的计算方式(附C#代码)
    NET中创建一个线程有几种方法
    ASP.NET中Cookie编程的基础知识
    js日期时间函数(经典+完善+实用)
    SQL语句大全
  • 原文地址:https://www.cnblogs.com/hont/p/16412338.html
Copyright © 2020-2023  润新知