diff options
Diffstat (limited to 'source/blender/geometry')
-rw-r--r-- | source/blender/geometry/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/geometry/GEO_fillet_curves.hh | 23 | ||||
-rw-r--r-- | source/blender/geometry/GEO_mesh_to_curve.hh | 12 | ||||
-rw-r--r-- | source/blender/geometry/GEO_set_curve_type.hh | 18 | ||||
-rw-r--r-- | source/blender/geometry/GEO_subdivide_curves.hh | 10 | ||||
-rw-r--r-- | source/blender/geometry/GEO_uv_parametrizer.h | 5 | ||||
-rw-r--r-- | source/blender/geometry/intern/add_curves_on_mesh.cc | 67 | ||||
-rw-r--r-- | source/blender/geometry/intern/fillet_curves.cc | 561 | ||||
-rw-r--r-- | source/blender/geometry/intern/mesh_to_curve_convert.cc | 23 | ||||
-rw-r--r-- | source/blender/geometry/intern/point_merge_by_distance.cc | 14 | ||||
-rw-r--r-- | source/blender/geometry/intern/realize_instances.cc | 36 | ||||
-rw-r--r-- | source/blender/geometry/intern/resample_curves.cc | 2 | ||||
-rw-r--r-- | source/blender/geometry/intern/set_curve_type.cc | 76 | ||||
-rw-r--r-- | source/blender/geometry/intern/subdivide_curves.cc | 96 | ||||
-rw-r--r-- | source/blender/geometry/intern/uv_parametrizer.c | 112 |
15 files changed, 832 insertions, 225 deletions
diff --git a/source/blender/geometry/CMakeLists.txt b/source/blender/geometry/CMakeLists.txt index df66a806c16..da83d9e8957 100644 --- a/source/blender/geometry/CMakeLists.txt +++ b/source/blender/geometry/CMakeLists.txt @@ -16,6 +16,7 @@ set(INC set(SRC intern/add_curves_on_mesh.cc + intern/fillet_curves.cc intern/mesh_merge_by_distance.cc intern/mesh_primitive_cuboid.cc intern/mesh_to_curve_convert.cc @@ -29,6 +30,7 @@ set(SRC intern/uv_parametrizer.c GEO_add_curves_on_mesh.hh + GEO_fillet_curves.hh GEO_mesh_merge_by_distance.hh GEO_mesh_primitive_cuboid.hh GEO_mesh_to_curve.hh diff --git a/source/blender/geometry/GEO_fillet_curves.hh b/source/blender/geometry/GEO_fillet_curves.hh new file mode 100644 index 00000000000..1f832f8b6cc --- /dev/null +++ b/source/blender/geometry/GEO_fillet_curves.hh @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_function_ref.hh" +#include "BLI_index_mask.hh" + +#include "BKE_curves.hh" + +namespace blender::geometry { + +bke::CurvesGeometry fillet_curves_poly(const bke::CurvesGeometry &src_curves, + IndexMask curve_selection, + const VArray<float> &radius, + const VArray<int> &counts, + bool limit_radius); + +bke::CurvesGeometry fillet_curves_bezier(const bke::CurvesGeometry &src_curves, + IndexMask curve_selection, + const VArray<float> &radius, + bool limit_radius); + +} // namespace blender::geometry diff --git a/source/blender/geometry/GEO_mesh_to_curve.hh b/source/blender/geometry/GEO_mesh_to_curve.hh index c480e4178cf..f619aaff217 100644 --- a/source/blender/geometry/GEO_mesh_to_curve.hh +++ b/source/blender/geometry/GEO_mesh_to_curve.hh @@ -1,12 +1,12 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma once + #include "BLI_index_mask.hh" -#pragma once +#include "BKE_curves.hh" struct Mesh; -struct Curves; -class MeshComponent; /** \file * \ingroup geo @@ -15,10 +15,10 @@ class MeshComponent; namespace blender::geometry { /** - * Convert the mesh into one or many poly splines. Since splines cannot have branches, - * intersections of more than three edges will become breaks in splines. Attributes that + * Convert the mesh into one or many poly curves. Since curves cannot have branches, + * intersections of more than three edges will become breaks in curves. Attributes that * are not built-in on meshes and not curves are transferred to the result curve. */ -Curves *mesh_to_curve_convert(const MeshComponent &mesh_component, const IndexMask selection); +bke::CurvesGeometry mesh_to_curve_convert(const Mesh &mesh, const IndexMask selection); } // namespace blender::geometry diff --git a/source/blender/geometry/GEO_set_curve_type.hh b/source/blender/geometry/GEO_set_curve_type.hh index 6a75450006b..f38e63b1fc8 100644 --- a/source/blender/geometry/GEO_set_curve_type.hh +++ b/source/blender/geometry/GEO_set_curve_type.hh @@ -2,17 +2,10 @@ #pragma once -#include "DNA_curves_types.h" - #include "BLI_function_ref.hh" #include "BLI_index_mask.hh" -struct Curves; -class CurveComponent; - -namespace blender::bke { -class CurvesGeometry; -} +#include "BKE_curves.hh" namespace blender::geometry { @@ -27,14 +20,13 @@ namespace blender::geometry { */ bool try_curves_conversion_in_place(IndexMask selection, CurveType dst_type, - FunctionRef<Curves &()> get_writable_curves_fn); + FunctionRef<bke::CurvesGeometry &()> get_writable_curves_fn); /** * Change the types of the selected curves, potentially changing the total point count. */ -Curves *convert_curves(const CurveComponent &src_component, - const bke::CurvesGeometry &src_curves, - IndexMask selection, - CurveType dst_type); +bke::CurvesGeometry convert_curves(const bke::CurvesGeometry &src_curves, + IndexMask selection, + CurveType dst_type); } // namespace blender::geometry diff --git a/source/blender/geometry/GEO_subdivide_curves.hh b/source/blender/geometry/GEO_subdivide_curves.hh index 66c2eb53496..ba55118baa4 100644 --- a/source/blender/geometry/GEO_subdivide_curves.hh +++ b/source/blender/geometry/GEO_subdivide_curves.hh @@ -4,11 +4,10 @@ #include "BLI_function_ref.hh" #include "BLI_index_mask.hh" +#include "BLI_virtual_array.hh" #include "BKE_curves.hh" -class CurveComponent; - namespace blender::geometry { /** @@ -18,9 +17,8 @@ namespace blender::geometry { * * \param selection: A selection of curves to consider when subdividing. */ -Curves *subdivide_curves(const CurveComponent &src_component, - const bke::CurvesGeometry &src_curves, - IndexMask selection, - const VArray<int> &cuts); +bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves, + IndexMask selection, + const VArray<int> &cuts); } // namespace blender::geometry diff --git a/source/blender/geometry/GEO_uv_parametrizer.h b/source/blender/geometry/GEO_uv_parametrizer.h index 2181f95945e..5285aefbd4c 100644 --- a/source/blender/geometry/GEO_uv_parametrizer.h +++ b/source/blender/geometry/GEO_uv_parametrizer.h @@ -103,7 +103,10 @@ void GEO_uv_parametrizer_pack(ParamHandle *handle, /** \name Average area for all charts * \{ */ -void GEO_uv_parametrizer_average(ParamHandle *handle, bool ignore_pinned); +void GEO_uv_parametrizer_average(ParamHandle *handle, + bool ignore_pinned, + bool scale_uv, + bool shear); /** \} */ diff --git a/source/blender/geometry/intern/add_curves_on_mesh.cc b/source/blender/geometry/intern/add_curves_on_mesh.cc index 34551bd474f..a69073af207 100644 --- a/source/blender/geometry/intern/add_curves_on_mesh.cc +++ b/source/blender/geometry/intern/add_curves_on_mesh.cc @@ -1,7 +1,9 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "BLI_length_parameterize.hh" + +#include "BKE_attribute_math.hh" #include "BKE_mesh_sample.hh" -#include "BKE_spline.hh" #include "GEO_add_curves_on_mesh.hh" @@ -145,16 +147,16 @@ static void interpolate_position_with_interpolation(CurvesGeometry &curves, const int added_curves_num = root_positions_cu.size(); threading::parallel_for(IndexRange(added_curves_num), 256, [&](const IndexRange range) { - for (const int i : range) { - const NeighborCurves &neighbors = neighbors_per_curve[i]; - const int curve_i = old_curves_num + i; + for (const int added_curve_i : range) { + const NeighborCurves &neighbors = neighbors_per_curve[added_curve_i]; + const int curve_i = old_curves_num + added_curve_i; const IndexRange points = curves.points_for_curve(curve_i); - const float length_cu = new_lengths_cu[i]; - const float3 &normal_su = new_normals_su[i]; + const float length_cu = new_lengths_cu[added_curve_i]; + const float3 &normal_su = new_normals_su[added_curve_i]; const float3 normal_cu = math::normalize(surface_to_curves_normal_mat * normal_su); - const float3 &root_cu = root_positions_cu[i]; + const float3 &root_cu = root_positions_cu[added_curve_i]; if (neighbors.is_empty()) { /* If there are no neighbors, just make a straight line. */ @@ -197,30 +199,41 @@ static void interpolate_position_with_interpolation(CurvesGeometry &curves, const IndexRange neighbor_points = curves.points_for_curve(neighbor_curve_i); const float3 &neighbor_root_cu = positions_cu[neighbor_points[0]]; - /* Use a temporary #PolySpline, because that's the easiest way to resample an - * existing curve right now. Resampling is necessary if the length of the new curve - * does not match the length of the neighbors or the number of handle points is - * different. */ - PolySpline neighbor_spline; - neighbor_spline.resize(neighbor_points.size()); - neighbor_spline.positions().copy_from(positions_cu.slice(neighbor_points)); - neighbor_spline.mark_cache_invalid(); + /* Sample the positions on neighbors and mix them into the final positions of the curve. + * Resampling is necessary if the length of the new curve does not match the length of the + * neighbors or the number of handle points is different. + * + * TODO: The lengths can be cached so they aren't recomputed if a curve is a neighbor for + * multiple new curves. Also, allocations could be avoided by reusing some arrays. */ + + const Span<float3> neighbor_positions_cu = positions_cu.slice(neighbor_points); + if (neighbor_positions_cu.size() == 1) { + /* Skip interpolating positions from neighbors with only one point. */ + continue; + } + Array<float, 32> lengths(length_parameterize::segments_num(neighbor_points.size(), false)); + length_parameterize::accumulate_lengths<float3>(neighbor_positions_cu, false, lengths); + const float neighbor_length_cu = lengths.last(); - const float neighbor_length_cu = neighbor_spline.length(); + Array<float, 32> sample_lengths(points.size()); const float length_factor = std::min(1.0f, length_cu / neighbor_length_cu); - const float resample_factor = (1.0f / (points.size() - 1.0f)) * length_factor; - for (const int j : IndexRange(points.size())) { - const Spline::LookupResult lookup = neighbor_spline.lookup_evaluated_factor( - j * resample_factor); - const float index_factor = lookup.evaluated_index + lookup.factor; - float3 p; - neighbor_spline.sample_with_index_factors<float3>( - neighbor_spline.positions(), {&index_factor, 1}, {&p, 1}); - const float3 relative_coord = p - neighbor_root_cu; - float3 rotated_relative_coord = relative_coord; + for (const int i : sample_lengths.index_range()) { + sample_lengths[i] = i * resample_factor * neighbor_length_cu; + } + + Array<int, 32> indices(points.size()); + Array<float, 32> factors(points.size()); + length_parameterize::sample_at_lengths(lengths, sample_lengths, indices, factors); + + for (const int i : IndexRange(points.size())) { + const float3 sample_cu = math::interpolate(neighbor_positions_cu[indices[i]], + neighbor_positions_cu[indices[i] + 1], + factors[i]); + const float3 relative_to_root_cu = sample_cu - neighbor_root_cu; + float3 rotated_relative_coord = relative_to_root_cu; mul_m3_v3(normal_rotation_cu, rotated_relative_coord); - positions_cu[points[j]] += neighbor.weight * rotated_relative_coord; + positions_cu[points[i]] += neighbor.weight * rotated_relative_coord; } } } diff --git a/source/blender/geometry/intern/fillet_curves.cc b/source/blender/geometry/intern/fillet_curves.cc new file mode 100644 index 00000000000..2cca91f40ae --- /dev/null +++ b/source/blender/geometry/intern/fillet_curves.cc @@ -0,0 +1,561 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_attribute_math.hh" +#include "BKE_curves.hh" +#include "BKE_curves_utils.hh" +#include "BKE_geometry_set.hh" + +#include "BLI_devirtualize_parameters.hh" +#include "BLI_math_geom.h" +#include "BLI_math_rotation.hh" +#include "BLI_task.hh" + +#include "GEO_fillet_curves.hh" + +namespace blender::geometry { + +/** + * Return a range used to retrieve values from an array of values stored per point, but with an + * extra element at the end of each curve. This is useful for offsets within curves, where it is + * convenient to store the first 0 and have the last offset be the total result curve size. + */ +static IndexRange curve_dst_offsets(const IndexRange points, const int curve_index) +{ + return {curve_index + points.start(), points.size() + 1}; +} + +template<typename T> +static void threaded_slice_fill(const Span<T> src, const Span<int> offsets, MutableSpan<T> dst) +{ + threading::parallel_for(src.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + dst.slice(bke::offsets_to_range(offsets, i)).fill(src[i]); + } + }); +} + +template<typename T> +static void duplicate_fillet_point_data(const bke::CurvesGeometry &src_curves, + const bke::CurvesGeometry &dst_curves, + const IndexMask curve_selection, + const Span<int> point_offsets, + const Span<T> src, + MutableSpan<T> dst) +{ + threading::parallel_for(curve_selection.index_range(), 512, [&](IndexRange range) { + for (const int curve_i : curve_selection.slice(range)) { + const IndexRange src_points = src_curves.points_for_curve(curve_i); + const IndexRange dst_points = dst_curves.points_for_curve(curve_i); + const Span<int> offsets = point_offsets.slice(curve_dst_offsets(src_points, curve_i)); + threaded_slice_fill(src.slice(src_points), offsets, dst.slice(dst_points)); + } + }); +} + +static void duplicate_fillet_point_data(const bke::CurvesGeometry &src_curves, + const bke::CurvesGeometry &dst_curves, + const IndexMask selection, + const Span<int> point_offsets, + const GSpan src, + GMutableSpan dst) +{ + attribute_math::convert_to_static_type(dst.type(), [&](auto dummy) { + using T = decltype(dummy); + duplicate_fillet_point_data( + src_curves, dst_curves, selection, point_offsets, src.typed<T>(), dst.typed<T>()); + }); +} + +static void calculate_result_offsets(const bke::CurvesGeometry &src_curves, + const IndexMask selection, + const Span<IndexRange> unselected_ranges, + const VArray<float> &radii, + const VArray<int> &counts, + const Span<bool> cyclic, + MutableSpan<int> dst_curve_offsets, + MutableSpan<int> dst_point_offsets) +{ + /* Fill the offsets array with the curve point counts, then accumulate them to form offsets. */ + bke::curves::fill_curve_counts(src_curves, unselected_ranges, dst_curve_offsets); + threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) { + for (const int curve_i : selection.slice(range)) { + const IndexRange src_points = src_curves.points_for_curve(curve_i); + const IndexRange offsets_range = curve_dst_offsets(src_points, curve_i); + + MutableSpan<int> point_offsets = dst_point_offsets.slice(offsets_range); + MutableSpan<int> point_counts = point_offsets.drop_back(1); + + counts.materialize_compressed(src_points, point_counts); + for (int &count : point_counts) { + /* Make sure the number of cuts is greater than zero and add one for the existing point. */ + count = std::max(count, 0) + 1; + } + if (!cyclic[curve_i]) { + /* Endpoints on non-cyclic curves cannot be filleted. */ + point_counts.first() = 1; + point_counts.last() = 1; + } + /* Implicitly "deselect" points with zero radius. */ + devirtualize_varray(radii, [&](const auto radii) { + for (const int i : IndexRange(src_points.size())) { + if (radii[i] == 0.0f) { + point_counts[i] = 1; + } + } + }); + + bke::curves::accumulate_counts_to_offsets(point_offsets); + + dst_curve_offsets[curve_i] = point_offsets.last(); + } + }); + bke::curves::accumulate_counts_to_offsets(dst_curve_offsets); +} + +static void calculate_directions(const Span<float3> positions, MutableSpan<float3> directions) +{ + for (const int i : positions.index_range().drop_back(1)) { + directions[i] = math::normalize(positions[i + 1] - positions[i]); + } + directions.last() = math::normalize(positions.first() - positions.last()); +} + +static void calculate_angles(const Span<float3> directions, MutableSpan<float> angles) +{ + angles.first() = M_PI - angle_v3v3(-directions.last(), directions.first()); + for (const int i : directions.index_range().drop_front(1)) { + angles[i] = M_PI - angle_v3v3(-directions[i - 1], directions[i]); + } +} + +/** + * Find the portion of the previous and next segments used by the current and next point fillets. + * If more than the total length of the segment would be used, scale the current point's radius + * just enough to make the two points meet in the middle. + */ +static float limit_radius(const float3 &position_prev, + const float3 &position, + const float3 &position_next, + const float angle_prev, + const float angle, + const float angle_next, + const float radius_prev, + const float radius, + const float radius_next) +{ + const float displacement = radius * std::tan(angle / 2.0f); + + const float displacement_prev = radius_prev * std::tan(angle_prev / 2.0f); + const float segment_length_prev = math::distance(position, position_prev); + const float total_displacement_prev = displacement_prev + displacement; + const float factor_prev = std::clamp(segment_length_prev / total_displacement_prev, 0.0f, 1.0f); + + const float displacement_next = radius_next * std::tan(angle_next / 2.0f); + const float segment_length_next = math::distance(position, position_next); + const float total_displacement_next = displacement_next + displacement; + const float factor_next = std::clamp(segment_length_next / total_displacement_next, 0.0f, 1.0f); + + return radius * std::min(factor_prev, factor_next); +} + +static void limit_radii(const Span<float3> positions, + const Span<float> angles, + const Span<float> radii, + const bool cyclic, + MutableSpan<float> radii_clamped) +{ + if (cyclic) { + /* First point. */ + radii_clamped.first() = limit_radius(positions.last(), + positions.first(), + positions[1], + angles.last(), + angles.first(), + angles[1], + radii.last(), + radii.first(), + radii[1]); + /* All middle points. */ + for (const int i : positions.index_range().drop_back(1).drop_front(1)) { + const int i_prev = i - 1; + const int i_next = i + 1; + radii_clamped[i] = limit_radius(positions[i_prev], + positions[i], + positions[i_next], + angles[i_prev], + angles[i], + angles[i_next], + radii[i_prev], + radii[i], + radii[i_next]); + } + /* Last point. */ + radii_clamped.last() = limit_radius(positions.last(1), + positions.last(), + positions.first(), + angles.last(1), + angles.last(), + angles.first(), + radii.last(1), + radii.last(), + radii.first()); + } + else { + const int i_last = positions.index_range().last(); + /* First point. */ + radii_clamped.first() = 0.0f; + /* All middle points. */ + for (const int i : positions.index_range().drop_back(1).drop_front(1)) { + const int i_prev = i - 1; + const int i_next = i + 1; + /* Use a zero radius for the first and last points, because they don't have fillets. + * This logic could potentially be unrolled, but it doesn't seem worth it. */ + const float radius_prev = i_prev == 0 ? 0.0f : radii[i_prev]; + const float radius_next = i_next == i_last ? 0.0f : radii[i_next]; + radii_clamped[i] = limit_radius(positions[i_prev], + positions[i], + positions[i_next], + angles[i_prev], + angles[i], + angles[i_next], + radius_prev, + radii[i], + radius_next); + } + /* Last point. */ + radii_clamped.last() = 0.0f; + } +} + +static void calculate_fillet_positions(const Span<float3> src_positions, + const Span<float> angles, + const Span<float> radii, + const Span<float3> directions, + const Span<int> dst_offsets, + MutableSpan<float3> dst) +{ + const int i_src_last = src_positions.index_range().last(); + threading::parallel_for(src_positions.index_range(), 512, [&](IndexRange range) { + for (const int i_src : range) { + const IndexRange arc = bke::offsets_to_range(dst_offsets, i_src); + const float3 &src = src_positions[i_src]; + if (arc.size() == 1) { + dst[arc.first()] = src; + continue; + } + + const int i_src_prev = i_src == 0 ? i_src_last : i_src - 1; + const float angle = angles[i_src]; + const float radius = radii[i_src]; + const float displacement = radius * std::tan(angle / 2.0f); + const float3 prev_dir = -directions[i_src_prev]; + const float3 &next_dir = directions[i_src]; + const float3 arc_start = src + prev_dir * displacement; + const float3 arc_end = src + next_dir * displacement; + + dst[arc.first()] = arc_start; + dst[arc.last()] = arc_end; + + const IndexRange middle = arc.drop_front(1).drop_back(1); + if (middle.is_empty()) { + continue; + } + + const float3 axis = -math::normalize(math::cross(prev_dir, next_dir)); + const float3 center_direction = math::normalize(math::midpoint(next_dir, prev_dir)); + const float distance_to_center = std::sqrt(pow2f(radius) + pow2f(displacement)); + const float3 center = src + center_direction * distance_to_center; + + /* Rotate each middle fillet point around the center. */ + const float segment_angle = angle / (middle.size() + 1); + for (const int i : IndexRange(middle.size())) { + const int point_i = middle[i]; + dst[point_i] = math::rotate_around_axis(arc_start, center, axis, segment_angle * (i + 1)); + } + } + }); +} + +/** + * Set handles for the "Bezier" mode where we rely on setting the inner handles to approximate a + * circular arc. The outer (previous and next) handles outside the result fillet segment are set + * to vector handles. + */ +static void calculate_bezier_handles_bezier_mode(const Span<float3> src_handles_l, + const Span<float3> src_handles_r, + const Span<int8_t> src_types_l, + const Span<int8_t> src_types_r, + const Span<float> angles, + const Span<float> radii, + const Span<float3> directions, + const Span<int> dst_offsets, + const Span<float3> dst_positions, + MutableSpan<float3> dst_handles_l, + MutableSpan<float3> dst_handles_r, + MutableSpan<int8_t> dst_types_l, + MutableSpan<int8_t> dst_types_r) +{ + const int i_src_last = src_handles_l.index_range().last(); + const int i_dst_last = dst_positions.index_range().last(); + threading::parallel_for(src_handles_l.index_range(), 512, [&](IndexRange range) { + for (const int i_src : range) { + const IndexRange arc = bke::offsets_to_range(dst_offsets, i_src); + if (arc.size() == 1) { + dst_handles_l[arc.first()] = src_handles_l[i_src]; + dst_handles_r[arc.first()] = src_handles_r[i_src]; + dst_types_l[arc.first()] = src_types_l[i_src]; + dst_types_r[arc.first()] = src_types_r[i_src]; + continue; + } + BLI_assert(arc.size() == 2); + const int i_dst_a = arc.first(); + const int i_dst_b = arc.last(); + + const int i_src_prev = i_src == 0 ? i_src_last : i_src - 1; + const float angle = angles[i_src]; + const float radius = radii[i_src]; + const float3 prev_dir = -directions[i_src_prev]; + const float3 &next_dir = directions[i_src]; + + const float3 &arc_start = dst_positions[arc.first()]; + const float3 &arc_end = dst_positions[arc.last()]; + + /* Calculate the point's handles on the outside of the fillet segment, + * connecting to the next or previous result points. */ + const int i_dst_prev = i_dst_a == 0 ? i_dst_last : i_dst_a - 1; + const int i_dst_next = i_dst_b == i_dst_last ? 0 : i_dst_b + 1; + dst_handles_l[i_dst_a] = bke::curves::bezier::calculate_vector_handle( + dst_positions[i_dst_a], dst_positions[i_dst_prev]); + dst_handles_r[i_dst_b] = bke::curves::bezier::calculate_vector_handle( + dst_positions[i_dst_b], dst_positions[i_dst_next]); + dst_types_l[i_dst_a] = BEZIER_HANDLE_VECTOR; + dst_types_r[i_dst_b] = BEZIER_HANDLE_VECTOR; + + /* The inner handles are aligned with the aligned with the outer vector + * handles, but have a specific length to best approximate a circle. */ + const float handle_length = (4.0f / 3.0f) * radius * std::tan(angle / 4.0f); + dst_handles_r[i_dst_a] = arc_start - prev_dir * handle_length; + dst_handles_l[i_dst_b] = arc_end - next_dir * handle_length; + dst_types_r[i_dst_a] = BEZIER_HANDLE_ALIGN; + dst_types_l[i_dst_b] = BEZIER_HANDLE_ALIGN; + } + }); +} + +/** + * In the poly fillet mode, all the inner handles are set to vector handles, along with the "outer" + * (previous and next) handles at each fillet. + */ +static void calculate_bezier_handles_poly_mode(const Span<float3> src_handles_l, + const Span<float3> src_handles_r, + const Span<int8_t> src_types_l, + const Span<int8_t> src_types_r, + const Span<int> dst_offsets, + const Span<float3> dst_positions, + MutableSpan<float3> dst_handles_l, + MutableSpan<float3> dst_handles_r, + MutableSpan<int8_t> dst_types_l, + MutableSpan<int8_t> dst_types_r) +{ + const int i_dst_last = dst_positions.index_range().last(); + threading::parallel_for(src_handles_l.index_range(), 512, [&](IndexRange range) { + for (const int i_src : range) { + const IndexRange arc = bke::offsets_to_range(dst_offsets, i_src); + if (arc.size() == 1) { + dst_handles_l[arc.first()] = src_handles_l[i_src]; + dst_handles_r[arc.first()] = src_handles_r[i_src]; + dst_types_l[arc.first()] = src_types_l[i_src]; + dst_types_r[arc.first()] = src_types_r[i_src]; + continue; + } + + /* The fillet's next and previous handles are vector handles, as are the inner handles. */ + dst_types_l.slice(arc).fill(BEZIER_HANDLE_VECTOR); + dst_types_r.slice(arc).fill(BEZIER_HANDLE_VECTOR); + + /* Calculate the point's handles on the outside of the fillet segment. This point + * won't be selected for a fillet if it is the first or last in a non-cyclic curve. */ + + const int i_dst_prev = arc.first() == 0 ? i_dst_last : arc.one_before_start(); + const int i_dst_next = arc.last() == i_dst_last ? 0 : arc.one_after_last(); + dst_handles_l[arc.first()] = bke::curves::bezier::calculate_vector_handle( + dst_positions[arc.first()], dst_positions[i_dst_prev]); + dst_handles_r[arc.last()] = bke::curves::bezier::calculate_vector_handle( + dst_positions[arc.last()], dst_positions[i_dst_next]); + + /* Set the values for the inner handles. */ + const IndexRange middle = arc.drop_front(1).drop_back(1); + for (const int i : middle) { + dst_handles_r[i] = bke::curves::bezier::calculate_vector_handle(dst_positions[i], + dst_positions[i - 1]); + dst_handles_l[i] = bke::curves::bezier::calculate_vector_handle(dst_positions[i], + dst_positions[i + 1]); + } + } + }); +} + +static bke::CurvesGeometry fillet_curves(const bke::CurvesGeometry &src_curves, + const IndexMask curve_selection, + const VArray<float> &radius_input, + const VArray<int> &counts, + const bool limit_radius, + const bool use_bezier_mode) +{ + const Vector<IndexRange> unselected_ranges = curve_selection.extract_ranges_invert( + src_curves.curves_range()); + + const Span<float3> positions = src_curves.positions(); + const VArraySpan<bool> cyclic{src_curves.cyclic()}; + const bke::AttributeAccessor src_attributes = src_curves.attributes(); + + bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves); + /* Stores the offset of every result point for every original point. + * The extra length is used in order to store an extra zero for every curve. */ + Array<int> dst_point_offsets(src_curves.points_num() + src_curves.curves_num()); + calculate_result_offsets(src_curves, + curve_selection, + unselected_ranges, + radius_input, + counts, + cyclic, + dst_curves.offsets_for_write(), + dst_point_offsets); + const Span<int> point_offsets = dst_point_offsets.as_span(); + + dst_curves.resize(dst_curves.offsets().last(), dst_curves.curves_num()); + bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write(); + MutableSpan<float3> dst_positions = dst_curves.positions_for_write(); + + VArraySpan<int8_t> src_types_l; + VArraySpan<int8_t> src_types_r; + Span<float3> src_handles_l; + Span<float3> src_handles_r; + MutableSpan<int8_t> dst_types_l; + MutableSpan<int8_t> dst_types_r; + MutableSpan<float3> dst_handles_l; + MutableSpan<float3> dst_handles_r; + if (src_curves.has_curve_with_type(CURVE_TYPE_BEZIER)) { + src_types_l = src_curves.handle_types_left(); + src_types_r = src_curves.handle_types_right(); + src_handles_l = src_curves.handle_positions_left(); + src_handles_r = src_curves.handle_positions_right(); + + dst_types_l = dst_curves.handle_types_left_for_write(); + dst_types_r = dst_curves.handle_types_right_for_write(); + dst_handles_l = dst_curves.handle_positions_left_for_write(); + dst_handles_r = dst_curves.handle_positions_right_for_write(); + } + + threading::parallel_for(curve_selection.index_range(), 512, [&](IndexRange range) { + Array<float3> directions; + Array<float> angles; + Array<float> radii; + Array<float> input_radii_buffer; + + for (const int curve_i : curve_selection.slice(range)) { + const IndexRange src_points = src_curves.points_for_curve(curve_i); + const Span<int> offsets = point_offsets.slice(curve_dst_offsets(src_points, curve_i)); + const IndexRange dst_points = dst_curves.points_for_curve(curve_i); + const Span<float3> src_positions = positions.slice(src_points); + + directions.reinitialize(src_points.size()); + calculate_directions(src_positions, directions); + + angles.reinitialize(src_points.size()); + calculate_angles(directions, angles); + + radii.reinitialize(src_points.size()); + if (limit_radius) { + input_radii_buffer.reinitialize(src_points.size()); + radius_input.materialize_compressed(src_points, input_radii_buffer); + limit_radii(src_positions, angles, input_radii_buffer, cyclic[curve_i], radii); + } + else { + radius_input.materialize_compressed(src_points, radii); + } + + calculate_fillet_positions(positions.slice(src_points), + angles, + radii, + directions, + offsets, + dst_positions.slice(dst_points)); + + if (src_curves.has_curve_with_type(CURVE_TYPE_BEZIER)) { + if (use_bezier_mode) { + calculate_bezier_handles_bezier_mode(src_handles_l.slice(src_points), + src_handles_r.slice(src_points), + src_types_l.slice(src_points), + src_types_r.slice(src_points), + angles, + radii, + directions, + offsets, + dst_positions.slice(dst_points), + dst_handles_l.slice(dst_points), + dst_handles_r.slice(dst_points), + dst_types_l.slice(dst_points), + dst_types_r.slice(dst_points)); + } + else { + calculate_bezier_handles_poly_mode(src_handles_l.slice(src_points), + src_handles_r.slice(src_points), + src_types_l.slice(src_points), + src_types_r.slice(src_points), + offsets, + dst_positions.slice(dst_points), + dst_handles_l.slice(dst_points), + dst_handles_r.slice(dst_points), + dst_types_l.slice(dst_points), + dst_types_r.slice(dst_points)); + } + } + } + }); + + for (auto &attribute : bke::retrieve_attributes_for_transfer( + src_attributes, + dst_attributes, + ATTR_DOMAIN_MASK_POINT, + {"position", "handle_type_left", "handle_type_right", "handle_right", "handle_left"})) { + duplicate_fillet_point_data( + src_curves, dst_curves, curve_selection, point_offsets, attribute.src, attribute.dst.span); + attribute.dst.finish(); + } + + if (!unselected_ranges.is_empty()) { + for (auto &attribute : bke::retrieve_attributes_for_transfer( + src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) { + bke::curves::copy_point_data( + src_curves, dst_curves, unselected_ranges, attribute.src, attribute.dst.span); + attribute.dst.finish(); + } + } + + return dst_curves; +} + +bke::CurvesGeometry fillet_curves_poly(const bke::CurvesGeometry &src_curves, + const IndexMask curve_selection, + const VArray<float> &radius, + const VArray<int> &count, + const bool limit_radius) +{ + return fillet_curves(src_curves, curve_selection, radius, count, limit_radius, false); +} + +bke::CurvesGeometry fillet_curves_bezier(const bke::CurvesGeometry &src_curves, + const IndexMask curve_selection, + const VArray<float> &radius, + const bool limit_radius) +{ + return fillet_curves(src_curves, + curve_selection, + radius, + VArray<int>::ForSingle(1, src_curves.points_num()), + limit_radius, + true); +} + +} // namespace blender::geometry diff --git a/source/blender/geometry/intern/mesh_to_curve_convert.cc b/source/blender/geometry/intern/mesh_to_curve_convert.cc index 681dfd15137..fdacb174462 100644 --- a/source/blender/geometry/intern/mesh_to_curve_convert.cc +++ b/source/blender/geometry/intern/mesh_to_curve_convert.cc @@ -30,13 +30,12 @@ static void copy_with_map(const VArray<T> &src, Span<int> map, MutableSpan<T> ds }); } -static Curves *create_curve_from_vert_indices(const MeshComponent &mesh_component, - const Span<int> vert_indices, - const Span<int> curve_offsets, - const IndexRange cyclic_curves) +static bke::CurvesGeometry create_curve_from_vert_indices(const Mesh &mesh, + const Span<int> vert_indices, + const Span<int> curve_offsets, + const IndexRange cyclic_curves) { - Curves *curves_id = bke::curves_new_nomain(vert_indices.size(), curve_offsets.size()); - bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id->geometry); + bke::CurvesGeometry curves(vert_indices.size(), curve_offsets.size()); curves.offsets_for_write().drop_back(1).copy_from(curve_offsets); curves.offsets_for_write().last() = vert_indices.size(); curves.fill_curve_types(CURVE_TYPE_POLY); @@ -44,8 +43,8 @@ static Curves *create_curve_from_vert_indices(const MeshComponent &mesh_componen curves.cyclic_for_write().fill(false); curves.cyclic_for_write().slice(cyclic_curves).fill(true); + const bke::AttributeAccessor mesh_attributes = bke::mesh_attributes(mesh); bke::MutableAttributeAccessor curves_attributes = curves.attributes_for_write(); - const bke::AttributeAccessor mesh_attributes = *mesh_component.attributes(); Set<bke::AttributeIDRef> source_attribute_ids = mesh_attributes.all_ids(); @@ -76,7 +75,7 @@ static Curves *create_curve_from_vert_indices(const MeshComponent &mesh_componen }); } - return curves_id; + return curves; } struct CurveFromEdgesOutput { @@ -220,16 +219,14 @@ static Vector<std::pair<int, int>> get_selected_edges(const Mesh &mesh, const In return selected_edges; } -Curves *mesh_to_curve_convert(const MeshComponent &mesh_component, const IndexMask selection) +bke::CurvesGeometry mesh_to_curve_convert(const Mesh &mesh, const IndexMask selection) { - const Mesh &mesh = *mesh_component.get_for_read(); - Vector<std::pair<int, int>> selected_edges = get_selected_edges(*mesh_component.get_for_read(), - selection); + Vector<std::pair<int, int>> selected_edges = get_selected_edges(mesh, selection); CurveFromEdgesOutput output = edges_to_curve_point_indices({mesh.mvert, mesh.totvert}, selected_edges); return create_curve_from_vert_indices( - mesh_component, output.vert_indices, output.curve_offsets, output.cyclic_curves); + mesh, output.vert_indices, output.curve_offsets, output.cyclic_curves); } } // namespace blender::geometry diff --git a/source/blender/geometry/intern/point_merge_by_distance.cc b/source/blender/geometry/intern/point_merge_by_distance.cc index 40bc02cbd34..ac10f4ef00c 100644 --- a/source/blender/geometry/intern/point_merge_by_distance.cc +++ b/source/blender/geometry/intern/point_merge_by_distance.cc @@ -18,8 +18,10 @@ PointCloud *point_merge_by_distance(const PointCloudComponent &src_points, const IndexMask selection) { const PointCloud &src_pointcloud = *src_points.get_for_read(); - const int src_size = src_pointcloud.totpoint; - Span<float3> positions{reinterpret_cast<float3 *>(src_pointcloud.co), src_size}; + bke::AttributeAccessor attributes = bke::pointcloud_attributes(src_pointcloud); + VArraySpan<float3> positions = attributes.lookup_or_default<float3>( + "position", ATTR_DOMAIN_POINT, float3(0)); + const int src_size = positions.size(); /* Create the KD tree based on only the selected points, to speed up merge detection and * balancing. */ @@ -106,10 +108,10 @@ PointCloud *point_merge_by_distance(const PointCloudComponent &src_points, const bke::AttributeAccessor src_attributes = *src_points.attributes(); bke::MutableAttributeAccessor dst_attributes = *dst_points.attributes_for_write(); - Set<bke::AttributeIDRef> attributes = src_attributes.all_ids(); + Set<bke::AttributeIDRef> attribute_ids = src_attributes.all_ids(); /* Transfer the ID attribute if it exists, using the ID of the first merged point. */ - if (attributes.contains("id")) { + if (attribute_ids.contains("id")) { VArraySpan<int> src = src_attributes.lookup_or_default<int>("id", ATTR_DOMAIN_POINT, 0); bke::SpanAttributeWriter<int> dst = dst_attributes.lookup_or_add_for_write_only_span<int>( "id", ATTR_DOMAIN_POINT); @@ -122,11 +124,11 @@ PointCloud *point_merge_by_distance(const PointCloudComponent &src_points, }); dst.finish(); - attributes.remove_contained("id"); + attribute_ids.remove_contained("id"); } /* Transfer all other attributes. */ - for (const bke::AttributeIDRef &id : attributes) { + for (const bke::AttributeIDRef &id : attribute_ids) { if (!id.should_be_kept()) { continue; } diff --git a/source/blender/geometry/intern/realize_instances.cc b/source/blender/geometry/intern/realize_instances.cc index 25bf00c5783..0544f304283 100644 --- a/source/blender/geometry/intern/realize_instances.cc +++ b/source/blender/geometry/intern/realize_instances.cc @@ -69,6 +69,7 @@ struct PointCloudRealizeInfo { /** Matches the order stored in #AllPointCloudsInfo.attributes. */ Array<std::optional<GVArraySpan>> attributes; /** Id attribute on the point cloud. If there are no ids, this #Span is empty. */ + Span<float3> positions; Span<int> stored_ids; }; @@ -665,6 +666,9 @@ static AllPointCloudsInfo preprocess_pointclouds(const GeometrySet &geometry_set pointcloud_info.stored_ids = ids_attribute.varray.get_internal_span().typed<int>(); } } + const VArray<float3> position_attribute = attributes.lookup_or_default<float3>( + "position", ATTR_DOMAIN_POINT, float3(0)); + pointcloud_info.positions = position_attribute.get_internal_span(); } return info; } @@ -673,18 +677,16 @@ static void execute_realize_pointcloud_task( const RealizeInstancesOptions &options, const RealizePointCloudTask &task, const OrderedAttributes &ordered_attributes, - PointCloud &dst_pointcloud, MutableSpan<GSpanAttributeWriter> dst_attribute_writers, - MutableSpan<int> all_dst_ids) + MutableSpan<int> all_dst_ids, + MutableSpan<float3> all_dst_positions) { const PointCloudRealizeInfo &pointcloud_info = *task.pointcloud_info; const PointCloud &pointcloud = *pointcloud_info.pointcloud; - const Span<float3> src_positions{(float3 *)pointcloud.co, pointcloud.totpoint}; const IndexRange point_slice{task.start_index, pointcloud.totpoint}; - MutableSpan<float3> dst_positions{(float3 *)dst_pointcloud.co + task.start_index, - pointcloud.totpoint}; - copy_transformed_positions(src_positions, task.transform, dst_positions); + copy_transformed_positions( + pointcloud_info.positions, task.transform, all_dst_positions.slice(point_slice)); /* Create point ids. */ if (!all_dst_ids.is_empty()) { @@ -726,6 +728,9 @@ static void execute_realize_pointcloud_tasks(const RealizeInstancesOptions &opti bke::MutableAttributeAccessor dst_attributes = bke::pointcloud_attributes_for_write( *dst_pointcloud); + SpanAttributeWriter<float3> positions = dst_attributes.lookup_or_add_for_write_only_span<float3>( + "position", ATTR_DOMAIN_POINT); + /* Prepare id attribute. */ SpanAttributeWriter<int> point_ids; if (all_pointclouds_info.create_id_attribute) { @@ -748,9 +753,9 @@ static void execute_realize_pointcloud_tasks(const RealizeInstancesOptions &opti execute_realize_pointcloud_task(options, task, ordered_attributes, - *dst_pointcloud, dst_attribute_writers, - point_ids.span); + point_ids.span, + positions.span); } }); @@ -758,6 +763,7 @@ static void execute_realize_pointcloud_tasks(const RealizeInstancesOptions &opti for (GSpanAttributeWriter &dst_attribute : dst_attribute_writers) { dst_attribute.finish(); } + positions.finish(); if (point_ids) { point_ids.finish(); } @@ -1238,6 +1244,11 @@ static void execute_realize_curve_tasks(const RealizeInstancesOptions &options, dst_component.replace(dst_curves_id); bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write(); + /* Copy settings from the first input geometry set with curves. */ + const RealizeCurveTask &first_task = tasks.first(); + const Curves &first_curves_id = *first_task.curve_info->curves; + bke::curves_copy_parameters(first_curves_id, *dst_curves_id); + /* Prepare id attribute. */ SpanAttributeWriter<int> point_ids; if (all_curves_info.create_id_attribute) { @@ -1295,6 +1306,15 @@ static void execute_realize_curve_tasks(const RealizeInstancesOptions &options, } }); + /* Type counts have to be updated eagerly. */ + dst_curves.runtime->type_counts.fill(0); + for (const RealizeCurveTask &task : tasks) { + for (const int i : IndexRange(CURVE_TYPES_NUM)) { + dst_curves.runtime->type_counts[i] += + task.curve_info->curves->geometry.runtime->type_counts[i]; + } + } + /* Tag modified attributes. */ for (GSpanAttributeWriter &dst_attribute : dst_attribute_writers) { dst_attribute.finish(); diff --git a/source/blender/geometry/intern/resample_curves.cc b/source/blender/geometry/intern/resample_curves.cc index c9b8a032ce6..2c3a6c6e0cf 100644 --- a/source/blender/geometry/intern/resample_curves.cc +++ b/source/blender/geometry/intern/resample_curves.cc @@ -162,8 +162,6 @@ static void gather_point_attributes_to_interpolate(const CurveComponent &src_com result.src_no_interpolation, result.dst_no_interpolation, result.dst_attributes); - - dst_curves.update_customdata_pointers(); } static Curves *resample_to_uniform(const CurveComponent &src_component, diff --git a/source/blender/geometry/intern/set_curve_type.cc b/source/blender/geometry/intern/set_curve_type.cc index 08888a74973..40ee2488a4b 100644 --- a/source/blender/geometry/intern/set_curve_type.cc +++ b/source/blender/geometry/intern/set_curve_type.cc @@ -1,9 +1,9 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "BKE_attribute.hh" #include "BKE_attribute_math.hh" #include "BKE_curves.hh" #include "BKE_curves_utils.hh" -#include "BKE_geometry_set.hh" #include "BLI_task.hh" @@ -322,39 +322,16 @@ static void retrieve_generic_point_attributes(const bke::AttributeAccessor &src_ }); } -static Curves *create_result_curves(const bke::CurvesGeometry &src_curves, - const IndexMask selection, - const CurveType dst_type) -{ - Curves *dst_curves_id = bke::curves_new_nomain(0, src_curves.curves_num()); - bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry); - CurveComponent dst_component; - dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable); - /* Directly copy curve attributes, since they stay the same (except for curve types). */ - CustomData_copy(&src_curves.curve_data, - &dst_curves.curve_data, - CD_MASK_ALL, - CD_DUPLICATE, - src_curves.curves_num()); - - dst_curves.fill_curve_types(selection, dst_type); - - return dst_curves_id; -} - -static Curves *convert_curves_to_bezier(const CurveComponent &src_component, - const bke::CurvesGeometry &src_curves, - const IndexMask selection) +static bke::CurvesGeometry convert_curves_to_bezier(const bke::CurvesGeometry &src_curves, + const IndexMask selection) { const VArray<int8_t> src_knot_modes = src_curves.nurbs_knots_modes(); const VArray<int8_t> src_types = src_curves.curve_types(); const VArray<bool> src_cyclic = src_curves.cyclic(); const Span<float3> src_positions = src_curves.positions(); - Curves *dst_curves_id = create_result_curves(src_curves, selection, CURVE_TYPE_BEZIER); - bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry); - CurveComponent dst_component; - dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable); + bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves); + dst_curves.fill_curve_types(selection, CURVE_TYPE_BEZIER); MutableSpan<int> dst_offsets = dst_curves.offsets_for_write(); retrieve_curve_sizes(src_curves, dst_curves.offsets_for_write()); @@ -367,8 +344,8 @@ static Curves *convert_curves_to_bezier(const CurveComponent &src_component, bke::curves::accumulate_counts_to_offsets(dst_offsets); dst_curves.resize(dst_offsets.last(), dst_curves.curves_num()); - const bke::AttributeAccessor src_attributes = *src_component.attributes(); - bke::MutableAttributeAccessor dst_attributes = *dst_component.attributes_for_write(); + const bke::AttributeAccessor src_attributes = src_curves.attributes(); + bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write(); GenericAttributes attributes; retrieve_generic_point_attributes(src_attributes, dst_attributes, attributes); @@ -501,21 +478,18 @@ static Curves *convert_curves_to_bezier(const CurveComponent &src_component, attribute.finish(); } - return dst_curves_id; + return dst_curves; } -static Curves *convert_curves_to_nurbs(const CurveComponent &src_component, - const bke::CurvesGeometry &src_curves, - const IndexMask selection) +static bke::CurvesGeometry convert_curves_to_nurbs(const bke::CurvesGeometry &src_curves, + const IndexMask selection) { const VArray<int8_t> src_types = src_curves.curve_types(); const VArray<bool> src_cyclic = src_curves.cyclic(); const Span<float3> src_positions = src_curves.positions(); - Curves *dst_curves_id = create_result_curves(src_curves, selection, CURVE_TYPE_NURBS); - bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry); - CurveComponent dst_component; - dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable); + bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves); + dst_curves.fill_curve_types(selection, CURVE_TYPE_NURBS); MutableSpan<int> dst_offsets = dst_curves.offsets_for_write(); retrieve_curve_sizes(src_curves, dst_curves.offsets_for_write()); @@ -527,8 +501,8 @@ static Curves *convert_curves_to_nurbs(const CurveComponent &src_component, bke::curves::accumulate_counts_to_offsets(dst_offsets); dst_curves.resize(dst_offsets.last(), dst_curves.curves_num()); - const bke::AttributeAccessor src_attributes = *src_component.attributes(); - bke::MutableAttributeAccessor dst_attributes = *dst_component.attributes_for_write(); + const bke::AttributeAccessor src_attributes = src_curves.attributes(); + bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write(); GenericAttributes attributes; retrieve_generic_point_attributes(src_attributes, dst_attributes, attributes); @@ -669,7 +643,7 @@ static Curves *convert_curves_to_nurbs(const CurveComponent &src_component, attribute.finish(); } - return dst_curves_id; + return dst_curves; } static bke::CurvesGeometry convert_curves_trivial(const bke::CurvesGeometry &src_curves, @@ -682,33 +656,31 @@ static bke::CurvesGeometry convert_curves_trivial(const bke::CurvesGeometry &src return dst_curves; } -Curves *convert_curves(const CurveComponent &src_component, - const bke::CurvesGeometry &src_curves, - const IndexMask selection, - const CurveType dst_type) +bke::CurvesGeometry convert_curves(const bke::CurvesGeometry &src_curves, + const IndexMask selection, + const CurveType dst_type) { switch (dst_type) { case CURVE_TYPE_CATMULL_ROM: case CURVE_TYPE_POLY: - return bke::curves_new_nomain(convert_curves_trivial(src_curves, selection, dst_type)); + return convert_curves_trivial(src_curves, selection, dst_type); case CURVE_TYPE_BEZIER: - return convert_curves_to_bezier(src_component, src_curves, selection); + return convert_curves_to_bezier(src_curves, selection); case CURVE_TYPE_NURBS: - return convert_curves_to_nurbs(src_component, src_curves, selection); + return convert_curves_to_nurbs(src_curves, selection); } BLI_assert_unreachable(); - return nullptr; + return {}; } bool try_curves_conversion_in_place(const IndexMask selection, const CurveType dst_type, - FunctionRef<Curves &()> get_writable_curves_fn) + FunctionRef<bke::CurvesGeometry &()> get_writable_curves_fn) { if (conversion_can_change_point_num(dst_type)) { return false; } - Curves &curves_id = get_writable_curves_fn(); - bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry); + bke::CurvesGeometry &curves = get_writable_curves_fn(); curves.fill_curve_types(selection, dst_type); curves.remove_attributes_based_on_types(); return true; diff --git a/source/blender/geometry/intern/subdivide_curves.cc b/source/blender/geometry/intern/subdivide_curves.cc index 914174235cd..8a6f3cbd5e3 100644 --- a/source/blender/geometry/intern/subdivide_curves.cc +++ b/source/blender/geometry/intern/subdivide_curves.cc @@ -12,26 +12,6 @@ namespace blender::geometry { /** - * \warning Only the curve domain of the input is copied, so the result is invalid! - */ -static Curves *create_result_curves(const bke::CurvesGeometry &src_curves) -{ - Curves *dst_curves_id = bke::curves_new_nomain(0, src_curves.curves_num()); - bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry); - CurveComponent dst_component; - dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable); - /* Directly copy curve attributes, since they stay the same. */ - CustomData_copy(&src_curves.curve_data, - &dst_curves.curve_data, - CD_MASK_ALL, - CD_DUPLICATE, - src_curves.curves_num()); - dst_curves.runtime->type_counts = src_curves.runtime->type_counts; - - return dst_curves_id; -} - -/** * Return a range used to retrieve values from an array of values stored per point, but with an * extra element at the end of each curve. This is useful for offsets within curves, where it is * convenient to store the first 0 and have the last offset be the total result curve size. @@ -76,40 +56,6 @@ static void calculate_result_offsets(const bke::CurvesGeometry &src_curves, bke::curves::accumulate_counts_to_offsets(dst_curve_offsets); } -struct AttributeTransferData { - /* Expect that if an attribute exists, it is stored as a contiguous array internally anyway. */ - GVArraySpan src; - bke::GSpanAttributeWriter dst; -}; - -static Vector<AttributeTransferData> retrieve_point_attributes( - const bke::AttributeAccessor &src_attributes, - bke::MutableAttributeAccessor &dst_attributes, - const Set<std::string> &skip = {}) -{ - Vector<AttributeTransferData> attributes; - src_attributes.for_all( - [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) { - if (meta_data.domain != ATTR_DOMAIN_POINT) { - /* Curve domain attributes are all copied directly to the result in one step. */ - return true; - } - if (id.is_named() && skip.contains(id.name())) { - return true; - } - - GVArray src = src_attributes.lookup(id, ATTR_DOMAIN_POINT); - BLI_assert(src); - bke::GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span( - id, ATTR_DOMAIN_POINT, meta_data.data_type); - BLI_assert(dst); - attributes.append({std::move(src), std::move(dst)}); - - return true; - }); - return attributes; -} - template<typename T> static inline void linear_interpolation(const T &a, const T &b, MutableSpan<T> dst) { @@ -342,10 +288,9 @@ static void subdivide_bezier_positions(const Span<float3> src_positions, cyclic, dst_types_l, dst_types_r, dst_positions, dst_handles_l, dst_handles_r); } -Curves *subdivide_curves(const CurveComponent &src_component, - const bke::CurvesGeometry &src_curves, - const IndexMask selection, - const VArray<int> &cuts) +bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves, + const IndexMask selection, + const VArray<int> &cuts) { const Vector<IndexRange> unselected_ranges = selection.extract_ranges_invert( src_curves.curves_range()); @@ -353,10 +298,7 @@ Curves *subdivide_curves(const CurveComponent &src_component, /* Cyclic is accessed a lot, it's probably worth it to make sure it's a span. */ const VArraySpan<bool> cyclic{src_curves.cyclic()}; - Curves *dst_curves_id = create_result_curves(src_curves); - bke::CurvesGeometry &dst_curves = bke::CurvesGeometry::wrap(dst_curves_id->geometry); - CurveComponent dst_component; - dst_component.replace(dst_curves_id, GeometryOwnershipType::Editable); + bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves); /* For each point, this contains the point offset in the corresponding result curve, * starting at zero. For example for two curves with four points each, the values might @@ -385,11 +327,12 @@ Curves *subdivide_curves(const CurveComponent &src_component, dst_curves.resize(dst_curves.offsets().last(), dst_curves.curves_num()); - const bke::AttributeAccessor src_attributes = *src_component.attributes(); - bke::MutableAttributeAccessor dst_attributes = *dst_component.attributes_for_write(); + const bke::AttributeAccessor src_attributes = src_curves.attributes(); + bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write(); auto subdivide_catmull_rom = [&](IndexMask selection) { - for (auto &attribute : retrieve_point_attributes(src_attributes, dst_attributes)) { + for (auto &attribute : bke::retrieve_attributes_for_transfer( + src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) { subdivide_attribute_catmull_rom(src_curves, dst_curves, selection, @@ -402,7 +345,8 @@ Curves *subdivide_curves(const CurveComponent &src_component, }; auto subdivide_poly = [&](IndexMask selection) { - for (auto &attribute : retrieve_point_attributes(src_attributes, dst_attributes)) { + for (auto &attribute : bke::retrieve_attributes_for_transfer( + src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) { subdivide_attribute_linear( src_curves, dst_curves, selection, point_offsets, attribute.src, attribute.dst.span); attribute.dst.finish(); @@ -443,13 +387,14 @@ Curves *subdivide_curves(const CurveComponent &src_component, } }); - for (auto &attribute : retrieve_point_attributes(src_attributes, - dst_attributes, - {"position", - "handle_type_left", - "handle_type_right", - "handle_right", - "handle_left"})) { + for (auto &attribute : bke::retrieve_attributes_for_transfer(src_attributes, + dst_attributes, + ATTR_DOMAIN_MASK_POINT, + {"position", + "handle_type_left", + "handle_type_right", + "handle_right", + "handle_left"})) { subdivide_attribute_linear( src_curves, dst_curves, selection, point_offsets, attribute.src, attribute.dst.span); attribute.dst.finish(); @@ -469,14 +414,15 @@ Curves *subdivide_curves(const CurveComponent &src_component, subdivide_nurbs); if (!unselected_ranges.is_empty()) { - for (auto &attribute : retrieve_point_attributes(src_attributes, dst_attributes)) { + for (auto &attribute : bke::retrieve_attributes_for_transfer( + src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) { bke::curves::copy_point_data( src_curves, dst_curves, unselected_ranges, attribute.src, attribute.dst.span); attribute.dst.finish(); } } - return dst_curves_id; + return dst_curves; } } // namespace blender::geometry diff --git a/source/blender/geometry/intern/uv_parametrizer.c b/source/blender/geometry/intern/uv_parametrizer.c index c75a302f8dc..38924c718c3 100644 --- a/source/blender/geometry/intern/uv_parametrizer.c +++ b/source/blender/geometry/intern/uv_parametrizer.c @@ -148,7 +148,8 @@ typedef struct PChart { } lscm; struct PChartPack { float rescale, area; - float size[2] /* , trans[2] */; + float size[2]; + float origin[2]; } pack; } u; @@ -3306,8 +3307,10 @@ static float p_face_stretch(PFace *f) area = p_face_uv_area_signed(f); - if (area <= 0.0f) { /* flipped face -> infinite stretch */ - return 1e10f; + if (area <= 0.0f) { + /* When a face is flipped, provide a large penalty. + * Add on a slight gradient to unflip the face, see also: T99781. */ + return 1e8f * (1.0f + p_edge_uv_length(e1) + p_edge_uv_length(e2) + p_edge_uv_length(e3)); } w = 1.0f / (2.0f * area); @@ -4243,7 +4246,10 @@ void GEO_uv_parametrizer_pack(ParamHandle *handle, } } -void GEO_uv_parametrizer_average(ParamHandle *phandle, bool ignore_pinned) +void GEO_uv_parametrizer_average(ParamHandle *phandle, + bool ignore_pinned, + bool scale_uv, + bool shear) { PChart *chart; int i; @@ -4256,17 +4262,93 @@ void GEO_uv_parametrizer_average(ParamHandle *phandle, bool ignore_pinned) } for (i = 0; i < phandle->ncharts; i++) { - PFace *f; chart = phandle->charts[i]; if (ignore_pinned && (chart->flag & PCHART_HAS_PINS)) { continue; } + p_chart_uv_bbox(chart, minv, maxv); + mid_v2_v2v2(chart->u.pack.origin, minv, maxv); + + if (scale_uv || shear) { + /* It's possible that for some "bad" inputs, the following iteration will converge slowly or + * perhaps even diverge. Rather than infinite loop, we only iterate a maximum of `max_iter` + * times. (Also useful when making changes to the calculation.) */ + int max_iter = 10; + for (int j = 0; j < max_iter; j++) { + /* An island could contain millions of polygons. When summing many small values, we need to + * use double precision in the accumulator to maintain accuracy. Note that the individual + * calculations only need to be at single precision.*/ + double scale_cou = 0; + double scale_cov = 0; + double scale_cross = 0; + double weight_sum = 0; + for (PFace *f = chart->faces; f; f = f->nextlink) { + float m[2][2], s[2][2]; + PVert *va = f->edge->vert; + PVert *vb = f->edge->next->vert; + PVert *vc = f->edge->next->next->vert; + s[0][0] = va->uv[0] - vc->uv[0]; + s[0][1] = va->uv[1] - vc->uv[1]; + s[1][0] = vb->uv[0] - vc->uv[0]; + s[1][1] = vb->uv[1] - vc->uv[1]; + /* Find the "U" axis and "V" axis in triangle co-ordinates. Normally this would require + * SVD, but in 2D we can use a cheaper matrix inversion instead.*/ + if (!invert_m2_m2(m, s)) { + continue; + } + float cou[3], cov[3]; /* i.e. Texture "U" and texture "V" in 3D co-ordinates.*/ + for (int k = 0; k < 3; k++) { + cou[k] = m[0][0] * (va->co[k] - vc->co[k]) + m[0][1] * (vb->co[k] - vc->co[k]); + cov[k] = m[1][0] * (va->co[k] - vc->co[k]) + m[1][1] * (vb->co[k] - vc->co[k]); + } + const float weight = p_face_area(f); + scale_cou += len_v3(cou) * weight; + scale_cov += len_v3(cov) * weight; + if (shear) { + normalize_v3(cov); + normalize_v3(cou); + + /* Why is scale_cross called `cross` when we call `dot`? The next line calculates: + * `scale_cross += length(cross(cross(cou, face_normal), cov))` + * By construction, both `cou` and `cov` are orthogonal to the face normal. + * By definition, the normal vector has unit length. */ + scale_cross += dot_v3v3(cou, cov) * weight; + } + weight_sum += weight; + } + if (scale_cou * scale_cov < 1e-10f) { + break; + } + const float scale_factor_u = scale_uv ? sqrtf(scale_cou / scale_cov) : 1.0f; + + /* Compute correction transform. */ + float t[2][2]; + t[0][0] = scale_factor_u; + t[1][0] = clamp_f((float)(scale_cross / weight_sum), -0.5f, 0.5f); + t[0][1] = 0; + t[1][1] = 1.0f / scale_factor_u; + + /* Apply the correction. */ + p_chart_uv_transform(chart, t); + + /* How far from the identity transform are we? [[1,0],[0,1]] */ + const float err = fabsf(t[0][0] - 1.0f) + fabsf(t[1][0]) + fabsf(t[0][1]) + + fabsf(t[1][1] - 1.0f); + + const float tolerance = 1e-6f; /* Trade accuracy for performance. */ + if (err < tolerance) { + /* Too slow? Use Richardson Extrapolation to accelerate the convergence.*/ + break; + } + } + } + chart->u.pack.area = 0.0f; /* 3d area */ chart->u.pack.rescale = 0.0f; /* UV area, abusing rescale for tmp storage, oh well :/ */ - for (f = chart->faces; f; f = f->nextlink) { + for (PFace *f = chart->faces; f; f = f->nextlink) { chart->u.pack.area += p_face_area(f); chart->u.pack.rescale += fabsf(p_face_uv_area_signed(f)); } @@ -4292,18 +4374,16 @@ void GEO_uv_parametrizer_average(ParamHandle *phandle, bool ignore_pinned) if (chart->u.pack.area != 0.0f && chart->u.pack.rescale != 0.0f) { fac = chart->u.pack.area / chart->u.pack.rescale; - /* Get the island center */ - p_chart_uv_bbox(chart, minv, maxv); - trans[0] = (minv[0] + maxv[0]) / -2.0f; - trans[1] = (minv[1] + maxv[1]) / -2.0f; - - /* Move center to 0,0 */ - p_chart_uv_translate(chart, trans); + /* Average scale. */ p_chart_uv_scale(chart, sqrtf(fac / tot_fac)); - /* Move to original center */ - trans[0] = -trans[0]; - trans[1] = -trans[1]; + /* Get the current island center. */ + p_chart_uv_bbox(chart, minv, maxv); + + /* Move to original center. */ + mid_v2_v2v2(trans, minv, maxv); + negate_v2(trans); + add_v2_v2(trans, chart->u.pack.origin); p_chart_uv_translate(chart, trans); } } |