简体   繁体   English

六边形/形状中的随机点

[英]random points in a hexarea / shape

I have a simple hexagonal grid, where I'm selecting a group of hexagons, which will then be filled with some random points.我有一个简单的六边形网格,我在其中选择一组六边形,然后将填充一些随机点。

Let me explain the exact procedure of generating the points:让我解释一下生成点的确切过程:

  • Using a list of hex coordinates I'm selecting hexagons.使用六边形坐标列表我选择六边形。
  • Group hexagons into areas.将六边形分组为区域。
  • Store all edge points, for each area individually.单独存储每个区域的所有边缘点。
  • Loop through areas:循环区域:
    • Calculate area boundary ( required for definig the range for random point generator )计算区域边界(定义随机点生成器的范围需要)
    • Draw area (bounding) square.绘制区域(边界)正方形。 (to see whether the computation was correct) (看计算是否正确)
    • Based on the bounding square min,max values generate random points基于边界平方最小值,最大值生成随机点
    • Test if point is inside the area shape.测试点是否在区域形状内。 (The shape is defined by area edge points) (形状由区域边缘点定义)
    • If the point passed the above test, it's the pushed into an array.如果点通过了上述测试,则​​将其推入数组。
  • loop through the array of points and draw them on screen.遍历点数组并在屏幕上绘制它们。

Now I tried these two methods to determine whether a point lies inside a specific shape.现在我尝试了这两种方法来确定一个点是否位于特定形状内。

cn_PnPoly: function( P, V, n ){ ///// Point , array of vertices , array size ////
        var cn = 0, vt = 0.0;
        for (var i=0; i< (n-1); i++) {    // edge from V[i]  to V[i+1]
           if (((V[i].y <= P.y) && (V[i+1].y > P.y))     // an upward crossing
            || ((V[i].y > P.y) && (V[i+1].y <=  P.y))) { // a downward crossing
                // compute  the actual edge-ray intersect x-coordinate
                vt = (P.y  - V[i].y) / (V[i+1].y - V[i].y);
                if (P.x <  V[i].x + vt * (V[i+1].x - V[i].x)) // P.x < intersect
                     ++cn;   // a valid crossing of y=P.y right of P.x
            }
        }
        return (cn&1);    // 0 if even (out), and 1 if  odd (in)
    },

result:结果:

在此处输入图片说明

isInHexBounary: function( p, points, l ){ ///// Point , array of vertices , array size ////
        var result = false;
          for (i = 0, j = l - 1; i < l; j = i++) {
            if ((points[i].y > p.y) != (points[j].y > p.y) && (p.x < (points[j].x - points[i].x) * (p.y - points[i].y) / (points[j].y-points[i].y) + points[i].x)) {
                result = !result;
             }
          }
          return result;
    },

result:结果:

在此处输入图片说明

I suppose the first method requires to have all the points in specific order, and that's why it's not working properly.我想第一种方法需要按特定顺序排列所有点,这就是它无法正常工作的原因。 But the second one seems to be working almost correct , apart from some parts.但是除了某些部分外,第二个似乎几乎正确工作。 Any idea what I'm doing wrong?知道我做错了什么吗?

Update:更新:

It's turning out that for mos tof the algorhytms it's required to have the points in a specific order, so I calculated angle of each point relative to the average center point.事实证明,对于大多数算法来说,需要按特定顺序排列点,所以我计算了每个点相对于平均中心点的角度。 And then sorted all the points by angle.然后按角度对所有点进行排序。 Both algorithms now return similar results.这两种算法现在返回相似的结果。 Although there is sitll some points leaking through the area shape.尽管有一些点通过区域形状泄漏。

findCenter: function( points ){
        var x = 0, y = 0, i, len = points.length;
        for (i = 0; i < len; i++) {
            x += points[i].x;
            y += points[i].y;
        }
        return {x: x / len, y: y / len};
    },

    findAngles: function(c, points){
        var i, len = points.length, p, dx, dy;
        for (i = 0; i < len; i++) {
            p = points[i];
            dx = p.x - c.x;
            dy = p.y - c.y;
            p.angle = Math.atan2(dy, dx);
        }
    },

    sortByAngle: function( points ){
        points.sort(function(a, b) {
          if (a.angle > b.angle) return 1;
          else if (a.angle < b.angle) return -1;
          return 0;
        });
    },

result:结果:

在此处输入图片说明

Frankly, I would make it simpler.坦率地说,我会让它更简单。

  1. Make sampling in one single hexagon.在一个单一的六边形中进行采样。 Just make bounding box with size (2s,2s cos(30)), sample (x,y) and reject it if it is outside the hexagon.只需制作大小为 (2s,2s cos(30))、样本 (x,y) 的边界框,如果它在六边形之外,则拒绝它。 Efficiency should be 3/4 (check hexagon area ).效率应为 3/4(检查六边形面积)。 Position this sampling hexagon at (0,0) such that sampling is very easy, and, what's more important, very TESTABLE将此采样六边形定位在 (0,0) 处,这样采样非常容易,而且更重要的是,非常可测试

  2. For each area maintain array of hexagon centers, say, with size N对于每个区域,保持六边形中心阵列,例如大小为 N

  3. Sample in two steps.分两步取样。 First, sample integer 1...N and select what hexagon in the area is about to be selected.首先,对整数 1...N 进行采样,然后选择该区域中将要选择的六边形。 Second, sample from your (x,y) from step #1, from your single hexagon at (0,0).其次,从步骤 #1 的 (x,y) 中采样,从 (0,0) 处的单个六边形中采样。 Last, shift sampled (x,y) by randomly selected hexagon center最后,通过随机选择的六边形中心移位采样 (x,y)

