/* * 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. */ #include "BLI_task.hh" #include "BLI_timeit.hh" #include "BKE_attribute_math.hh" #include "BKE_spline.hh" #include "UI_interface.h" #include "UI_resources.h" #include "node_geometry_util.hh" namespace blender::nodes { static void geo_node_curve_subdivide_declare(NodeDeclarationBuilder &b) { b.add_input(N_("Geometry")); b.add_input(N_("Cuts")); b.add_input(N_("Cuts"), "Cuts_001").default_value(1).min(0).max(1000); b.add_output(N_("Geometry")); } static void geo_node_curve_subdivide_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); uiItemR(layout, ptr, "cuts_type", 0, IFACE_("Cuts"), ICON_NONE); } static void geo_node_curve_subdivide_init(bNodeTree *UNUSED(tree), bNode *node) { NodeGeometryCurveSubdivide *data = (NodeGeometryCurveSubdivide *)MEM_callocN( sizeof(NodeGeometryCurveSubdivide), __func__); data->cuts_type = GEO_NODE_ATTRIBUTE_INPUT_INTEGER; node->storage = data; } static void geo_node_curve_subdivide_update(bNodeTree *ntree, bNode *node) { NodeGeometryPointTranslate &node_storage = *(NodeGeometryPointTranslate *)node->storage; update_attribute_input_socket_availabilities( *ntree, *node, "Cuts", (GeometryNodeAttributeInputMode)node_storage.input_type); } static Array get_subdivided_offsets(const Spline &spline, const VArray &cuts, const int spline_offset) { Array offsets(spline.segments_size() + 1); int offset = 0; for (const int i : IndexRange(spline.segments_size())) { offsets[i] = offset; offset = offset + std::max(cuts[spline_offset + i], 0) + 1; } offsets.last() = offset; return offsets; } template static void subdivide_attribute(Span src, const Span offsets, const bool is_cyclic, MutableSpan dst) { const int src_size = src.size(); threading::parallel_for(IndexRange(src_size - 1), 1024, [&](IndexRange range) { for (const int i : range) { const int cuts = offsets[i + 1] - offsets[i]; dst[offsets[i]] = src[i]; const float factor_delta = 1.0f / (cuts + 1.0f); for (const int cut : IndexRange(cuts)) { const float factor = (cut + 1) * factor_delta; dst[offsets[i] + cut] = attribute_math::mix2(factor, src[i], src[i + 1]); } } }); if (is_cyclic) { const int i = src_size - 1; const int cuts = offsets[i + 1] - offsets[i]; dst[offsets[i]] = src.last(); const float factor_delta = 1.0f / (cuts + 1.0f); for (const int cut : IndexRange(cuts)) { const float factor = (cut + 1) * factor_delta; dst[offsets[i] + cut] = attribute_math::mix2(factor, src.last(), src.first()); } } else { dst.last() = src.last(); } } /** * In order to generate a Bezier spline with the same shape as the input spline, apply the * De Casteljau algorithm iteratively for the provided number of cuts, constantly updating the * previous result point's right handle and the left handle at the end of the segment. * * \note Non-vector segments in the result spline are given free handles. This could possibly be * improved with another pass that sets handles to aligned where possible, but currently that does * not provide much benefit for the increased complexity. */ static void subdivide_bezier_segment(const BezierSpline &src, const int index, const int offset, const int result_size, Span src_positions, Span src_handles_left, Span src_handles_right, MutableSpan dst_positions, MutableSpan dst_handles_left, MutableSpan dst_handles_right, MutableSpan dst_type_left, MutableSpan dst_type_right) { const bool is_last_cyclic_segment = index == (src.size() - 1); const int next_index = is_last_cyclic_segment ? 0 : index + 1; /* The first point in the segment is always copied. */ dst_positions[offset] = src_positions[index]; if (src.segment_is_vector(index)) { if (is_last_cyclic_segment) { dst_type_left.first() = BezierSpline::HandleType::Vector; } dst_type_left.slice(offset + 1, result_size).fill(BezierSpline::HandleType::Vector); dst_type_right.slice(offset, result_size).fill(BezierSpline::HandleType::Vector); const float factor_delta = 1.0f / result_size; for (const int cut : IndexRange(result_size)) { const float factor = cut * factor_delta; dst_positions[offset + cut] = attribute_math::mix2( factor, src_positions[index], src_positions[next_index]); } } else { if (is_last_cyclic_segment) { dst_type_left.first() = BezierSpline::HandleType::Free; } dst_type_left.slice(offset + 1, result_size).fill(BezierSpline::HandleType::Free); dst_type_right.slice(offset, result_size).fill(BezierSpline::HandleType::Free); const int i_segment_last = is_last_cyclic_segment ? 0 : offset + result_size; /* Create a Bezier segment to update iteratively for every subdivision * and references to the meaningful values for ease of use. */ BezierSpline temp; temp.resize(2); float3 &segment_start = temp.positions().first(); float3 &segment_end = temp.positions().last(); float3 &handle_prev = temp.handle_positions_right().first(); float3 &handle_next = temp.handle_positions_left().last(); segment_start = src_positions[index]; segment_end = src_positions[next_index]; handle_prev = src_handles_right[index]; handle_next = src_handles_left[next_index]; for (const int cut : IndexRange(result_size - 1)) { const float parameter = 1.0f / (result_size - cut); const BezierSpline::InsertResult insert = temp.calculate_segment_insertion(0, 1, parameter); /* Copy relevant temporary data to the result. */ dst_handles_right[offset + cut] = insert.handle_prev; dst_handles_left[offset + cut + 1] = insert.left_handle; dst_positions[offset + cut + 1] = insert.position; /* Update the segment to prepare it for the next subdivision. */ segment_start = insert.position; handle_prev = insert.right_handle; handle_next = insert.handle_next; } /* Copy the handles for the last segment from the temporary spline. */ dst_handles_right[offset + result_size - 1] = handle_prev; dst_handles_left[i_segment_last] = handle_next; } } static void subdivide_bezier_spline(const BezierSpline &src, const Span offsets, BezierSpline &dst) { Span src_positions = src.positions(); Span src_handles_left = src.handle_positions_left(); Span src_handles_right = src.handle_positions_right(); MutableSpan dst_positions = dst.positions(); MutableSpan dst_handles_left = dst.handle_positions_left(); MutableSpan dst_handles_right = dst.handle_positions_right(); MutableSpan dst_type_left = dst.handle_types_left(); MutableSpan dst_type_right = dst.handle_types_right(); threading::parallel_for(IndexRange(src.size() - 1), 512, [&](IndexRange range) { for (const int i : range) { subdivide_bezier_segment(src, i, offsets[i], offsets[i + 1] - offsets[i], src_positions, src_handles_left, src_handles_right, dst_positions, dst_handles_left, dst_handles_right, dst_type_left, dst_type_right); } }); if (src.is_cyclic()) { const int i_last = src.size() - 1; subdivide_bezier_segment(src, i_last, offsets[i_last], offsets.last() - offsets[i_last], src_positions, src_handles_left, src_handles_right, dst_positions, dst_handles_left, dst_handles_right, dst_type_left, dst_type_right); } else { dst_positions.last() = src_positions.last(); } } static void subdivide_builtin_attributes(const Spline &src_spline, const Span offsets, Spline &dst_spline) { const bool is_cyclic = src_spline.is_cyclic(); subdivide_attribute(src_spline.radii(), offsets, is_cyclic, dst_spline.radii()); subdivide_attribute(src_spline.tilts(), offsets, is_cyclic, dst_spline.tilts()); switch (src_spline.type()) { case Spline::Type::Poly: { const PolySpline &src = static_cast(src_spline); PolySpline &dst = static_cast(dst_spline); subdivide_attribute(src.positions(), offsets, is_cyclic, dst.positions()); break; } case Spline::Type::Bezier: { const BezierSpline &src = static_cast(src_spline); BezierSpline &dst = static_cast(dst_spline); subdivide_bezier_spline(src, offsets, dst); dst.mark_cache_invalid(); break; } case Spline::Type::NURBS: { const NURBSpline &src = static_cast(src_spline); NURBSpline &dst = static_cast(dst_spline); subdivide_attribute(src.positions(), offsets, is_cyclic, dst.positions()); subdivide_attribute(src.weights(), offsets, is_cyclic, dst.weights()); break; } } } static void subdivide_dynamic_attributes(const Spline &src_spline, const Span offsets, Spline &dst_spline) { const bool is_cyclic = src_spline.is_cyclic(); src_spline.attributes.foreach_attribute( [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { std::optional src = src_spline.attributes.get_for_read(attribute_id); BLI_assert(src); if (!dst_spline.attributes.create(attribute_id, meta_data.data_type)) { /* Since the source spline of the same type had the attribute, adding it should work. */ BLI_assert_unreachable(); } std::optional dst = dst_spline.attributes.get_for_write(attribute_id); BLI_assert(dst); attribute_math::convert_to_static_type(dst->type(), [&](auto dummy) { using T = decltype(dummy); subdivide_attribute(src->typed(), offsets, is_cyclic, dst->typed()); }); return true; }, ATTR_DOMAIN_POINT); } static SplinePtr subdivide_spline(const Spline &spline, const VArray &cuts, const int spline_offset) { if (spline.size() <= 1) { return spline.copy(); } /* Since we expect to access each value many times, it should be worth it to make sure count * of cuts is a real span (especially considering the note below). Using the offset at each * point facilitates subdividing in parallel later. */ Array offsets = get_subdivided_offsets(spline, cuts, spline_offset); const int result_size = offsets.last() + int(!spline.is_cyclic()); SplinePtr new_spline = spline.copy_only_settings(); new_spline->resize(result_size); subdivide_builtin_attributes(spline, offsets, *new_spline); subdivide_dynamic_attributes(spline, offsets, *new_spline); return new_spline; } /** * \note Passing the virtual array for the entire spline is possibly quite inefficient here when * the attribute was on the point domain and stored separately for each spline already, and it * prevents some other optimizations like skipping splines with a single attribute value of < 1. * However, it allows the node to access builtin attribute easily, so it the makes most sense this * way until the attribute API is refactored. */ static std::unique_ptr subdivide_curve(const CurveEval &input_curve, const VArray &cuts) { const Array control_point_offsets = input_curve.control_point_offsets(); const Span input_splines = input_curve.splines(); std::unique_ptr output_curve = std::make_unique(); output_curve->resize(input_splines.size()); output_curve->attributes = input_curve.attributes; MutableSpan output_splines = output_curve->splines(); threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { for (const int i : range) { output_splines[i] = subdivide_spline(*input_splines[i], cuts, control_point_offsets[i]); } }); return output_curve; } static void geo_node_subdivide_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input("Geometry"); geometry_set = bke::geometry_set_realize_instances(geometry_set); if (!geometry_set.has_curve()) { params.set_output("Geometry", geometry_set); return; } const CurveComponent &component = *geometry_set.get_component_for_read(); VArray cuts = params.get_input_attribute("Cuts", component, ATTR_DOMAIN_POINT, 0); if (cuts.is_single() && cuts.get_internal_single() < 1) { params.set_output("Geometry", geometry_set); return; } std::unique_ptr output_curve = subdivide_curve(*component.get_for_read(), cuts); params.set_output("Geometry", GeometrySet::create_with_curve(output_curve.release())); } } // namespace blender::nodes void register_node_type_geo_legacy_curve_subdivide() { static bNodeType ntype; geo_node_type_base( &ntype, GEO_NODE_LEGACY_CURVE_SUBDIVIDE, "Curve Subdivide", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_curve_subdivide_declare; ntype.draw_buttons = blender::nodes::geo_node_curve_subdivide_layout; node_type_storage(&ntype, "NodeGeometryCurveSubdivide", node_free_standard_storage, node_copy_standard_storage); node_type_init(&ntype, blender::nodes::geo_node_curve_subdivide_init); node_type_update(&ntype, blender::nodes::geo_node_curve_subdivide_update); ntype.geometry_node_execute = blender::nodes::geo_node_subdivide_exec; nodeRegisterType(&ntype); }