(* ::Package:: *)

(* ::Title:: *)
(*Render*)


(* ::Subtitle:: *)
(*Various rendering techniques on grids.*)


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


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


Package["OpenVDBLink`"]


PackageExport["OpenVDBLevelSetRender"]
PackageExport["OpenVDBLevelSetViewer"]


OpenVDBLevelSetRender::usage = "OpenVDBLevelSetRender[expr] returns a 3D render Image of an OpenVDB level set grid.";


OpenVDBLevelSetViewer::usage = "OpenVDBLevelSetViewer[expr] returns a manipulatable 3D render Image of an OpenVDB level set grid.";


(* ::Section:: *)
(*Shader and theme names*)


(* ::Subsection::Closed:: *)
(*Shaders*)


$dielectricList = {"Rubber", "Plastic", "Diffuse", "Glazed", "Satin", "Clay", "Matte", "Foil"};
$metalList = {"Gold", "Aluminum", "Brass", "Bronze", "Copper", "Electrum", "Iron", "Pewter"};
$rainbowList = {"Normal", "Position"};
$depthList = {"Depth"};


(* ::Subsection::Closed:: *)
(*Themes*)


$renderColorThemes = <|
                 (*     front face             back face           closed face              background *)
    "Default"    -> {RGBColor["#4D94FF"], RGBColor["#FFA24E"], RGBColor["#4D94FF"], RGBColor[1, 1, 1]},
    "Monochrome" -> {RGBColor[1, 1, 1],   RGBColor[1, 1, 1],   RGBColor[1, 1, 1],   RGBColor[0, 0, 0]},
    "Bold"       -> {ColorData[106, 2],   ColorData[106, 1],   ColorData[106, 2],   Lighter[ColorData[106, 4], 0.5]},
    "Cool"       -> {ColorData[107, 1],   ColorData[107, 4],   ColorData[107, 1],   RGBColor["#E6EBFF"]},
    "Neon"       -> {ColorData[109, 8],   ColorData[109, 2],   ColorData[109, 8],   ColorData[109, 3]},
    "Pastel"     -> {RGBColor["#D5B0F6"], RGBColor["#F5AE6F"], RGBColor["#D5B0F6"], RGBColor["#B9FFDB"]},
    "Soft"       -> {RGBColor["#CFCFE1"], RGBColor["#EBBBAC"], RGBColor["#CFCFE1"], RGBColor["#3C3C78"]},
    "Vibrant"    -> {ColorData[112, 2],   ColorData[112, 1],   ColorData[112, 2],   RGBColor["#FFDC80"]},
    "Warm"       -> {ColorData[113, 1],   ColorData[113, 2],   ColorData[113, 1],   RGBColor["#FFE7B7"]}
|>;


(* ::Section:: *)
(*OpenVDBLevelSetRender*)


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


Options[OpenVDBLevelSetRender] = Join[
    {Background -> Automatic, "ClosedClipping" -> False, "FrameTranslation" -> Automatic, ImageResolution -> Automatic,
        "IsoValue" -> 0.0, "OrthographicFrame" -> Automatic, PerformanceGoal :> $PerformanceGoal},
    Options[Graphics3D, {ImageSize, ViewAngle, ViewCenter, ViewPoint, ViewProjection, ViewRange, ViewVertical}]
];


OpenVDBLevelSetRender[args___] /; !CheckArgs[OpenVDBLevelSetRender[args], {1, 2}] = $Failed;


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


OpenVDBLevelSetRender[args___] := mLevelSetRender[args]


(* ::Subsection::Closed:: *)
(*iLevelSetRender*)


Options[iLevelSetRender] = Options[OpenVDBLevelSetRender];


iLevelSetRender[vdb_?OpenVDBScalarGridQ, shading_, opts:OptionsPattern[]] /; levelSetQ[vdb] :=
    Block[{ropts, res},
        ropts = parseRenderOptions[vdb, shading, opts];
        (
            res = oLevelSetRender[vdb, ropts];

            res /; res =!= $Failed

        ) /; AssociationQ[ropts]
    ]


iLevelSetRender[vdb_, opts:OptionsPattern[]] := iLevelSetRender[vdb, Automatic, opts]


iLevelSetRender[___] = $Failed;


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


registerForLevelSet[iLevelSetRender, 1];


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


addCodeCompletion[OpenVDBLevelSetRender][None, Join[Keys[$renderColorThemes], $dielectricList, $metalList], None];


(* ::Subsection::Closed:: *)
(*oLevelSetRender*)


Options[oLevelSetRender] = Options[iLevelSetRender];


oLevelSetRender[vdb_, ropts_] /; emptyVDBQ[vdb] || (!fogVolume[vdb] && vdb["BackgroundValue"] <= ropts["IsoValue"]) :=
    Block[{bg, res, ires},
        bg = RGBColor @@ ropts["Background"];
        res = ropts["Resolution"];
        ires = ropts["ImageResolution"];

        ConstantImage[bg, res, "Byte", ImageResolution -> ires]
    ]


