(* ::Package:: *)

(* ::Title:: *)
(*LevelSet*)


(* ::Subtitle:: *)
(*Create a level set representation of a region.*)


(* ::Text:: *)
(*Copyright Contributors to the OpenVDB Project*)
(*SPDX-License-Identifier: Apache-2.0*)


(* ::Section:: *)
(*Initialization & Usage*)


Package["OpenVDBLink`"]


PackageExport["OpenVDBLevelSet"]


OpenVDBLevelSet::usage = "OpenVDBLevelSet[reg] creates a signed distance level set representation of reg.";


(* ::Section:: *)
(*OpenVDBLevelSet*)


(* ::Subsection::Closed:: *)
(*OpenVDBLevelSet*)


Options[OpenVDBLevelSet] = {"Creator" :> $OpenVDBCreator, "Name" -> None, "ScalarType" -> "Float"};


OpenVDBLevelSet[args___] /; !CheckArgs[OpenVDBLevelSet[args], {1, 3}] = $Failed;


OpenVDBLevelSet[args___] :=
    With[{res = pOpenVDBLevelSet[args]},
        res /; res =!= $Failed
    ]


OpenVDBLevelSet[args___] := mOpenVDBLevelSet[args]


(* ::Subsection::Closed:: *)
(*pOpenVDBLevelSet*)


Options[pOpenVDBLevelSet] = Options[OpenVDBLevelSet];


pOpenVDBLevelSet[expr_, opts:OptionsPattern[]] := pOpenVDBLevelSet[expr, $OpenVDBSpacing, $OpenVDBHalfWidth, opts]


pOpenVDBLevelSet[expr_, spacing_?Positive, opts:OptionsPattern[]] := pOpenVDBLevelSet[expr, spacing, $OpenVDBHalfWidth, opts]


pOpenVDBLevelSet[expr_, spacing_?Positive, width_?Positive, OptionsPattern[]] :=
    Block[{reg, type, vdb},
        reg = processSDFInput[expr];
        type = OptionValue["ScalarType"];
        (
            vdb = iOpenVDBLevelSet[reg, spacing, width, type, True];
            (
                OpenVDBSetProperty[vdb, "Creator" -> OptionValue["Creator"]];
                OpenVDBSetProperty[vdb, "Name" -> OptionValue["Name"]];

                vdb

            ) /; OpenVDBScalarGridQ[vdb]

        ) /; reg =!= $Failed && validScalarTypeQ[type]
    ]


pOpenVDBLevelSet[___] = $Failed;


(* ::Subsection::Closed:: *)
(*iOpenVDBLevelSet*)


(* ::Subsubsection::Closed:: *)
(*Surface mesh / complex*)


iOpenVDBLevelSet[mr_?triangleSurfaceMeshQ, args__] :=
    triangleSurfaceComplexSignedDistanceField[MeshCoordinates[mr], Join @@ MeshCells[mr, 2, "Multicells" -> True][[All, 1]], mr["ConnectivityMatrix"[1, 2]], args]


iOpenVDBLevelSet[{coords_?coordinatesQ, pcells:_List|_Polygon}, args__] :=
    With[{cells = stripPolygonCells[pcells]},
        triangleSurfaceComplexSignedDistanceField[coords, cells, None, args] /; validTriangleCellsQ[cells, Length[coords]]
    ]


iOpenVDBLevelSet[mr_?surfaceMesh3DQ, args__] :=
    Block[{tri},
        tri = Quiet[laxMeshBlock @ Region`Mesh`TriangulateMeshCells[mr, MaxCellMeasure -> \[Infinity]]];

        iOpenVDBLevelSet[tri, args] /; triangleSurfaceMeshQ[tri]
    ]


(* ::Subsubsection::Closed:: *)
(*Surface with thickness*)


iOpenVDBLevelSet[{mr_MeshRegion?triangleSurfaceMeshQ, r_?Positive}, args__] :=
    thickSurfaceSignedDistanceField[MeshCoordinates[mr], Join @@ MeshCells[mr, 2, "Multicells" -> True][[All, 1]], r, args]


iOpenVDBLevelSet[{coords_?coordinatesQ, pcells:_List|_Polygon, r_?Positive}, args__] :=
    With[{cells = stripPolygonCells[pcells]},
        thickSurfaceSignedDistanceField[coords, cells, r, args] /; validTriangleCellsQ[cells, Length[coords]]
    ]


thickSurfaceSignedDistanceField[coords_, cells_, r_, spacing_, width_, type_, signedQ_] :=
    Block[{vdb = OpenVDBCreateGrid[spacing, type]},
        vdb["offsetSurfaceLevelSet"[coords, cells-1, r, spacing, width, signedQ]];

        vdb
    ]


(* ::Subsubsection::Closed:: *)
(*Tube regions*)


iOpenVDBLevelSet[capsule_CapsuleShape?ConstantRegionQ, args__] := iOpenVDBLevelSet[Tube @@ capsule, args]


iOpenVDBLevelSet[Tube[ptspec_, r_?Positive], args__] :=
    Block[{segdata = tubeSegmentData[ptspec]},
        tubeComplexSignedDistanceField[Sequence @@ segdata, r, args] /; segdata =!= $Failed
    ]


iOpenVDBLevelSet[tube_Tube, args__] :=
    Block[{mr = Quiet[DiscretizeGraphics[tube]]},
        iOpenVDBLevelSet[mr, args] /; MeshRegionQ[mr]
    ]


