Geometry Nodes: Add Sample Volume node

This commit adds a new experimental node "Sample Volume".
It's purpose is to be able to sample values from a volume grid given
a position field.
The 'Grid' input accepts a named attribute input with the name
of the volume grid to sample. Values will be implicitly converted
from the grid type to the specified value type.

Pull Request: #107656
This commit is contained in:
2023-05-16 19:08:08 +02:00
parent cc498697ab
commit 3195c1a669
9 changed files with 435 additions and 0 deletions

View File

@@ -613,6 +613,7 @@ class NODE_MT_category_GEO_VOLUME(Menu):
layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeMeanFilterSDFVolume")
node_add_menu.add_node_type(layout, "GeometryNodeOffsetSDFVolume")
node_add_menu.add_node_type(layout, "GeometryNodeSampleVolume")
node_add_menu.add_node_type(layout, "GeometryNodeSDFVolumeSphere")
node_add_menu.add_node_type(layout, "GeometryNodeInputSignedDistance")
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)

View File

@@ -1304,6 +1304,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
#define GEO_NODE_SIMULATION_INPUT 2100
#define GEO_NODE_SIMULATION_OUTPUT 2101
#define GEO_NODE_INPUT_SIGNED_DISTANCE 2102
#define GEO_NODE_SAMPLE_VOLUME 2103
/** \} */

View File

