我正在使用泊松采样技术来生成一些树,但为了使它更容易,我想使用一个盒子碰撞器来轻松地定义我想生成树的区域。
我之所以使用矩形是因为它很简单,而且采样器默认使用它来检查值是否在矩形内,方法是使用
bool isInsideRect = Rect.Contains(Vector2 point)
然而,矩形显然是一个二维用户界面元素,这只使用x,y坐标,宽度和高度,没有深度。
然而,我想使用一个3D BoxCollider来轻松地可视化和改变我想使用的区域的大小,在我的情况下,在一个矩形区域内产卵树。
所以我想弄清楚的主要事情是如何从BoxCollider中获取值,并找出我的样本是否在这些范围内。
我认为采样器是这里的问题所在。因为它会在盒子外面吐出一个随机样本,之后就没有了。
以下是我如何使用采样器:
// Iterate over all tree spawning areas on that Island
foreach (BoxCollider spawningArea in island.GetTreeSpawningBoxes())
{
i = 0;
// Initialize a new Poisson Disc Sampler
PoissonDiscSampler sampler = new PoissonDiscSampler(spawningArea, 18);
// Get Samples and Iterate over all of them
foreach (Vector2 sample in sampler.Samples())
{
// Ignore half of the samples
if (i % 2 != 0) return;
// Poisson Disc Sampled Position
Vector3 pos = new Vector3(sample.x, 0, sample.y);
// Random Y-axis rotation (0 - 359)
Quaternion rot = Quaternion.identity;
rot.eulerAngles = new Vector3(0, Random.Range(0, 360), 0);
// Spawn the tree
var tree = Instantiate
(
StaticResources.instance.SailingTrees[Random.Range
(0, StaticResources.instance.SailingTrees.Length - 1)],
pos,
rot
);
tree.transform.SetParent(island.transform);
}
}
如果有另一种方法,而不是使用一个盒子对撞机为每个地区,或也许另一个解决方案,所以我不需要使用矩形,那么我洗耳恭听!
我用BoxCollider设置了一个岛,并尝试使用这些边界来查看它是否在内部并生成树,但它只生成1棵树,随机地在BoxCollider之外,然后没有其他东西,这使我相信这是第一个随机样本,之后它找不到更多的,因为第一个已经在盒子外面了。
下面是代码:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
/// Poisson-disc sampling using Bridson's algorithm.
/// Adapted from Mike Bostock's Javascript source: http://bl.ocks.org /mbostock/19168c663618b7f07158
///
/// See here for more information about this algorithm:
/// http://devmag.org.za/2009/05/03/poisson-disk-sampling/
/// http://bl.ocks.org/mbostock/dbb02448b0f93e4c82c3
///
/// Usage:
/// PoissonDiscSampler sampler = new PoissonDiscSampler(10, 5, 0.3f);
/// foreach (Vector2 sample in sampler.Samples()) {
/// // ... do something, like instantiate an object at (sample.x, sample.y) for example:
/// Instantiate(someObject, new Vector3(sample.x, 0, sample.y), Quaternion.identity);
/// }
///
/// Author: Gregory Schlomoff (gregory.schlomoff@gmail.com)
/// Released in the public domain
/// <summary>
/// Poisson-disc sampling using Bridson's algorithm.
/// </summary>
///
/// --------------------------------------------------------------------
///
/// Improved and Optimized by Me
///
/// New Usage:
/// PoissonDiscSampler sampler = new PoissonDiscSampler(BoxCollider box, float radius);
///
public class PoissonDiscSampler
{
private const int k = 30; // Maximum number of attempts before marking a sample as inactive.
private BoxCollider box;
private readonly float radius2; // radius squared
private readonly float cellSize;
private Vector2[,] grid;
private List<Vector2> activeSamples = new List<Vector2>();
/// Create a sampler with the following parameters:
///
/// width: each sample's x coordinate will be between [0, width]
/// height: each sample's y coordinate will be between [0, height]
/// radius: each sample will be at least `radius` units away from any other sample, and at most 2 * `radius`.
public PoissonDiscSampler(BoxCollider box, float radius)
{
this.box = box;
radius2 = radius * radius;
cellSize = radius / Mathf.Sqrt(2);
grid = new Vector2[Mathf.CeilToInt(box.size.x / cellSize),
Mathf.CeilToInt(box.size.z / cellSize)];
}
/// Return a lazy sequence of samples. You typically want to call this in a foreach loop, like so:
/// foreach (Vector2 sample in sampler.Samples()) { ... }
public IEnumerable<Vector2> Samples()
{
// First sample is choosen randomly
yield return AddSample(new Vector2(Random.value * box.size.x, Random.value * box.size.z));
while (activeSamples.Count > 0)
{
// Pick a random active sample
int i = (int)Random.value * activeSamples.Count;
Vector2 sample = activeSamples[i];
// Try `k` random candidates between [radius, 2 * radius] from that sample.
bool found = false;
for (int j = 0; j < k; ++j)
{
float angle = 2 * Mathf.PI * Random.value;
float r = Mathf.Sqrt(Random.value * 3 * radius2 + radius2);
// See: http://stackoverflow.com/questions/9048095/create-random-number-within-an-annulus/9048443#9048443
Vector2 candidate = sample + r * new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));
// Accept candidates if it's inside the rect and farther than 2 * radius to any existing sample.
if (box.bounds.Contains(candidate) && IsFarEnough(candidate))
{
found = true;
yield return AddSample(candidate);
break;
}
}
// If we couldn't find a valid candidate after k attempts, remove this sample from the active samples queue
if (!found)
{
activeSamples[i] = activeSamples[activeSamples.Count - 1];
activeSamples.RemoveAt(activeSamples.Count - 1);
}
}
}
private bool IsFarEnough(Vector2 sample)
{
GridPos pos = new GridPos(sample, cellSize);
int xmin = Mathf.Max(pos.x - 2, 0);
int ymin = Mathf.Max(pos.y - 2, 0);
int xmax = Mathf.Min(pos.x + 2, grid.GetLength(0) - 1);
int ymax = Mathf.Min(pos.y + 2, grid.GetLength(1) - 1);
for (int y = ymin; y <= ymax; y++)
{
for (int x = xmin; x <= xmax; x++)
{
Vector2 s = grid[x, y];
if (s != Vector2.zero)
{
Vector2 d = s - sample;
if (d.x * d.x + d.y * d.y < radius2) return false;
}
}
}
return true;
// Note: we use the zero vector to denote an unfilled cell in the grid. This means that if we were
// to randomly pick (0, 0) as a sample, it would be ignored for the purposes of proximity-testing
// and we might end up with another sample too close from (0, 0). This is a very minor issue.
}
/// Adds the sample to the active samples queue and the grid before returning it
private Vector2 AddSample(Vector2 sample)
{
activeSamples.Add(sample);
GridPos pos = new GridPos(sample, cellSize);
grid[pos.x, pos.y] = sample;
return sample;
}
/// Helper struct to calculate the x and y indices of a sample in the grid
private struct GridPos
{
public int x;
public int y;
public GridPos(Vector2 sample, float cellSize)
{
x = (int)(sample.x / cellSize);
y = (int)(sample.y / cellSize);
}
}
}
谢谢你,谢谢你
我知道我可以使用光线追踪,但我宁愿不使用,因为它相当昂贵。我真的在寻找一种数学方法,使用box.bounds. contains()似乎是最便宜的方法。
1条答案
按热度按时间92dk7w1h1#
然而,矩形显然是一个二维用户界面元素,这只使用x,y坐标,宽度和高度,没有深度。
确实如此;)这就是
Rect
(矩形的缩写)。使用
Collider.bounds
有一个缺点:它不是很精确,如进一步解释here。This似乎是一个有效的选项。
只是把它翻译成一个扩展方法:
这里的想法
最后一点尤其正确,因为Unity中的光线投射不会检测到您是否已经在碰撞器中启动。
Collider.Raycast
比Physics.Raycast
便宜得多,因为它只对一个特定的对撞机进行光线投射,这通常意味着光线首先被转换到该特定对撞机的局部空间,然后你基本上对一个轴对齐的盒子进行光线投射,这当然更容易做到。