oLevelSetRender[vdb_, ropts_] /; KeyExistsQ[materialParameters, ropts["Shader"]] :=
    Block[{ires, pbrparams, args, im},
        ires = ropts["ImageResolution"];
        args = Lookup[ropts, $PBRrenderLevelSetArgumentKeys];
        pbrparams = materialParameters[ropts["Shader"]];
        (
            im = vdb["renderGridPBR"[##]]& @@ Join[args, Values[pbrparams][[4 ;; -1]]];

            Image[im, ImageResolution -> ires] /; ImageQ[im]
        ) /; AssociationQ[pbrparams]
    ]


oLevelSetRender[vdb_, ropts_] :=
    Block[{ires, args, im},
        ires = ropts["ImageResolution"];
        args = Lookup[ropts, $renderLevelSetArgumentKeys];
        im = vdb["renderGrid"[##]]& @@ args;

        Image[im, ImageResolution -> ires] /; ImageQ[im]
    ]


oLevelSetRender[___] = $Failed;


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


mLevelSetRender[args___] := messageRenderFunction[OpenVDBLevelSetRender, args]


(* ::Section:: *)
(*OpenVDBLevelSetViewer*)


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


Options[OpenVDBLevelSetViewer] = Options[OpenVDBLevelSetRender];


OpenVDBLevelSetViewer[args___] /; !CheckArgs[OpenVDBLevelSetViewer[args], {1, 2}] = $Failed;


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


OpenVDBLevelSetViewer[args___] := mLevelSetViewer[args]


(* ::Subsection::Closed:: *)
(*iLevelSetViewer*)


Options[iLevelSetViewer] = Options[OpenVDBLevelSetViewer];


iLevelSetViewer[vdb_?OpenVDBScalarGridQ, shading_, opts:OptionsPattern[]] /; levelSetQ[vdb] :=
    Block[{ropts, args, im},
        ropts = parseRenderOptions[vdb, shading, opts];
        (
            iDynamicRender[vdb, shading, ropts, opts]

        ) /; AssociationQ[ropts]
    ]


iLevelSetViewer[vdb_, opts:OptionsPattern[]] := iLevelSetViewer[vdb, Automatic, opts]


iLevelSetViewer[___] = $Failed;


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


registerForLevelSet[iLevelSetViewer, 1];


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


addCodeCompletion[OpenVDBLevelSetViewer][None, Join[Keys[$renderColorThemes], $dielectricList, $metalList], None];


(* ::Subsection::Closed:: *)
(*iDynamicRender*)


Options[iDynamicRender] = Options[OpenVDBLevelSetViewer];


iDynamicRender[vdb_, shading_, iropts_, opts:OptionsPattern[]] :=
    DynamicModule[{t, l, b, vp, ovp, vv, ovv, va, ova, vc, vr, sz, dx = 0.0, dy = 0.0, dz = 0.0, origva, origvc, origvr, origvp, origvv, mem, iso, oiso, mniso, mxiso, ir,
        td\[Delta], \[Delta], shader, oshader, vrng, ovrng, volQ, ivolQ, projection, oprojection, origprojection, oframe, ooframe, c1, oc1, c2, oc2, c3, oc3, bg, obg, clp, im, antialq, oantialq},
        {t, l, b} = Lookup[iropts, {"Translate", "LookAt", "Bounds"}];
        td\[Delta] = {0.005, 0.005, 0.1}*Max[Abs[Subtract @@@ b]];

        {vp, vv} = OptionValue[{ViewPoint, ViewVertical}];
        va = constructViewAngle[OptionValue[ViewAngle], t, l, b];
        vc = parseRenderViewCenter[OptionValue[ViewCenter], t, l, {{0, 1}, {0, 1}, {0, 1}}];

        origvc = vc;
        origvp = ovp = vp;
        origvv = ovv = vv;
        origva = ova = va;

        origvr = ovrng = initialViewRange[OptionValue[ViewRange], vp, vc, vv, va, b];
        volQ = ivolQ = TrueQ[OptionValue["ClosedClipping"]];

        ir = OptionValue[ImageResolution];
        sz = initialImageSize[iropts["ImageSize"], OptionValue[ImageSize]];
        oshader = canonicalizeShader[shading][[1]];

        projection = Replace[OptionValue[ViewProjection], Automatic -> "Perspective", {0}];
        origprojection = oprojection = projection;
        oframe = orthographicSphericalFrame[b];
        ooframe = oframe;

        {c1, c2, c3, bg} = If[TrueQ[customColorQ[oshader]],
            RGBColor @@@ Lookup[iropts, {"BaseColorFront", "BaseColorBack", "BaseColorClosed", "Background"}],
            $renderColorThemes["Default"]
        ];
        {oc1, oc2, oc3, obg} = {c1, c2, c3, bg};

        oiso = iropts["IsoValue"];
        {mniso, mxiso} = {-1, 1}Ramp[vdb["BackgroundValue"] - 1.5voxelSize[vdb]];
        mem = Quotient[vdb["MemoryUsage"], Replace[$ProcessorCount, Except[_Integer?Positive] -> 1, {0}]];

        oantialq = OptionValue[PerformanceGoal] =!= "Speed";

        Manipulate[
            Which[
                dz != 0,
                vp = zoomViewPoint[vp, vc, td\[Delta][[3]]dz, vv, va, b],
                dx != 0 || dy != 0,
                vc = translateViewCenter[vc, {td\[Delta][[1]]dx, td\[Delta][[2]]dy}]
            ];
            Overlay[
                {
                    Dynamic @ Image[im = iRender[
                        vdb, makeShader[shader, c1, c2, c3], "BoundingBox" -> b, ViewPoint -> vp, ViewVertical -> vv, ViewAngle -> va, ViewCenter -> vc,
                        ViewRange -> Scaled[vrng], ViewProjection -> projection, "OrthographicFrame" -> oframe, Background -> bg,
                        ImageSize -> dRenderImageSize[sz, \[Delta], OptionValue[PerformanceGoal], mem], "IsoValue" -> Clip[iso, .999{mniso, mxiso}],
                        PerformanceGoal -> If[antialq && !$ControlActiveSetting, "Quality", "Speed"], ImageResolution -> ir,
                        "ClosedClipping" -> volQ, opts
                    ], ImageSize -> sz],
                    Graphics3D[{},
                        Boxed -> False, Method -> {}, ImageSize -> Dynamic[sz],
                        ViewPoint -> Dynamic[vp], ViewVertical -> Dynamic[vv], ViewAngle -> Dynamic[va], ViewCenter -> Dynamic[vc]
                    ]
                },
                {1, 2},
                2
            ],
            OpenerView[{
            Style["Appearance", Medium],
                Column[{
                    Control[{{shader, oshader, "shading"},
                        KeyValueMap[#1 -> Row[{If[#2 === Automatic, Dynamic[c1], #2], ToLowerCase[#1]}, Spacer[2]]&, $dynamicRenderShaders],
                        ControlType -> PopupMenu
                    }],
                    Row[{
                        Control[{{antialq, oantialq, "anti aliasing"}, {True, False}}],
                        Control[{{volQ, ivolQ, "closed clipping"}, {True, False}}]
                    }, Spacer[2]],
                    Control[{{\[Delta], 1, "resolution"}, 0, 1}],
                    If[mxiso > 0, Control[{{iso, oiso, "iso\[Hyphen]value"}, mniso, mxiso}], Nothing],
                    Row[{
                        "themes",
                        Row[KeyValueMap[themeButton[ToLowerCase[#1], {c1, c2, c3, bg} = #2, Enabled -> Dynamic[customColorQ[shader]]]&, $renderColorThemes]]
                    }, Spacer[2]],
                    Dynamic @ Which[
                        !customColorQ[shader],
                        Row[{
                            Control[{{bg, obg, "background"}, obg, ControlType -> ColorSetter}]
                        }, Spacer[10]],
                        !volQ,
                        Row[{
                            Control[{{bg, obg, "background"}, obg, ControlType -> ColorSetter}],
                            Control[{{c1, oc1, "front face"}, oc1, ControlType -> ColorSetter}],
                            Control[{{c2, oc2, "back face"}, oc2, ControlType -> ColorSetter}], Spacer[3],
                            If[c1 =!= c2, themeButton["flip faces", If[c1 === c3, c3 = c2]; {c1, c2} = {c2, c1}], Nothing]
                        }, Spacer[1]],
                        c1 === c2 === c3,
                        Row[{
                            Control[{{bg, obg, "background"}, obg, ControlType -> ColorSetter}],
                            Control[{{c1, oc1, "front face"}, oc1, ControlType -> ColorSetter}],
                            Control[{{c2, oc2, "back face"}, oc2, ControlType -> ColorSetter}],
                            Control[{{c3, oc3, "closed face"}, oc3, ControlType -> ColorSetter}]
                        }, Spacer[1]],
                        True,
                        Grid[{{
                            Control[{{c1, oc1, "front face"}, oc1, ControlType -> ColorSetter}],
                            Control[{{c2, oc2, "back face"}, oc2, ControlType -> ColorSetter}], Spacer[3],
                            If[c1 =!= c2, Row[{themeButton["flip faces", If[c1 === c3, c3 = c2]; {c1, c2} = {c2, c1}], ""}], Nothing]
                        }, {
                            Control[{{bg, obg, "background"}, obg, ControlType -> ColorSetter}],
                            Control[{{c3, oc3, "closed face"}, oc3, ControlType -> ColorSetter}], Spacer[3],
                            If[c1 =!= c3, Row[{themeButton["set closed as front", c3 = c1], ""}], Nothing]
                        }},
                        Alignment -> {{Right, Right, Left, Left}, Automatic},
                        Spacings -> {1, Automatic}]
                    ]
                },
                Spacings -> {Automatic, {Automatic, Automatic, Automatic, 1, 1}}]
            }, Method -> "Active"],
            OpenerView[{
            Style["Orientation", Medium],
                Column[{
                    Control[{{vv, ovv, "view vertical"}, {{0,0,1} -> "(0,0,1)", {0,0,-1} -> "(0,0,\[Hyphen]1)", {1,0,0} -> "(1,0,0)", {-1,0,0} -> "(\[Hyphen]1,0,0)", {0,1,0} -> "(0,1,0)", {0,-1,0} -> "(0,\[Hyphen]1,0)"}, ControlType -> Setter}],
                    Control[{{vp, ovp, "view point"}, {{1.3,-2.4,2} -> "default", {-3,0,0} -> "left", {3,0,0} -> "right", {0,-3,0} -> "front", {0,3,0} -> "back", {0,0,-3} -> "below", {0,0,3} -> "above"}, ControlType -> Setter}],
                    Row[{
                        "view point\[ThinSpace]&\[ThinSpace]center",
                        Framed[
                            Grid[{{
                                    EventHandler[panSlider[Dynamic[dz, (dz = If[Abs[#] < .05, 0., #])&], Enabled -> Dynamic[projection =!= "Orthographic"]], {"MouseUp" :> (dz = 0)}, PassEventsDown -> True],
                                    EventHandler[panSlider[Dynamic[dx, (dx = If[Abs[#] < .05, 0., #])&]], {"MouseUp" :> (dx = 0)}, PassEventsDown -> True],
                                    EventHandler[panSlider[Dynamic[dy, (dy = If[Abs[#] < .05, 0., #])&]], {"MouseUp" :> (dy = 0)}, PassEventsDown -> True]
                                },
                                {"out\[ThinSpace]\[TwoWayRule]\[ThinSpace]in", "left\[ThinSpace]\[TwoWayRule]\[ThinSpace]right", "down\[ThinSpace]\[TwoWayRule]\[ThinSpace]up"}},
                                Spacings -> {1, 0.5}
                            ],
                            ContentPadding -> True,
                            FrameStyle -> None,
                            ImageMargins -> {{0,0},{2,0}}
                        ]
                    }, Spacer[2]]
                }]
            }, Method -> "Active"],
            OpenerView[{
            Style["Field of View", Medium],
                Column[{
                    Row[{
                        Control[{{vrng, ovrng, "clipping"}, 0, 1, ControlType -> IntervalSlider, MinIntervalSize -> 0.01, Method -> "Stop"}],
                        Control[{{volQ, ivolQ, "closed"}, {True, False}}]
                    }, Spacer[3]],
                    Dynamic @ If[projection =!= "Orthographic",
                        Control[{{va, ova, "FOV (rad)"}, 0, 75*\[Pi]/180}],
                        Control[{{oframe, ooframe, "frame width"}, 0, ooframe}]
                    ],
                    Control[{{projection, oprojection, "projection"}, {"Perspective" -> "perspective", "Orthographic" -> "orthographic"}}]
                }]
            }, Method -> "Active"],
            OpenerView[{
            Style["General", Medium],
                Row[{
                    Button["Copy image",
                        CopyToClipboard[im]],
                    Button["Copy view settings",
                        CopyToClipboard[copyString @ {If[projection =!= "Orthographic", ViewAngle -> va, "OrthographicFrame" -> oframe], ViewCenter -> vc,
                            ViewRange -> unscaledViewRange[vrng, vp, vc, vv, va, b], ViewPoint -> vp, ViewProjection -> projection, ViewVertical -> vv}]],
                    Button["Reset view settings",
                        va = origva; vc = origvc; vrng = origvr; vp = origvp; vv = origvv; projection = origprojection; oframe = ooframe;]
                }]
            }, Method -> "Active"]
        ],
        Initialization -> With[{args = Sequence @@ Append[vdb, $SessionID]}, iDynamicRenderTrack[args] = vdb],
        Deinitialization :> With[{args = Sequence @@ Append[vdb, $SessionID]}, If[Head[iDynamicRenderTrack[args]] =!= iDynamicRenderTrack, iDynamicRenderTrack[args]=.]]
    ]


(* ::Subsection::Closed:: *)
(*iRender*)


Options[iRender] = Options[iLevelSetRender];


iRender[args__] :=
    With[{res = Quiet[iLevelSetRender[args]]},
        res /; ImageQ[res]
    ]


iRender[___] = Image[{{1}}];


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


Scan[(customColorQ[#] = True )&, $dielectricList];
Scan[(customColorQ[#] = False)&, $metalList];
Scan[(customColorQ[#] = False)&, $rainbowList];
Scan[(customColorQ[#] = True)&, $depthList];


$dynamicRenderShaders := $dynamicRenderShaders = Association @ Join[
    # -> Automatic& /@ $dielectricList,
    # -> materialParameters[#]["BaseColorFront"]& /@ $metalList,
    # -> $rainbowSwatch& /@ $rainbowList,
    # -> $depthSwatch& /@ $depthList
]


dynamicRenderShaders = $dynamicRenderShaders[If[ListQ[#], First[#, ""], #]]&;


$rainbowSwatch = Image[ImagePad[Image[Table[With[{r = Sqrt[x^2 + y^2], \[Theta] = ArcTan[.0000001-y, x]}, Hue[\[Theta]/(2\[Pi])+1/2., Clip[r,{0,1}],1]], {x, -1., 1., .1}, {y, -1., 1., .1}]], 1], "Byte", ImageSize -> 11];


$depthSwatch = Image[ImagePad[Image[Table[With[{r = Sqrt[x^2 + y^2], \[Theta] = ArcTan[.0000001-y, x]}, Hue[0, 0, Clip[1-r, {0,1}]^0.5]], {x, -1., 1., .1}, {y, -1., 1., .1}]], 1], "Byte", ImageSize -> 11];


makeShader[shader_, colors__] :=
    If[TrueQ[customColorQ[shader]] || !KeyExistsQ[materialParameters, shader],
        {shader, colors},
        Prepend[Lookup[materialParameters[shader], {"BaseColorFront", "BaseColorBack", "BaseColorClosed"}], shader]
    ]


initialViewRange[{min_?NumericQ, max_?NumericQ}, vp_, vc_, vv_, va_, bds_] /; min <= max :=
    Block[{t, l, vr},
        t = parseRenderViewPoint[vp, bds];
        l = parseRenderViewCenter[vc, t, vv, va, bds];
        vr = parseRenderViewRange[Scaled[{0, 1}], t, l, bds];
        Clip[Rescale[{min, max}, vr], {0, 1}]
    ]
initialViewRange[___] = {0, 1};


unscaledViewRange[vrng_, vp_, vc_, vv_, va_, bds_] :=
    Block[{t, l},
        t = parseRenderViewPoint[vp, bds];
        l = parseRenderViewCenter[vc, t, vv, va, bds];
        parseRenderViewRange[Scaled[vrng], t, l, bds]
    ]


copyString[expr_List] := StringTake[ToString[expr, InputForm], {2, -2}]


copyString[expr_] := ToString[expr, InputForm]


initialImageSize[{sx_, _}, Automatic] := {sx, sx}
initialImageSize[sz_, _] := sz


dRenderImageSize[sz_, \[Delta]_, pgoal_, mem_] :=
    With[{s = sz*\[Delta]},
        Clip[Round[dPGoalFactor[pgoal, s, mem]*s], {1, \[Infinity]}]
    ]


(* ::Text:: *)
(*TODO figure out when rotating becomes laggy. I think it's some function of image size, vdb memory footprint, and average depth of the first layer.*)


dPGoalFactor["Speed", sz_, mem_] :=
    If[TrueQ[(Times @@ sz) > Min[450^2, Divide[500000000.*720^2, mem]] && $ControlActiveSetting],
        0.5,
        1.0
    ]
dPGoalFactor[__] = 1.0;


panSlider[var_, opts___] := Slider[var, {-1, 1}, opts, Appearance -> "UpArrow", ImageSize -> {75, 22}]


orthographicSphericalFrame[bds_] :=
    With[{reg = BoundingRegion[Tuples[bds], "MinBall"]},
        2reg[[2]] /; MatchQ[reg, Ball[_, _Real]]
    ]


orthographicSphericalFrame[___] = $Failed;


renderColor[__, "Normal"|"NormalClosed"|"Position"|"PositionClosed"] = White;
renderColor[c1_, c1_, _] := c1
renderColor[c1_, c2_, _] := FaceForm[c1, c2]


SetAttributes[themeButton, HoldRest];
themeButton[label_, action_, opts___] := Button[Style[label, Small], action, opts, Appearance -> "Palette"]


translateViewCenter[{vc_, {x_, y_}}, {dx_, dy_}] := {vc, {x+dx, y+dy}}
translateViewCenter[vc_, {dx_, dy_}] := {vc, {0.5+dx, 0.5+dy}}


zoomViewPoint[vp_, vc_, dz_, vv_, va_, bds_] :=
    Block[{translate, lookat, dir, center, mx},
        translate = parseRenderViewPoint[vp, bds];
        lookat = parseRenderViewCenter[vc, translate, vv, va, bds];
        dir = Min[.1dz*Power[Norm[Subtract[lookat, translate]], 1.25], 1.5]Normalize[Subtract[lookat, translate]];

        translate += dir;
        lookat    += dir;

        center = Mean /@ bds;
        mx = Replace[Max[Abs[Subtract @@@ bds]], _?NonPositive -> 1.0, {0}];

        Divide[Subtract[translate, center], mx]
    ]


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


mLevelSetViewer[args___] := messageRenderFunction[OpenVDBLevelSetViewer, args]


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


(* ::Subsection::Closed:: *)
(*$renderLevelSetArgumentKeys*)


$renderLevelSetArgumentKeys = {"IsoValue", "BaseColorFront", "BaseColorBack", "BaseColorClosed", "Background", "Translate", "LookAt", "Up",
    "Range", "FOV", "Shader", "Camera", "Samples", "Resolution", "Frame", "DepthParameters", "Lighting", "Step", "IsClosed"};


$PBRrenderLevelSetArgumentKeys = {"IsoValue", "Background", "Translate", "LookAt", "Up",
    "Range", "FOV", "Camera", "Samples", "Resolution", "Frame", "IsClosed", "BaseColorFront", "BaseColorBack", "BaseColorClosed"};


(* ::Subsection::Closed:: *)
(*Option parsing utilities*)


(* ::Subsubsection::Closed:: *)
(*parseRenderOptions*)


Options[parseRenderOptions] = Join[Options[iLevelSetRender], {"BoundingBox" -> Automatic}];


parseRenderOptions[vdb_, shading_, opts:OptionsPattern[]] :=
    Block[{res},
        res = iparseRenderOptions[vdb, shading, opts];

        res /; AssociationQ[res] && NoneTrue[res, FailureQ]
    ]


parseRenderOptions[___] = $Failed;


(* ::Subsubsection::Closed:: *)
(*iparseRenderOptions*)


Options[iparseRenderOptions] = Options[parseRenderOptions];


iparseRenderOptions[vdb_, shading_, OptionsPattern[]] :=
    Block[{bds, translate, lookat1, \[Alpha], lookat, up, transdist, depthdata, imgresolution, shader, ropts},
        bds = parseBoundingBox[vdb, OptionValue["BoundingBox"]];

        translate = parseRenderViewPoint[OptionValue[ViewPoint], bds];
        up = parseRenderViewVertical[OptionValue[ViewVertical], OptionValue[ViewPoint]];

        lookat1 = parseRenderViewCenterNoOffset[OptionValue[ViewCenter], bds];
        \[Alpha] = constructViewAngle[OptionValue[ViewAngle], translate, lookat1, bds];
        lookat = parseRenderViewCenter[OptionValue[ViewCenter], translate, up, \[Alpha], bds];

        shader = canonicalizeShader[shading];

        depthdata = parseDepthParameters[shader];
        imgresolution = parseRenderImageResolution[OptionValue[ImageResolution]];

        <|
            "Bounds" -> bds,
            "IsoValue" -> parseIsoValue[OptionValue["IsoValue"]],
            "Background" -> parseRenderBackgroundColor[OptionValue[Background], shader],
            "Translate" -> translate,
            "LookAt" -> lookat,
            "Up" -> up,
            "Range" -> parseRenderViewRange[OptionValue[ViewRange], translate, lookat, bds],
            "FOV" -> parseRenderViewAngle[OptionValue[ViewAngle], translate, lookat, bds],
            "Shader" -> parseRenderShader[First[shader, $Failed]],
            "Camera" -> parseRenderViewProjection[OptionValue[ViewProjection], OptionValue[ViewPoint]],
            "Samples" -> parseRenderPerformance[OptionValue[PerformanceGoal]],
            "Resolution" -> parseRenderImageSize[OptionValue[ImageSize], imgresolution, translate, up, bds],
            "ImageSize" -> parseRenderUnscaledImageSize[OptionValue[ImageSize], translate, up, bds],
            "ImageResolution" -> imgresolution,
            "Frame" -> parseOrthographicFrame[OptionValue[ViewProjection], OptionValue["OrthographicFrame"], translate, lookat, up, bds],
            "DepthParameters" -> depthdata,
            "Lighting" -> RotationTransform[0.25, {-1, 1, 1}][translate - lookat],
            "Step" -> {1.0, 2.0},
            "BaseColorFront" -> parseRenderColor[shader],
            "BaseColorBack" -> parseRenderColor2[shader],
            "BaseColorClosed" -> parseRenderColor3[shader],
            "IsClosed" -> TrueQ[OptionValue["ClosedClipping"]]
        |>
    ]


(* ::Subsubsection::Closed:: *)
(*renderFailureOption*)


renderFailureOption[assoc_] :=
    (
        If[FailureQ[assoc["Bounds"]], Return["BoundingBox"]];
        If[FailureQ[assoc["IsoValue"]], Return["IsoValue"]];

        If[FailureQ[assoc["Shader"]], Return["Shader"]];
        If[FailureQ[assoc["Background"]], Return[Background]];

        If[FailureQ[assoc["Translate"]], Return[ViewPoint]];
        If[FailureQ[assoc["Up"]], Return[ViewVertical]];
        If[FailureQ[assoc["FOV"]], Return[ViewAngle]];
        If[FailureQ[assoc["LookAt"]], Return[ViewCenter]];
        If[FailureQ[assoc["Range"]], Return[ViewRange]];

        If[FailureQ[assoc["Camera"]], Return[ViewProjection]];
        If[FailureQ[assoc["Samples"]], Return[PerformanceGoal]];
        If[FailureQ[assoc["ImageResolution"]], Return[ImageResolution]];
        If[FailureQ[assoc["ImageSize"]], Return[ImageSize]];

        $Failed
    )


(* ::Subsubsection::Closed:: *)
(*canonicalizeShader*)


$defaultShaderSpec = "Rubber";
$defaultColorSpec = "Default";


canonicalizeShader[shader_String] /; KeyExistsQ[materialParameters, shader] := Prepend[Lookup[materialParameters[shader], {"BaseColorFront", "BaseColorBack", "BaseColorClosed"}], shader]


canonicalizeShader[theme_String] /; KeyExistsQ[$renderColorThemes, theme] := {$defaultShaderSpec, theme}


canonicalizeShader[shader:("Depth"|{"Depth", _?NumericQ, __})] := {shader, RGBColor[1, 1, 1], RGBColor[1, 1, 1], RGBColor[1, 1, 1]}


canonicalizeShader[{shader_, theme_String}] := {shader, theme}


canonicalizeShader[{shader_, icolor_?ColorQ}] :=
    With[{color = ColorConvert[icolor, "RGB"][[1 ;; 3]]},
        {shader, color, color, color}
    ]


canonicalizeShader[{shader_, icolorf_?ColorQ, icolorb_?ColorQ}] :=
    With[{
            colorf = ColorConvert[icolorf, "RGB"][[1 ;; 3]],
            colorb = ColorConvert[icolorb, "RGB"][[1 ;; 3]]
        },
        {shader, colorf, colorb, colorf}
    ]


canonicalizeShader[{shader_, colorf_?ColorQ, colorb_?ColorQ, colorc_?ColorQ}] :=
    {shader, ColorConvert[colorf, "RGB"][[1 ;; 3]], ColorConvert[colorb, "RGB"][[1 ;; 3]], ColorConvert[colorc, "RGB"][[1 ;; 3]]}


canonicalizeShader[Automatic] = {$defaultShaderSpec, $defaultColorSpec};


canonicalizeShader[{colors__?ColorQ}] := canonicalizeShader[{$defaultShaderSpec, colors}]


canonicalizeShader[color_?ColorQ] := canonicalizeShader[{$defaultShaderSpec, color}]


canonicalizeShader[shader_] := {shader, $defaultColorSpec}


canonicalizeShader[___] = $Failed;


(* ::Subsubsection::Closed:: *)
(*parseBoundingBox*)


parseBoundingBox[vdb_, bds_] := If[bds === Automatic, vdb["WorldBoundingBox"], bds]


(* ::Subsubsection::Closed:: *)
(*parseIsoValue*)


parseIsoValue[Automatic] = 0.0;
parseIsoValue[x_?realQ] := x
parseIsoValue[___] = $Failed;


(* ::Subsubsection::Closed:: *)
(*parseRenderColor*)


parseRenderColor[{"Normal"|"NormalClosed"|"Position"|"PositionClosed", __}] = {1.0, 1.0, 1.0};
parseRenderColor[{_, theme_String}] :=
    With[{color = $renderColorThemes[theme][[1]]},
        List @@ color /; ColorQ[color]
    ]
parseRenderColor[{_, color_?ColorQ, _, _}] := List @@ color
parseRenderColor[___] = $Failed;


parseRenderColor2[{"Normal"|"NormalClosed"|"Position"|"PositionClosed", __}] = {1.0, 1.0, 1.0};
parseRenderColor2[{_, theme_String}] :=
    With[{color = $renderColorThemes[theme][[2]]},
        List @@ color /; ColorQ[color]
    ]
parseRenderColor2[{_, _, color_?ColorQ, _}] := List @@ color
parseRenderColor2[___] = $Failed;


parseRenderColor3[{"Normal"|"NormalClosed"|"Position"|"PositionClosed", __}] = {1.0, 1.0, 1.0};
parseRenderColor3[{_, theme_String}] :=
    With[{color = $renderColorThemes[theme][[3]]},
        List @@ color /; ColorQ[color]
    ]
parseRenderColor3[{_, _, _, color_?ColorQ}] := List @@ color
parseRenderColor3[___] = $Failed;


(* ::Subsubsection::Closed:: *)
(*parseRenderBackgroundColor*)


parseRenderBackgroundColor[None, shader_] := parseRenderBackgroundColor[White, shader]
parseRenderBackgroundColor[Automatic, shader:{"Depth"|{"Depth", ___}|"DepthClosed"|{"DepthClosed", ___}, __}] := parseRenderBackgroundColor[Black, shader]
parseRenderBackgroundColor[Automatic, shader_] := parseRenderBackgroundColor[White, shader]
parseRenderBackgroundColor[Automatic, {shader_, t_String}] /; KeyExistsQ[$renderColorThemes, t] := parseRenderBackgroundColor[$renderColorThemes[t][[4]], {shader, t}]
parseRenderBackgroundColor[color_?ColorQ, __] := List @@ ColorConvert[color, "RGB"][[1 ;; 3]]
parseRenderBackgroundColor[___] = $Failed;


(* ::Subsubsection::Closed:: *)
(*parseRenderViewPoint*)


canonicalizeViewPoint[Left]  = {-2,0,0};
canonicalizeViewPoint[Right] = { 2,0,0};
canonicalizeViewPoint[Front] = {0,-2,0};
canonicalizeViewPoint[Back]  = {0, 2,0};
canonicalizeViewPoint[Below] = {0,0,-2};
canonicalizeViewPoint[Above] = {0,0, 2};
canonicalizeViewPoint[vp_] := vp


parseRenderViewPoint[vp_List, bds_?bounds3DQ] /; VectorQ[vp, NumericQ] && Length[vp] === 3 :=
    Block[{vpfinite, mx},
        vpfinite = vp /. inf_DirectedInfinity :> Sign[inf]*1000;
        mx = Replace[Max[Abs[Subtract @@@ bds]], _?NonPositive -> 1.0, {0}];

        mx * vpfinite + (Mean /@ bds)
    ]


parseRenderViewPoint[dir:(Left|Right|Front|Back|Below|Above),  bds_] := parseRenderViewPoint[canonicalizeViewPoint[dir], bds]


parseRenderViewPoint[dirs:{(Left|Right|Front|Back|Below|Above)..}, bds_] :=
    Block[{canons, vps},
        canons = canonicalizeViewPoint /@ dirs;
        (
            vps = GatherBy[canons, Unitize][[All, -1]];

            Total[parseRenderViewPoint[canonicalizeViewPoint[#], bds]& /@ vps]

        ) /; MatrixQ[canons]
    ]


parseRenderViewPoint[___] = $Failed;


(* ::Subsubsection::Closed:: *)
(*parseRenderViewCenter*)


parseRenderViewCenter[{vc_List, {ox_?NumericQ, oy_?NumericQ}}, translate_, up_, \[Alpha]_, bds_?bounds3DQ] :=
    Block[{lookat = parseRenderViewCenterNoOffset[vc, bds], v, cross, rot},
        (
            v = translate-lookat;
            cross = Cross[v, up];

            rot = RotationTransform[(oy-0.5)*\[Alpha], cross, translate] @* RotationTransform[(ox-0.5)*\[Alpha], up, translate];

            rot[lookat]

        ) /; lookat =!= $Failed
    ]
parseRenderViewCenter[vc_, __, bds_] := parseRenderViewCenterNoOffset[vc, bds];


parseRenderViewCenterNoOffset[Automatic, bds_] := parseRenderViewCenterNoOffset[{0.5, 0.5, 0.5}, bds]
parseRenderViewCenterNoOffset[vc_List, bds_?bounds3DQ] /; VectorQ[vc, NumericQ] && Length[vc] === 3 := MapThread[Dot, {Transpose[{1-vc, vc}], bds}]
parseRenderViewCenterNoOffset[{vc_List, _}, bds_?bounds3DQ] := parseRenderViewCenterNoOffset[vc, bds]
parseRenderViewCenterNoOffset[___] = $Failed;


(* ::Subsubsection::Closed:: *)
(*parseRenderViewVertical*)


parseRenderViewVertical[vv_List, vp_] /; VectorQ[vp, NumericQ] && Length[vp] === 3 :=
    If[degenerateViewVerticalQ[vv, vp],
        fixDegenerateViewVertical[vp],
        vv
    ]
parseRenderViewVertical[___] = $Failed;


(* ::Subsubsection::Closed:: *)
(*parseRenderViewRange*)


parseRenderViewRange[Automatic|All, vp_, vc_, bds_] :=
    Block[{corners, inview, far},
        corners = Tuples[bds];
        inview = Pick[corners, RegionMember[HalfSpace[vp-vc, vp], corners]];
        If[Length[inview] == 0,
            parseRenderViewRange[{0.0, 0.0}, vp, vc, bds],
            far = Sqrt[Max[Total[(Transpose[inview] - vp)^2]]];
            parseRenderViewRange[{0.0, far}, vp, vc, bds]
        ]
    ]


parseRenderViewRange[{min_?NumericQ, max_?NumericQ}, ___] /; min <= max :=
    Block[{clip},
        clip = Clip[{min, max}, {.001, \[Infinity]}];
        If[Equal @@ clip,
            clip + {0, 0.001},
            clip
        ]
    ]


parseRenderViewRange[Scaled[vrng:{min_?NumericQ, max_?NumericQ}], vp_, vc_, bds_] /; min <= max :=
    Block[{padding, vrmin, vrmax},
        padding = 0.02*Max[Abs[Subtract @@@ bds]];
        vrmin = Clip[SignedRegionDistance[Cuboid @@ Transpose[bds], vp] - padding, {0.1, \[Infinity]}];
        vrmax = Ramp[Sqrt[Max[Total[Subtract[Transpose[Tuples[bds]], vp]^2, {1}]]] + .11];

        vrng*(vrmax-vrmin) + vrmin
    ]


parseRenderViewRange[___] = $Failed;


(* ::Subsubsection::Closed:: *)
(*parseRenderViewAngle*)


$renderFocalLength = 50.0;


parseRenderViewAngle[args__] :=
    With[{aperture = viewAngleAperture[args]},
        {aperture, $renderFocalLength} /; aperture =!= $Failed && NumericQ[$renderFocalLength]
    ]
parseRenderViewAngle[___] = $Failed;


viewAngleAperture[args__] :=
    With[{\[Alpha] = constructViewAngle[args]},
        2$renderFocalLength*Tan[Clip[\[Alpha], {.001, 3.14059}]/2] /; \[Alpha] =!= $Failed
    ];
viewAngleAperture[___] = $Failed;


constructViewAngle[All, translate_, lookat_, bds_] :=
    With[{v = lookat - translate},
        2.0*Max[VectorAngle[v, # - translate]& /@ Tuples[bds]]
    ];
constructViewAngle[Automatic, args__] :=
    With[{\[Alpha] = constructViewAngle[All, args]},
        Min[35.0*Degree, \[Alpha]] /; \[Alpha] =!= $Failed
    ];
constructViewAngle[\[Alpha]_?NumericQ, args__] /; 0 <= \[Alpha] <= \[Pi] := \[Alpha]
constructViewAngle[___] = $Failed;


(* ::Subsubsection::Closed:: *)
(*parseRenderShader*)


parseRenderShader["Diffuse"] = 0;


parseRenderShader["Matte"] = 1;


parseRenderShader["Normal"] = 2;


parseRenderShader["Position"] = 3;


parseRenderShader["Depth"] = 4;
parseRenderShader[{"Depth", ___}] = parseRenderShader["Depth"];


parseRenderShader[shader_String] /; KeyExistsQ[materialParameters, shader] := shader


parseRenderShader[___] = $Failed;


(* ::Subsubsection::Closed:: *)
(*parseRenderPerformance*)


parseRenderPerformance["Speed"] = 1;
parseRenderPerformance["Quality"] = 9;
parseRenderPerformance[___] = $Failed;


(* ::Subsubsection::Closed:: *)
(*parseDepthParameters*)


$imin = 0.0;
$imax = 1.0;
$gamma = 1.0;


parseDepthParameters[{{"Depth"|"DepthClosed", args___}, __}] := iParseDepthParameters[args]
parseDepthParameters[___] = {$imin, $imax, $gamma};


iParseDepthParameters[] = {$imin, $imax, $gamma};
iParseDepthParameters[gamma_?Positive] := {$imin, $imax, gamma}
iParseDepthParameters[gamma_?Positive, {imin_, imax_}] /; 0 <= imin <= imax <= 1 := {imin, imax, gamma}
iParseDepthParameters[___] = $Failed;


(* ::Subsubsection::Closed:: *)
(*parseRenderViewProjection*)


parseRenderViewProjection["Perspective", _]  = 0;
parseRenderViewProjection["Orthographic", _] = 1;
parseRenderViewProjection[Automatic, vp_] :=
    With[{p = If[FreeQ[vp, _DirectedInfinity], "Perspective", "Orthographic"]},
        parseRenderViewProjection[p, vp]
    ]
parseRenderViewProjection[___] = $Failed;


(* ::Subsubsection::Closed:: *)
(*parseRenderImageResolution*)


parseRenderImageResolution[Automatic] := imageResolution[]
parseRenderImageResolution[x_?Positive] := x
parseRenderImageResolution[___] = $Failed;


imageResolution[] := Replace[Max[Quiet[CurrentValue[{"ConnectedDisplays", "Resolution"}]]], Except[_Real|_Integer] -> 72., {0}]


(* ::Subsubsection::Closed:: *)
(*parseRenderImageSize*)


parseRenderImageSize[x:(_Integer|Automatic), args__] := parseRenderImageSize[{x, Automatic}, args]
parseRenderImageSize[{x_Integer?Positive, y_Integer?Positive}, res_, __] := res/72*{x, y}
parseRenderImageSize[{x_, y_}, res_, vp_List, vv_List, bds_?bounds3DQ] :=
    Block[{aspectratio, w, h},
        aspectratio = 1.0;(*viewPointAspectRatio[bds, vp, vv];*)
        w = Round[Replace[x, Automatic -> 360*res/72, {0}]];
        h = Round[Replace[y, Automatic -> w*aspectratio, {0}]];

        {w, h}
    ]
parseRenderImageSize[___] = $Failed


viewPointAspectRatio[bds_, vp_, vv_] :=
    Block[{rot, proj1, up2d, proj2},
        rot = RotationTransform[{vp-(Mean /@ bds), {0,0,1}}];
        proj1 = rot[Tuples[bds]][[All, 1;;2]];
        up2d = rot[vv][[1 ;; 2]];

        proj2 = RotationTransform[{up2d, {0, 1}}][proj1];
        Divide @@ Clip[Reverse[Abs[Subtract @@@ CoordinateBounds[proj2]]], {0.001, \[Infinity]}]
    ]


(* ::Subsubsection::Closed:: *)
(*parseRenderUnscaledImageSize*)


parseRenderUnscaledImageSize[x:(_Integer|Automatic), args__] := parseRenderUnscaledImageSize[{x, Automatic}, args]
parseRenderUnscaledImageSize[{x_Integer?Positive, y_Integer?Positive}, __] := {x, y}
parseRenderUnscaledImageSize[{x_, y_}, vp_List, vv_List, bds_?bounds3DQ] :=
    Block[{aspectratio, w, h},
        aspectratio = viewPointAspectRatio[bds, vp, vv];
        w = Round[Replace[x, Automatic -> 360, {0}]];
        h = Round[Replace[y, Automatic -> w*aspectratio, {0}]];

        {w, h}
    ]
parseRenderUnscaledImageSize[___] = $Failed


viewPointAspectRatio[___] = 1.0;


(* ::Subsubsection::Closed:: *)
(*parseOrthographicFrame*)


parseOrthographicFrame["Orthographic", x_?NumericQ, __] := Clip[x, {0.001, \[Infinity]}]
parseOrthographicFrame["Orthographic", Automatic, vp_, vc_, vv_, bds_] :=
    Block[{rot, proj1, up2d, proj2},
        rot = RotationTransform[{vp-vc, {0,0,1}}];
        proj1 = rot[Tuples[bds]][[All, 1;;2]];
        up2d = rot[vv][[1 ;; 2]];

        proj2 = RotationTransform[{up2d, {0, 1}}][proj1];
        Clip[Abs[Subtract @@@ CoordinateBounds[proj2]][[1]], {0.001, \[Infinity]}]
    ]
parseOrthographicFrame["Perspective"|Automatic, __] = 1.0;
parseOrthographicFrame[___] = $Failed;


(* ::Subsection::Closed:: *)
(*Degenerate ViewVertical*)


degenerateViewVerticalQ[vv_, vp_] := Chop[Norm[Cross[vv, canonicalizeViewPoint[vp]]]] === 0


fixDegenerateViewVertical[vp_] := Normalize[RotateLeft[canonicalizeViewPoint[vp]]]


(* ::Subsection::Closed:: *)
(*MaterialShading parameters*)


(* ::Text:: *)
(*Found through ctrl-shift-e on MaterialShading[] outputs.*)


materialParameters = <||>;


(* ::Subsubsection::Closed:: *)
(*Aluminum*)


materialParameters["Aluminum"] = <|
    "BaseColorFront" -> RGBColor[0.95, 0.95, 0.95],
    "BaseColorBack" -> RGBColor[0.95, 0.95, 0.95],
    "BaseColorClosed" -> RGBColor[0.95, 0.95, 0.95],
    "MetallicCoefficient" -> 0.8,
    "RoughnessCoefficient" -> 0.75,
    "SpecularAnisotropyCoefficient" -> 0.6,
    "Reflectance" -> 0.5,
    "CoatColor" -> {1.0, 1.0, 1.0},
    "CoatRoughnessCoefficient" -> 0.0,
    "CoatAnisotropyCoefficient" -> 0.0,
    "CoatReflectance" -> 0.5,
    "SpecularColorMultiplier" -> 1.0,
    "DiffuseColorMultiplier" -> 1.0,
    "CoatColorMultiplier" -> 0.0
|>;


(* ::Subsubsection::Closed:: *)
(*Brass*)


materialParameters["Brass"] = <|
    "BaseColorFront" -> RGBColor[0.9, 0.855, 0.45],
    "BaseColorBack" -> RGBColor[0.9, 0.855, 0.45],
    "BaseColorClosed" -> RGBColor[0.9, 0.855, 0.45],
    "MetallicCoefficient" -> 0.8,
    "RoughnessCoefficient" -> 0.65,
    "SpecularAnisotropyCoefficient" -> 0.4,
    "Reflectance" -> 0.5,
    "CoatColor" -> {1.0, 1.0, 1.0},
    "CoatRoughnessCoefficient" -> 0.0,
    "CoatAnisotropyCoefficient" -> 0.0,
    "CoatReflectance" -> 0.5,
    "SpecularColorMultiplier" -> 1,
    "DiffuseColorMultiplier" -> 1.0,
    "CoatColorMultiplier" -> 0.0
|>;


(* ::Subsubsection::Closed:: *)
(*Bronze*)


materialParameters["Bronze"] = <|
    "BaseColorFront" -> RGBColor[0.9, 0.68625, 0.45],
    "BaseColorBack" -> RGBColor[0.9, 0.68625, 0.45],
    "BaseColorClosed" -> RGBColor[0.9, 0.68625, 0.45],
    "MetallicCoefficient" -> 0.8,
    "RoughnessCoefficient" -> 0.65,
    "SpecularAnisotropyCoefficient" -> 0.3,
    "Reflectance" -> 0.5,
    "CoatColor" -> {1.0, 1.0, 1.0},
    "CoatRoughnessCoefficient" -> 0.0,
    "CoatAnisotropyCoefficient" -> 0.0,
    "CoatReflectance" -> 0.5,
    "SpecularColorMultiplier" -> 1,
    "DiffuseColorMultiplier" -> 1.0,
    "CoatColorMultiplier" -> 0.0
|>;


(* ::Subsubsection::Closed:: *)
(*Copper*)


materialParameters["Copper"] = <|
    "BaseColorFront" -> RGBColor[1.0, 0.65, 0.5],
    "BaseColorBack" -> RGBColor[1.0, 0.65, 0.5],
    "BaseColorClosed" -> RGBColor[1.0, 0.65, 0.5],
    "MetallicCoefficient" -> 0.8,
    "RoughnessCoefficient" -> 0.65,
    "SpecularAnisotropyCoefficient" -> 0.3,
    "Reflectance" -> 0.5,
    "CoatColor" -> {1.0, 1.0, 1.0},
    "CoatRoughnessCoefficient" -> 0.0,
    "CoatAnisotropyCoefficient" -> 0.0,
    "CoatReflectance" -> 0.5,
    "SpecularColorMultiplier" -> 1,
    "DiffuseColorMultiplier" -> 1.0,
    "CoatColorMultiplier" -> 0.0
|>;


(* ::Subsubsection::Closed:: *)
(*Electrum*)


materialParameters["Electrum"] = <|
    "BaseColorFront" -> RGBColor[0.9, 0.774, 0.45],
    "BaseColorBack" -> RGBColor[0.9, 0.774, 0.45],
    "BaseColorClosed" -> RGBColor[0.9, 0.774, 0.45],
    "MetallicCoefficient" -> 0.7,
    "RoughnessCoefficient" -> 0.7,
    "SpecularAnisotropyCoefficient" -> 0.3,
    "Reflectance" -> 0.5,
    "CoatColor" -> {1.0, 1.0, 1.0},
    "CoatRoughnessCoefficient" -> 0.0,
    "CoatAnisotropyCoefficient" -> 0.0,
    "CoatReflectance" -> 0.5,
    "SpecularColorMultiplier" -> 1,
    "DiffuseColorMultiplier" -> 1.0,
    "CoatColorMultiplier" -> 0.0
|>;


(* ::Subsubsection::Closed:: *)
(*Gold*)


materialParameters["Gold"] = <|
    "BaseColorFront" -> RGBColor[1.0, 0.75, 0.0],
    "BaseColorBack" -> RGBColor[1.0, 0.75, 0.0],
    "BaseColorClosed" -> RGBColor[1.0, 0.75, 0.0],
    "MetallicCoefficient" -> 0.8,
    "RoughnessCoefficient" -> 0.65,
    "SpecularAnisotropyCoefficient" -> 0.3,
    "Reflectance" -> 0.5,
    "CoatColor" -> {1.0, 1.0, 1.0},
    "CoatRoughnessCoefficient" -> 0.0,
    "CoatAnisotropyCoefficient" -> 0.0,
    "CoatReflectance" -> 0.5,
    "SpecularColorMultiplier" -> 1.0,
    "DiffuseColorMultiplier" -> 1.0,
    "CoatColorMultiplier" -> 0.0
|>;


(* ::Subsubsection::Closed:: *)
(*Iron*)


materialParameters["Iron"] = <|
    "BaseColorFront" -> RGBColor[0.6, 0.576, 0.54],
    "BaseColorBack" -> RGBColor[0.6, 0.576, 0.54],
    "BaseColorClosed" -> RGBColor[0.6, 0.576, 0.54],
    "MetallicCoefficient" -> 0.7,
    "RoughnessCoefficient" -> 0.6,
    "SpecularAnisotropyCoefficient" -> 0.3,
    "Reflectance" -> 0.5,
    "CoatColor" -> {1.0, 1.0, 1.0},
    "CoatRoughnessCoefficient" -> 0.0,
    "CoatAnisotropyCoefficient" -> 0.0,
    "CoatReflectance" -> 0.5,
    "SpecularColorMultiplier" -> 1,
    "DiffuseColorMultiplier" -> 1.0,
    "CoatColorMultiplier" -> 0.0
|>;


(* ::Subsubsection::Closed:: *)
(*Pewter*)


materialParameters["Pewter"] = <|
    "BaseColorFront" -> RGBColor[0.9, 0.864, 0.81],
    "BaseColorBack" -> RGBColor[0.9, 0.864, 0.81],
    "BaseColorClosed" -> RGBColor[0.9, 0.864, 0.81],
    "MetallicCoefficient" -> 1,
    "RoughnessCoefficient" -> 0.75,
    "SpecularAnisotropyCoefficient" -> 0.3,
    "Reflectance" -> 0.5,
    "CoatColor" -> {1.0, 1.0, 1.0},
    "CoatRoughnessCoefficient" -> 0.0,
    "CoatAnisotropyCoefficient" -> 0.0,
    "CoatReflectance" -> 0.5,
    "SpecularColorMultiplier" -> 1,
    "DiffuseColorMultiplier" -> 1.0,
    "CoatColorMultiplier" -> 0.0
|>;


(* ::Subsubsection::Closed:: *)
(*Silver*)


materialParameters["Silver"] = <|
    "BaseColorFront" -> RGBColor[1.0, 1.0, 1.0],
    "BaseColorBack" -> RGBColor[1.0, 1.0, 1.0],
    "BaseColorClosed" -> RGBColor[1.0, 1.0, 1.0],
    "MetallicCoefficient" -> 1,
    "RoughnessCoefficient" -> 0.75,
    "SpecularAnisotropyCoefficient" -> 0.3,
    "Reflectance" -> 0.5,
    "CoatColor" -> {1.0, 1.0, 1.0},
    "CoatRoughnessCoefficient" -> 0.0,
    "CoatAnisotropyCoefficient" -> 0.0,
    "CoatReflectance" -> 0.5,
    "SpecularColorMultiplier" -> 1,
    "DiffuseColorMultiplier" -> 1.0,
    "CoatColorMultiplier" -> 0.0
|>;


(* ::Subsubsection::Closed:: *)
(*Clay*)


materialParameters["Clay"] = <|
    "BaseColorFront" -> RGBColor[0.8, 0.352, 0.16],
    "BaseColorBack" -> RGBColor[0.8, 0.352, 0.16],
    "BaseColorClosed" -> RGBColor[0.8, 0.352, 0.16],
    "MetallicCoefficient" -> 0,
    "RoughnessCoefficient" -> 0,
    "SpecularAnisotropyCoefficient" -> 0.0,
    "Reflectance" -> 0.5,
    "CoatColor" -> {1.0, 1.0, 1.0},
    "CoatRoughnessCoefficient" -> 0.0,
    "CoatAnisotropyCoefficient" -> 0.0,
    "CoatReflectance" -> 0.5,
    "SpecularColorMultiplier" -> 0.0,
    "DiffuseColorMultiplier" -> 1.0,
    "CoatColorMultiplier" -> 0.0
|>;


(* ::Subsubsection::Closed:: *)
(*Foil*)


materialParameters["Foil"] = <|
    "BaseColorFront" -> RGBColor[0.5, 1.0, 0.0],
    "BaseColorBack" -> RGBColor[0.5, 1.0, 0.0],
    "BaseColorClosed" -> RGBColor[0.5, 1.0, 0.0],
    "MetallicCoefficient" -> 0.5,
    "RoughnessCoefficient" -> 0.6,
    "SpecularAnisotropyCoefficient" -> 0.0,
    "Reflectance" -> 0.5,
    "CoatColor" -> {1.0, 1.0, 1.0},
    "CoatRoughnessCoefficient" -> 0.6,
    "CoatAnisotropyCoefficient" -> 0.0,
    "CoatReflectance" -> 0.5,
    "SpecularColorMultiplier" -> 1,
    "DiffuseColorMultiplier" -> 1.0,
    "CoatColorMultiplier" -> 0.75
|>;


(* ::Subsubsection::Closed:: *)
(*Glazed*)


materialParameters["Glazed"] = <|
    "BaseColorFront" -> RGBColor[1.0, 0.24, 0.0],
    "BaseColorBack" -> RGBColor[1.0, 0.24, 0.0],
    "BaseColorClosed" -> RGBColor[1.0, 0.24, 0.0],
    "MetallicCoefficient" -> 0.5,
    "RoughnessCoefficient" -> 0.6,
    "SpecularAnisotropyCoefficient" -> 0.6,
    "Reflectance" -> 0.5,
    "CoatColor" -> {1.0, 1.0, 1.0},
    "CoatRoughnessCoefficient" -> 0.2,
    "CoatAnisotropyCoefficient" -> 0.6,
    "CoatReflectance" -> 0.6,
    "SpecularColorMultiplier" -> 1.0,
    "DiffuseColorMultiplier" -> 1.0,
    "CoatColorMultiplier" -> 0.75
|>;


(* ::Subsubsection::Closed:: *)
(*Plastic*)


materialParameters["Plastic"] = <|
    "BaseColorFront" -> RGBColor[0.3, 0.58, 1.0],
    "BaseColorBack" -> RGBColor[0.3, 0.58, 1.0],
    "BaseColorClosed" -> RGBColor[0.3, 0.58, 1.0],
    "MetallicCoefficient" -> 0.0,
    "RoughnessCoefficient" -> 1.0,
    "SpecularAnisotropyCoefficient" -> 0.0,
    "Reflectance" -> 0.5,
    "CoatColor" -> {1.0, 1.0, 1.0},
    "CoatRoughnessCoefficient" -> 0.3,
    "CoatAnisotropyCoefficient" -> 0.5,
    "CoatReflectance" -> 0.5,
    "SpecularColorMultiplier" -> 1.0,
    "DiffuseColorMultiplier" -> 1.0,
    "CoatColorMultiplier" -> 0.75
|>;


(* ::Subsubsection::Closed:: *)
(*Rubber*)


materialParameters["Rubber"] = <|
    "BaseColorFront" -> RGBColor[0.5, 0.5, 0.5],
    "BaseColorBack" -> RGBColor[0.5, 0.5, 0.5],
    "BaseColorClosed" -> RGBColor[0.5, 0.5, 0.5],
    "MetallicCoefficient" -> 0.2,
    "RoughnessCoefficient" -> 0.6,
    "SpecularAnisotropyCoefficient" -> 0.0,
    "Reflectance" -> 0.5,
    "CoatColor" -> {1.0, 1.0, 1.0},
    "CoatRoughnessCoefficient" -> 0.0,
    "CoatAnisotropyCoefficient" -> 0.0,
    "CoatReflectance" -> 0.5,
    "SpecularColorMultiplier" -> 1,
    "DiffuseColorMultiplier" -> 1.0,
    "CoatColorMultiplier" -> 0.0
|>;


(* ::Subsubsection::Closed:: *)
(*Satin*)


materialParameters["Satin"] = <|
    "BaseColorFront" -> RGBColor[0.75, 0.5, 1.0],
    "BaseColorBack" -> RGBColor[0.75, 0.5, 1.0],
    "BaseColorClosed" -> RGBColor[0.75, 0.5, 1.0],
    "MetallicCoefficient" -> 0.5,
    "RoughnessCoefficient" -> 0.66,
    "SpecularAnisotropyCoefficient" -> 0.8,
    "Reflectance" -> 0.5,
    "CoatColor" -> {1.0, 1.0, 1.0},
    "CoatRoughnessCoefficient" -> 0.0,
    "CoatAnisotropyCoefficient" -> 0.0,
    "CoatReflectance" -> 0.5,
    "SpecularColorMultiplier" -> 1.0,
    "DiffuseColorMultiplier" -> 1.0,
    "CoatColorMultiplier" -> 0.0
|>;


(* ::Subsubsection::Closed:: *)
(*Base materials*)


$baseMaterials = Keys[materialParameters];


(* ::Subsection::Closed:: *)
(*messageRenderFunction*)


Options[messageRenderFunction] = Options[OpenVDBLevelSetRender];


messageRenderFunction[head_, expr_, ___] /; messageScalarGridQ[expr, head] = $Failed;


messageRenderFunction[head_, expr_, ___] /; messageLevelSetGridQ[expr, head] = $Failed;


messageRenderFunction[head_, vdb_, opts:OptionsPattern[]] := messageRenderFunction[head, vdb, Automatic, opts]


messageRenderFunction[head_, vdb_, shading_, opts:OptionsPattern[]] /; !OptionQ[shading] :=
    Block[{assoc, opt},
        assoc = iparseRenderOptions[vdb, shading, opts];
        (
            opt = renderFailureOption[assoc];
            (
                If[opt === "Shader",
                    Message[head::shaderval, shading],
                    Message[head::renderval, opt -> OptionValue[opt]]
                ];
                $Failed

            ) /; opt =!= $Failed

        ) /; AssociationQ[assoc]
    ]


messageRenderFunction[___] = $Failed


General::renderval = "`1` is an invalid render setting.";
General::shaderval = "`1` is an invalid shader setting.";
