diff options
Diffstat (limited to 'source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc')
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc | 595 |
1 files changed, 36 insertions, 559 deletions
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc index fb8a488ddae..4586bb24464 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc @@ -1,16 +1,12 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include "BLI_task.hh" - #include "UI_interface.h" #include "UI_resources.h" -#include "DNA_node_types.h" +#include "GEO_fillet_curves.hh" #include "node_geometry_util.hh" -#include "BKE_spline.hh" - namespace blender::nodes::node_geo_curve_fillet_cc { NODE_STORAGE_FUNCS(NodeGeometryCurveFillet) @@ -44,571 +40,18 @@ static void node_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) static void node_init(bNodeTree *UNUSED(tree), bNode *node) { NodeGeometryCurveFillet *data = MEM_cnew<NodeGeometryCurveFillet>(__func__); - data->mode = GEO_NODE_CURVE_FILLET_BEZIER; node->storage = data; } -struct FilletParam { - GeometryNodeCurveFilletMode mode; - - /* Number of points to be added. */ - VArray<int> counts; - - /* Radii for fillet arc at all vertices. */ - VArray<float> radii; - - /* Whether or not fillets are allowed to overlap. */ - bool limit_radius; -}; - -/* A data structure used to store fillet data about all vertices to be filleted. */ -struct FilletData { - Span<float3> positions; - Array<float3> directions, axes; - Array<float> radii, angles; - Array<int> counts; -}; - static void node_update(bNodeTree *ntree, bNode *node) { const NodeGeometryCurveFillet &storage = node_storage(*node); const GeometryNodeCurveFilletMode mode = (GeometryNodeCurveFilletMode)storage.mode; - bNodeSocket *poly_socket = ((bNodeSocket *)node->inputs.first)->next; - nodeSetSocketAvailability(ntree, poly_socket, mode == GEO_NODE_CURVE_FILLET_POLY); } -/* Function to get the center of a fillet. */ -static float3 get_center(const float3 vec_pos2prev, - const float3 pos, - const float3 axis, - const float angle) -{ - float3 vec_pos2center; - rotate_normalized_v3_v3v3fl(vec_pos2center, vec_pos2prev, axis, M_PI_2 - angle / 2.0f); - vec_pos2center *= 1.0f / sinf(angle / 2.0f); - - return vec_pos2center + pos; -} - -/* Function to get the center of the fillet using fillet data */ -static float3 get_center(const float3 vec_pos2prev, const FilletData &fd, const int index) -{ - const float angle = fd.angles[index]; - const float3 axis = fd.axes[index]; - const float3 pos = fd.positions[index]; - - return get_center(vec_pos2prev, pos, axis, angle); -} - -/* Calculate the direction vectors from each vertex to their previous vertex. */ -static Array<float3> calculate_directions(const Span<float3> positions) -{ - const int num = positions.size(); - Array<float3> directions(num); - - for (const int i : IndexRange(num - 1)) { - directions[i] = math::normalize(positions[i + 1] - positions[i]); - } - directions[num - 1] = math::normalize(positions[0] - positions[num - 1]); - - return directions; -} - -/* Calculate the axes around which the fillet is built. */ -static Array<float3> calculate_axes(const Span<float3> directions) -{ - const int num = directions.size(); - Array<float3> axes(num); - - axes[0] = math::normalize(math::cross(-directions[num - 1], directions[0])); - for (const int i : IndexRange(1, num - 1)) { - axes[i] = math::normalize(math::cross(-directions[i - 1], directions[i])); - } - - return axes; -} - -/* Calculate the angle of the arc formed by the fillet. */ -static Array<float> calculate_angles(const Span<float3> directions) -{ - const int num = directions.size(); - Array<float> angles(num); - - angles[0] = M_PI - angle_v3v3(-directions[num - 1], directions[0]); - for (const int i : IndexRange(1, num - 1)) { - angles[i] = M_PI - angle_v3v3(-directions[i - 1], directions[i]); - } - - return angles; -} - -/* Calculate the segment count in each filleted arc. */ -static Array<int> calculate_counts(const FilletParam &fillet_param, - const int num, - const int spline_offset, - const bool cyclic) -{ - Array<int> counts(num, 1); - if (fillet_param.mode == GEO_NODE_CURVE_FILLET_POLY) { - for (const int i : IndexRange(num)) { - counts[i] = fillet_param.counts[spline_offset + i]; - } - } - if (!cyclic) { - counts[0] = counts[num - 1] = 0; - } - - return counts; -} - -/* Calculate the radii for the vertices to be filleted. */ -static Array<float> calculate_radii(const FilletParam &fillet_param, - const int num, - const int spline_offset) -{ - Array<float> radii(num, 0.0f); - if (fillet_param.limit_radius) { - for (const int i : IndexRange(num)) { - radii[i] = std::max(fillet_param.radii[spline_offset + i], 0.0f); - } - } - else { - for (const int i : IndexRange(num)) { - radii[i] = fillet_param.radii[spline_offset + i]; - } - } - - return radii; -} - -/* Calculate the number of vertices added per vertex on the source spline. */ -static int calculate_point_counts(MutableSpan<int> point_counts, - const Span<float> radii, - const Span<int> counts) -{ - int added_count = 0; - for (const int i : IndexRange(point_counts.size())) { - /* Calculate number of points to be added for the vertex. */ - if (radii[i] != 0.0f) { - added_count += counts[i]; - point_counts[i] = counts[i] + 1; - } - } - - return added_count; -} - -static FilletData calculate_fillet_data(const Spline &spline, - const FilletParam &fillet_param, - int &added_count, - MutableSpan<int> point_counts, - const int spline_offset) -{ - const int num = spline.size(); - - FilletData fd; - fd.directions = calculate_directions(spline.positions()); - fd.positions = spline.positions(); - fd.axes = calculate_axes(fd.directions); - fd.angles = calculate_angles(fd.directions); - fd.counts = calculate_counts(fillet_param, num, spline_offset, spline.is_cyclic()); - fd.radii = calculate_radii(fillet_param, num, spline_offset); - - added_count = calculate_point_counts(point_counts, fd.radii, fd.counts); - - return fd; -} - -/* Limit the radius based on angle and radii to prevent overlapping. */ -static void limit_radii(FilletData &fd, const bool cyclic) -{ - MutableSpan<float> radii(fd.radii); - Span<float> angles(fd.angles); - Span<float3> positions(fd.positions); - - const int num = radii.size(); - const int fillet_count = cyclic ? num : num - 2; - const int start = cyclic ? 0 : 1; - Array<float> max_radii(num, FLT_MAX); - - if (cyclic) { - /* Calculate lengths between adjacent control points. */ - const float len_prev = math::distance(positions[0], positions[num - 1]); - const float len_next = math::distance(positions[0], positions[1]); - - /* Calculate tangent lengths of fillets in control points. */ - const float tan_len = radii[0] * tan(angles[0] / 2.0f); - const float tan_len_prev = radii[num - 1] * tan(angles[num - 1] / 2.0f); - const float tan_len_next = radii[1] * tan(angles[1] / 2.0f); - - float factor_prev = 1.0f, factor_next = 1.0f; - if (tan_len + tan_len_prev > len_prev) { - factor_prev = len_prev / (tan_len + tan_len_prev); - } - if (tan_len + tan_len_next > len_next) { - factor_next = len_next / (tan_len + tan_len_next); - } - - /* Scale max radii by calculated factors. */ - max_radii[0] = radii[0] * std::min(factor_next, factor_prev); - max_radii[1] = radii[1] * factor_next; - max_radii[num - 1] = radii[num - 1] * factor_prev; - } - - /* Initialize max_radii to largest possible radii. */ - float prev_dist = math::distance(positions[1], positions[0]); - for (const int i : IndexRange(1, num - 2)) { - const float temp_dist = math::distance(positions[i], positions[i + 1]); - max_radii[i] = std::min(prev_dist, temp_dist) / tan(angles[i] / 2.0f); - prev_dist = temp_dist; - } - - /* Max radii calculations for each index. */ - for (const int i : IndexRange(start, fillet_count - 1)) { - const float len_next = math::distance(positions[i], positions[i + 1]); - const float tan_len = radii[i] * tan(angles[i] / 2.0f); - const float tan_len_next = radii[i + 1] * tan(angles[i + 1] / 2.0f); - - /* Scale down radii if too large for segment. */ - float factor = 1.0f; - if (tan_len + tan_len_next > len_next) { - factor = len_next / (tan_len + tan_len_next); - } - max_radii[i] = std::min(max_radii[i], radii[i] * factor); - max_radii[i + 1] = std::min(max_radii[i + 1], radii[i + 1] * factor); - } - - /* Assign the max_radii to the fillet data's radii. */ - for (const int i : IndexRange(num)) { - radii[i] = std::min(radii[i], max_radii[i]); - } -} - -/* - * Create a mapping from each vertex in the destination spline to that of the source spline. - * Used for copying the data from the source spline. - */ -static Array<int> create_dst_to_src_map(const Span<int> point_counts, const int total_points) -{ - Array<int> map(total_points); - MutableSpan<int> map_span{map}; - int index = 0; - - for (const int i : point_counts.index_range()) { - map_span.slice(index, point_counts[i]).fill(i); - index += point_counts[i]; - } - - BLI_assert(index == total_points); - - return map; -} - -template<typename T> -static void copy_attribute_by_mapping(const Span<T> src, - MutableSpan<T> dst, - const Span<int> mapping) -{ - for (const int i : dst.index_range()) { - dst[i] = src[mapping[i]]; - } -} - -/* Copy radii and tilts from source spline to destination. Positions are handled later in update - * positions methods. */ -static void copy_common_attributes_by_mapping(const Spline &src, - Spline &dst, - const Span<int> mapping) -{ - copy_attribute_by_mapping(src.radii(), dst.radii(), mapping); - copy_attribute_by_mapping(src.tilts(), dst.tilts(), mapping); - - src.attributes.foreach_attribute( - [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { - std::optional<GSpan> src_attribute = src.attributes.get_for_read(attribute_id); - if (dst.attributes.create(attribute_id, meta_data.data_type)) { - std::optional<GMutableSpan> dst_attribute = dst.attributes.get_for_write(attribute_id); - if (dst_attribute) { - attribute_math::convert_to_static_type(dst_attribute->type(), [&](auto dummy) { - using T = decltype(dummy); - copy_attribute_by_mapping( - src_attribute->typed<T>(), dst_attribute->typed<T>(), mapping); - }); - return true; - } - } - BLI_assert_unreachable(); - return false; - }, - ATTR_DOMAIN_POINT); -} - -/* Update the vertex positions and handle positions of a Bezier spline based on fillet data. */ -static void update_bezier_positions(const FilletData &fd, - BezierSpline &dst_spline, - const BezierSpline &src_spline, - const Span<int> point_counts) -{ - Span<float> radii(fd.radii); - Span<float> angles(fd.angles); - Span<float3> axes(fd.axes); - Span<float3> positions(fd.positions); - Span<float3> directions(fd.directions); - - const int num = radii.size(); - - int i_dst = 0; - for (const int i_src : IndexRange(num)) { - const int count = point_counts[i_src]; - - /* Skip if the point count for the vertex is 1. */ - if (count == 1) { - dst_spline.positions()[i_dst] = src_spline.positions()[i_src]; - dst_spline.handle_types_left()[i_dst] = src_spline.handle_types_left()[i_src]; - dst_spline.handle_types_right()[i_dst] = src_spline.handle_types_right()[i_src]; - dst_spline.handle_positions_left()[i_dst] = src_spline.handle_positions_left()[i_src]; - dst_spline.handle_positions_right()[i_dst] = src_spline.handle_positions_right()[i_src]; - i_dst++; - continue; - } - - /* Calculate the angle to be formed between any 2 adjacent vertices within the fillet. */ - const float segment_angle = angles[i_src] / (count - 1); - /* Calculate the handle length for each added vertex. Equation: L = 4R/3 * tan(A/4) */ - const float handle_length = 4.0f * radii[i_src] / 3.0f * tan(segment_angle / 4.0f); - /* Calculate the distance by which each vertex should be displaced from their initial position. - */ - const float displacement = radii[i_src] * tan(angles[i_src] / 2.0f); - - /* Position the end points of the arc and their handles. */ - const int end_i = i_dst + count - 1; - const float3 prev_dir = i_src == 0 ? -directions[num - 1] : -directions[i_src - 1]; - const float3 next_dir = directions[i_src]; - dst_spline.positions()[i_dst] = positions[i_src] + displacement * prev_dir; - dst_spline.positions()[end_i] = positions[i_src] + displacement * next_dir; - dst_spline.handle_positions_right()[i_dst] = dst_spline.positions()[i_dst] - - handle_length * prev_dir; - dst_spline.handle_positions_left()[end_i] = dst_spline.positions()[end_i] - - handle_length * next_dir; - dst_spline.handle_types_right()[i_dst] = dst_spline.handle_types_left()[end_i] = - BEZIER_HANDLE_ALIGN; - dst_spline.handle_types_left()[i_dst] = dst_spline.handle_types_right()[end_i] = - BEZIER_HANDLE_VECTOR; - dst_spline.mark_cache_invalid(); - - /* Calculate the center of the radius to be formed. */ - const float3 center = get_center(dst_spline.positions()[i_dst] - positions[i_src], fd, i_src); - /* Calculate the vector of the radius formed by the first vertex. */ - float3 radius_vec = dst_spline.positions()[i_dst] - center; - float radius; - radius_vec = math::normalize_and_get_length(radius_vec, radius); - - dst_spline.handle_types_right().slice(1, count - 2).fill(BEZIER_HANDLE_ALIGN); - dst_spline.handle_types_left().slice(1, count - 2).fill(BEZIER_HANDLE_ALIGN); - - /* For each of the vertices in between the end points. */ - for (const int j : IndexRange(1, count - 2)) { - int index = i_dst + j; - /* Rotate the radius by the segment angle and determine its tangent (used for getting handle - * directions). */ - float3 new_radius_vec, tangent_vec; - rotate_normalized_v3_v3v3fl(new_radius_vec, radius_vec, -axes[i_src], segment_angle); - rotate_normalized_v3_v3v3fl(tangent_vec, new_radius_vec, axes[i_src], M_PI_2); - radius_vec = new_radius_vec; - tangent_vec *= handle_length; - - /* Adjust the positions of the respective vertex and its handles. */ - dst_spline.positions()[index] = center + new_radius_vec * radius; - dst_spline.handle_positions_left()[index] = dst_spline.positions()[index] + tangent_vec; - dst_spline.handle_positions_right()[index] = dst_spline.positions()[index] - tangent_vec; - } - - i_dst += count; - } -} - -/* Update the vertex positions of a Poly spline based on fillet data. */ -static void update_poly_positions(const FilletData &fd, - Spline &dst_spline, - const Spline &src_spline, - const Span<int> point_counts) -{ - Span<float> radii(fd.radii); - Span<float> angles(fd.angles); - Span<float3> axes(fd.axes); - Span<float3> positions(fd.positions); - Span<float3> directions(fd.directions); - - const int num = radii.size(); - - int i_dst = 0; - for (const int i_src : IndexRange(num)) { - const int count = point_counts[i_src]; - - /* Skip if the point count for the vertex is 1. */ - if (count == 1) { - dst_spline.positions()[i_dst] = src_spline.positions()[i_src]; - i_dst++; - continue; - } - - const float segment_angle = angles[i_src] / (count - 1); - const float displacement = radii[i_src] * tan(angles[i_src] / 2.0f); - - /* Position the end points of the arc. */ - const int end_i = i_dst + count - 1; - const float3 prev_dir = i_src == 0 ? -directions[num - 1] : -directions[i_src - 1]; - const float3 next_dir = directions[i_src]; - dst_spline.positions()[i_dst] = positions[i_src] + displacement * prev_dir; - dst_spline.positions()[end_i] = positions[i_src] + displacement * next_dir; - - /* Calculate the center of the radius to be formed. */ - const float3 center = get_center(dst_spline.positions()[i_dst] - positions[i_src], fd, i_src); - /* Calculate the vector of the radius formed by the first vertex. */ - float3 radius_vec = dst_spline.positions()[i_dst] - center; - - for (const int j : IndexRange(1, count - 2)) { - /* Rotate the radius by the segment angle */ - float3 new_radius_vec; - rotate_normalized_v3_v3v3fl(new_radius_vec, radius_vec, -axes[i_src], segment_angle); - radius_vec = new_radius_vec; - - dst_spline.positions()[i_dst + j] = center + new_radius_vec; - } - - i_dst += count; - } -} - -static SplinePtr fillet_spline(const Spline &spline, - const FilletParam &fillet_param, - const int spline_offset) -{ - const int num = spline.size(); - const bool cyclic = spline.is_cyclic(); - - if (num < 3) { - return spline.copy(); - } - - /* Initialize the point_counts with 1s (at least one vertex on dst for each vertex on src). */ - Array<int> point_counts(num, 1); - - int added_count = 0; - /* Update point_counts array and added_count. */ - FilletData fd = calculate_fillet_data( - spline, fillet_param, added_count, point_counts, spline_offset); - if (fillet_param.limit_radius) { - limit_radii(fd, cyclic); - } - - const int total_points = added_count + num; - const Array<int> dst_to_src = create_dst_to_src_map(point_counts, total_points); - SplinePtr dst_spline_ptr = spline.copy_only_settings(); - (*dst_spline_ptr).resize(total_points); - copy_common_attributes_by_mapping(spline, *dst_spline_ptr, dst_to_src); - - switch (spline.type()) { - case CURVE_TYPE_BEZIER: { - const BezierSpline &src_spline = static_cast<const BezierSpline &>(spline); - BezierSpline &dst_spline = static_cast<BezierSpline &>(*dst_spline_ptr); - if (fillet_param.mode == GEO_NODE_CURVE_FILLET_POLY) { - dst_spline.handle_types_left().fill(BEZIER_HANDLE_VECTOR); - dst_spline.handle_types_right().fill(BEZIER_HANDLE_VECTOR); - update_poly_positions(fd, dst_spline, src_spline, point_counts); - } - else { - update_bezier_positions(fd, dst_spline, src_spline, point_counts); - } - break; - } - case CURVE_TYPE_POLY: { - update_poly_positions(fd, *dst_spline_ptr, spline, point_counts); - break; - } - case CURVE_TYPE_NURBS: { - const NURBSpline &src_spline = static_cast<const NURBSpline &>(spline); - NURBSpline &dst_spline = static_cast<NURBSpline &>(*dst_spline_ptr); - copy_attribute_by_mapping(src_spline.weights(), dst_spline.weights(), dst_to_src); - update_poly_positions(fd, dst_spline, src_spline, point_counts); - break; - } - case CURVE_TYPE_CATMULL_ROM: { - BLI_assert_unreachable(); - break; - } - } - - return dst_spline_ptr; -} - -static std::unique_ptr<CurveEval> fillet_curve(const CurveEval &input_curve, - const FilletParam &fillet_param) -{ - Span<SplinePtr> input_splines = input_curve.splines(); - - std::unique_ptr<CurveEval> output_curve = std::make_unique<CurveEval>(); - const int splines_num = input_splines.size(); - output_curve->resize(splines_num); - MutableSpan<SplinePtr> output_splines = output_curve->splines(); - Array<int> spline_offsets = input_curve.control_point_offsets(); - - threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { - for (const int i : range) { - output_splines[i] = fillet_spline(*input_splines[i], fillet_param, spline_offsets[i]); - } - }); - output_curve->attributes = input_curve.attributes; - - return output_curve; -} - -static void calculate_curve_fillet(GeometrySet &geometry_set, - const GeometryNodeCurveFilletMode mode, - const Field<float> &radius_field, - const std::optional<Field<int>> &count_field, - const bool limit_radius) -{ - if (!geometry_set.has_curves()) { - return; - } - - FilletParam fillet_param; - fillet_param.mode = mode; - - CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>(); - GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; - const int domain_num = component.attribute_domain_num(ATTR_DOMAIN_POINT); - fn::FieldEvaluator field_evaluator{field_context, domain_num}; - - field_evaluator.add(radius_field); - - if (mode == GEO_NODE_CURVE_FILLET_POLY) { - field_evaluator.add(*count_field); - } - - field_evaluator.evaluate(); - - fillet_param.radii = field_evaluator.get_evaluated<float>(0); - if (fillet_param.radii.is_single() && fillet_param.radii.get_internal_single() < 0.0f) { - return; - } - - if (mode == GEO_NODE_CURVE_FILLET_POLY) { - fillet_param.counts = field_evaluator.get_evaluated<int>(1); - } - - fillet_param.limit_radius = limit_radius; - - const std::unique_ptr<CurveEval> input_curve = curves_to_curve_eval(*component.get_for_read()); - std::unique_ptr<CurveEval> output_curve = fillet_curve(*input_curve, fillet_param); - - geometry_set.replace_curves(curve_eval_to_curves(*output_curve)); -} - static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); @@ -625,7 +68,41 @@ static void node_geo_exec(GeoNodeExecParams params) } geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - calculate_curve_fillet(geometry_set, mode, radius_field, count_field, limit_radius); + if (!geometry_set.has_curves()) { + return; + } + + const Curves &curves_id = *geometry_set.get_curves_for_read(); + const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry); + bke::CurvesFieldContext context{curves, ATTR_DOMAIN_POINT}; + fn::FieldEvaluator evaluator{context, curves.points_num()}; + evaluator.add(radius_field); + + switch (mode) { + case GEO_NODE_CURVE_FILLET_BEZIER: { + evaluator.evaluate(); + bke::CurvesGeometry dst_curves = geometry::fillet_curves_bezier( + curves, curves.curves_range(), evaluator.get_evaluated<float>(0), limit_radius); + Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); + bke::curves_copy_parameters(curves_id, *dst_curves_id); + geometry_set.replace_curves(dst_curves_id); + break; + } + case GEO_NODE_CURVE_FILLET_POLY: { + evaluator.add(*count_field); + evaluator.evaluate(); + bke::CurvesGeometry dst_curves = geometry::fillet_curves_poly( + curves, + curves.curves_range(), + evaluator.get_evaluated<float>(0), + evaluator.get_evaluated<int>(1), + limit_radius); + Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); + bke::curves_copy_parameters(curves_id, *dst_curves_id); + geometry_set.replace_curves(dst_curves_id); + break; + } + } }); params.set_output("Curve", std::move(geometry_set)); |