65.9K
CodeProject 正在变化。 阅读更多。
Home

六边形镶嵌的生成

starIconstarIconstarIconstarIconstarIcon

5.00/5 (13投票s)

2018年6月24日

Ms-RL

5分钟阅读

viewsIcon

29352

downloadIcon

321

在本文中,我将解释如何生成六边形镶嵌及其在 Unity 3D 中的绘制方法

引言

在本文中,我将设计一种在平面上生成六边形镶嵌的算法。之后,我将使用 Unity3D 进行绘制。

想法

六边形的生成从一个点开始,该点将作为中心。然后,另一圈六边形将围绕中心,并成为新一圈六边形的中心。生成在达到给定圈数时结束。

在下面的图中,原始中心以红色着色,第一圈以黄色着色,第二圈以绿色着色(第二圈的中心由红色和黄色六边形组成)。

输入

Radius - 圈数

Side - 六边形的边长。

输出

该算法可以输出生成的每个六边形的中心;但是,在此实现中,我们将使用 Unity3D 来绘制六边形。

算法

想法是通过查看最后一个生成的中心来生成新六边形的中心。

让我们从一个二维欧几里得空间开始,固定一个点 O 作为中心,并以基 {(1,0), (0,1)} 作为我们轴的基(一个简单的具有正交 x 和 y 轴的笛卡尔平面)。注意:我们将使用二维空间进行算法设计,但将在三维空间中生成镶嵌,因此在算法实现时需要考虑这一点

我们可以从点 (0,0) 开始,这将是第一个六边形的中心。设 S 为六边形的边长。

可以以“螺旋”方式生成所有六边形的中心(见下图:最暗的六边形是第一个,最亮的六边形是最后一个;中间,六边形的阴影代表生成顺序,越暗表示“先生成”,越亮表示“后生成”)。

通过这种生成方法,要生成点 Pn,只需知道点 Pn-1 的坐标,然后执行 Pn =<sub> </sub>Pn-1 + (a,b) 即可,其中 (a,b) 是一个可以轻松找到的偶数,只需记住六边形的属性即可。

因此,可能的偶数 (a,b)

  • DR - (1.5, -sq3/2)
  • DX - (0, -sq3)
  • DL - (-1.5, -sq3/2)
  • UL - (-1.5, sq3/2)
  • UX - (0, sq3)
  • UR - (1.5, sq3/2)

其中:sq3 = sqrt(3)  U/D -> 向上/向下方向,L/X/R -> 向左/无/向右方向

因此,为了从中心 (0, 0) 生成“六边形螺旋”的中心,我们需要执行以下操作。

nDR - nDX - nDL - nUL - nUX - UX - Exit? - nUR

其中 n 是当前圈数,n<action> 表示“在当前中心生成一个六边形,将与 action 对应的偶数 **乘以六边形的边长** 加到前一个中心,并重复 n 次”。Exit? 表示如果数量。因此,要生成第一个六边形

0DR - 0DX - 0DL - 0UL - 0UX - UX - Exit? - 0UR --> UX (在 (0, 0) 生成一个六边形并向上移动,使下一个中心为 (0, sq3 * s),其中 s 是六边形的边长)。

第一圈六边形

1DR - 1DX - 1DL - 1UL - 1UX - UX - Exit? - 1UR

从前一个中心 (0, sq3 * s) 开始,生成 6+1 个六边形,位于

  • (0, sq3 * s)
  • (1.5*s, sq3*s/2)
  • (1.5*s, -sq3*s/2)
  • (0, -sq3*s)
  • (-1.5*s,-sq3*s/2)
  • (-1.5*s,sq3*s/2)
  • (-1.5*s,3*sq3*s/2)

留下中心在 (0, 2*sq3*s)

下一张图显示了算法生成的第一个 19 个中心

Unity 实现

该算法在 C# 中实现起来非常容易

using UnityEngine;

public class GenerateHexFloor : MonoBehaviour {

    public GameObject Hexagon;
    public uint Radius;
    public float HexSideMultiplier = 1;

    private const float sq3 = 1.7320508075688772935274463415059F;

    // Use this for initialization
    void Start () {
        //Point of the next hexagon to be spawned
        Vector3 currentPoint = transform.position;

        if (Hexagon.transform.localScale.x != Hexagon.transform.localScale.z)
        {
            Debug.LogError("Hexagon has not uniform scale: cannot determine its side. Aborting");
            return;
        }

        //Spawn scheme: nDR, nDX, nDL, nUL, nUX, End??, UX, nUR
        Vector3[] mv = {
            new Vector3(1.5f,0, -sq3*0.5f),       //DR
            new Vector3(0,0, -sq3),               //DX
            new Vector3(-1.5f,0, -sq3*0.5f),      //DL
            new Vector3(-1.5f,0, sq3*0.5f),       //UL
            new Vector3(0,0, sq3),                //UX
            new Vector3(1.5f,0, sq3*0.5f)         //UR
        };

        int lmv = mv.Length;
        float HexSide = Hexagon.transform.localScale.x * HexSideMultiplier;

        for (int mult = 0; mult <= Radius; mult++)
        {
            int hn = 0;
            for (int j = 0; j < lmv; j++)
            {
                for (int i = 0; i < mult; i++, hn++)
                {
                    GameObject h = Instantiate(Hexagon, currentPoint, Hexagon.transform.rotation, transform);
                    h.name = string.Format("Hex Layer: {0}, n: {1}", mult, hn);
                    currentPoint += (mv[j] * HexSide);
                }
                if (j == 4)
                {
                    GameObject h = Instantiate(Hexagon, currentPoint, Hexagon.transform.rotation, transform);
                    h.name = string.Format("Hex Layer: {0}, n: {1}", mult, hn);
                    currentPoint += (mv[j] * HexSide);
                    hn++;
                    if (mult == Radius)
                        break;      //Finished
                }
            }
        }
    }

}

注释

有了想法,写代码就变得非常非常容易。我只想评论一些我的实现选择。

首先,对于任何不了解 Unity 3D 工作原理的人来说,基本上,任何继承自 MonoBehaviour 的类的公共字段都可以从编辑器设置并用作输入字段,因此该类的每个实例都可以轻松地从编辑器设置其自己的参数。所以我要求提供一个六边形的 3D 模型(你可以找到一个附在本文章中的模型,使用 Blender 3D 建模),提供镶嵌的半径,以及字段 HexSideMultipler 用于将六边形的边长乘以一个常数。我通过查看模型的缩放来获取六边形的边长(这就是为什么我要求 x 和 z 缩放相同)。

然后我从附加脚本的 GameObject 的原点开始:在 Unity 中,任何继承自 MonoBehaviour 的类都可以附加到任何 GameObject。在这种情况下,这个 GameObject 是 3D 空间中的一个简单点,提供 x、y 和 z 坐标。所以我是从那个点开始构建六边形镶嵌的。

其余的代码,鉴于我之前解释的想法,是自明的:我只是从前一个点(我称之为 currentPoint)开始生成每个六边形的中心。*我(使用 Instantiate 方法)生成每个六边形,然后计算下一个点。*

关注点

尽管这个算法似乎效率不高,因为它包含三个嵌套的 for 循环,但它是最优的,因为我们对每个六边形进行一次迭代。嵌套循环的出现是因为如果镶嵌的半径是 R,并不意味着总共有 R 个六边形!

Gist

我之前在一个 gist 中发布过这段代码,但解释没有这里深入。如果你想访问 gist,这是链接:https://gist.github.com/LuxGiammi/8c1e17feecf7d3c33a1a493657a4d153

 

© . All rights reserved.