public static RenderGeometry CreateIcosidodecahedronGeometry(float size)
    {
        RenderGeometry geometry = new RenderGeometry();

        float goldenRatio = (1 + Mathf.Sqrt(5)) / 2;
        float dL          = size / 2;
        float dM          = dL / 2;
        float dS          = dM / goldenRatio;
        float dL2         = Mathf.Lerp(dL, dS * 2, 0.5f);

        var corners = new Dictionary <IntVector3, Vertex>();

        foreach (var keyAndPosition in Combine(
                     VertexSymmetryGroup("100", Key(2, 0, 0), Vec(dL, 0, 0)),
                     VertexSymmetryGroup("211", Key(2, 1, 1), Vec(dL2, dS, dM))))
        {
            corners[keyAndPosition.Key] = geometry.CreateVertex(keyAndPosition.Value);
        }
        foreach (IntVector3[] keys in Combine(
                     FaceSymmetryGroup("101", Key(2, 0, 0), Key(2, 1, 1), Key(2, -1, 1)),
                     FaceSymmetryGroup("111", Key(2, 1, 1), Key(1, 2, 1), Key(1, 1, 2)),
                     FaceSymmetryGroup("110", Key(1, 2, 1), Key(2, 1, 1), Key(2, 0, 0), Key(2, 1, -1), Key(1, 2, -1))))
        {
            geometry.CreateFace(keys.Select(key => corners[key]).ToArray());
        }
        return(geometry);
    }
    public static RenderGeometry CreateRhombicuboctahedronGeometry(float size, float ratio)
    {
        if (ratio <= 0)
        {
            return(CreateCubeGeometry(Vector3.one * size, new int[] { 1, 1, 1 }));
        }
        if (ratio >= 1)
        {
            return(CreateOctahedronGeometry(size, 1));
        }

        RenderGeometry geometry = new RenderGeometry();

        var corners = new Dictionary <IntVector3, Vertex>();

        foreach (var keyAndPosition in VertexSymmetryGroup("211", Key(2, 1, 1), Vec(1, 1 - ratio, 1 - ratio) * (size / 2)))
        {
            corners[keyAndPosition.Key] = geometry.CreateVertex(keyAndPosition.Value);
        }
        foreach (IntVector3[] keys in Combine(
                     FaceSymmetryGroup("100", Key(2, -1, -1), Key(2, 1, -1), Key(2, 1, 1), Key(2, -1, 1)),
                     FaceSymmetryGroup("110", Key(2, 1, -1), Key(1, 2, -1), Key(1, 2, 1), Key(2, 1, 1)),
                     FaceSymmetryGroup("111", Key(2, 1, 1), Key(1, 2, 1), Key(1, 1, 2))))
        {
            geometry.CreateFace(keys.Select(key => corners[key]).ToArray());
        }
        return(geometry);
    }
    public static RenderGeometry CreateCubeFrameGeometry(float size, float ratio)
    {
        RenderGeometry geometry = new RenderGeometry();

        var corners = new Dictionary <IntVector3, Vertex>();
        var lengths = new Dictionary <int, float> {
            [-2] = -size / 2, [-1] = -size / 2 * ratio, [1] = size / 2 * ratio, [2] = size / 2
        };

        foreach (int x in lengths.Keys)
        {
            foreach (int y in lengths.Keys)
            {
                foreach (int z in lengths.Keys)
                {
                    corners[Key(x, y, z)] = geometry.CreateVertex(new Vector3(lengths[x], lengths[y], lengths[z]));
                }
            }
        }

        foreach (IntVector3[] keys in Combine(
                     FaceSymmetryGroup("211", Key(2, 1, 1), Key(2, 2, 1), Key(2, 2, 2), Key(2, 1, 2)),
                     FaceSymmetryGroup("210", Key(2, 1, -1), Key(2, 2, -1), Key(2, 2, 1), Key(2, 1, 1)),
                     FaceSymmetryGroup("210", Key(1, 1, -1), Key(2, 1, -1), Key(2, 1, 1), Key(1, 1, 1))))
        {
            geometry.CreateFace(keys.Select(i => corners[i]).ToArray());
        }
        return(geometry);
    }
    public static RenderGeometry CreateCuboctahedronGeometry(float size, float ratio)
    {
        if (ratio <= 0)
        {
            return(CreateTetrahedronGeometry(size, 1));
        }
        if (ratio >= 1)
        {
            RenderGeometry result = CreateTetrahedronGeometry(size, 1);
            result.ApplyRotation(Quaternion.AngleAxis(90, Vector3.up));
            return(result);
        }

        RenderGeometry geometry = new RenderGeometry();

        var corners = new Dictionary <IntVector3, Vertex>();

        foreach (var symmetry in Symmetry.SymmetryGroup("110"))
        {
            IntVector3 key      = symmetry.Apply(Key(1, 1, 0));
            Vector3    position = symmetry.Apply(Vec(1, 1, (1 - ratio * 2) * (symmetry.isNegative ? -1 : 1)) * (size / 2));
            corners[key] = geometry.CreateVertex(position);
        }
        foreach (IntVector3[] keys in Combine(
                     FaceSymmetryGroup("100", Key(1, 0, -1), Key(1, 1, 0), Key(1, 0, 1), Key(1, -1, 0)),
                     FaceSymmetryGroup("111", Key(1, 1, 0), Key(0, 1, 1), Key(1, 0, 1))))
        {
            geometry.CreateFace(keys.Select(key => corners[key]).ToArray());
        }
        return(geometry);
    }
        private RenderGeometry BuildTileBaseGeometry()
        {
            var geometry = new RenderGeometry();

            parent.tile.face.edges.ForEach(e => geometry.CreateVertex(e.vertex.p - parent.faceCenter));
            geometry.CreateFace(geometry.vertices.ToArray());

            new FaceCurving(BASE_CURVATURE).Apply(geometry);
            return(geometry);
        }
        private RenderGeometry BuildTileBlockGeometry()
        {
            var geometry = new RenderGeometry();

            parent.tile.face.edges.ForEach(e => geometry.CreateVertex(e.vertex.p - parent.faceCenter));
            parent.tile.face.edges.ForEach(e => geometry.CreateVertex(CalculateTileTopVertexPosition(e)));

            int n = geometry.vertices.Count / 2;

            for (int i = 0; i < n; i++)
            {
                geometry.CreateFace(geometry.vertices[i], geometry.vertices[(i + 1) % n], geometry.vertices[(i + 1) % n + n], geometry.vertices[i + n]);
            }
            geometry.CreateFace(Enumerable.Range(n, n).Select(i => geometry.vertices[i]).ToArray());

            new FaceMerging(1f).Apply(geometry);
            new FaceCurving(BLOCK_CURVATURE).Apply(geometry);
            new EdgeSmoothing(BLOCK_SMOOTH_RADIUS, 10).Apply(geometry);
            return(geometry);
        }
    public static RenderGeometry CreateRhombicosidodecahedronGeometry(float size, float ratio)
    {
        if (ratio <= 0)
        {
            return(CreateIcosahedronGeometry(size, 1));
        }
        if (ratio >= 1)
        {
            return(CreateDodecahedronGeometry(size));
        }

        RenderGeometry geometry = new RenderGeometry();

        float goldenRatio = (1 + Mathf.Sqrt(5)) / 2;
        float dL          = size / 2;
        float dM          = dL / goldenRatio;
        float dS          = dM / goldenRatio;
        float dLtoM       = Mathf.Lerp(dL, dM, ratio);
        float dLtoS       = Mathf.Lerp(dL, dS, ratio);
        float dMtoL       = Mathf.Lerp(dM, dL, ratio);
        float dMto0       = Mathf.Lerp(dM, 0, ratio);
        float d0toM       = dM * ratio;
        float d0toS       = dS * ratio;

        var corners = new Dictionary <IntVector3, Vertex>();

        foreach (var keyAndPosition in Combine(
                     VertexSymmetryGroup("110", Key(3, 2, 0), Vec(dLtoS, dMtoL, 0)),
                     VertexSymmetryGroup("211", Key(3, 1, 1), Vec(dL, dMto0, d0toS)),
                     VertexSymmetryGroup("211", Key(3, 2, 1), Vec(dLtoM, dM, d0toM))))
        {
            corners[keyAndPosition.Key] = geometry.CreateVertex(keyAndPosition.Value);
        }
        foreach (IntVector3[] keys in Combine(
                     FaceSymmetryGroup("101", Key(3, -1, 1), Key(3, 1, 1), Key(2, 0, 3)),
                     FaceSymmetryGroup("111", Key(3, 2, 1), Key(1, 3, 2), Key(2, 1, 3)),
                     FaceSymmetryGroup("110", Key(3, 1, -1), Key(3, 2, -1), Key(3, 2, 0), Key(3, 2, 1), Key(3, 1, 1)),
                     FaceSymmetryGroup("211", Key(3, 1, 1), Key(3, 2, 1), Key(2, 1, 3), Key(2, 0, 3)),
                     FaceSymmetryGroup("100", Key(3, -1, -1), Key(3, 1, -1), Key(3, 1, 1), Key(3, -1, 1))))
        {
            geometry.CreateFace(keys.Select(key => corners[key]).ToArray());
        }
        return(geometry);
    }
    public void Apply(RenderGeometry geometry)
    {
        var splitHalfedges = new HashSet <Halfedge>(geometry.halfedges.Where(e => IsValidEdge(e, geometry)));
        var additionalBoundaryHalfedges = new HashSet <Halfedge>();
        var splitVertices = splitHalfedges.Select(e => e.vertex).Distinct().ToArray();

        var splitHalfedgeToNewVertexPosition       = new Dictionary <Halfedge, Vector3>();
        var splitHalfedgeToVertexNormal            = new Dictionary <Halfedge, Vector3>();
        var splitHalfedgeToPreviousOpposite        = new Dictionary <Halfedge, Halfedge>();
        var splitVertexToSurroundingSplitHalfedges = new Dictionary <Vertex, Halfedge[]>();

        // Precalculates new vertex positions. Memorize old normals and old opposites of split halfedges.
        foreach (Halfedge edge in splitHalfedges)
        {
            Halfedge nextSplitEdge = NextEdgeThat(edge, e => splitHalfedges.Contains(e) || e.isBoundary);
            if (nextSplitEdge != edge)
            {
                splitHalfedgeToNewVertexPosition[edge] = CalculateVertexPosition(edge, nextSplitEdge.opposite, geometry);
                if (nextSplitEdge.isBoundary)
                {
                    Halfedge otherEdge          = nextSplitEdge.next.opposite;
                    Halfedge otherNextSplitEdge = NextEdgeThat(edge, e => splitHalfedges.Contains(e));
                    splitHalfedgeToNewVertexPosition[otherEdge] = CalculateVertexPosition(otherEdge, otherNextSplitEdge.opposite, geometry);

                    additionalBoundaryHalfedges.Add(otherEdge);
                    splitHalfedgeToVertexNormal[otherEdge] = geometry.GetEffectiveNormal(otherEdge);
                }
            }
            else
            {
                splitHalfedgeToNewVertexPosition[edge] = edge.vertex.p;
            }
            splitHalfedgeToVertexNormal[edge]     = geometry.GetEffectiveNormal(edge);
            splitHalfedgeToPreviousOpposite[edge] = edge.opposite;
        }

        // Memorize the old connected split halfedges around any given split vertex.
        foreach (Vertex vertex in splitVertices)
        {
            splitVertexToSurroundingSplitHalfedges[vertex] = vertex.edges.Where(e => splitHalfedges.Contains(e) || additionalBoundaryHalfedges.Contains(e)).ToArray();
        }

        // Split the geometry. A pair of halfedges only needs to be split once.
        foreach (Halfedge edge in splitHalfedges)
        {
            if (!edge.opposite.isBoundary)
            {
                geometry.DisconnectEdge(edge);
            }
        }

        // Set new vertex positions
        foreach (Halfedge edge in splitHalfedges.Concat(additionalBoundaryHalfedges))
        {
            edge.vertex.p = splitHalfedgeToNewVertexPosition[edge];
        }

        var vertexNormals = splitHalfedgeToVertexNormal.ToDictionary(entry => entry.Key.vertex, entry => entry.Value);

        void AddFace(params Vertex[] faceVertices)
        {
            Face newFace = geometry.CreateFace(faceVertices.ToArray());

            geometry.SetFaceType(newFace, RenderGeometry.FaceType.Smooth);
            newFace.edges.ForEach(e => geometry.SetNormal(e, vertexNormals[e.vertex]));
        }

        // Create one edge face for each split edge.
        foreach (Halfedge edge in splitHalfedges)
        {
            if (!edge.opposite.isBoundary)
            {
                continue;
            }

            Halfedge otherEdge    = splitHalfedgeToPreviousOpposite[edge];
            var      faceVertices = new List <Vertex>();
            AddIfNotPresent(faceVertices, edge.vertex);
            AddIfNotPresent(faceVertices, edge.prev.vertex);
            AddIfNotPresent(faceVertices, otherEdge.vertex);
            AddIfNotPresent(faceVertices, otherEdge.prev.vertex);

            if (faceVertices.Count >= 3)
            {
                AddFace(faceVertices.ToArray());
            }
        }

        // Create one corner face for each split vertex.
        foreach (Vertex vertex in splitVertices)
        {
            Halfedge[] surroundingSplitHalfedges = splitVertexToSurroundingSplitHalfedges[vertex];
            int        n = surroundingSplitHalfedges.Length;
            if (n < 3)
            {
                continue;
            }

            Vertex[] faceVertices = surroundingSplitHalfedges.Select(e => e.vertex).Reverse().ToArray();
            if (n == 3)
            {
                AddFace(faceVertices.ToArray());
            }
            else
            {
                Vertex faceCenter = geometry.CreateVertex(faceVertices.Average(v => v.p));
                vertexNormals[faceCenter] = faceVertices.Average(v => vertexNormals[v]).normalized;
                for (int i = 0; i < n; i++)
                {
                    AddFace(faceVertices[i], faceVertices[(i + 1) % n], faceCenter);
                }
            }
        }
    }
    public static RenderGeometry CreateBuildingBlockGeometry(Vector3 size, bool[,,] corners)
    {
        RenderGeometry geometry = new RenderGeometry();

        bool cornerUsed(IntVector3 key) => corners[(key.x + 1) / 2, (key.y + 1) / 2, (key.z + 1) / 2];

        var cornerVerts = new Dictionary <IntVector3, Vertex>();

        foreach (IntVector3 key in IntVector3.allTriaxialDirections)
        {
            if (cornerUsed(key))
            {
                cornerVerts[key] = geometry.CreateVertex(key * (size / 2));
            }
        }

        void construct(string symmetryType, IntVector3[] faceKeys, IntVector3[] outsideKeys)
        {
            int n = faceKeys.Length;

            foreach (Symmetry symmetry in Symmetry.SymmetryGroup(symmetryType))
            {
                if (symmetry.Apply(outsideKeys).Any(key => cornerUsed(key)))
                {
                    continue;
                }

                IntVector3[] newFaceKeys = symmetry.Apply(faceKeys);
                if (newFaceKeys.All(key => cornerUsed(key)))
                {
                    geometry.CreateFace(newFaceKeys.Select(key => cornerVerts[key]).ToArray());
                }
                else if (n == 4)
                {
                    for (int i = 0; i < 4; i++)
                    {
                        IntVector3[] subFaceKeys = new[] { newFaceKeys[i], newFaceKeys[(i + 1) % 4], newFaceKeys[(i + 2) % 4] };
                        if (subFaceKeys.All(key => cornerUsed(key)))
                        {
                            geometry.CreateFace(subFaceKeys.Select(key => cornerVerts[key]).ToArray());
                        }
                    }
                }
            }
        }

        construct("100",
                  new[] { Key(1, -1, -1), Key(1, 1, -1), Key(1, 1, 1), Key(1, -1, 1) },
                  new IntVector3[0]);
        construct("100",
                  new[] { Key(-1, -1, -1), Key(-1, 1, -1), Key(-1, 1, 1), Key(-1, -1, 1) },
                  new[] { Key(1, -1, -1), Key(1, 1, -1), Key(1, 1, 1), Key(1, -1, 1) });
        construct("110",
                  new[] { Key(-1, 1, -1), Key(-1, 1, 1), Key(1, -1, 1), Key(1, -1, -1) },
                  new[] { Key(1, 1, -1), Key(1, 1, 1) });
        construct("111",
                  new[] { Key(-1, 1, 1), Key(1, -1, 1), Key(1, 1, -1) },
                  new[] { Key(1, 1, 1) });
        construct("111",
                  new[] { Key(1, -1, -1), Key(-1, 1, -1), Key(-1, -1, 1) },
                  new[] { Key(-1, 1, 1), Key(1, -1, 1), Key(1, 1, -1), Key(1, 1, 1) });

        return(geometry);
    }