UPDATE更新

在此处输入图片说明

Actually, I believe there is a way to sample point (x,y) in a single hexagon with 100% efficiency without any rejection/acceptance.实际上,我相信有一种方法可以在没有任何拒绝/接受的情况下以 100% 的效率在单个六边形中对点 (x,y) 进行采样。 Look at picture above.看上面的图片。 Red is whole hexagon, and rectangular blue area is where you sample points.红色是整个六边形,矩形蓝色区域是您采样点的地方。 If sampled point is within red area you take it and move on.如果采样点在红色区域内,则将其取下并继续。 If it is not in the red and inside blue A triangle, you map it into black A' triangle.如果它不在红色和蓝色A三角形内,则将其映射到黑色A'三角形中。 If point is not in the red but in the blue B triangle, you remap it into black B' triangle如果点不在红色而是在蓝色B三角形中,则将其重新映射为黑色B'三角形

Remapping is quite simple linear transformation.重映射是非常简单的线性变换。

Finally you have sampling with three random numbers as input (one used to select destination hexagon, and two used to sample random point) and it will guaranteed to return random point somewhere in the desired area最后,您使用三个随机数作为输入进行采样(一个用于选择目标六边形,两个用于采样随机点),它将保证在所需区域的某处返回随机点

UPDATE II更新二

As noted by Denis Sheremet, better mapping would be A->B' and B->A' .正如 Denis Sheremet 所指出的,更好的映射是A->B'B->A' Assuming hexagon center at (0,0), overall just two reflections - one over center and another over middle of the triangle假设六边形中心在 (0,0),总体上只有两个反射 - 一个在中心上方,另一个在三角形的中间

I implemented Severin Pappadeux's awesome answer in C++ (but should be trivially convertible to any other language):我在 C++ 中实现了Severin Pappadeux 的精彩答案(但应该可以轻松转换为任何其他语言):

    // randA and randB must be in the range [0, 1]
    static glm::vec2 SampleHexagon(float randA, float randB) {
        // Algorithm based on https://stackoverflow.com/a/39262805/3841944 by user Severin Pappadeux
        //
        // Hexagon map:
        //  ___________________
        // |   /|         |\   |
        // |  / |         | \b'|
        // | / a|         |A \ |
        // |/___|    C    |___\|
        // |\   |         |   /|
        // | \ b|         |B / |
        // |  \ |         | /a'|
        // |   \___________/___|
        // |    |         | <- "startA"
        // |    |         |<-->| <- "widthA"
        // |    | <- "startC"
        // |<----------------->| "width"
        //
        // C = center part
        // a, b = left (upper and lower) part
        // A, B = right (upper and lower) part
        // a' -> area that will be remapped to a
        // b' -> area that will be remapped to b

        // The algorithm is to:
        // 1. Generate random points in the rectangle spanning C, A, B, b' and a'
        // 2. Move all points in a' and b' into a and b
        // 3. (Optional) Make hexagon regular
        // 4. (Optional) Remap coordinates to range [-1, 1]

        // Coordinates are in the range [0, 1]
        const float width  = 1;
        const float widthC = width / 2;
        const float widthA = (width - widthC) / 2;
        const float startC = widthA;
        const float startA = startC + widthC;
        const float slope  = .5f / widthA;


        // 1. Remap x into the rectangle spanning C, A, B, b' and a'
        float x = startC + randA * .75f;
        float y = randB;


        // 2. Move all points in a' and b' into a and b

        // If we are in the last third (A, b', B and a')
        if (startA < x) {
            float localX = x - startA;

            if (y > .5f) {  // And the upper half of it (in A or b')
                float localY = y - .5f;
                if (localY > .5f - localX * slope) { // And above the diagonal
                    // Move the point so it is in the triangle to 'b'
                    x -= startA;
                    y -= .5f;
                }
            } else { // And the lower half of it (in B or a')
                float localY = y;
                if (localY < localX * slope) {  // And we are below the diagonal
                    // Move the point so it is in the triangle to 'a'
                    x -= startA;
                    y += .5f;
                }
            }
        }

        // 3. Make to regular hexagon (currently the hexagon is too high, because we assumed width == height)

        // Divide the hexagon into 6 regular triangles and use the Pythagorean theorem, and this calculation should be fairly obvious
        float regularHexagonWidthToHeightRatio = std::sqrt(1.f*1.f - .5f*.5f);
        y = (y - .5f) * regularHexagonWidthToHeightRatio + .5f; // Center around 0, scale, center to .5

        // 4. Remap coordinates to -1 to 1 (optional)
        x = x * 2 - 1;
        y = y * 2 - 1;

        return { x, y };
    }

Again many thanks to Severin Pappadeux, upvote his answer.再次非常感谢 Severin Pappadeux,支持他的回答。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM