unity3d C# Unity 3D -如何使用BoxCollider来检查一个位置是否在它的内部?

zf2sa74q  于 2022-11-15  发布在  C#
关注(0)|答案(1)|浏览(374)

我正在使用泊松采样技术来生成一些树,但为了使它更容易,我想使用一个盒子碰撞器来轻松地定义我想生成树的区域。
我之所以使用矩形是因为它很简单,而且采样器默认使用它来检查值是否在矩形内,方法是使用

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()似乎是最便宜的方法。

92dk7w1h

92dk7w1h1#

然而,矩形显然是一个二维用户界面元素,这只使用x,y坐标,宽度和高度,没有深度。
确实如此;)这就是Rect(矩形的缩写)。
使用Collider.bounds有一个缺点:它不是很精确,如进一步解释here
This似乎是一个有效的选项。
只是把它翻译成一个扩展方法:

public static class ColliderExtensions
{
    public static bool Contains(this Collider collider, Vector3 worldPosition)
    {
        var direction = collider.bounds.center - worldPosition;
        var ray = new Ray(worldPosition, direction);
          
        var contains = collider.Raycast(ray, out var hit, direction.magnitude));

        return contains;
    }
}

这里的想法

  • 你把你的3D世界位置点
  • 你朝着对撞机的中心移动。
  • 如果你“击中”了,那就意味着你以前不在对撞机里,否则你的点就包含在对撞机里了

最后一点尤其正确,因为Unity中的光线投射不会检测到您是否已经在碰撞器中启动。
Collider.RaycastPhysics.Raycast便宜得多,因为它只对一个特定的对撞机进行光线投射,这通常意味着光线首先被转换到该特定对撞机的局部空间,然后你基本上对一个轴对齐的盒子进行光线投射,这当然更容易做到。

相关问题