Commit: 85908e9edf3dfefdc36714f07a554f480ff5d230 Author: Jacques Lucke Date: Fri Jan 20 12:09:29 2023 +0100 Branches: master https://developer.blender.org/rB85908e9edf3dfefdc36714f07a554f480ff5d230
Geometry Nodes: new Interpolate Curves node This adds a new `Interpolate Curves` node. It allows generating new curves between a set of existing guide curves. This is essential for procedural hair. Usage: - One has to provide a set of guide curves and a set of root positions for the generated curves. New curves are created starting from these root positions. The N closest guide curves are used for the interpolation. - An additional up vector can be provided for every guide curve and root position. This is typically a surface normal or nothing. This allows generating child curves that are properly oriented based on the surface orientation. - Sometimes a point should only be interpolated using a subset of the guides. This can be achieved using the `Guide Group ID` and `Point Group ID` inputs. The curve generated at a specific point will only take the guides with the same id into account. This allows e.g. for hair parting. - The `Max Neighbors` input limits how many guide curves are taken into account for every interpolated curve. Differential Revision: https://developer.blender.org/D16642 =================================================================== M release/scripts/startup/bl_ui/node_add_menu_geometry.py M source/blender/blenkernel/BKE_node.h M source/blender/blenlib/BLI_length_parameterize.hh M source/blender/blenlib/BLI_task.hh M source/blender/blenlib/intern/offset_indices.cc M source/blender/nodes/NOD_static_types.h M source/blender/nodes/geometry/CMakeLists.txt M source/blender/nodes/geometry/node_geometry_register.cc M source/blender/nodes/geometry/node_geometry_register.hh A source/blender/nodes/geometry/nodes/node_geo_interpolate_curves.cc M tests/python/CMakeLists.txt =================================================================== diff --git a/release/scripts/startup/bl_ui/node_add_menu_geometry.py b/release/scripts/startup/bl_ui/node_add_menu_geometry.py index 2554734d903..cdbd05b74a3 100644 --- a/release/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/release/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -101,6 +101,7 @@ class NODE_MT_geometry_node_GEO_CURVE_OPERATIONS(Menu): node_add_menu.add_node_type(layout, "GeometryNodeDeformCurvesOnSurface") node_add_menu.add_node_type(layout, "GeometryNodeFillCurve") node_add_menu.add_node_type(layout, "GeometryNodeFilletCurve") + node_add_menu.add_node_type(layout, "GeometryNodeInterpolateCurves") node_add_menu.add_node_type(layout, "GeometryNodeResampleCurve") node_add_menu.add_node_type(layout, "GeometryNodeReverseCurve") node_add_menu.add_node_type(layout, "GeometryNodeSampleCurve") diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 915ca87621a..386fe7fc77f 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1562,6 +1562,8 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i /** \} */ +#define GEO_NODE_INTERPOLATE_CURVES 2000 + void BKE_node_system_init(void); void BKE_node_system_exit(void); diff --git a/source/blender/blenlib/BLI_length_parameterize.hh b/source/blender/blenlib/BLI_length_parameterize.hh index d81bcbe1e7a..df00e004060 100644 --- a/source/blender/blenlib/BLI_length_parameterize.hh +++ b/source/blender/blenlib/BLI_length_parameterize.hh @@ -105,7 +105,7 @@ inline void sample_at_length(const Span<float> accumulated_segment_lengths, BLI_assert(lengths.size() > 0); BLI_assert(sample_length >= 0.0f); - BLI_assert(sample_length <= lengths.last()); + BLI_assert(sample_length <= lengths.last() + 0.00001f); if (hint != nullptr && hint->segment_index >= 0) { const float length_in_segment = sample_length - hint->segment_start; diff --git a/source/blender/blenlib/BLI_task.hh b/source/blender/blenlib/BLI_task.hh index e7d9a21439a..c726691ad46 100644 --- a/source/blender/blenlib/BLI_task.hh +++ b/source/blender/blenlib/BLI_task.hh @@ -37,7 +37,7 @@ namespace blender::threading { template<typename Range, typename Function> -void parallel_for_each(Range &range, const Function &function) +void parallel_for_each(Range &&range, const Function &function) { #ifdef WITH_TBB tbb::parallel_for_each(range, function); diff --git a/source/blender/blenlib/intern/offset_indices.cc b/source/blender/blenlib/intern/offset_indices.cc index fee57e32ffa..2ac11fe631e 100644 --- a/source/blender/blenlib/intern/offset_indices.cc +++ b/source/blender/blenlib/intern/offset_indices.cc @@ -9,7 +9,7 @@ void accumulate_counts_to_offsets(MutableSpan<int> counts_to_offsets, const int int offset = start_offset; for (const int i : counts_to_offsets.index_range().drop_back(1)) { const int count = counts_to_offsets[i]; - BLI_assert(count > 0); + BLI_assert(count >= 0); counts_to_offsets[i] = offset; offset += count; } diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index b9f747b2e4f..33fc7249fad 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -430,6 +430,8 @@ DefNode(GeometryNode, GEO_NODE_VIEWER, def_geo_viewer, "VIEWER", Viewer, "Viewer DefNode(GeometryNode, GEO_NODE_VOLUME_CUBE, 0, "VOLUME_CUBE", VolumeCube, "Volume Cube", "Generate a dense volume with a field that controls the density at each grid voxel based on its position") DefNode(GeometryNode, GEO_NODE_VOLUME_TO_MESH, def_geo_volume_to_mesh, "VOLUME_TO_MESH", VolumeToMesh, "Volume to Mesh", "Generate a mesh on the \"surface\" of a volume") +DefNode(GeometryNode, GEO_NODE_INTERPOLATE_CURVES, 0, "INTERPOLATE_CURVES", InterpolateCurves, "Interpolate Curves", "Generate new curves on points by interpolating between existing curves") + /* undefine macros */ #undef DefNode diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 8012b463a54..9fd3feff27e 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -106,6 +106,7 @@ set(SRC nodes/node_geo_input_tangent.cc nodes/node_geo_instance_on_points.cc nodes/node_geo_instances_to_points.cc + nodes/node_geo_interpolate_curves.cc nodes/node_geo_is_viewport.cc nodes/node_geo_join_geometry.cc nodes/node_geo_material_replace.cc diff --git a/source/blender/nodes/geometry/node_geometry_register.cc b/source/blender/nodes/geometry/node_geometry_register.cc index 8d24d9bd732..149fb6752ab 100644 --- a/source/blender/nodes/geometry/node_geometry_register.cc +++ b/source/blender/nodes/geometry/node_geometry_register.cc @@ -90,6 +90,7 @@ void register_geometry_nodes() register_node_type_geo_input_tangent(); register_node_type_geo_instance_on_points(); register_node_type_geo_instances_to_points(); + register_node_type_geo_interpolate_curves(); register_node_type_geo_is_viewport(); register_node_type_geo_join_geometry(); register_node_type_geo_material_replace(); diff --git a/source/blender/nodes/geometry/node_geometry_register.hh b/source/blender/nodes/geometry/node_geometry_register.hh index ca64e7b9904..5984bfa83ce 100644 --- a/source/blender/nodes/geometry/node_geometry_register.hh +++ b/source/blender/nodes/geometry/node_geometry_register.hh @@ -87,6 +87,7 @@ void register_node_type_geo_input_spline_resolution(); void register_node_type_geo_input_tangent(); void register_node_type_geo_instance_on_points(); void register_node_type_geo_instances_to_points(); +void register_node_type_geo_interpolate_curves(); void register_node_type_geo_is_viewport(); void register_node_type_geo_join_geometry(); void register_node_type_geo_material_replace(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_interpolate_curves.cc b/source/blender/nodes/geometry/nodes/node_geo_interpolate_curves.cc new file mode 100644 index 00000000000..2142fa666cb --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_interpolate_curves.cc @@ -0,0 +1,862 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "node_geometry_util.hh" + +#include "BLI_kdtree.h" +#include "BLI_length_parameterize.hh" +#include "BLI_task.hh" + +#include "BKE_curves.hh" +#include "BKE_curves_utils.hh" + +#include "DNA_pointcloud_types.h" + +namespace blender::nodes::node_geo_interpolate_curves_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>(N_("Guide Curves")) + .description(N_("Base curves that new curves are interpolated between")); + b.add_input<decl::Vector>(N_("Guide Up")) + .field_on({0}) + .hide_value() + .description(N_("Optional up vector that is typically a surface normal")); + b.add_input<decl::Int>(N_("Guide Group ID")) + .field_on({0}) + .hide_value() + .description(N_("Splits guides into separate groups. New curves interpolate existing curves " + "from a single group")); + b.add_input<decl::Geometry>(N_("Points")) + .description(N_("First control point positions for new interpolated curves")); + b.add_input<decl::Vector>(N_("Point Up")) + .field_on({3}) + .hide_value() + .description(N_("Optional up vector that is typically a surface normal")); + b.add_input<decl::Int>(N_("Point Group ID")) + .field_on({3}) + .hide_value() + .description(N_("The curve group to interpolate in")); + b.add_input<decl::Int>(N_("Max Neighbors")) + .default_value(4) + .min(1) + .description(N_( + "Maximum amount of close guide curves that are taken into account for interpolation")); + b.add_output<decl::Geometry>(N_("Curves")).propagate_all(); + b.add_output<decl::Int>(N_("Closest Index")) + .field_on_all() + .description(N_("Index of the closest guide curve for each generated curve")); + b.add_output<decl::Float>(N_("Closest Weight")) + .field_on_all() + .description(N_("Weight of the closest guide curve for each generated curve")); +} + +/** + * Guides are split into groups. Every point will only interpolate between guides within the group + * with the same id. + */ +static MultiValueMap<int, int> separate_guides_by_group(const VArray<int> &guide_group_ids) +{ + MultiValueMap<int, int> guides_by_group; + for (const int curve_i : guide_group_ids.index_range()) { + const int group = guide_group_ids[curve_i]; + guides_by_group.add(group, curve_i); + } + return guides_by_group; +} + +/** + * Checks if all curves within a group have the same number of points. If yes, a better + * interpolation algorithm can be used, that does not require resampling curves. + */ +static Map<int, int> compute_points_per_curve_by_group( + const MultiValueMap<int, int> &guides_by_group, const bke::CurvesGeometry &guide_curves) +{ + const OffsetIndices points_by_curve = guide_curves.points_by_curve(); + Map<int, int> points_per_curve_by_group; + for (const auto &[group, guide_curve_indices] : guides_by_group.items()) { + int group_control_points = points_by_curve.size(guide_curve_indices[0]); + for (const int guide_curve_i : guide_curve_indices.as_span().drop_front(1)) { + const int control_points = points_by_curve.size(guide_curve_i); + if (group_control_points != control_points) { + group_control_points = -1; + break; + } + } + if (group_control_points != -1) { + points_per_curve_by_group.add(group, group_control_points); + } + } + return points_per_curve_by_group; +} + +/** + * Build a kdtree for every guide group. + */ +static Map<int, KDTree_3d *> build_kdtrees_for_root_positions( + const MultiValueMap<int, int> &guides_by_group, const bke::CurvesGeometry &guide_curves) +{ + Map<int, KDTree_3d *> kdtrees; + const Span<float3> positions = guide_curves.positions(); + const Span<int> offsets = guide_curves.offsets(); + + for (const auto item : guides_by_group.items()) { + const int group = item.key; + const Span<int> guide_indices = item.value; + + K @@ Diff output truncated at 10240 characters. @@ _______________________________________________ Bf-blender-cvs mailing list Bf-blender-cvs@blender.org List details, subscription details or unsubscribe: https://lists.blender.org/mailman/listinfo/bf-blender-cvs