//semplifica l'octree
    public OctreeNode SimplifyOctree(OctreeNode node, float threshold)
    {
        if (node == null)
        {
            return(null);
        }

        if (node.type != NodeType.INTERNAL)
        {
            // can't simplify!
            return(node);
        }

        QefSolver qef = new QefSolver();

        int[] signs = new int[8] {
            -1, -1, -1, -1, -1, -1, -1, -1,
        };
        int  midsign       = -1;
        int  edgeCount     = 0;
        bool isCollapsible = true;

        for (int i = 0; i < 8; i++)
        {
            node.children[i] = SimplifyOctree(node.children[i], threshold);

            if (node.children[i] != null)
            {
                OctreeNode child = node.children[i];
                if (child.type == NodeType.INTERNAL)
                {
                    isCollapsible = false;
                }
                else
                {
                    qef.Add(child.nodeInfo.qef);
                    midsign  = (child.nodeInfo.corners >> (7 - i)) & 1;
                    signs[i] = (child.nodeInfo.corners >> i) & 1;
                    edgeCount++;
                }
            }
        }
        if (!isCollapsible)
        {
            // at least one child is an internal node, can't collapse
            return(node);
        }

        Vector3 qefPosition = new Vector3();
        float   error       = qef.Solve(qefPosition, QEF_ERROR, QEF_SWEEPS, QEF_ERROR);
        Vector3 position    = new Vector3(qefPosition.x, qefPosition.y, qefPosition.z);

        // at this point the masspoint will actually be a sum, so divide to make it the average
        if (error > threshold)
        {
            // this collapse breaches the threshold
            return(node);
        }

        if (position.x < node.min.x || position.x > (node.min.x + node.size) ||
            position.y < node.min.y || position.y > (node.min.y + node.size) ||
            position.z < node.min.z || position.z > (node.min.z + node.size))
        {
            position = qef.GetMassPoint();
        }

        // change the node from an internal node to a 'pseudo leaf' node
        NodeInfo info = new NodeInfo();

        for (int i = 0; i < 8; i++)
        {
            if (signs[i] == -1)
            {
                // Undetermined, use centre sign instead
                info.corners |= (midsign << i);
            }
            else
            {
                info.corners |= (signs[i] << i);
            }
        }
        info.avgNormal = new Vector3(0, 0, 0);
        for (int i = 0; i < 8; i++)
        {
            if (node.children[i] != null)
            {
                if (node.children[i].type == NodeType.PSEUDO ||
                    node.children[i].type == NodeType.LEAF)
                {
                    info.avgNormal += node.children[i].nodeInfo.avgNormal;
                }
            }
        }
        info.avgNormal = info.avgNormal.normalized;
        info.position  = position;
        info.qef       = qef.GetData();

        for (int i = 0; i < 8; i++)
        {
            DestroyOctree(node.children[i]);
            node.children[i] = null;
        }
        node.type = NodeType.PSEUDO;
        node.nodeInfo.Set(info);
        return(node);
    }
    //costruisce il nodo terminale (foglia) calcolando le NodeInfo per la generazione della mesh
    public OctreeNode BuildLeaf(OctreeNode leaf, int size)
    {
        if (leaf == null || leaf.size != size)
        {
            return(null);
        }

        int corners = 0;

        for (int i = 0; i < 8; i++)
        {
            Vector3 cornerPos = leaf.min + (CHILD_MIN_OFFSETS[i] * size);
            float   density   = Density.DensityFunc(cornerPos);
            int     material  = density < 0.0f ? SOLID : AIR;
            corners |= (material << i);
        }

        if (corners == 0 || corners == 255)
        {
            leaf = null;
            return(null);
        }

        int       edgeCount     = 0;
        Vector3   averageNormal = new Vector3();
        QefSolver qef           = new QefSolver();

        for (int i = 0; i < 12 && edgeCount < MAX_CROSSINGS; i++)
        {
            int c1 = edgevmap[i][0];
            int c2 = edgevmap[i][1];

            int m1 = (corners >> c1) & 1;
            int m2 = (corners >> c2) & 1;

            if ((m1 == AIR && m2 == AIR) || (m1 == SOLID && m2 == SOLID))
            {
                continue;
            }

            Vector3 p1 = leaf.min + (CHILD_MIN_OFFSETS[c1] * size);
            Vector3 p2 = leaf.min + (CHILD_MIN_OFFSETS[c2] * size);
            Vector3 p  = ApproximateZeroCrossingPosition(p1, p2, 8);
            Vector3 n  = CalculateSurfaceNormal(p);
            qef.Add(p.x, p.y, p.z, n.x, n.y, n.z);

            averageNormal += n;
            edgeCount++;
        }

        Vector3 qefPosition = new Vector3();

        qef.Solve(qefPosition, QEF_ERROR, QEF_SWEEPS, QEF_ERROR);

        NodeInfo info = new NodeInfo();

        info.index    = -1;
        info.corners  = 0;
        info.position = qefPosition;
        info.qef      = qef.GetData();

        Vector3 min = leaf.min;
        Vector3 max = new Vector3(leaf.min.x + leaf.size, leaf.min.y + leaf.size, leaf.min.z + leaf.size);

        if (info.position.x < min.x || info.position.x > max.x ||
            info.position.y < min.y || info.position.y > max.y ||
            info.position.z < min.z || info.position.z > max.z)
        {
            info.position = qef.GetMassPoint();
        }

        info.avgNormal = (averageNormal / (float)edgeCount).normalized;
        info.corners   = corners;

        leaf.type     = NodeType.LEAF;
        leaf.nodeInfo = info;
        return(leaf);
    }