(* ::Package:: *) (* ::Title:: *) (*DistanceMeasure*) (* ::Subtitle:: *) (*Perform membership, nearest, and distance queries on a grid.*) (* ::Text:: *) (*Copyright Contributors to the OpenVDB Project*) (*SPDX-License-Identifier: Apache-2.0*) (* ::Section:: *) (*Initialization & Usage*) Package["OpenVDBLink`"] PackageExport["OpenVDBMember"] PackageExport["OpenVDBNearest"] PackageExport["OpenVDBDistance"] PackageExport["OpenVDBSignedDistance"] PackageExport["OpenVDBFillWithBalls"] OpenVDBMember::usage = "OpenVDBMember[expr, pt] determines if the point pt lies within the region given by a scalar grid."; OpenVDBNearest::usage = "OpenVDBNearest[expr, pt] finds closest point to pt on the iso surface of a scalar grid."; OpenVDBDistance::usage = "OpenVDBDistance[expr, pt] finds minimum distance from pt to the iso surface of a scalar grid."; OpenVDBSignedDistance::usage = "OpenVDBSignedDistance[expr, pt] finds minimum distance from pt to the iso surface of a scalar grid and returns a negative value if pt lies within the region given by the grid."; OpenVDBFillWithBalls::usage = "OpenVDBFillWithBalls[expr, n, {rmin, rmax}] fills a closed scalar grid with up to n adaptively-sized balls with radii between rmin and rmax."; (* ::Section:: *) (*Membership*) (* ::Subsection::Closed:: *) (*OpenVDBMember*) (* ::Subsubsection::Closed:: *) (*Main*) Options[OpenVDBMember] = {"IsoValue" -> Automatic}; OpenVDBMember[args___] /; !CheckArgs[OpenVDBMember[args], 2] = $Failed; OpenVDBMember[args___] := With[{res = iOpenVDBMember[args]}, res /; res =!= $Failed ] OpenVDBMember[args___] := messageCPTFunction[OpenVDBMember, args] (* ::Subsubsection::Closed:: *) (*iOpenVDBMember*) (* ::Text:: *) (*Skipping call to regimeConvert to avoid duplicating points when unnecessary.*) Options[iOpenVDBMember] = Options[OpenVDBMember]; iOpenVDBMember[vdb_?OpenVDBScalarGridQ, pts_?coordinatesQ -> $indexregime, OptionsPattern[]] := Block[{isovalue, mems}, isovalue = gridIsoValue[OptionValue["IsoValue"], vdb]; ( mems = vdb["gridMember"[pts, isovalue]]; If[fogVolumeQ[vdb], Subtract[1, mems], mems] /; VectorQ[mems, IntegerQ] ) /; realQ[isovalue] ] iOpenVDBMember[vdb_?OpenVDBScalarGridQ, pts_?coordinatesQ -> $worldregime, opts___] := iOpenVDBMember[vdb, regimeConvert[vdb, pts, $worldregime -> $indexregime] -> $indexregime, opts] iOpenVDBMember[vdb_, pts_?coordinateQ -> regime_, opts___] := With[{res = iOpenVDBMember[vdb, {pts} -> regime, opts]}, res[[1]] /; VectorQ[res, IntegerQ] && Length[res] === 1 ] iOpenVDBMember[vdb_, pts_List, opts___] := iOpenVDBMember[vdb, pts -> $worldregime, opts] iOpenVDBMember[___] = $Failed; (* ::Subsubsection::Closed:: *) (*Argument conform & completion*) registerForLevelSet[iOpenVDBMember, 1]; SyntaxInformation[OpenVDBMember] = {"ArgumentsPattern" -> {_, _, OptionsPattern[]}}; OpenVDBDefaultSpace[OpenVDBMember] = $worldregime; (* ::Section:: *) (*Nearest*) (* ::Subsection::Closed:: *) (*OpenVDBNearest*) (* ::Subsubsection::Closed:: *) (*Main*) Options[OpenVDBNearest] = {"IsoValue" -> Automatic}; OpenVDBNearest[args___] /; !CheckArgs[OpenVDBNearest[args], 2] = $Failed; OpenVDBNearest[args___] := With[{res = iOpenVDBNearest[args]}, res /; res =!= $Failed ] OpenVDBNearest[args___] := messageCPTFunction[OpenVDBNearest, args] (* ::Subsubsection::Closed:: *) (*iOpenVDBNearest*) (* ::Text:: *) (*Skipping call to regimeConvert to avoid duplicating points when unnecessary.*) Options[iOpenVDBNearest] = Options[OpenVDBNearest]; iOpenVDBNearest[vdb_?OpenVDBScalarGridQ, pts_?coordinatesQ -> $worldregime, OptionsPattern[]] := Block[{isovalue, nearest}, isovalue = gridIsoValue[OptionValue["IsoValue"], vdb]; ( nearest = vdb["gridNearest"[pts, isovalue]]; nearest /; MatrixQ[nearest, NumericQ] ) /; realQ[isovalue] ] iOpenVDBNearest[vdb_?OpenVDBScalarGridQ, pts_?coordinatesQ -> $indexregime, opts___] := iOpenVDBNearest[vdb, regimeConvert[vdb, pts, $indexregime -> $worldregime] -> $worldregime, opts] iOpenVDBNearest[vdb_, pts_?coordinateQ -> regime_, opts___] := With[{res = iOpenVDBNearest[vdb, {pts} -> regime, opts]}, res[[1]] /; MatrixQ[res] && Dimensions[res] === {1, 3} ] iOpenVDBNearest[vdb_, pts_List, opts___] := iOpenVDBNearest[vdb, pts -> $worldregime, opts] iOpenVDBNearest[___] = $Failed; (* ::Subsubsection::Closed:: *) (*Argument conform & completion*) registerForLevelSet[iOpenVDBNearest, 1]; SyntaxInformation[OpenVDBNearest] = {"ArgumentsPattern" -> {_, _, OptionsPattern[]}}; OpenVDBDefaultSpace[OpenVDBNearest] = $worldregime; (* ::Section:: *) (*Distance*) (* ::Subsection::Closed:: *) (*OpenVDBDistance*) (* ::Subsubsection::Closed:: *) (*Main*) Options[OpenVDBDistance] = {"IsoValue" -> Automatic}; OpenVDBDistance[args___] /; !CheckArgs[OpenVDBDistance[args], 2] = $Failed; OpenVDBDistance[args___] := With[{res = iOpenVDBDistance[args]}, res /; res =!= $Failed ] OpenVDBDistance[args___] := messageCPTFunction[OpenVDBDistance, args] (* ::Subsubsection::Closed:: *) (*iOpenVDBDistance*) (* ::Text:: *) (*Skipping call to regimeConvert to avoid duplicating points when unnecessary.*) Options[iOpenVDBDistance] = Options[OpenVDBDistance]; iOpenVDBDistance[vdb_?OpenVDBScalarGridQ, pts_?coordinatesQ -> $worldregime, OptionsPattern[]] := Block[{isovalue, dists}, isovalue = gridIsoValue[OptionValue["IsoValue"], vdb]; ( dists = vdb["gridDistance"[pts, isovalue]]; dists /; VectorQ[dists, NumericQ] ) /; realQ[isovalue] ] iOpenVDBDistance[vdb_?OpenVDBScalarGridQ, pts_?coordinatesQ -> $indexregime, opts___] := iOpenVDBDistance[vdb, regimeConvert[vdb, pts, $indexregime -> $worldregime] -> $worldregime, opts] iOpenVDBDistance[vdb_, pts_?coordinateQ -> regime_, opts___] := With[{res = iOpenVDBDistance[vdb, {pts} -> regime, opts]}, res[[1]] /; VectorQ[res, NumericQ] && Length[res] === 1 ] iOpenVDBDistance[vdb_, pts_List, opts___] := iOpenVDBDistance[vdb, pts -> $worldregime, opts] iOpenVDBDistance[___] = $Failed; (* ::Subsubsection::Closed:: *) (*Argument conform & completion*) registerForLevelSet[iOpenVDBDistance, 1]; SyntaxInformation[OpenVDBDistance] = {"ArgumentsPattern" -> {_, _, OptionsPattern[]}}; OpenVDBDefaultSpace[OpenVDBDistance] = $worldregime; (* ::Subsection::Closed:: *) (*OpenVDBSignedDistance*) (* ::Subsubsection::Closed:: *) (*Main*) Options[OpenVDBSignedDistance] = {"IsoValue" -> Automatic}; OpenVDBSignedDistance[args___] /; !CheckArgs[OpenVDBSignedDistance[args], 2] = $Failed; OpenVDBSignedDistance[args___] := With[{res = iOpenVDBSignedDistance[args]}, res /; res =!= $Failed ] OpenVDBSignedDistance[args___] := messageCPTFunction[OpenVDBSignedDistance, args] (* ::Subsubsection::Closed:: *) (*iOpenVDBSignedDistance*) (* ::Text:: *) (*Skipping call to regimeConvert to avoid duplicating points when unnecessary.*) Options[iOpenVDBSignedDistance] = Options[OpenVDBSignedDistance]; iOpenVDBSignedDistance[vdb_?OpenVDBScalarGridQ, pts_?coordinatesQ -> $worldregime, OptionsPattern[]] := Block[{isovalue, dists}, isovalue = gridIsoValue[OptionValue["IsoValue"], vdb]; ( dists = vdb["gridSignedDistance"[pts, isovalue]]; If[fogVolumeQ[vdb], Minus[dists], dists] /; VectorQ[dists, NumericQ] ) /; realQ[isovalue] ] iOpenVDBSignedDistance[vdb_?OpenVDBScalarGridQ, pts_?coordinatesQ -> $indexregime, opts___] := iOpenVDBSignedDistance[vdb, regimeConvert[vdb, pts, $indexregime -> $worldregime] -> $worldregime, opts] iOpenVDBSignedDistance[vdb_, pts_?coordinateQ -> regime_, opts___] := With[{res = iOpenVDBSignedDistance[vdb, {pts} -> regime, opts]}, res[[1]] /; VectorQ[res, NumericQ] && Length[res] === 1 ] iOpenVDBSignedDistance[vdb_, pts_List, opts___] := iOpenVDBSignedDistance[vdb, pts -> $worldregime, opts] iOpenVDBSignedDistance[___] = $Failed; (* ::Subsubsection::Closed:: *) (*Argument conform & completion*) registerForLevelSet[iOpenVDBSignedDistance, 1]; SyntaxInformation[OpenVDBSignedDistance] = {"ArgumentsPattern" -> {_, _, OptionsPattern[]}}; OpenVDBDefaultSpace[OpenVDBSignedDistance] = $worldregime; (* ::Section:: *) (*Balls*) (* ::Subsection::Closed:: *) (*OpenVDBFillWithBalls*) (* ::Subsubsection::Closed:: *) (*Main*) Options[OpenVDBFillWithBalls] = {"IsoValue" -> Automatic, "Overlapping" -> False, "ReturnType" -> Automatic, "SeedCount" -> Automatic}; OpenVDBFillWithBalls[args___] /; !CheckArgs[OpenVDBFillWithBalls[args], {2, 3}] = $Failed; OpenVDBFillWithBalls[args___] := With[{res = iOpenVDBFillWithBalls[args]}, res /; res =!= $Failed ] OpenVDBFillWithBalls[args___] := mOpenVDBFillWithBalls[args] (* ::Subsubsection::Closed:: *) (*iOpenVDBFillWithBalls*) Options[iOpenVDBFillWithBalls] = Options[OpenVDBFillWithBalls]; iOpenVDBFillWithBalls[vdb_?OpenVDBScalarGridQ, n_Integer?Positive, rspec_, OptionsPattern[]] := Block[{parsedrspec, isovalue, overlappingQ, rettype, seedcnt, rmin, rmax, res}, parsedrspec = parseRadiiSpec[vdb, rspec]; isovalue = gridIsoValue[OptionValue["IsoValue"], vdb]; overlappingQ = TrueQ[OptionValue["Overlapping"]]; rettype = parseBallReturnType[OptionValue["ReturnType"]]; seedcnt = parseBallSeedCount[OptionValue["SeedCount"]]; ( {rmin, rmax} = parsedrspec; res = vdb["fillWithBalls"[1, n, overlappingQ, rmin, rmax, isovalue, seedcnt]]; returnBalls[res, rettype] /; MatrixQ[res] ) /; parsedrspec =!= $Failed && realQ[isovalue] && rettype =!= $Failed && seedcnt > 0 ] iOpenVDBFillWithBalls[vdb_?OpenVDBScalarGridQ, n_, opts:OptionsPattern[]] := iOpenVDBFillWithBalls[vdb, n, {0.5vdb["VoxelSize"], \[Infinity]} -> $worldregime, opts] iOpenVDBFillWithBalls[___] = $Failed; (* ::Subsubsection::Closed:: *) (*Argument conform & completion*) registerForLevelSet[iOpenVDBFillWithBalls, 1]; SyntaxInformation[OpenVDBFillWithBalls] = {"ArgumentsPattern" -> {_, _, _., OptionsPattern[]}}; OpenVDBDefaultSpace[OpenVDBFillWithBalls] = $worldregime; (* ::Subsubsection::Closed:: *) (*Utilities*) parseRadiiSpec[vdb_, rspec:Except[_Rule]] := parseRadiiSpec[vdb, rspec -> $worldregime] parseRadiiSpec[vdb_, (r_?NumericQ) -> regime_] := parseRadiiSpec[vdb, {0.5vdb["VoxelSize"], r} -> regime] parseRadiiSpec[vdb_, {rmin_, rmax_} -> regime_?regimeQ] /; rmin <= rmax := Block[{bds, \[Delta], rcap}, bds = vdb["IndexDimensions"]; \[Delta] = vdb["VoxelSize"]; ( rcap = Round[0.5*Last[bds] + 3]; Clip[regimeConvert[vdb, {rmin, rmax}, regime -> $indexregime], {0.0, rcap}] ) /; ListQ[bds] && Positive[\[Delta]] ] parseRadiiSpec[___] = $Failed; (* default on the openvdb value side *) parseBallSeedCount[Automatic] = 10000; parseBallSeedCount[n_Integer?Positive] := n parseBallSeedCount[___] = $Failed; parseBallReturnType[ret:("Regions" | "Balls")] = "Regions"; parseBallReturnType[ret:("PackedArray" | "Packed")] = "PackedArray"; parseBallReturnType[Automatic] = parseBallReturnType["Regions"]; parseBallReturnType[___] = $Failed returnBalls[balls_, ret_] := Block[{balls2}, balls2 = If[balls[[-1, 4]] == 0.0, DeleteDuplicates[balls], balls ]; If[ret === "PackedArray", balls2, If[#4 == 0.0, Point[{#1, #2, #3}], Ball[{#1, #2, #3}, #4] ]& @@@ balls2 ] ] (* ::Subsubsection::Closed:: *) (*Messages*) Options[mOpenVDBFillWithBalls] = Options[OpenVDBFillWithBalls]; mOpenVDBFillWithBalls[expr_, ___] /; messageScalarGridQ[expr, OpenVDBFillWithBalls] = $Failed; mOpenVDBFillWithBalls[vdb_, expr_, rest___] /; !IntegerQ[expr] || !TrueQ[expr > 0] := ( Message[OpenVDBFillWithBalls::intpm, HoldForm[OpenVDBFillWithBalls[vdb, expr, rest]], 2]; $Failed ) mOpenVDBFillWithBalls[vdb_, n_, rspec_List -> regime_, args___] := messageRegimeSpecQ[regime, OpenVDBFillWithBalls] || mOpenVDBFillWithBalls[vdb, n, rspec, args] mOpenVDBFillWithBalls[vdb_, n_, rspec_, rest___] /; !MatchQ[rspec, {a_, b_} /; 0 <= a <= b] := ( Message[OpenVDBFillWithBalls::rspec, 3, HoldForm[OpenVDBFillWithBalls[vdb, n, rspec, rest]]]; $Failed ) mOpenVDBFillWithBalls[args__, Longest[OptionsPattern[]]] := ( If[messageIsoValueQ[OptionValue["IsoValue"], OpenVDBFillWithBalls], Return[$Failed] ]; If[parseBallReturnType[OptionValue["ReturnType"]] === $Failed, Message[OpenVDBFillWithBalls::rettype]; Return[$Failed] ]; If[parseBallSeedCount[OptionValue["SeedCount"]] === $Failed, Message[OpenVDBFillWithBalls::seedcnt]; Return[$Failed] ]; $Failed ) mOpenVDBFillWithBalls[___] = $Failed; OpenVDBFillWithBalls::rspec = "`1` at position `2` should be a list of two increasing non\[Hyphen]negative radii."; OpenVDBFillWithBalls::rettype = "The setting for \"ReturnType\" should be one of \"Regions\", \"PackedArray\", or Automatic."; OpenVDBFillWithBalls::seedcnt = "The setting for \"SeedCount\" should either be a positive integer or Automatic."; (* ::Section:: *) (*Utilities*) (* ::Subsection::Closed:: *) (*messageCPTFunction*) Options[messageCPTFunction] = Options[OpenVDBMember]; messageCPTFunction[head_, expr_, ___] /; messageScalarGridQ[expr, head] = $Failed; messageCPTFunction[head_, _, expr_, ___] /; messageCoordinateSpecQ[expr, head] = $Failed; messageCPTFunction[head_, args__, Longest[OptionsPattern[]]] := ( If[messageIsoValueQ[OptionValue["IsoValue"], head], Return[$Failed] ]; $Failed ) messageCPTFunction[___] = $Failed;