[게임 개발] Unity의 임의 위치_ 원/타원/삼각형/다각형/

0. 서문

게임을 만들다 보면 특정 지점을 랜덤화해야 하는 경우가 많고, 모양도 다양해서 랜덤화하고 싶을 때마다 어떻게 해야 하는지 잊어버리기 쉽습니다. 다음은 다양한 공통 모양의 기본 무작위 방법에 대한 요약입니다.

1. 직사각형의 랜덤

살짝~

/// <summary>
/// 在矩形区域内随机一个点
/// </summary>
public static Vector2 InRect(Rect rect)
{
    
    
    Vector2 pos = new Vector2();
    pos.x = Random.Range(0, rect.width) + rect.x;
    pos.y = Random.Range(0, rect.height) + rect.y;
    return pos;
}

2. 서클 내 랜덤

일반적으로 순환 랜덤에는 두 가지 유형이 있습니다. 하나는 극좌표를 통해 임의화하는 것이고 다른 하나는 점이 원 안에 있는지 여부를 결정하기 위해 먼저 일반 임의 사각형을 사용하는 것입니다. 두 번째 유형은 실제로 광범위하게 사용되며 "6. 랜덤 샘플링 거부" 에서 별도로 설명하겠습니다 . 여기서는 첫 번째 유형에 대해 먼저 설명합니다.

극좌표를 통해 원을 무작위화 하고 반지름과 각도를 각각 무작위화하는 것은 상대적으로 간단합니다. 그러나 직접 임의성 의 경우 확률이 균일하지 않습니다 . 이해하기도 비교적 쉬운데, 예를 들어 임의의 반지름이 1과 2인 원의 크기는 다르지만 즐길 확률은 같다. 원이 더 높습니다. 이때 제곱근은 [0, r*r]의 범위 내에서 임의로 수행하면 이 확률을 보상할 수 있으며, 대입법으로 구체적인 유도를 시도할 수 있다 . 여기에 코드를 넣어

public struct Circle
{
    
    
    public Vector2 center;
    public float radius;

    public Circle(Vector2 center, float radius)
    {
    
    
        this.center = center;
        this.radius = radius;
    }
}
/// <summary>
/// 在圆形区域内随机一个点
/// </summary>
public static Vector2 InCircle(Circle circle)
{
    
    
    //  通过极坐标来随机
    float r = Mathf.Sqrt(Random.Range(0, circle.radius));
    float angle = Random.Range(0, Mathf.PI * 2);
    Vector2 pos = new Vector2(Mathf.Cos(angle) * r, Mathf.Sin(angle) * r);
    pos += circle.center;
    return pos;
}

데카르트 좌표의 각도는 x를 먼저 난수화한 다음 난수화를 위해 y의 범위를 구하는 것도 확률이 고르지 않은 문제가 있을 것이고, 구체적인 처리를 다시 유추해야 하므로 여기서는 다루지 않겠습니다.

3. 삼각형 내 랜덤

삼각형 무작위 사고는 훨씬 더 골칫거리입니다. 사고 방식 일뿐입니다. 먼저 삼각형의 한 변의 높이를 랜덤화하고, 이 높이를 랜덤화하고, 이 높이의 경우 평행한 변의 길이를 랜덤화할 수 있습니다.

예를 들어, 아래 그림에서 먼저 h를 랜덤화한 다음 r을 랜덤화하여 포인트 p를 얻습니다.
여기에 이미지 설명 삽입
이 경우에도 불균형 일반화의 문제가 발생하겠지만 여기서는 평행선이 만들어지기 때문에 h와 얻어진 평행선이 비례적으로 확대 축소되는 것을 알 수 있으므로 Round the way와 유사한 것을 사용할 수 있습니다. 무작위로. 하지만 구현하기가 너무 번거롭고 너무 어렵고 졸업한 사람들에게 수학은 너무 어렵습니다.

여기에서 비슷한 방법을 찾았는데 매우 간단합니다.구체적인 인수를 알고 싶다면 아래 링크를 참조하십시오.
여기에 이미지 설명 삽입
그런 다음 구현 코드는 다음과 같습니다.

public struct Triangle
{
    
    
    public Vector2 a;
    public Vector2 b;
    public Vector2 c;