@@ -1636,6 +1636,13 @@ typedef struct NodeGeometryDistributePointsInVolume {
uint8_t mode;
} NodeGeometryDistributePointsInVolume;
typedef struct NodeGeometrySampleVolume {
/* eCustomDataType. */
int8_t grid_type;
/* GeometryNodeSampleVolumeInterpolationMode */
int8_t interpolation_mode;
} NodeGeometrySampleVolume;
typedef struct NodeFunctionCompare {
/* NodeCompareOperation */
int8_t operation;
@@ -2439,6 +2446,12 @@ typedef enum GeometryNodeScaleElementsMode {
GEO_NODE_SCALE_ELEMENTS_SINGLE_AXIS = 1,
} GeometryNodeScaleElementsMode;
typedef enum GeometryNodeSampleVolumeInterpolationMode {
GEO_NODE_SAMPLE_VOLUME_INTERPOLATION_MODE_NEAREST = 0,
GEO_NODE_SAMPLE_VOLUME_INTERPOLATION_MODE_TRILINEAR = 1,
GEO_NODE_SAMPLE_VOLUME_INTERPOLATION_MODE_TRIQUADRATIC = 2,
} GeometryNodeSampleVolumeInterpolationMode;
typedef enum NodeCombSepColorMode {
NODE_COMBSEP_COLOR_RGB = 0,
NODE_COMBSEP_COLOR_HSV = 1,

View File

@@ -11000,6 +11000,44 @@ static void def_geo_attribute_capture(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}
static void def_geo_sample_volume(StructRNA *srna)
{
static const EnumPropertyItem interpolation_mode_items[] = {
{GEO_NODE_SAMPLE_VOLUME_INTERPOLATION_MODE_NEAREST, "NEAREST", 0, "Nearest Neighbor", ""},
{GEO_NODE_SAMPLE_VOLUME_INTERPOLATION_MODE_TRILINEAR, "TRILINEAR", 0, "Trilinear", ""},
{GEO_NODE_SAMPLE_VOLUME_INTERPOLATION_MODE_TRIQUADRATIC,
"TRIQUADRATIC",
0,
"Triquadratic",
""},
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem grid_type_items[] = {
{CD_PROP_FLOAT, "FLOAT", 0, "Float", "Floating-point value"},
{CD_PROP_FLOAT3, "FLOAT_VECTOR", 0, "Vector", "3D vector with floating-point values"},
{CD_PROP_INT32, "INT", 0, "Integer", "32-bit integer"},
{CD_PROP_BOOL, "BOOLEAN", 0, "Boolean", "True or false"},
{0, NULL, 0, NULL, NULL},
};
PropertyRNA *prop;
RNA_def_struct_sdna_from(srna, "NodeGeometrySampleVolume", "storage");
prop = RNA_def_property(srna, "interpolation_mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, interpolation_mode_items);
RNA_def_property_ui_text(
prop, "Interpolation Mode", "How to interpolate the values from neighboring voxels");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
prop = RNA_def_property(srna, "grid_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, grid_type_items);
RNA_def_property_enum_default(prop, CD_PROP_FLOAT);
RNA_def_property_ui_text(prop, "Grid Type", "Type of grid to sample data from");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_GeometryNode_socket_update");
}
static void def_geo_image(StructRNA *srna)
{
PropertyRNA *prop;

View File

@@ -402,6 +402,7 @@ DefNode(GeometryNode, GEO_NODE_SAMPLE_INDEX, def_geo_sample_index, "SAMPLE_INDEX
DefNode(GeometryNode, GEO_NODE_SAMPLE_NEAREST_SURFACE, def_geo_sample_nearest_surface, "SAMPLE_NEAREST_SURFACE", SampleNearestSurface, "Sample Nearest Surface", "Calculate the interpolated value of a mesh attribute on the closest point of its surface")
DefNode(GeometryNode, GEO_NODE_SAMPLE_NEAREST, def_geo_sample_nearest, "SAMPLE_NEAREST", SampleNearest, "Sample Nearest", "Find the element of a geometry closest to a position. Similar to the \"Index of Nearest\" node")
DefNode(GeometryNode, GEO_NODE_SAMPLE_UV_SURFACE, def_geo_sample_uv_surface, "SAMPLE_UV_SURFACE", SampleUVSurface, "Sample UV Surface", "Calculate the interpolated values of a mesh attribute at a UV coordinate")
DefNode(GeometryNode, GEO_NODE_SAMPLE_VOLUME, def_geo_sample_volume, "SAMPLE_VOLUME", SampleVolume, "Sample Volume", "Calculate the interpolated values of a Volume grid at the specified position")
DefNode(GeometryNode, GEO_NODE_SCALE_ELEMENTS, def_geo_scale_elements, "SCALE_ELEMENTS", ScaleElements, "Scale Elements", "Scale groups of connected edges and faces")
DefNode(GeometryNode, GEO_NODE_SCALE_INSTANCES, 0, "SCALE_INSTANCES", ScaleInstances, "Scale Instances", "Scale geometry instances in local or global space")
DefNode(GeometryNode, GEO_NODE_SDF_VOLUME_SPHERE, 0, "SDF_VOLUME_SPHERE", SDFVolumeSphere, "SDF Volume Sphere", "Generate an SDF Volume Sphere")

View File

@@ -153,6 +153,7 @@ set(SRC
nodes/node_geo_sample_nearest.cc
nodes/node_geo_sample_nearest_surface.cc
nodes/node_geo_sample_uv_surface.cc
nodes/node_geo_sample_volume.cc
nodes/node_geo_scale_elements.cc
nodes/node_geo_scale_instances.cc
nodes/node_geo_sdf_volume_sphere.cc

View File

@@ -137,6 +137,7 @@ void register_geometry_nodes()
register_node_type_geo_sample_nearest_surface();
register_node_type_geo_sample_nearest();
register_node_type_geo_sample_uv_surface();
register_node_type_geo_sample_volume();
register_node_type_geo_scale_elements();
register_node_type_geo_scale_instances();
register_node_type_geo_sdf_volume_sphere();

View File

@@ -134,6 +134,7 @@ void register_node_type_geo_sample_index();
void register_node_type_geo_sample_nearest_surface();
void register_node_type_geo_sample_nearest();
void register_node_type_geo_sample_uv_surface();
void register_node_type_geo_sample_volume();
void register_node_type_geo_scale_elements();
void register_node_type_geo_scale_instances();
void register_node_type_geo_sdf_volume_sphere();

View File

@@ -0,0 +1,378 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "DEG_depsgraph_query.h"
#include "BKE_type_conversions.hh"
#include "BKE_volume.h"
#include "BLI_virtual_array.hh"
#include "NOD_add_node_search.hh"
#include "NOD_socket_search_link.hh"
#include "node_geometry_util.hh"
#include "UI_interface.h"
#include "UI_resources.h"
#ifdef WITH_OPENVDB
# include <openvdb/openvdb.h>
# include <openvdb/tools/Interpolation.h>
#endif
namespace blender::nodes::node_geo_sample_volume_cc {
NODE_STORAGE_FUNCS(NodeGeometrySampleVolume)
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>(CTX_N_(BLT_I18NCONTEXT_ID_ID, "Volume"))
.translation_context(BLT_I18NCONTEXT_ID_ID)
.supported_type(GEO_COMPONENT_TYPE_VOLUME);
std::string grid_socket_description = N_(
"Expects a Named Attribute with the name of a Grid in the Volume");
b.add_input<decl::Vector>(N_("Grid"), "Grid_Vector")
.field_on_all()
.hide_value()
.description(grid_socket_description);
b.add_input<decl::Float>(N_("Grid"), "Grid_Float")
.field_on_all()
.hide_value()
.description(grid_socket_description);
b.add_input<decl::Bool>(N_("Grid"), "Grid_Bool")
.field_on_all()
.hide_value()
.description(grid_socket_description);
b.add_input<decl::Int>(N_("Grid"), "Grid_Int")
.field_on_all()
.hide_value()
.description(grid_socket_description);
b.add_input<decl::Vector>(N_("Position")).implicit_field(implicit_field_inputs::position);
b.add_output<decl::Vector>(N_("Value"), "Value_Vector").dependent_field({5});
b.add_output<decl::Float>(N_("Value"), "Value_Float").dependent_field({5});
b.add_output<decl::Bool>(N_("Value"), "Value_Bool").dependent_field({5});
b.add_output<decl::Int>(N_("Value"), "Value_Int").dependent_field({5});
}
static void search_node_add_ops(GatherAddNodeSearchParams &params)
{
if (!U.experimental.use_new_volume_nodes) {
return;
}
blender::nodes::search_node_add_ops_for_basic_node(params);
}
static void search_link_ops(GatherLinkSearchOpParams &params)
{
if (!U.experimental.use_new_volume_nodes) {
return;
}
const NodeDeclaration &declaration = *params.node_type().fixed_declaration;
search_link_ops_for_declarations(params, declaration.inputs.as_span().take_back(1));
search_link_ops_for_declarations(params, declaration.inputs.as_span().take_front(1));
const std::optional<eCustomDataType> type = node_data_type_to_custom_data_type(
(eNodeSocketDatatype)params.other_socket().type);
if (type && *type != CD_PROP_STRING) {
/* The input and output sockets have the same name. */
params.add_item(IFACE_("Grid"), [type](LinkSearchOpParams &params) {
bNode &node = params.add_node("GeometryNodeSampleVolume");
node_storage(node).grid_type = *type;
params.update_and_connect_available_socket(node, "Grid");
});
}
}
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
uiItemR(layout, ptr, "grid_type", 0, "", ICON_NONE);
uiItemR(layout, ptr, "interpolation_mode", 0, "", ICON_NONE);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
NodeGeometrySampleVolume *data = MEM_cnew<NodeGeometrySampleVolume>(__func__);
data->grid_type = CD_PROP_FLOAT;
data->interpolation_mode = GEO_NODE_SAMPLE_VOLUME_INTERPOLATION_MODE_TRILINEAR;
node->storage = data;
}
static void node_update(bNodeTree *ntree, bNode *node)
{
const NodeGeometrySampleVolume &storage = node_storage(*node);
const eCustomDataType grid_type = eCustomDataType(storage.grid_type);
bNodeSocket *socket_value_geometry = static_cast<bNodeSocket *>(node->inputs.first);
bNodeSocket *socket_value_vector = socket_value_geometry->next;
bNodeSocket *socket_value_float = socket_value_vector->next;
bNodeSocket *socket_value_boolean = socket_value_float->next;
bNodeSocket *socket_value_int32 = socket_value_boolean->next;
bke::nodeSetSocketAvailability(ntree, socket_value_vector, grid_type == CD_PROP_FLOAT3);
bke::nodeSetSocketAvailability(ntree, socket_value_float, grid_type == CD_PROP_FLOAT);
bke::nodeSetSocketAvailability(ntree, socket_value_boolean, grid_type == CD_PROP_BOOL);
bke::nodeSetSocketAvailability(ntree, socket_value_int32, grid_type == CD_PROP_INT32);
bNodeSocket *out_socket_value_vector = static_cast<bNodeSocket *>(node->outputs.first);
bNodeSocket *out_socket_value_float = out_socket_value_vector->next;
bNodeSocket *out_socket_value_boolean = out_socket_value_float->next;
bNodeSocket *out_socket_value_int32 = out_socket_value_boolean->next;
bke::nodeSetSocketAvailability(ntree, out_socket_value_vector, grid_type == CD_PROP_FLOAT3);
bke::nodeSetSocketAvailability(ntree, out_socket_value_float, grid_type == CD_PROP_FLOAT);
bke::nodeSetSocketAvailability(ntree, out_socket_value_boolean, grid_type == CD_PROP_BOOL);
bke::nodeSetSocketAvailability(ntree, out_socket_value_int32, grid_type == CD_PROP_INT32);
}
#ifdef WITH_OPENVDB
static const StringRefNull get_grid_name(GField &field)
{
if (const auto *attribute_field_input = dynamic_cast<const AttributeFieldInput *>(&field.node()))
{
return attribute_field_input->attribute_name();
}
return "";
}
static const blender::CPPType *vdb_grid_type_to_cpp_type(const VolumeGridType grid_type)
{
switch (grid_type) {
case VOLUME_GRID_FLOAT:
return &CPPType::get<float>();
case VOLUME_GRID_VECTOR_FLOAT:
return &CPPType::get<float3>();
case VOLUME_GRID_INT:
return &CPPType::get<int>();
case VOLUME_GRID_BOOLEAN:
return &CPPType::get<bool>();
default:
break;
}
return nullptr;
}
template<typename GridT>
void sample_grid(openvdb::GridBase::ConstPtr base_grid,
const Span<float3> positions,
const IndexMask mask,
GMutableSpan dst,
const GeometryNodeSampleVolumeInterpolationMode interpolation_mode)
{
using ValueT = typename GridT::ValueType;
using AccessorT = typename GridT::ConstAccessor;
const GridT::ConstPtr grid = openvdb::gridConstPtrCast<GridT>(base_grid);
AccessorT accessor = grid->getConstAccessor();
auto sample_data = [&](auto sampler) {
mask.foreach_index([&](const int64_t i) {
const float3 &pos = positions[i];
ValueT value = sampler.wsSample(openvdb::Vec3R(pos.x, pos.y, pos.z));
/* Special case for vector. */
if constexpr (std::is_same_v<GridT, openvdb::VectorGrid>) {
openvdb::Vec3f vec = static_cast<openvdb::Vec3f>(value);
dst.typed<float3>()[i] = float3(vec.asV());
}
else {
dst.typed<ValueT>()[i] = value;
}
});
};
switch (interpolation_mode) {
case GEO_NODE_SAMPLE_VOLUME_INTERPOLATION_MODE_TRILINEAR: {
openvdb::tools::GridSampler<AccessorT, openvdb::tools::BoxSampler> sampler(
accessor, grid->transform());
sample_data(sampler);
break;
}
case GEO_NODE_SAMPLE_VOLUME_INTERPOLATION_MODE_TRIQUADRATIC: {
openvdb::tools::GridSampler<AccessorT, openvdb::tools::QuadraticSampler> sampler(
accessor, grid->transform());
sample_data(sampler);
break;
}
case GEO_NODE_SAMPLE_VOLUME_INTERPOLATION_MODE_NEAREST:
default: {
openvdb::tools::GridSampler<AccessorT, openvdb::tools::PointSampler> sampler(
accessor, grid->transform());
sample_data(sampler);
break;
}
}
}
class SampleVolumeFunction : public mf::MultiFunction {
openvdb::GridBase::ConstPtr base_grid_;
VolumeGridType grid_type_;
GeometryNodeSampleVolumeInterpolationMode interpolation_mode_;
mf::Signature signature_;
public:
SampleVolumeFunction(openvdb::GridBase::ConstPtr base_grid,
GeometryNodeSampleVolumeInterpolationMode interpolation_mode)
: base_grid_(std::move(base_grid)), interpolation_mode_(interpolation_mode)
{
grid_type_ = BKE_volume_grid_type_openvdb(*base_grid_);
const CPPType *grid_cpp_type = vdb_grid_type_to_cpp_type(grid_type_);
BLI_assert(grid_cpp_type != nullptr);
mf::SignatureBuilder builder{"Sample Volume", signature_};
builder.single_input<float3>("Position");
builder.single_output("Value", *grid_cpp_type);
this->set_signature(&signature_);
}
void call(IndexMask mask, mf::Params params, mf::Context /*context*/) const override
{
const VArraySpan<float3> positions = params.readonly_single_input<float3>(0, "Position");
GMutableSpan dst = params.uninitialized_single_output(1, "Value");
switch (grid_type_) {
case VOLUME_GRID_FLOAT:
sample_grid<openvdb::FloatGrid>(base_grid_, positions, mask, dst, interpolation_mode_);
break;
case VOLUME_GRID_INT:
sample_grid<openvdb::Int32Grid>(base_grid_, positions, mask, dst, interpolation_mode_);
break;
case VOLUME_GRID_BOOLEAN:
sample_grid<openvdb::BoolGrid>(base_grid_, positions, mask, dst, interpolation_mode_);
break;
case VOLUME_GRID_VECTOR_FLOAT:
sample_grid<openvdb::VectorGrid>(base_grid_, positions, mask, dst, interpolation_mode_);
break;
default:
BLI_assert_unreachable();
break;
}
}
};
static GField get_input_attribute_field(GeoNodeExecParams &params, const eCustomDataType data_type)
{
switch (data_type) {
case CD_PROP_FLOAT:
return params.extract_input<Field<float>>("Grid_Float");
case CD_PROP_FLOAT3:
return params.extract_input<Field<float3>>("Grid_Vector");
case CD_PROP_BOOL:
return params.extract_input<Field<bool>>("Grid_Bool");
case CD_PROP_INT32:
return params.extract_input<Field<int>>("Grid_Int");
default:
BLI_assert_unreachable();
}
return {};
}
static void output_attribute_field(GeoNodeExecParams &params, GField field)
{
switch (bke::cpp_type_to_custom_data_type(field.cpp_type())) {
case CD_PROP_FLOAT:
params.set_output("Value_Float", Field<float>(field));
break;
case CD_PROP_FLOAT3:
params.set_output("Value_Vector", Field<float3>(field));
break;
case CD_PROP_BOOL:
params.set_output("Value_Bool", Field<bool>(field));
break;
case CD_PROP_INT32:
params.set_output("Value_Int", Field<int>(field));
break;
default:
break;
}
}
#endif /* WITH_OPENVDB */
static void node_geo_exec(GeoNodeExecParams params)
{
#ifdef WITH_OPENVDB
GeometrySet geometry_set = params.extract_input<GeometrySet>("Volume");
if (!geometry_set.has_volume()) {
params.set_default_remaining_outputs();
return;
}
const NodeGeometrySampleVolume &storage = node_storage(params.node());
const eCustomDataType output_field_type = eCustomDataType(storage.grid_type);
auto interpolation_mode = GeometryNodeSampleVolumeInterpolationMode(storage.interpolation_mode);
GField grid_field = get_input_attribute_field(params, output_field_type);
const StringRefNull grid_name = get_grid_name(grid_field);
if (grid_name == "") {
params.error_message_add(NodeWarningType::Error, TIP_("Grid name needs to be specified"));
params.set_default_remaining_outputs();
return;
}
const VolumeComponent *component = geometry_set.get_component_for_read<VolumeComponent>();
const Volume *volume = component->get_for_read();
BKE_volume_load(volume, DEG_get_bmain(params.depsgraph()));
const VolumeGrid *volume_grid = BKE_volume_grid_find_for_read(volume, grid_name.c_str());
if (volume_grid == nullptr) {
params.set_default_remaining_outputs();
return;
}
openvdb::GridBase::ConstPtr base_grid = BKE_volume_grid_openvdb_for_read(volume, volume_grid);
const VolumeGridType grid_type = BKE_volume_grid_type_openvdb(*base_grid);
/* Check that the grid type is supported. */
const CPPType *grid_cpp_type = vdb_grid_type_to_cpp_type(grid_type);
if (grid_cpp_type == nullptr) {
params.set_default_remaining_outputs();
params.error_message_add(NodeWarningType::Error, TIP_("The grid type is unsupported"));
return;
}
/* Use to the Nearest Neighbor sampler for Bool grids (no interpolation). */
if (grid_type == VOLUME_GRID_BOOLEAN &&
interpolation_mode != GEO_NODE_SAMPLE_VOLUME_INTERPOLATION_MODE_NEAREST)
{
interpolation_mode = GEO_NODE_SAMPLE_VOLUME_INTERPOLATION_MODE_NEAREST;
}
Field<float3> position_field = params.extract_input<Field<float3>>("Position");
auto fn = std::make_shared<SampleVolumeFunction>(std::move(base_grid), interpolation_mode);
auto op = FieldOperation::Create(std::move(fn), {position_field});
GField output_field = GField(std::move(op));
output_field = bke::get_implicit_type_conversions().try_convert(
output_field, *bke::custom_data_type_to_cpp_type(output_field_type));
output_attribute_field(params, std::move(output_field));
#else
params.set_default_remaining_outputs();
params.error_message_add(NodeWarningType::Error,
TIP_("Disabled, Blender was compiled without OpenVDB"));
#endif
}
} // namespace blender::nodes::node_geo_sample_volume_cc
void register_node_type_geo_sample_volume()
{
namespace file_ns = blender::nodes::node_geo_sample_volume_cc;
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_SAMPLE_VOLUME, "Sample Volume", NODE_CLASS_CONVERTER);
node_type_storage(
&ntype, "NodeGeometrySampleVolume", node_free_standard_storage, node_copy_standard_storage);
ntype.initfunc = file_ns::node_init;
ntype.updatefunc = file_ns::node_update;
ntype.declare = file_ns::node_declare;
ntype.geometry_node_execute = file_ns::node_geo_exec;
ntype.draw_buttons = file_ns::node_layout;
ntype.gather_add_node_search_ops = file_ns::search_node_add_ops;
ntype.gather_link_search_ops = file_ns::search_link_ops;
ntype.geometry_node_execute = file_ns::node_geo_exec;
nodeRegisterType(&ntype);
}