(* ::Package:: *)

(* ::Title:: *)
(*Mesh*)


(* ::Subtitle:: *)
(*Create a mesh representation of a level set or fog volume.*)


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


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


Package["OpenVDBLink`"]


PackageExport["OpenVDBMesh"]


OpenVDBMesh::usage = "OpenVDBMesh[expr] creates a mesh representation of an OpenVDB scalar grid.";


(* ::Section:: *)
(*OpenVDBMesh*)


(* ::Subsection::Closed:: *)
(*Main*)


Options[OpenVDBMesh] = {"Adaptivity" -> 0., "CloseBoundary" -> True, "IsoValue" -> Automatic, "ReturnQuads" -> False};


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


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


OpenVDBMesh[args___] := mOpenVDBMesh[args]


(* ::Subsection::Closed:: *)
(*iOpenVDBMesh*)


Options[iOpenVDBMesh] = Options[OpenVDBMesh];


iOpenVDBMesh[expr_, opts:OptionsPattern[]] := iOpenVDBMesh[expr, Automatic, opts]


iOpenVDBMesh[vdb_?OpenVDBScalarGridQ, itype_, OptionsPattern[]] :=
    Block[{type, adaptivity, isovalue, quadQ, data, res},
        type = parseLevelSetMeshType[itype];
        (
            {adaptivity, isovalue, quadQ} = OptionValue[{"Adaptivity", "IsoValue", "ReturnQuads"}];

            adaptivity = Clip[adaptivity, {0., 1.}];
            isovalue = gridIsoValue[isovalue, vdb];
            quadQ = TrueQ[quadQ];
            (
                data = levelSetMeshData[vdb, isovalue, adaptivity, quadQ];
                (
                    res = constructLevelSetMesh[data, type];

                    res /; res =!= $Failed

                ) /; data =!= $Failed

            ) /; realQ[isovalue] && 0 <= adaptivity <= 1

        ) /; StringQ[type]
    ]


iOpenVDBMesh[vdb_, itype_, bds_List, opts:OptionsPattern[]] /; MatrixQ[bds, realQ] := iOpenVDBMesh[vdb, itype, bds -> $worldregime, opts]


iOpenVDBMesh[vdb_?OpenVDBScalarGridQ, itype_, bds_List -> regime_?regimeQ, opts:OptionsPattern[]] :=
    Block[{clipopts, clip},
        clipopts = FilterRules[{opts, Options[OpenVDBMesh]}, Options[OpenVDBClip]];

        clip = OpenVDBClip[vdb, bds -> regime, Sequence @@ clipopts];

        iOpenVDBMesh[clip, itype, opts] /; OpenVDBScalarGridQ[clip]
    ]


iOpenVDBMesh[___] = $Failed;


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


registerForLevelSet[iOpenVDBMesh, 1];


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


addCodeCompletion[OpenVDBMesh][None, {"MeshRegion", "BoundaryMeshRegion", "ComplexData", "FaceData"}, None];


OpenVDBDefaultSpace[OpenVDBMesh] = $worldregime;


(* ::Subsection::Closed:: *)
(*Utilities*)


(* ::Subsubsection::Closed:: *)
(*parseLevelSetMeshType*)


parseLevelSetMeshType[Automatic] = "MeshRegion";
parseLevelSetMeshType["MeshRegion"] = "MeshRegion";
parseLevelSetMeshType[MeshRegion] = "MeshRegion";
parseLevelSetMeshType["BoundaryMeshRegion"] = "BoundaryMeshRegion";
parseLevelSetMeshType[BoundaryMeshRegion] = "BoundaryMeshRegion";
parseLevelSetMeshType["ComplexData"] = "ComplexData";
parseLevelSetMeshType["FaceData"] = "FaceData";
parseLevelSetMeshType[_] = $Failed;


(* ::Subsubsection::Closed:: *)
(*levelSetMeshData*)


levelSetMeshData[vdb_?emptyVDBQ, isovalue_, __] := makeFacelessRegion[vdb, isovalue]