iOpenVDBLevelSet[{mr_MeshRegion?lineMesh3DQ, r_?Positive}, args__] :=
    tubeComplexSignedDistanceField[MeshCoordinates[mr], Join @@ MeshCells[mr, 1, "Multicells" -> True][[All, 1]], r, args]


iOpenVDBLevelSet[{coords_?coordinatesQ, lcells:_List|_Line, r_?Positive}, args__] :=
    With[{cells = stripLineCells[lcells]},
        tubeComplexSignedDistanceField[coords, cells, r, args] /; validLineCellsQ[cells, Length[coords]]
    ]


tubeComplexSignedDistanceField[coords_, cells_, r_, spacing_, width_, type_, signedQ_] :=
    Block[{vdb = OpenVDBCreateGrid[spacing, type], coords2, cells2},
        coords2 = Join[coords, coords[[cells[[All, 2]]]] + .0001];
        cells2 = Transpose[Append[Transpose[cells], Range[Length[coords]+1, Length[coords]+Length[cells]]]]-1;

        vdb["offsetSurfaceLevelSet"[coords2, cells2, r, spacing, width, signedQ]];

        vdb
    ]


(* ::Subsubsection::Closed:: *)
(*Torus*)


iOpenVDBLevelSet[torus:(Torus|FilledTorus)[___]?ConstantRegionQ, spacing_, args__] :=
    Block[{torusspec, c, rin, rout, rmid, rtube, n, pts},
        torusspec = torusData @@ torus;
        (
            {c, rin, rout} = torusspec;

            rmid = N[Mean[{rin, rout}]];
            rtube = 0.5(rout - rin);
            n = Ceiling[2\[Pi]*rmid/spacing];

            pts = Append[c[[3]]] /@ CirclePoints[c[[1 ;; 2]], rmid, n];
            AppendTo[pts, First[pts]];

            iOpenVDBLevelSet[Tube[pts, rtube], spacing, args]

        ) /; torusspec =!= $Failed
    ]


torusData[] = {{0, 0, 0}, 0.5, 1.0};
torusData[c_?VectorQ] := {c, 0.5, 1.0}
torusData[c_?VectorQ, {rin_?Positive, rout_?Positive}] /; rout > rin := {c, rin, rout}
torusData[___] = $Failed;


(* ::Subsubsection::Closed:: *)
(*Ball*)


iOpenVDBLevelSet[ball:(Ball|Sphere)[___]?ConstantRegionQ, spacing_, width_, type_, signedQ_] /; RegionEmbeddingDimension[ball] == 3 :=
    Block[{ballspec, c, r, vdb},
        ballspec = singleBallData @@ ball;
        (
            {c, r} = ballspec;
            vdb = OpenVDBCreateGrid[spacing, type];
            vdb["ballLevelSet"[c, r, spacing, width, signedQ]];

            vdb

        ) /; ballspec =!= $Failed
    ]


singleBallData[] = {{0, 0, 0}, 1};
singleBallData[3] = {{0, 0, 0}, 1}
singleBallData[c_?VectorQ] := {c, 1}
singleBallData[c_?VectorQ, r_?Positive] := {c, r}
singleBallData[___] = $Failed;


(* ::Subsubsection::Closed:: *)
(*SphericalShell*)


iOpenVDBLevelSet[shell:HoldPattern[SphericalShell][___]?ConstantRegionQ, args___] /; RegionEmbeddingDimension[shell] == 3 :=
    Block[{shellspec, c, r1, r2, ballout, ballin},
        shellspec = sphericalShellData @@ shell;
        (
            {c, {r1, r2}} = shellspec;
            ballout = iOpenVDBLevelSet[Ball[c, r2], args];
            (
                ballin = iOpenVDBLevelSet[Ball[c, r1], args];

                OpenVDBDifferenceFrom[ballout, ballin] /; OpenVDBScalarGridQ[ballin]

            ) /; OpenVDBScalarGridQ[ballout]

        ) /; shellspec =!= $Failed
    ]


sphericalShellData[] = {{0, 0, 0}, {1/2, 1}};
sphericalShellData[{r1_, r2_}] := {{0, 0, 0}, {r1, r2}}
sphericalShellData[c_?VectorQ] := {c, {1/2, 1}}
sphericalShellData[c_?VectorQ, r_?Positive] := {c, {r/2, r}}
sphericalShellData[c_?VectorQ, {r1_, r2_}] := {c, {r1, r2}}
sphericalShellData[r_] := {{0, 0, 0}, {r/2, r}}
sphericalShellData[___] = $Failed;


(* ::Subsubsection::Closed:: *)
(*Cuboid*)


iOpenVDBLevelSet[cuboid_Cuboid?ConstantRegionQ, spacing_, width_, type_, signedQ_] :=
    Block[{bds, vdb},
        bds = RegionBounds[cuboid];
        (
            vdb = OpenVDBCreateGrid[spacing, type];
            vdb["cuboidLevelSet"[bds, spacing, width, signedQ]];

            vdb

        ) /; And @@ Less @@@ bds
    ]


iOpenVDBLevelSet[hex_Hexahedron?ConstantRegionQ, args__] /; Volume[hex] == Volume[BoundingRegion[hex]] := iOpenVDBLevelSet[BoundingRegion[hex], args]


(* ::Subsubsection::Closed:: *)
(*Special polyhedra*)


