【问题标题】:random points in a hexarea / shape六边形/形状中的随机点
【发布时间】:2016-08-31 08:55:47
【问题描述】:

我有一个简单的六边形网格,我在其中选择一组六边形,然后将填充一些随机点。

让我解释一下生成点的确切过程:

  • 我使用十六进制坐标列表来选择六边形。
  • 将六边形组合成区域。
  • 为每个区域单独存储所有边缘点。
  • 循环通过区域:
    • 计算区域边界(用于定义随机点生成器的范围)
    • 绘制区域(边界)正方形。 (查看计算是否正确)
    • 根据边界平方的最小值、最大值生成随机点
    • 测试点是否在区域形状内。 (形状由区域边缘点定义)
    • 如果该点通过了上述测试,则​​将其推入数组中。
  • 循环遍历点数组并将它们绘制在屏幕上。

现在我尝试了这两种方法来确定一个点是否位于特定形状内。

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)
    },

结果:

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;
    },

结果:

我想第一种方法要求所有点都按特定顺序排列,这就是它无法正常工作的原因。 但第二个似乎工作几乎是正确的,除了某些部分。知道我做错了什么吗?

更新:

事实证明,对于大多数算法,它需要以特定顺序排列点,因此我计算了每个点相对于平均中心点的角度。然后按角度对所有点进行排序。两种算法现在都返回相似的结果。虽然有一些点通过区域形状泄漏。

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;
        });
    },

结果:

【问题讨论】:

    标签: javascript random shape point hexagonal-tiles


    【解决方案1】:

    坦率地说,我会让它更简单。

    1. 在一个六边形中进行采样。只需制作大小为 (2s,2s cos(30))、样本 (x,y) 的边界框,如果它在六边形之外,则拒绝它。效率应该是 3/4(检查hexagon area)。将此采样六边形定位在 (0,0) 处,这样采样非常容易,更重要的是,非常可测试

    2. 为每个区域维护六边形中心数组,例如,大小为 N

    3. 分两步取样。首先,采样整数 1...N 并选择什么 该区域的六边形即将被选中。其次,从您的样品 (x,y) 从第 1 步开始,从 (0,0) 处的单个六边形开始。最后的, 通过随机选择的六边形中心移动采样 (x,y)

    更新

    实际上,我相信有一种方法可以在单个六边形中以 100% 的效率对点 (x,y) 进行采样,而不会出现任何拒绝/接受。看上面的图片。红色是整个六边形,矩形蓝色区域是采样点的位置。如果采样点在红色区域内,则将其拿走并继续前进。如果它不在红色和内部蓝色A 三角形中,则将其映射为黑色A' 三角形。如果点不是红色而是蓝色B 三角形,则将其重新映射为黑色B' 三角形

    重映射是非常简单的线性变换。

    最后,您使用三个随机数作为输入进行采样(一个用于选择 目标六边形,两个用于采样随机点),它将保证在所需区域的某处返回随机点

    更新二

    正如 Denis Sheremet 所指出的,更好的映射将是 A-&gt;B'B-&gt;A'。假设六边形中心在 (0,0),总体上只有两个反射 - 一个在中心,另一个在三角形的中间

    【讨论】:

    • 边界圆可能更有效。您只需要采样径向坐标 (a,r) 而不是笛卡尔 (x,y)
    • @DenisSheremet 没错,效率是 0.827 与 0.75,但随后有 sin/cos 调用转换为笛卡尔。
    • @DenisSheremet 我实际上想过 100% 有效的六边形采样,请检查更新
    • 很酷很简单的解决方案,也不需要区域检查
    • 你也可以交换 A' 和 B' 区域,让转换更加简单
    【解决方案2】:

    我在 C++ 中实现了Severin Pappadeux's awesome answer(但应该可以轻松转换为任何其他语言):

        // 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 };
        }
    

    再次感谢 Severin Pappadeux,支持他的回答。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-11-20
      • 1970-01-01
      • 2011-03-21
      • 1970-01-01
      • 2016-10-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多