    public Triangle(Vector2 a, Vector2 b, Vector2 c)
    {
    
    
        this.a = a;
        this.b = b;
        this.c = c;
    }
}
/// <summary>
/// 在三角形区域内随机一个点
/// </summary>
public static Vector2 InTriangle(Triangle triangle)
{
    
    
    // Vector2 a, Vector2 b, Vector2 c
    Vector2 pos = new Vector2();
    float r1 = Random.Range(0f, 1f);
    float r2 = Random.Range(0f, 1f);
    pos = (1 - Mathf.Sqrt(r1)) * triangle.a +
        Mathf.Sqrt(r1) * (1 - r2) * triangle.b +
        Mathf.Sqrt(r1) * r2 * triangle.c;
    return pos;
}

4. 폴리곤 내 랜덤

다각형 무작위는 먼저 다각형을 삼각형으로 분해한 다음 삼각형의 면적에 따라 다른 가중치를 할당하고 삼각형을 무작위화한 다음 삼각형 내에서 무작위를 수행할 수 있습니다. 따라서 이 질문은 다음과 같이 분해할 수 있다.

  • 다각형을 삼각형으로 나누기
  • 삼각형 면적 계산
  • 무작위로 무게
  • 삼각형 안의 임의의 점

이 방법은 사실 꽤 번거롭습니다.또 다른 방법은 방금 언급한 것처럼 먼저 직사각형을 단순하고 무작위로 만든 다음 다각형 내에 있는지 여부를 판단하여 무작위 목적을 달성하는 것입니다.이것은 여전히 ​​"6. 무작위 샘플링 거부 " 자세히 소개합니다.

그런 다음 지금 문제를 차례로 처리해 봅시다.

(1) 다각형은 삼각형으로 나뉩니다.

1번에 관해서는 사실 상당히 번거롭습니다.삼각형으로 나누는 방법은 상당히 다양한 방법이 있습니다.귀를 자르는 방법은 현재 제가 찾아볼 수 있는 일반적인 방법입니다.구체적인 내용은 이전에 언급한 바 있습니다. 링크는 다음과 같으니 여기서는 길게 설명하지 않겠습니다.

https://blog.csdn.net/Blue_carrot_/article/details/131192660

(2) 삼각형 면적 계산

공식은 여기에서 직접 계산하는 데 사용 됩니다 .

/// <summary>
/// 获取三角形面积
/// </summary>
/// <returns></returns>
public float Area()
{
    
    
    //  S=√[p(p-l1)(p-l2)(p-l3)](p为半周长)
    float l1 = (b - a).magnitude;
    float l2 = (c - b).magnitude;
    float l3 = (a - c).magnitude;
    float p = (l1 + l2 + l3) * 0.5f;
    return Mathf.Sqrt(p * (p - l1) * (p - l2) * (p - l3));
}

(3) 데이터 캐시

사실 여기서 우리는 이 경우 임의의 폴리곤을 조작하는 것이 매우 번거롭다는 것을 알 수 있습니다. 그러나 삼각형으로 나누고 삼각형의 면적을 계산하는 것을 포함하여 실제로 데이터를 캐시할 수 있으므로 다음에 사용할 수 있으며 훨씬 빠릅니다. 그러면 다음과 같이 데이터 구조를 정의할 수 있습니다.

public struct PolygonRandomData
{
    
    
    public Polygon polygon;
    public Triangle[] triangles;
    public float polygonArea;
    public float[] trianglesArea;

    public PolygonRandomData(Polygon polygon)
    {
    
    
        // 三角化
        this.polygon = polygon;
        triangles = polygon.Triangulate();

        // 计算面积
        float area;
        trianglesArea = new float[triangles.Length];
        polygonArea = 0;
        for (int i = 0; i < triangles.Length; i++)
        {
    
    
            area = triangles[i].Area();
            polygonArea += area;
            trianglesArea[i] = area;
        }
    }
}

임의성을 반복해야 하는 경우 데이터를 먼저 캐시하여 오버헤드를 줄일 수 있습니다.

(4) 가중치에 의한 무작위

가중치는 무작위이며, 현재 방법은 0에서 시작하여 가중치와 임의의 숫자까지 중첩을 통과하여 도달 여부를 판단하여 목표를 달성하는 것입니다. 코드는 다음과 같습니다. 여기서 가중치 합은 외부에서 제공합니다. 방금 전에 면적 합을 이미 계산했기 때문에 다시 계산할 필요가 없습니다.

/// <summary>
/// 按照数组内数的非负数权重,获取随机的索引。
/// 如果数组为空或者长度为0,将返回-1。
/// </summary>
/// <param name="weightArr">权重数组,应为非负数</param>
/// <param name="weightSum">给定的权重和,应为weightArr的权重之和</param>
/// <returns></returns>
public static int IndexInWeightArr(float[] weightArr, float weightSum)
{
    
    
    int index = -1;
    float cur = Random.Range(0, Mathf.Max(0, weightSum));
    float sum = 0;
    if (weightArr != null && weightArr.Length > 0)
    {
    
    
        index = 0;
        for (; index < weightArr.Length; index++)
        {
    
    
            sum += weightArr[index];
            if (sum > cur)
            {
    
    
                break;
            }
        }
        if (index >= weightArr.Length)
        {
    
    
            index = weightArr.Length - 1;
        }
    }
    return index;
}

아래는 가중치 배열만 제공하는 방법입니다.

/// <summary>
/// 按照数组内数的非负数权重,获取随机的索引。
/// 如果数组为空或者长度为0,将返回-1。
/// </summary>
/// <param name="weightArr">权重数组,应为非负数</param>
/// <returns></returns>
public static int IndexInWeightArr(float[] weightArr)
{
    
    
    float weightSum = ArrayMathF.Sum(weightArr);
    return IndexInWeightArr(weightArr, weightSum);
}
public static float Sum<T>(T arr) where T : IEnumerable<float>
{
    
    
    float sum = 0;
    if (arr != null)
    {
    
    
        foreach (var value in arr)
        {
    
    
            sum += value;
        }
    }
    return sum;
}

(5) 랜덤화 실현

삼각형의 무작위성에 관해서는 이전에 이미 해결한 바 있으며 이제 다음과 같이 다각형의 무작위성을 실현할 수 있습니다!

 /// <summary>
/// 在多边形区域内内随机一个点
/// </summary>
public static Vector2 InPolygon(Polygon polygon, Vector2 defaultValue)
{
    
    
    return InPolygon(new PolygonRandomData(polygon), defaultValue);
}

/// <summary>
/// 在多边形区域内内随机一个点,polygonData为多边形三角化相关数据
/// </summary>
public static Vector2 InPolygon(PolygonRandomData polygonData, Vector2 defaultValue)
{
    
    
    Vector2 pos = defaultValue;
    int index = IndexInWeightArr(polygonData.trianglesArea, polygonData.polygonArea);
    if (index != -1)
    {
    
    
        pos = InTriangle(polygonData.triangles[index]);
    }
    return pos;
}

여기서 defaultValue는 주로 다각형이 단순 다각형이 아닐 수 있음을 고려하여 사용하는데, 예를 들어 변에 교차점이 있거나 점이 3개 미만인 경우 삼각형 분할에 실패하는 문제가 발생하므로 이 값은 후속 식별 또는 보호를 제공하는 데 사용됩니다.

5. 랜덤 포인트 테스트

위에 열거된 상황이 상당히 많으므로 여기에서 임의 효과에 대한 간단한 테스트를 수행합니다. 각 그래프는 빨간색으로, 각 그래프는 녹색으로 500개의 점을 무작위로 그려 테스트 결과는 다음과 같습니다.
여기에 이미지 설명 삽입
랜덤 포인트의 분포가 비교적 균일하고 효과가 나쁘지 않음을 알 수 있습니다.

6. 임의 샘플링 거부

드디어 왔습니다. 일반적으로 우리는 구체적으로 설명할 수 없거나 임의의 방법으로 유추하기 어려운 이상한 모양이 많이 있으며 추가 조건이 있는 새로운 모양도 있습니다. 그러면 이때 샘플링을 거부하는 방법을 사용할 수 있습니다. 이 방법은 간단한 확률 분포에서 복잡한 확률 분포를 얻는 것입니다.

예를 들어 원 안의 한 점을 랜덤화하고 싶다면 먼저 사각형 안에서 랜덤화한 다음 원 안에 있는지 판단하고 그렇지 않으면 거부 하고 다시 랜덤화합니다. 이런 식으로 분포가 균일한 원을 얻을 수 있습니다. 이 프로세스는 샘플링을 거부하는 것이므로 프로그래밍 방식으로 구현하면 다음과 같을 수 있습니다.

/// <summary>
/// 拒绝采样,在矩形区域内随机一个符合条件的点
/// </summary>
/// <param name="rect"></param>
/// <param name="judgeFunc"></param>
/// <returns></returns>
public static Vector2 RejectSampling(Rect rect, System.Func<Vector2, bool> judgeFunc)
{
    
    
    return RejectSampling(rect, judgeFunc, Vector2.zero, -1);
}

/// <summary>
/// 拒绝采样,在矩形区域内随机一个点,并判断是否符合条件,不符合再次随机
/// </summary>
/// <param name="rect">范围</param>
/// <param name="judgeFunc">判断条件</param>
/// <param name="defaultValue">失败返回坐标</param>
/// <param name="maxRandomTime">最大尝试次数,当 maxRandomTime <= 0 时,将无限尝试直到要求被满足</param>
/// <returns></returns>        
public static Vector2 RejectSampling(Rect rect, System.Func<Vector2, bool> judgeFunc, Vector2 defaultValue, int maxRandomTime)
{
    
    
    Vector2 pos;
    for (; maxRandomTime != 0; maxRandomTime--)
    {
    
    
        pos = InRect(rect);
        if (judgeFunc(pos))
        {
    
    
            return pos;
        }
    }
    return defaultValue;
}

프로그램에서 루프를 계속하는 것은 위험하기 때문에 여기서 내가 실제로 추가 정지 조건을 부여했음을 알 수 있습니다. 이 조건이 범위 내에서 달성할 수 없으면 직접 무한 루프를 수행하므로 범위가 주어집니다. 여전히 유용합니다. 보호.

델리게이트를 이용하여 상태를 판단하는 방법은 델리게이트 생성 시 추가 오버헤드가 발생하지만, 일반적인 pass-by-value 호출의 오버헤드는 일반 함수와 유사하다는 점에 유의하시기 바랍니다. 그러니 너무 걱정하지 마세요. 여러 번 무작위화하고 싶다면 커미션을 먼저 절약할 수 있습니다.

7. 샘플링된 샘플 거부

여기에서 샘플링 거부의 효과를 테스트해 보자 실제로 샘플링을 거부하는 무작위 단계는 두 단계로 변경될 수 있습니다.

  • 임의의 범위를 결정하고 래핑 사각형 가져오기
  • 점이 모양 내부에 있는지 여부를 확인하기 위해 임의 조건을 결정합니다.

다음으로 이 거부 샘플링 프로세스를 시도하기 위해 두 가지 번거로운 그래픽인 타원과 다각형을 더 사용합니다.

(1) 타원 내 랜덤

타원 내부의 임의의 점, 타원을 감싸는 사각형 으로 변환 하고 타원 내부에 있는지 여부를 판단합니다 . 이것은 비교적 간단합니다. 직접 코딩하십시오.

public struct Ellipse
{
    
    
    public Vector2 center;
    public float a;
    public float b;

    public Ellipse(Vector2 center, float a, float b)
    {
    
    
        this.center = center;
        this.a = a;
        this.b = b;
    }

    public Rect OutsideRect()
    {
    
    
        Vector2 size = new Vector2(a, b);
        return new Rect(center - size, size * 2);
    }

    public bool Inside(Vector2 pos)
    {
    
    
        pos -= center;
        return pos.x * pos.x / (a * a) + pos.y * pos.y / (b * b) < 1;
    }
}

그러면 두 가지 조건을 구한 후 임의로 시작할 수 있으며 다음과 같이 타원의 점을 구할 수 있습니다.

System.Func<Vector2, bool> judgeFunc = ellipse.Inside;
Rect ellipseOutsideRect = ellipse.OutsideRect();
Vector2 pos = RandomU.RejectSampling(ellipseOutsideRect, judgeFunc, Vector2.zero, 100);

(2) 폴리곤 내 랜덤

다각형의 주변 사각형을 판단하여 모든 점을 직접 통과하고 xmin, xmax, ymin 및 ymax를 찾아 사각형을 얻을 수 있습니다. 코드는 다음과 같습니다.

public Rect OutsideRect()
{
    
    
    if (points.Length <= 0)
    {
    
    
        return new Rect(0, 0, 0, 0);
    }
    Vector2 min = points[0];
    Vector2 max = points[0];
    for (int i = 0; i < points.Length; i++)
    {
    
    
        max.x = Mathf.Max(points[i].x, max.x);
        max.y = Mathf.Max(points[i].y, max.y);
        min.x = Mathf.Min(points[i].x, min.x);
        min.y = Mathf.Min(points[i].y, min.y);
    }
    return new Rect(min, max - min);
}

