public static void CullSphereToHemisphere(MeshTool geometry)
    {
        int lastTri = geometry.indices.Length / 3 - 1;
        var vertUse = new byte[geometry.VertexCount];

        for (int i = 0; i <= lastTri; ++i)
        {
            var triangle = geometry.GetTriangle(i);

            Vector3 a = geometry.positions[triangle.x];
            Vector3 b = geometry.positions[triangle.y];
            Vector3 c = geometry.positions[triangle.z];

            if (Vector3.Cross(b - a, c - a).y < 0)
            {
                geometry.SwapTriangle(i, lastTri);
                lastTri -= 1;
                i       -= 1;
            }
            else
            {
                vertUse[triangle.x] += 1;
                vertUse[triangle.y] += 1;
                vertUse[triangle.z] += 1;
            }
        }

        int lastVert = geometry.VertexCount - 1;

        for (int i = 0; i <= lastVert; ++i)
        {
            if (vertUse[i] == 0)
            {
                geometry.SwapVertex(i, lastVert, indices: true);
                Swap(ref vertUse[i], ref vertUse[lastVert]);
                lastVert -= 1;
                i        -= 1;
            }
        }

        geometry.SetVertexCount(lastVert + 1);
        geometry.SetIndexCount((lastTri + 1) * 3, preserve: true, lazy: false);

        geometry.mesh.Clear();
        geometry.Apply(positions: true, normals: true, indices: true);
    }
    public static void HemisphereOld(MeshTool geometry,
                                     float radius,
                                     int complexity,
                                     bool correction)
    {
        int subdivisions = complexity / 6;
        int sides        = (complexity % 6) + 3;

        if (correction)
        {
            // try to make the average of apoth/radius = desired radius
            // * desired = (radius + apoth) / 2
            // apoth = radius * cos(pi / sides)
            // * desired = (radius + radius * cos(pi / sides)) / 2
            // * desired = radius * (1 + cos(pi / sides)) / 2
            // rearrange to determine radius from desired radius
            // * radius = desired / ((1 + cos(pi / sides)) / 2)

            radius = radius * 2 / (1 + Mathf.Cos(Mathf.PI / (sides * (subdivisions + 1))));
        }

        // pyramid
        int vertCount = sides + 1;
        int faceCount = sides;
        int edgeCount = sides * 2;

        for (int i = 0; i < subdivisions; ++i)
        {
            vertCount += edgeCount;
            edgeCount += faceCount * 6;
            faceCount *= 4;
        }

        geometry.SetVertexCount(vertCount);
        geometry.SetIndexCount(faceCount * 3, lazy: true);

        geometry.ActiveVertices = sides + 1;
        geometry.ActiveIndices  = sides * 3;

        geometry.positions[0] = new Vector3(0, radius, 0);
        geometry.normals[0]   = new Vector3(0, 1, 0);

        float da = Mathf.PI * 2 / sides;

        for (int i = 1; i < vertCount; ++i)
        {
            var normal = new Vector3(Mathf.Cos(da * i), 0, Mathf.Sin(da * i));

            geometry.positions[i] = FasterMath.Mul(normal, radius);
            geometry.normals[i]   = normal;
        }

        for (int i = 1; i < sides; ++i)
        {
            geometry.SetTriangle(i, 0, i + 1, i);
        }

        geometry.SetTriangle(0, 0, 1, sides);

        int prevTriCount = geometry.ActiveIndices / 3;
        int nextTriCount = 0;

        var edges = new Dictionary <int, ushort>(prevTriCount * 6);

        for (int i = 0; i < subdivisions; i++)
        {
            nextTriCount = prevTriCount * 4;

            for (int j = 0; j < prevTriCount; ++j)
            {
                var tri = geometry.GetTriangle(j);

                int a = GetMiddlePoint((ushort)tri.x, (ushort)tri.y, geometry, radius, edges);
                int b = GetMiddlePoint((ushort)tri.y, (ushort)tri.z, geometry, radius, edges);
                int c = GetMiddlePoint((ushort)tri.z, (ushort)tri.x, geometry, radius, edges);

                geometry.SetTriangle(prevTriCount + j * 3 + 0, tri.x, a, c);
                geometry.SetTriangle(prevTriCount + j * 3 + 1, tri.y, b, a);
                geometry.SetTriangle(prevTriCount + j * 3 + 2, tri.z, c, b);
                geometry.SetTriangle(j, a, b, c);
            }

            prevTriCount = nextTriCount;
            edges.Clear();
        }

        geometry.mesh.Clear();
        geometry.Apply(positions: true, normals: true, indices: true);
    }
    public static void Sphere(MeshTool geometry,
                              float radius,
                              int level,
                              bool correction)
    {
        var geodesic = geodesics[Mathf.Min(level, geodesics.Count - 1)];

        geometry.Topology = MeshTopology.Triangles;
        geometry.SetVertexCount(geodesic.VertexCount);
        geometry.SetIndexCount(geodesic.FaceCount * 3, lazy: true);

        if (correction)
        {
            radius *= geodesic.Correction;
        }

        if (geodesic.Base == Solid.Tetrahedron)
        {
            Tetrahedron(geometry, radius);
        }
        if (geodesic.Base == Solid.Octahedron)
        {
            Octahedron(geometry, radius);
        }
        if (geodesic.Base == Solid.Icosohedron)
        {
            Icosohedron(geometry, radius);
        }

        int prevTriCount = geometry.ActiveIndices / 3;
        int nextTriCount = 0;

        // only need as much edge memory as the last iteration, which adds an six
        // edges per triangle of the previous iteration
        int penulTriCount = geodesic.FaceCount / 4;
        var edges         = new Dictionary <int, ushort>(penulTriCount * 6);

        for (int i = 0; i < geodesic.Subdivisions; i++)
        {
            nextTriCount = prevTriCount * 4;

            for (int j = 0; j < prevTriCount; ++j)
            {
                var tri = geometry.GetTriangle(j);

                int a = GetMiddlePoint((ushort)tri.x, (ushort)tri.y, geometry, radius, edges);
                int b = GetMiddlePoint((ushort)tri.y, (ushort)tri.z, geometry, radius, edges);
                int c = GetMiddlePoint((ushort)tri.z, (ushort)tri.x, geometry, radius, edges);

                geometry.SetTriangle(prevTriCount + j * 3 + 0, tri.x, a, c);
                geometry.SetTriangle(prevTriCount + j * 3 + 1, tri.y, b, a);
                geometry.SetTriangle(prevTriCount + j * 3 + 2, tri.z, c, b);
                geometry.SetTriangle(j, a, b, c);
            }

            prevTriCount = nextTriCount;
            edges.Clear();
        }

        geometry.mesh.Clear();
        geometry.Apply(positions: true, normals: true, indices: true);
    }