levelSetMeshData[vdb_, isovalue_, adaptivity_, quadQ_] :=
    Block[{rawdata, doffset, coordlen, trilen, quadlen, coords, cells},
        rawdata = vdb["meshData"[isovalue, adaptivity, !quadQ]];
        (
            doffset = 3;
            {coordlen, trilen, quadlen} = {3, 3, 4} * rawdata[[1 ;; 3]];

            coords = Partition[rawdata[[doffset+1 ;; doffset+coordlen]], 3];

            cells = Reverse[Round[#]+1, {2}]& /@ {
                If[trilen > 0, Partition[rawdata[[doffset+coordlen+1 ;; doffset+coordlen+trilen]], 3], Nothing],
                If[quadlen > 0, Partition[rawdata[[doffset+coordlen+trilen+1 ;; -1]], 4], Nothing]
            };

            {coords, cells}

        ) /; ListQ[rawdata] && Length[rawdata] > 3
    ]


levelSetMeshData[vdb_, isovalue_, __] := makeFacelessRegion[vdb, isovalue]


makeFacelessRegion[vdb_, isovalue_] :=
    If[TrueQ[vdb["getBackgroundValue"[]] < isovalue],
        FullRegion[3],
        EmptyRegion[3]
    ]


(* ::Subsubsection::Closed:: *)
(*constructLevelSetMesh*)


constructLevelSetMesh[{coords_, cells:{__}}, type:("MeshRegion"|"BoundaryMeshRegion")] :=
    Block[{hasQuads, head, mr},
        hasQuads = Max[Length[#[[1]]]& /@ cells] == 4;
        head = If[type === "BoundaryMeshRegion", BoundaryMeshRegion, MeshRegion];

        mr = Quiet @ head[
            coords,
            Polygon /@ cells,
            Method -> {
                If[type === "BoundaryMeshRegion", "CheckIntersections" -> False, Nothing],
                If[hasQuads, "CoplanarityTolerance" -> 14, Nothing],
                "DeleteDuplicateCells" -> False,
                "DeleteDuplicateCoordinates" -> False,
                "EliminateUnusedCoordinates" -> False,
                "TJunction" -> False
            }
        ];

        mr /; RegionQ[mr]
    ]


constructLevelSetMesh[{coords_, cells_}, "ComplexData"] := {coords, cells}


constructLevelSetMesh[{coords_, cells_}, "FaceData"] := Partition[coords[[Flatten[#]]], Length[#[[1]]]]& /@ cells


constructLevelSetMesh[EmptyRegion[3], "MeshRegion"] = EmptyRegion[3];
constructLevelSetMesh[EmptyRegion[3], "BoundaryMeshRegion"] = EmptyRegion[3];
constructLevelSetMesh[EmptyRegion[3], "ComplexData"] = {{}, {}};
constructLevelSetMesh[EmptyRegion[3], "FaceData"] = {};


constructLevelSetMesh[FullRegion[3], "MeshRegion"] = EmptyRegion[3];
constructLevelSetMesh[FullRegion[3], "BoundaryMeshRegion"] = FullRegion[3];
constructLevelSetMesh[FullRegion[3], "ComplexData"] = {{}, {}};
constructLevelSetMesh[FullRegion[3], "FaceData"] = {};


constructLevelSetMesh[___] = $Failed;


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


Options[mOpenVDBMesh] = Options[OpenVDBMesh];


mOpenVDBMesh[expr_, ___] /; messageScalarGridQ[expr, OpenVDBMesh] = $Failed;


mOpenVDBMesh[_, type_, ___] /; parseLevelSetMeshType[type] === $Failed :=
    (
        Message[OpenVDBMesh::ret, type, 2];
        $Failed
    )


mOpenVDBMesh[_, _, bbox:Except[_?OptionQ], ___] /; message3DBBoxQ[bbox, OpenVDBMesh] = $Failed;


mOpenVDBMesh[__, OptionsPattern[]] :=
    (
        If[messageIsoValueQ[OptionValue["IsoValue"], OpenVDBMesh],
            Return[$Failed]
        ];

        If[!TrueQ[0 <= OptionValue["Adaptivity"] <= 1],
            Message[OpenVDBMesh::adapt];
            Return[$Failed]
        ];

        $Failed
    )


mOpenVDBMesh[___] = $Failed;


OpenVDBMesh::ret = "`1` at position `2` is not one of \"MeshRegion\", \"BoundaryMeshRegion\", \"ComplexData\", \"FaceData\", or Automatic.";


OpenVDBMesh::adapt = "The setting for \"Adaptivity\" must be a number between 0 and 1, inclusively.";
