From ff7a557c67096fc8de870e5fb43caa17b649c538 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 26 Jan 2021 17:37:58 +0100 Subject: Geometry Nodes: new Points to Volume node This implements a new geometry node based on T84606. It is the first node that generates a `VolumeComponent`. Differential Revision: https://developer.blender.org/D10169 --- source/blender/nodes/CMakeLists.txt | 1 + source/blender/nodes/NOD_geometry.h | 1 + source/blender/nodes/NOD_static_types.h | 1 + .../geometry/nodes/node_geo_points_to_volume.cc | 259 +++++++++++++++++++++ 4 files changed, 262 insertions(+) create mode 100644 source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc (limited to 'source/blender/nodes') diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index d796c7c309c..405a8dcbf46 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -160,6 +160,7 @@ set(SRC geometry/nodes/node_geo_point_scale.cc geometry/nodes/node_geo_point_separate.cc geometry/nodes/node_geo_point_translate.cc + geometry/nodes/node_geo_points_to_volume.cc geometry/nodes/node_geo_subdivision_surface.cc geometry/nodes/node_geo_transform.cc geometry/nodes/node_geo_triangulate.cc diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 1b3a8200beb..d78f76e0b52 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -48,6 +48,7 @@ void register_node_type_geo_attribute_color_ramp(void); void register_node_type_geo_point_rotate(void); void register_node_type_geo_align_rotation_to_vector(void); void register_node_type_geo_sample_texture(void); +void register_node_type_geo_points_to_volume(void); #ifdef __cplusplus } diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 4c300916357..cc2f6a294f2 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -290,6 +290,7 @@ DefNode(GeometryNode, GEO_NODE_ALIGN_ROTATION_TO_VECTOR, def_geo_align_rotation_ DefNode(GeometryNode, GEO_NODE_POINT_SCALE, def_geo_point_scale, "POINT_SCALE", PointScale, "Point Scale", "") DefNode(GeometryNode, GEO_NODE_POINT_TRANSLATE, def_geo_point_translate, "POINT_TRANSLATE", PointTranslate, "Point Translate", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE, def_geo_attribute_sample_texture, "ATTRIBUTE_SAMPLE_TEXTURE", AttributeSampleTexture, "Attribute Sample Texture", "") +DefNode(GeometryNode, GEO_NODE_POINTS_TO_VOLUME, def_geo_points_to_volume, "POINTS_TO_VOLUME", PointsToVolume, "Points to Volume", "") /* undefine macros */ #undef DefNode diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc new file mode 100644 index 00000000000..b90ef2034a8 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc @@ -0,0 +1,259 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifdef WITH_OPENVDB +# include +# include +# include +#endif + +#include "node_geometry_util.hh" + +#include "BKE_lib_id.h" +#include "BKE_volume.h" + +static bNodeSocketTemplate geo_node_points_to_volume_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_FLOAT, N_("Density"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX}, + {SOCK_FLOAT, N_("Voxel Size"), 0.3f, 0.0f, 0.0f, 0.0f, 0.01f, FLT_MAX}, + {SOCK_FLOAT, N_("Voxel Amount"), 64.0f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX}, + {SOCK_STRING, N_("Radius")}, + {SOCK_FLOAT, N_("Radius"), 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_point_translate_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { + +#ifdef WITH_OPENVDB +namespace { +/* Implements the interface required by #openvdb::tools::ParticlesToLevelSet. */ +struct ParticleList { + using PosType = openvdb::Vec3R; + + Span positions; + Span radii; + + size_t size() const + { + return (size_t)positions.size(); + } + + void getPos(size_t n, openvdb::Vec3R &xyz) const + { + xyz = &positions[n].x; + } + + void getPosRad(size_t n, openvdb::Vec3R &xyz, openvdb::Real &radius) const + { + xyz = &positions[n].x; + radius = radii[n]; + } +}; +} // namespace + +static openvdb::FloatGrid::Ptr generate_volume_from_points(const Span positions, + const Span radii, + const float density) +{ + /* Create a new grid that will be filled. #ParticlesToLevelSet requires the background value to + * be positive. It will be set to zero later on. */ + openvdb::FloatGrid::Ptr new_grid = openvdb::FloatGrid::create(1.0f); + + /* Create a narrow-band level set grid based on the positions and radii. */ + openvdb::tools::ParticlesToLevelSet op{*new_grid}; + /* Don't ignore particles based on their radius. */ + op.setRmin(0.0f); + op.setRmax(FLT_MAX); + ParticleList particles{positions, radii}; + op.rasterizeSpheres(particles); + op.finalize(); + + /* Convert the level set to a fog volume. This also sets the background value to zero. Inside the + * fog there will be a density of 1. */ + openvdb::tools::sdfToFogVolume(*new_grid); + + /* Take the desired density into account. */ + openvdb::tools::foreach (new_grid->beginValueOn(), + [&](const openvdb::FloatGrid::ValueOnIter &iter) { + iter.modifyValue([&](float &value) { value *= density; }); + }); + return new_grid; +} + +static float compute_voxel_size(const GeoNodeExecParams ¶ms, + Span positions, + const float radius) +{ + const NodeGeometryPointsToVolume &storage = + *(const NodeGeometryPointsToVolume *)params.node().storage; + + if (storage.resolution_mode == GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_SIZE) { + return params.get_input("Voxel Size"); + } + + if (positions.is_empty()) { + return 0.0f; + } + + float3 min, max; + INIT_MINMAX(min, max); + minmax_v3v3_v3_array(min, max, (float(*)[3])positions.data(), positions.size()); + + const float voxel_amount = params.get_input("Voxel Amount"); + if (voxel_amount <= 1) { + return 0.0f; + } + + /* The voxel size adapts to the final size of the volume. */ + const float diagonal = float3::distance(min, max); + const float extended_diagonal = diagonal + 2.0f * radius; + const float voxel_size = extended_diagonal / voxel_amount; + return voxel_size; +} + +static void gather_point_data_from_component(const GeoNodeExecParams ¶ms, + const GeometryComponent &component, + Vector &r_positions, + Vector &r_radii) +{ + Float3ReadAttribute positions = component.attribute_get_for_read( + "position", ATTR_DOMAIN_POINT, {0, 0, 0}); + FloatReadAttribute radii = params.get_input_attribute( + "Radius", component, ATTR_DOMAIN_POINT, 0.0f); + + r_positions.extend(positions.get_span()); + r_radii.extend(radii.get_span()); +} + +static void convert_to_grid_index_space(const float voxel_size, + MutableSpan positions, + MutableSpan radii) +{ + const float voxel_size_inv = 1.0f / voxel_size; + for (const int i : positions.index_range()) { + positions[i] *= voxel_size_inv; + /* Better align generated grid with source points. */ + positions[i] -= float3(0.5f); + radii[i] *= voxel_size_inv; + } +} + +static void initialize_volume_component_from_points(const GeometrySet &geometry_set_in, + GeometrySet &geometry_set_out, + const GeoNodeExecParams ¶ms) +{ + Vector positions; + Vector radii; + + if (geometry_set_in.has()) { + gather_point_data_from_component( + params, *geometry_set_in.get_component_for_read(), positions, radii); + } + if (geometry_set_in.has()) { + gather_point_data_from_component( + params, *geometry_set_in.get_component_for_read(), positions, radii); + } + + const float max_radius = *std::max_element(radii.begin(), radii.end()); + const float voxel_size = compute_voxel_size(params, positions, max_radius); + if (voxel_size == 0.0f || positions.is_empty()) { + return; + } + + Volume *volume = (Volume *)BKE_id_new_nomain(ID_VO, nullptr); + BKE_volume_init_grids(volume); + + VolumeGrid *c_density_grid = BKE_volume_grid_add(volume, "density", VOLUME_GRID_FLOAT); + openvdb::FloatGrid::Ptr density_grid = openvdb::gridPtrCast( + BKE_volume_grid_openvdb_for_write(volume, c_density_grid, false)); + + const float density = params.get_input("Density"); + convert_to_grid_index_space(voxel_size, positions, radii); + openvdb::FloatGrid::Ptr new_grid = generate_volume_from_points(positions, radii, density); + /* This merge is cheap, because the #density_grid is empty. */ + density_grid->merge(*new_grid); + density_grid->transform().postScale(voxel_size); + + VolumeComponent &volume_component = geometry_set_out.get_component_for_write(); + volume_component.replace(volume); +} +#endif + +static void geo_node_points_to_volume_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set_in = params.extract_input("Geometry"); + GeometrySet geometry_set_out; + +#ifdef WITH_OPENVDB + initialize_volume_component_from_points(geometry_set_in, geometry_set_out, params); +#endif + + params.set_output("Geometry", std::move(geometry_set_out)); +} + +static void geo_node_points_to_volume_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryPointsToVolume *data = (NodeGeometryPointsToVolume *)MEM_callocN( + sizeof(NodeGeometryPointsToVolume), __func__); + data->resolution_mode = GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT; + data->input_type_radius = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; + node->storage = data; + + bNodeSocket *radius_attribute_socket = nodeFindSocket(node, SOCK_IN, "Radius"); + bNodeSocketValueString *radius_attribute_socket_value = + (bNodeSocketValueString *)radius_attribute_socket->default_value; + STRNCPY(radius_attribute_socket_value->value, "radius"); +} + +static void geo_node_points_to_volume_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryPointsToVolume *data = (NodeGeometryPointsToVolume *)node->storage; + bNodeSocket *voxel_size_socket = nodeFindSocket(node, SOCK_IN, "Voxel Size"); + bNodeSocket *voxel_amount_socket = nodeFindSocket(node, SOCK_IN, "Voxel Amount"); + nodeSetSocketAvailability(voxel_amount_socket, + data->resolution_mode == + GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT); + nodeSetSocketAvailability( + voxel_size_socket, data->resolution_mode == GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_SIZE); + + update_attribute_input_socket_availabilities( + *node, "Radius", (GeometryNodeAttributeInputMode)data->input_type_radius); +} + +} // namespace blender::nodes + +void register_node_type_geo_points_to_volume() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_POINTS_TO_VOLUME, "Points to Volume", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_points_to_volume_in, geo_node_point_translate_out); + node_type_storage(&ntype, + "NodeGeometryPointsToVolume", + node_free_standard_storage, + node_copy_standard_storage); + node_type_size(&ntype, 170, 120, 700); + node_type_init(&ntype, blender::nodes::geo_node_points_to_volume_init); + node_type_update(&ntype, blender::nodes::geo_node_points_to_volume_update); + ntype.geometry_node_execute = blender::nodes::geo_node_points_to_volume_exec; + nodeRegisterType(&ntype); +} -- cgit v1.2.3