558 lines
14 KiB
Mathematica
558 lines
14 KiB
Mathematica
(* ::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;
|