iOpenVDBLevelSet[poly_?specialPolyhedonQ, args__] :=
    With[{data = polyhedronTriangleData[poly]},
        (
            triangleSurfaceComplexSignedDistanceField[##, None, args, False]& @@ data

        ) /; data =!= $Failed
    ]


(* ::Subsubsection::Closed:: *)
(*EmptyRegion / FullRegion*)


iOpenVDBLevelSet[EmptyRegion[3], spacing_, width_, ___] := OpenVDBCreateGrid[spacing, "BackgroundValue" -> spacing*width, "GridClass" -> $gridLevelSet]


iOpenVDBLevelSet[FullRegion[3], spacing_, __] := OpenVDBCreateGrid[spacing, "BackgroundValue" -> -10^12., "GridClass" -> $gridLevelSet]


(* ::Subsubsection::Closed:: *)
(*BooleanRegion*)


(* ::Text:: *)
(*Would be nice to allow any Boolean function, but then we will need Boolean operations that don't clear the second input, which we don't have right now.*)


iOpenVDBLevelSet[reg:BooleanRegion[bfunc_, regs_]?ConstantRegionQ, args__] /; RegionEmbeddingDimension[reg] == 3 :=
    Block[{op},
        op = booleanHead[bfunc, Length[regs]];
        (
            If[op === Or,
                unionRegionSDFs[regs, args],
                intersectRegionSDFs[regs, args]
            ]

        ) /; op =!= $Failed
    ]


booleanHead[bfunc_, n_] :=
    Block[{vars, expr},
        vars = \[FormalX] /@ Range[n];
        expr = bfunc @@ vars;
        Which[
            expr === Or @@ vars, Or,
            expr === And @@ vars, And,
            True, $Failed
        ]
    ]


unionRegionSDFs[args__] := booleanRegionSDFs["gridUnion", args]
intersectRegionSDFs[args__] := booleanRegionSDFs["gridIntersection", args]
booleanRegionSDFs[boolVDB_, regs_, args__] :=
    Block[{vdb1, vdb2},
        vdb1 = iOpenVDBLevelSet[First[regs], args];
        If[vdb1 === $Failed,
            Return[$Failed]
        ];

        Do[
            vdb2 = iOpenVDBLevelSet[reg, args];
            If[vdb2 === $Failed,
                OpenVDBDeleteGrid[vdb1];
                Return[$Failed]
            ];
            vdb1[boolVDB[vdb2[[1]]]];,
            {reg, Rest[regs]}
        ];

        vdb1
    ]


(* ::Subsubsection::Closed:: *)
(*TransformedRegion*)


iOpenVDBLevelSet[treg:TransformedRegion[reg_, tfunc_TransformationFunction]?ConstantRegionQ, args__] /; RegionEmbeddingDimension[treg] == 3 :=
    With[{res = iOpenVDBLevelSet[reg, args]},
        OpenVDBTransform[res, tfunc] /; res =!= $Failed
    ]


(* ::Subsubsection::Closed:: *)
(*RegionBoundary*)


iOpenVDBLevelSet[RegionBoundary[reg_], args__, _] := iOpenVDBLevelSet[reg, args, False]


(* ::Subsubsection::Closed:: *)
(*General 3D region*)


iOpenVDBLevelSet[reg_?ConstantRegionQ, args__] /; RegionEmbeddingDimension[reg] == 3 :=
    Block[{bmr},
        bmr = Quiet[laxMeshBlock @ BoundaryDiscretizeRegion[reg]];

        iOpenVDBLevelSet[bmr, args] /; BoundaryMeshRegionQ[bmr]
    ]


(* ::Subsubsection::Closed:: *)
(*Generic behavior*)


iOpenVDBLevelSet[rspec_List, args__] /; VectorQ[rspec, !NumericQ[#]&] := unionRegionSDFs[rspec, args]


iOpenVDBLevelSet[___] = $Failed;


(* ::Subsection::Closed:: *)
(*Argument conform & completion*)


SyntaxInformation[OpenVDBLevelSet] = {"ArgumentsPattern" -> {_, _., _., OptionsPattern[]}};


(* ::Subsection::Closed:: *)
(*Messages*)


Options[mOpenVDBLevelSet] = Options[OpenVDBLevelSet];


mOpenVDBLevelSet[expr_, ___] /; !validToLevelSetQ[expr] :=
    (
        Message[OpenVDBLevelSet::reg, expr, 1];
        $Failed
    )


mOpenVDBLevelSet[_, OptionsPattern[]] /; !TrueQ[$OpenVDBSpacing > 0] :=
    (
        Message[OpenVDBLevelSet::novoxsz];
        $Failed
    )


mOpenVDBLevelSet[_, _., OptionsPattern[]] /; !TrueQ[$OpenVDBHalfWidth > 0] :=
    (
        Message[OpenVDBLevelSet::nowidth];
        $Failed
    )


mOpenVDBLevelSet[_, vx_, ___] /; !TrueQ[vx > 0] && !OptionQ[vx] :=
    (
        Message[OpenVDBLevelSet::nonpos, vx, 2];
        $Failed
    )


mOpenVDBLevelSet[_, _, w_, ___] /; !TrueQ[w > 0] && !OptionQ[w] :=
    (
        Message[OpenVDBLevelSet::nonpos, w, 3];
        $Failed
    )


mOpenVDBLevelSet[__, OptionsPattern[]] :=
    Block[{validQ = validScalarTypeQ[OptionValue["ScalarType"]]},
        (
            Message[OpenVDBLevelSet::nonscalar, OptionValue["ScalarType"]];
            $Failed
        ) /; !validQ
    ]


mOpenVDBLevelSet[___] = $Failed;


OpenVDBLevelSet::reg = "`1` at position `2` is not a constant 3D region.";


OpenVDBLevelSet::novoxsz = "No grid spacing is provided since $OpenVDBSpacing is not a positive number.";
OpenVDBLevelSet::nowidth = "No half band width is provided since $OpenVDBHalfWidth is not a positive number."


OpenVDBLevelSet::nonpos = "`1` at position `2` is expected to be a positive number";


OpenVDBLevelSet::nonscalar = "`1` is not a valid setting for \"ScalarType\". Evaluate OpenVDBGridTypes[\"Scalar\"] for a list of valid types.";


(* ::Section:: *)
(*Utilities*)


(* ::Subsection::Closed:: *)
(*Coordinate & cell utilities*)


validTriangleCellsQ[cells_, max_] := MatrixQ[cells, IntegerQ] && Length[cells[[1]]] == 3 && Min[cells] >= 1 && Max[cells] <= max
validLineCellsQ[cells_, max_] := MatrixQ[cells, IntegerQ] && Length[cells[[1]]] == 2 && Min[cells] >= 1 && Max[cells] <= max


stripPolygonCells[Polygon[cells_]] := cells
stripPolygonCells[pcells:{__Polygon}] :=
    With[{cells = pcells[[All, 1]]},
        Which[
            VectorQ[cells, VectorQ], cells,
            VectorQ[cells, MatrixQ], Join @@ cells,
            VectorQ[cells, ArrayQ[#, 1|2]&], Join[Join @@ Select[cells, MatrixQ], Select[cells, VectorQ]],
            True, $Failed
        ]
    ]
stripPolygonCells[expr_] := expr


stripLineCells[Line[cells_]] := cells
stripLineCells[lcells:{__Line}] :=
    With[{cells = lcells[[All, 1]]},
        Which[
            VectorQ[cells, VectorQ], cells,
            VectorQ[cells, MatrixQ], Join @@ cells,
            VectorQ[cells, ArrayQ[#, 1|2]&], Join[Join @@ Select[cells, MatrixQ], Select[cells, VectorQ]],
            True, $Failed
        ]
    ]
stripLineCells[{i1_Integer, i2_Integer}] := {{i1, i2}}
stripLineCells[expr_] := expr


(* ::Subsection::Closed:: *)
(*processSDFInput*)


processSDFInput[expr_] :=
    With[{res = Join @@ expandMultisetRegion /@ If[ListQ[expr], Identity, List][expr]},
        If[Length[res] == 1,
            First[res],
            res
        ]
    ];


expandMultisetRegion[(h:Ball | Sphere)[pts_?MatrixQ, r___]?ConstantRegionQ] /; Length[pts[[1]]] == 3 := h[#, r]& /@ pts


expandMultisetRegion[reg:(Cone | Cylinder)[pts_?ArrayQ, ___]?ConstantRegionQ] /; MatchQ[Dimensions[pts], {_, _, 3}] := Thread[reg]


expandMultisetRegion[reg:(Hexahedron | Prism | Pyramid | Tetrahedron)[pts_?ArrayQ]?ConstantRegionQ] /; MatchQ[Dimensions[pts], {_, _, 3}] := Thread[reg]


expandMultisetRegion[reg_] := {reg}


(* ::Subsection::Closed:: *)
(*tubeSegmentData*)


tubeSegmentData[pts_List] /; MatrixQ[pts, realQ] && Length[pts[[1]]] == 3 := {pts, Partition[Range[Length[pts]], 2, 1]}


tubeSegmentData[pts_List] /; VectorQ[pts, MatrixQ[#, realQ] && Length[#[[1]]] == 3&] :=
    {
        Join @@ pts,
        Join @@ Plus[Partition[Range[Length[#]], 2, 1]& /@ pts, Prepend[Most[Accumulate[Length /@ pts]], 0]]
    }


tubeSegmentData[pts_List] :=
    With[{data = tubeSegmentData /@ pts},
        (
            {
                Join @@ data[[All, 1]],
                Join @@ Plus[data[[All, 2]], Prepend[Most[Accumulate[Length /@ data[[All, 1]]]], 0]]
            }

        ) /; FreeQ[data, $Failed, {1}]
    ]


tubeSegmentData[Line[pts_]] := tubeSegmentData[pts]


tubeSegmentData[bc:(_BSplineCurve|_BezierCurve)] :=
    With[{mr = Quiet[DiscretizeGraphics[bc]]},
        {MeshCoordinates[mr], Join @@ MeshCells[mr, 1, "Multicell" -> True][[All, 1]]} /; MeshRegionQ[mr] && RegionDimension[mr] == 1
    ]


tubeSegmentData[___] = $Failed


(* ::Subsection::Closed:: *)
(*polyhedronTriangleData*)


specialPolyhedonQ[reg_] := TrueQ[specialPolyhedonHead[Head[reg]]] && ConstantRegionQ[reg] && RegionEmbeddingDimension[reg] === 3
specialPolyhedonHead[Cube] = True;
specialPolyhedonHead[Cuboid] = True;
specialPolyhedonHead[Dodecahedron] = True;
specialPolyhedonHead[Hexahedron] = True;
specialPolyhedonHead[Icosahedron] = True;
specialPolyhedonHead[Octahedron] = True;
specialPolyhedonHead[Parallelepiped] = True;
specialPolyhedonHead[Prism] = True;
specialPolyhedonHead[Pyramid] = True;
specialPolyhedonHead[Simplex] = True;
specialPolyhedonHead[Tetrahedron] = True;


polyhedronTriangleData[c_Cube] := cubeTriangleData @@ c
polyhedronTriangleData[c_Cuboid] := cuboidTriangleData @@ c
polyhedronTriangleData[d_Dodecahedron] := dodecahedronTriangleData @@ d
polyhedronTriangleData[h_Hexahedron] := hexahedronTriangleData @@ h
polyhedronTriangleData[i_Icosahedron] := icosahedronTriangleData @@ i
polyhedronTriangleData[o_Octahedron] := octahedronTriangleData @@ o
polyhedronTriangleData[p_Parallelepiped] := parallelepipedTriangleData @@ p
polyhedronTriangleData[p_Prism] := prismTriangleData @@ p
polyhedronTriangleData[p_Pyramid] := pyramidTriangleData @@ p
polyhedronTriangleData[s_Simplex] := simplexTriangleData @@ s
polyhedronTriangleData[t_Tetrahedron] := tetrahedronTriangleData @@ t
polyhedronTriangleData[___] = $Failed;


platonicSpecs[args___] :=
    Block[{data = {args}, center, angles, l, cuboidres},
        center = {0, 0, 0};
        angles = {0, 0};
        l = 1;

        Switch[Prepend[If[ListQ[#], Length[#], 0]& /@ data, Length[data]],
            {0}, Null,
            {1, 0}, {l} = data,
            {1, 2}, {angles} = data,
            {1, 3}, {center} = data,
            {2, 2, 0}, {angles, l} = data,
            {2, 3, 0}, {center, l} = data,
            {2, 3, 2}, {center, angles} = data,
            {3, __}, {center, angles, l} = data,
            _, Return[$Failed]
        ];

        {center, angles, l}
    ]


rotatePolyhedronCoordinates[coords_, {\[Theta]_, \[Phi]_}, center_] := (RotationTransform[\[Phi], {0, 1, 0}, center] @* RotationTransform[\[Theta], {0, 0, 1}, center])[coords]


cubeTriangleData[args___] :=
    Block[{center, angles, l, cuboidres},
        {center, angles, l} = platonicSpecs[args];

        cuboidres = cuboidTriangleData[center - 0.5l{1, 1, 1}, center + 0.5l{1, 1, 1}];
        If[angles =!= {0, 0},
            cuboidres[[1]] = rotatePolyhedronCoordinates[cuboidres[[1]], angles, center];
        ];

        cuboidres
    ]


cuboidTriangleData[l_] := cuboidTriangleData[l, l+1]
cuboidTriangleData[l_, u_] :=
    {
        Tuples[Transpose[{l, u}]],
        {{1,2,4},{1,4,3},{1,5,6},{1,6,2},{1,7,5},{1,3,7},{2,8,4},{2,6,8},{3,4,8},{3,8,7},{5,7,6},{6,7,8}}
    }


$dodecacoords = {{-1.3763819204711736, 0., 0.2628655560595668}, {1.3763819204711736, 0., -0.2628655560595668}, {-0.42532540417601994, -1.3090169943749475, 0.2628655560595668}, {-0.42532540417601994, 1.3090169943749475, 0.2628655560595668}, {1.1135163644116066, -0.8090169943749475, 0.2628655560595668}, {1.1135163644116066, 0.8090169943749475, 0.2628655560595668}, {-0.2628655560595668, -0.8090169943749475, 1.1135163644116066}, {-0.2628655560595668, 0.8090169943749475, 1.1135163644116066}, {-0.6881909602355868, -0.5, -1.1135163644116068}, {-0.6881909602355868, 0.5, -1.1135163644116068}, {0.6881909602355868, -0.5, 1.1135163644116066}, {0.6881909602355868, 0.5, 1.1135163644116066}, {0.85065080835204, 0., -1.1135163644116068}, {-1.1135163644116068, -0.8090169943749475, -0.2628655560595668}, {-1.1135163644116068, 0.8090169943749475, -0.2628655560595668}, {-0.8506508083520399, 0., 1.1135163644116066}, {0.2628655560595668, -0.8090169943749475, -1.1135163644116068}, {0.2628655560595668, 0.8090169943749475, -1.1135163644116068}, {0.42532540417601994, -1.3090169943749475, -0.2628655560595668}, {0.42532540417601994, 1.3090169943749475, -0.2628655560595668}};
dodecahedronTriangleData[args___] :=
    Block[{center, angles, l, dcoords, dcells},
        {center, angles, l} = platonicSpecs[args];

        dcoords = Transpose[Transpose[l*$dodecacoords] + center];
        If[angles =!= {0, 0},
            dcoords = rotatePolyhedronCoordinates[dcoords, angles, center];
        ];

        dcells = {{15,10,9},{15,9,14},{15,14,1},{2,6,12},{2,12,11},{2,11,5},{5,11,7},
            {5,7,3},{5,3,19},{11,12,8},{11,8,16},{11,16,7},{12,6,20},{12,20,4},{12,4,8},
            {6,2,13},{6,13,18},{6,18,20},{2,5,19},{2,19,17},{2,17,13},{4,20,18},{4,18,10},
            {4,10,15},{18,13,17},{18,17,9},{18,9,10},{17,19,3},{17,3,14},{17,14,9},{3,7,16},
            {3,16,1},{3,1,14},{16,8,4},{16,4,15},{16,15,1}};

        {dcoords, dcells}
    ]


hexahedronTriangleData[pts_] := {pts, {{1,3,2},{1,4,3},{1,6,5},{1,2,6},{1,5,8},{1,8,4},{3,4,8},{3,8,7},{2,3,7},{2,7,6},{5,6,8},{6,7,8}}}


$icosacoords = {{0., 0., -0.9510565162951536}, {0., 0., 0.9510565162951536}, {-0.85065080835204, 0., -0.42532540417601994}, {0.85065080835204, 0., 0.42532540417601994}, {0.6881909602355868, -0.5, -0.42532540417601994}, {0.6881909602355868, 0.5, -0.42532540417601994}, {-0.6881909602355868, -0.5, 0.42532540417601994}, {-0.6881909602355868, 0.5, 0.42532540417601994}, {-0.2628655560595668, -0.8090169943749475, -0.42532540417601994}, {-0.2628655560595668, 0.8090169943749475, -0.42532540417601994}, {0.2628655560595668, -0.8090169943749475, 0.42532540417601994}, {0.2628655560595668, 0.8090169943749475, 0.42532540417601994}};
icosahedronTriangleData[args___] :=
    Block[{center, angles, l, icoords, icells},
        {center, angles, l} = platonicSpecs[args];

        icoords = Transpose[Transpose[l*$icosacoords] + center];
        If[angles =!= {0, 0},
            icoords = rotatePolyhedronCoordinates[icoords, angles, center];
        ];

        icells = {{2,12,8},{2,8,7},{2,7,11},{2,11,4},{2,4,12},{5,9,1},{6,5,1},{10,6,1},
            {3,10,1},{9,3,1},{12,10,8},{8,3,7},{7,9,11},{11,5,4},{4,6,12},{5,11,9},
            {6,4,5},{10,12,6},{3,8,10},{9,7,3}};

        {icoords, icells}
    ]


$octacoords = {{0., 0.7071067811865475, 0.}, {0.7071067811865475, 0., 0.}, {0., -0.7071067811865475, 0.}, {-0.7071067811865475, 0., 0.}, {0., 0., 0.7071067811865475}, {0., 0., -0.7071067811865475}};
octahedronTriangleData[args___] :=
    Block[{center, angles, l, ocoords, ocells},
        {center, angles, l} = platonicSpecs[args];

        ocoords = Transpose[Transpose[l*$octacoords] + center];
        If[angles =!= {0, 0},
            ocoords = rotatePolyhedronCoordinates[ocoords, angles, center];
        ];

        ocells = {{5,2,1},{5,3,2},{5,4,3},{4,5,1},{2,6,1},{2,3,6},{4,6,3},{1,6,4}};

        {ocoords, ocells}
    ]


parallelepipedTriangleData[center_, {v1_, v2_, v3_}] :=
    {
        Transpose[Transpose[{{0,0,0}, v1, v2, v1+v2, v3, v1+v3, v2+v3, v1+v2+v3}] + center],
        {{1,2,4},{1,4,3},{1,5,6},{1,6,2},{1,7,5},{1,3,7},{2,8,4},{2,6,8},{3,4,8},{3,8,7},{5,7,6},{6,7,8}}
    }


prismTriangleData[pts_] := {pts, {{1,3,2},{4,5,6},{2,3,6},{2,6,5},{1,2,5},{1,5,4},{1,6,3},{1,4,6}}}


pyramidTriangleData[pts_] := {pts, {{1,2,5},{2,3,5},{3,4,5},{4,1,5},{1,3,2},{1,4,3}}}


simplexTriangleData[3] = simplexTriangleData[{{0,0,0},{1,0,0},{0,1,0},{0,0,1}}]
simplexTriangleData[pts_] := tetrahedronTriangleData[pts]


$tetcoords = {{0., 0., 0.6123724356957945}, {-0.2886751345948129, -0.5, -0.20412414523193154}, {-0.2886751345948129, 0.5, -0.20412414523193154}, {0.5773502691896258, 0., -0.20412414523193154}};
tetrahedronTriangleData[pts_?MatrixQ] := {pts, {{1,2,4},{1,3,2},{1,4,3},{2,3,4}}}
tetrahedronTriangleData[args___] :=
    Block[{center, angles, l, tetres},
        {center, angles, l} = platonicSpecs[args];

        tetres = tetrahedronTriangleData[Transpose[Transpose[l*$tetcoords] + center]];
        If[angles =!= {0, 0},
            tetres[[1]] = rotatePolyhedronCoordinates[tetres[[1]], angles, center];
        ];

        tetres
    ]


(* ::Subsection::Closed:: *)
(*triangleSurfaceComplexSignedDistanceField*)


triangleSurfaceComplexSignedDistanceField[coords_, cells_, C12_, spacing_, width_, type_, signedQ_, nestingQ_:True] :=
    Block[{vdb, nestedcells, nestedvdb},
        vdb = OpenVDBCreateGrid[spacing, type];
        nestedcells = If[TrueQ[nestingQ] && ArrayQ[C12],
            nestedComponentHierarchy[coords, cells, C12],
            {cells}
        ];

        vdb["meshLevelSet"[coords, nestedcells[[1]] - 1, spacing, width, signedQ]];

        Do[
            nestedvdb = OpenVDBCreateGrid[spacing, type];
            nestedvdb["meshLevelSet"[coords, nestedcells[[i]] - 1, spacing, width, signedQ]];
            If[EvenQ[i],
                OpenVDBDifferenceFrom[vdb, nestedvdb],
                OpenVDBUnionTo[vdb, nestedvdb]
            ],
            {i, 2, Length[nestedcells]}
        ];

        vdb
    ]


nestedComponentHierarchy[coords_, cells_, C12_] :=
    Block[{C22, comps, conncells, n, adj, depths, depthmembers},
        C22 = triangleTriangleConnectivity[coords, cells, C12];
        comps = SparseArray`StronglyConnectedComponents[C22];

        (* only one component *)
        If[Length[comps] == 1, Return[{cells}]];

        conncells = cells[[#]]& /@ comps;

        n = Length[conncells];
        adj = boundaryNestingAdjacency[coords, Polygon /@ conncells];
        depths = nestingDepths[n, adj];

        (* multiple components, but none nested *)
        If[Max[depths] == 0, Return[{cells}]];

        depthmembers = GatherBy[SortBy[Transpose[{depths, Range[n]}], First], First][[All, All, 2]];

        (Join @@ conncells[[#]])& /@ depthmembers
    ]


triangleTriangleConnectivity[_, _, C12_SparseArray] := Transpose[C12] . C12


triangleTriangleConnectivity[coords_, cells_, _] :=
    With[{C12 = edgeTriangleAdjacencyMatrix[coords, cells]},
        Transpose[C12] . C12
    ]


(* ::Text:: *)
(*https://mathematica.stackexchange.com/a/160444/4346*)


getEdgesFromTriangles = Compile[{{f, _Integer, 1}},
    {
        Sort[{Compile`GetElement[f, 1], Compile`GetElement[f, 2]}],
        Sort[{Compile`GetElement[f, 2], Compile`GetElement[f, 3]}],
        Sort[{Compile`GetElement[f, 3], Compile`GetElement[f, 1]}]
    },
    RuntimeAttributes -> {Listable},
    Parallelization -> True
];
takeSortedThread = Compile[{{data, _Integer, 1}, {ran, _Integer, 1}},
   Sort[Part[data, ran[[1]] ;; ran[[2]]]],
    RuntimeAttributes -> {Listable},
    Parallelization -> True
   ];
extractIntegerFromSparseMatrix = Compile[
   {{vals, _Integer, 1}, {rp, _Integer, 1}, {ci, _Integer,
     1}, {background, _Integer},
    {i, _Integer}, {j, _Integer}},
   Block[{k},
    k = rp[[i]] + 1;
    While[k < rp[[i + 1]] + 1 && ci[[k]] != j, ++k];
    If[k == rp[[i + 1]] + 1, background, vals[[k]]]
    ],
    RuntimeAttributes -> {Listable},
    Parallelization -> True
   ];


edgeTriangleAdjacencyMatrix[coords_, cells_] :=
 Module[{edgesfrompolygons, edges, edgelookupcontainer,
    polyranges, polygonsneighedges, edgepolygonadjacencymatrix, acc},
  edgesfrompolygons = Flatten[getEdgesFromTriangles[cells], 1];
 edges = DeleteDuplicates[edgesfrompolygons];
  edgelookupcontainer =
   SparseArray[
    Rule[Join[edges, Transpose[Transpose[edges][[{2, 1}]]]],
     Join[Range[1, Length[edges]], Range[1, Length[edges]]]], {Length[coords], Length[coords]}];
  acc = Range[0, 3Length[cells], 3];
  polyranges = Transpose[{Most[acc] + 1, Rest[acc]}];
  polygonsneighedges = takeSortedThread[extractIntegerFromSparseMatrix[
      edgelookupcontainer["NonzeroValues"],
      edgelookupcontainer["RowPointers"],
      Flatten@edgelookupcontainer["ColumnIndices"],
      edgelookupcontainer["Background"],
      edgesfrompolygons[[All, 1]],
      edgesfrompolygons[[All, 2]]],
     polyranges];
  Transpose@With[{
      n = Length[edges], m = Length[cells],
      data = Flatten[polygonsneighedges]
      },
     SparseArray @@ {Automatic, {m, n},
       0, {1, {acc, Transpose[{data}]}, ConstantArray[1, Length[data]]}}
     ]
  ]


(* ::Subsection::Closed:: *)
(*Mesh utilities*)


(* ::Subsubsection::Closed:: *)
(*Q functions*)


meshQ[expr_] := MeshRegionQ[expr] || BoundaryMeshRegionQ[expr]


mesh3DQ[expr_] := (MeshRegionQ[expr] || BoundaryMeshRegionQ[expr]) && RegionEmbeddingDimension[expr] === 3


surfaceMesh3DQ[mr_] :=
    And[
        mesh3DQ[mr],
        BoundaryMeshRegionQ[mr] || RegionDimension[mr] === 2,
        (* has faces and all faces are triangles -- this allows for meshes with points, lines, etc, which will all be ignored *)
        !FreeQ[mr["MeshCellTypes"], {Polygon, {_, _}}]
    ]


triangleSurfaceMeshQ[mr_] := surfaceMesh3DQ[mr] && FreeQ[mr["MeshCellTypes"], {Polygon, {_, Except[3]}}]


lineMesh3DQ[mr_] := mesh3DQ[mr] && RegionDimension[mr] === 1


(* ::Subsubsection::Closed:: *)
(*laxMeshBlock*)


SetAttributes[laxMeshBlock, HoldFirst];


laxMeshBlock[code_] :=
    Block[{bmethod},
        Internal`WithLocalSettings[
            bmethod = OptionValue[BoundaryMeshRegion, Method];
            SetOptions[BoundaryMeshRegion, Method -> Join[{"CheckIntersections" -> False}, Replace[bmethod, Except[_List] -> {}, {0}]]];,

            code,

            SetOptions[BoundaryMeshRegion, Method -> bmethod];
        ]
    ]


(* ::Subsubsection::Closed:: *)
(*Nesting*)


(* ::Text:: *)
(*Uses the same idea as Region`Mesh`BoundaryNestingArrays, but only tests one point for crossing count instead of all points. This assumes no intersecting facets.*)


polygonBoundingBox[Polygon[coords_]] := CoordinateBoundingBox[coords]
polygonBoundingBox[data_List] := CoordinateBoundingBox[polygonBoundingBox /@ data]


polygonCoordinate[Polygon[data_]] := iPolygonCoordinate[data];
polygonCoordinate[data_List] := polygonCoordinate[First[data]];


iPolygonCoordinate[lis_List] :=
    If[Length[lis] == 3 && VectorQ[lis, NumericQ],
        lis,
        iPolygonCoordinate[lis[[1]]]
    ]


boundingBoxNesting[{min1_, max1_}, {min2_, max2_}] :=
    Module[{minless, maxless},

        If[Or @@ MapThread[Less, {max1, min2}],
            Return["Disjoint"]
        ];

        If[Or @@ MapThread[Less, {max2, min1}],
            Return["Disjoint"]
        ];

        minless = Union[MapThread[Less, {min1, min2}]];
        maxless = Union[MapThread[Less, {max1, max2}]];

        If[Length[minless] > 1 || Length[maxless] > 1 || minless === maxless,
            Return[Indeterminate]
        ];

        If[TrueQ[First[minless]],
            {"Inside", 2, 1},
            {"Inside", 1, 2}
        ]
    ]


pointInsideQ[polys_, pt_] := OddQ[Region`Mesh`CrossingCount[polys, pt]]


(* ::Text:: *)
(*Returns {{i1, j1}, {i2, j2}, ...} where the i1 component contains the j1 component, etc.*)


boundaryNestingAdjacency[coords_, cells_] :=
    Block[{np, polycomps, bb, pts, nesting},

        np = Length[cells];
        polycomps = Region`Mesh`ToCoordinates[cells, coords];
        bb = polygonBoundingBox /@ polycomps;
        pts = polygonCoordinate /@ polycomps;

        nesting = Reap[
            Do[
                Switch[
                    boundingBoxNesting[bb[[j]], bb[[i]]],
                    Indeterminate,
                        If[pointInsideQ[polycomps[[i]], pts[[j]]],
                            Sow[{i, j}],
                            If[pointInsideQ[polycomps[[j]], pts[[i]]],
                                Sow[{j, i}]
                            ]
                        ],
                    {"Inside", 1, 2},
                        If[pointInsideQ[polycomps[[i]], pts[[j]]],
                            Sow[{i, j}]
                        ],
                    {"Inside", 2, 1},
                        If[pointInsideQ[polycomps[[j]], pts[[i]]],
                            Sow[{j, i}]
                        ],
                    "Disjoint",
                        Null,
                    _,
                        Throw[0]
                ],
                {i, 1, np - 1},
                {j, i + 1, np}
            ]
        ][[-1]];

        Sort[Flatten[nesting, 1]]
]


nestingLevels[np_Integer, adj_] :=
    Block[{depths, roots, depthlist},

        (* The number of times a component appears in the second location is its depth in the inclusion tree. *)
        depths = SplitBy[SortBy[Tally[adj[[All, 2]]], Last], Last];

        roots = Complement[Range[np], adj[[All, 2]]];

        (* creates {{i01, i02, i03, ...}, {i11, i12, i13, ...}, {i21, i22, i23, ...}, ...}, where idj means the index has depth d. *)
        Prepend[depths[[All, All, 1]], roots]
    ]


nestingDepths[np_Integer, adj_] :=
    Block[{depthlist, df},
        depthlist = nestingLevels[np, adj];

        df[_] = 0;
        Do[Scan[(df[#] = i)&, depthlist[[i+1]]], {i, 0, Length[depthlist]-1}];

        df /@ Range[np]
    ]


(* ::Subsection::Closed:: *)
(*validToLevelSetQ*)


validToLevelSetQ[reg_?RegionQ] := ConstantRegionQ[reg] && RegionEmbeddingDimension[reg] === 3


validToLevelSetQ[{coords_?coordinatesQ, ocells:_List|_Polygon}] :=
    With[{cells = stripPolygonCells[ocells]},
        validTriangleCellsQ[cells, Length[coords]]
    ]


validToLevelSetQ[{coords_?coordinatesQ, ocells:_List|_Polygon|_Line, r_?Positive}] :=
    With[{cells = stripLineCells[stripPolygonCells[ocells]]},
        validTriangleCellsQ[cells, Length[coords]] || validLineCellsQ[cells, Length[coords]]
    ]


validToLevelSetQ[___] = False;


(* ::Subsection::Closed:: *)
(*validScalarTypeQ*)


validScalarTypeQ[type_] := KeyExistsQ[$GridClassData[$scalarType], type]


validScalarTypeQ[___] = $Failed;