점이 폴리곤 내부에 있는지 판단하는 방법에는 여러 가지가 있는데(포인트가 폴리곤의 측면에 있으면 내부가 아님) 여기서는 아래 그림과 같이 광선의 교차점을 판단하는 방법을 사용합니다.
여기에 이미지 설명 삽입
점 A는 폴리곤 외부에서 짝수 개의 광선 교차점을 가질 것이고 , 포인트 B는 폴리곤 내부에서 홀수 개의 광선 교차점을 갖게 될 것이므로 판단의 근거로 사용할 수 있습니다. 그리고 일반적 으로 이해하고 계산하기 쉬운 수평 오른쪽 광선이 사용됩니다 . 고려해야 할 특별한 경우는 다각형의 점이 광선에 있는 경우 교차하는 것으로 판단할 수 있습니까? 아래 그림과 같이.
여기에 이미지 설명 삽입
여기 CDF 포인트는 다양한 상황에서 교차 성능을 가지며 여기에서 다른 제한을 만들 수 있습니다.

  • 가장자리 세그먼트에 광선에 끝점이 있는 경우(예: F, D) 아래쪽 끝점(N으로 표시)만 고려하고 위쪽 끝점(M으로 표시)은 무시합니다. 즉, N은 광선 위에 있고, 교차점 수 + 1이며, M은 광선 상에 있든 없든 교차점으로 간주되지 않습니다. (여기서 상한과 하한의 반대도 같은 효과를 낸다.)
  • 가장자리의 광선이 모두 끝점에 있는 경우 교차하지 않는 것으로 간주됩니다.

이 두 과정을 더한 후 교점 수를 세어 폴리곤 내에 있는지 이전에 제안한 교점 수의 패리티 판단을 만족할 수 있을까? 시도해 볼 수 있습니다. 그러면 코드는 다음과 같습니다.

/// <summary>
/// 点是否在多边形(在边上视为在多边外)
/// </summary>
/// <param name="pos"></param>
/// <returns></returns>
public bool Inside(Vector2 pos)
{
    
    
    int j = points.Length - 1;
    bool inside = false;
    Vector2 pi, pj;
    for (int i = 0; i < points.Length; i++)
    {
    
    
        pi = points[i];
        pj = points[j];
        j = i;
        // 水平右方向射线,看交点个数
        if ((
            // 点的y值是否在两点之间,区间只选一边,
            // 这样每个就只会算一次,避免当有点在射线上的干扰
            (pi.y <= pos.y && pos.y < pj.y) || (pj.y <= pos.y && pos.y < pi.y)) &&
            // x轴截距
            pos.x < (pj.x - pi.x) * (pos.y - pi.y) / (pj.y - pi.y) + pi.x)
        {
    
    
            inside = !inside;
        }
    }
    return inside;
}

(3) 효과

위의 두 가지 모양이 효과를 보기 위해서는 상당히 균일합니다. 효율성 문제는 실제로 나쁘지 않습니다. 실제로 그래프에 무작위로 배치될 확률이 상대적으로 높고 여러 번 반복하면 기본적으로 무작위이기 때문입니다.
여기에 이미지 설명 삽입

(4) 점이 삼각형/원 안에 있는지 여부

이 방법은 다른 그래픽과 비슷합니다. 다음은 다른 그래픽에서 포인트가 모양 내에 있는지 여부를 확인할 수 있는 방법에 대한 간략한 목록입니다.
삼각형:

/// <summary>
/// 是否在三角形内
/// </summary>
/// <returns></returns>
public bool Inside(Vector2 pos)
{
    
    
    Vector3 pa = a - pos;
    Vector3 pb = b - pos;
    Vector3 pc = c - pos;
    Vector3 pab = Vector3.Cross(pa, pb);
    Vector3 pbc = Vector3.Cross(pb, pc);
    Vector3 pca = Vector3.Cross(pc, pa);
    float d1 = Vector3.Dot(pab, pbc);
    float d2 = Vector3.Dot(pab, pca);
    float d3 = Vector3.Dot(pbc, pca);
    return d1 > 0 && d2 > 0 && d3 > 0;
}

둥근:

/// <summary>
/// 是否在圆形内
/// </summary>
/// <returns></returns>
public bool Inside(Vector2 pos)
{
    
    
    pos -= center;
    return pos.x * pos.x + pos.y * pos.y < radius * radius;
}

8. 끝났다

여기까지 입니다 게임 내에서 랜덤 처리를 하시고 참고가 되었으면 좋겠습니다~

관련 참조 기사
삼각형 내 임의 처리
http://www.cs.princeton.edu/~funk/tog02.pdf
https://www.jianshu.com/p/36fa431311ac
불규칙 삼각형 면적 계산
https://blog. csdn.net/n_moling/article/details/115381804
Unity3d는 포인트가 폴리곤 내에 있는지 판단합니다
https://blog.csdn.net/zouxin_88/article/details/109678109

Supongo que te gusta

Origin blog.csdn.net/Blue_carrot_/article/details/131169450
Recomendado
Clasificación