diff options
author | Lukas Tönne <lukas.toenne@gmail.com> | 2022-07-26 00:43:48 +0300 |
---|---|---|
committer | Lukas Tönne <lukas.toenne@gmail.com> | 2022-07-26 00:43:48 +0300 |
commit | f081e76037432cb926be45e980eac201d337032c (patch) | |
tree | dd1225521c12ea21c0b0c3d51ec1ae3f8730755f /source/blender/geometry | |
parent | fa2084ae58a77b1201289b6bedac427f73c762d1 (diff) | |
parent | 462f99bf38648a08226b1fba423315aec2bc577b (diff) |
Merge branch 'master' into geometry-nodes-iterative-cachegeometry-nodes-rigid-body-integration
Diffstat (limited to 'source/blender/geometry')
20 files changed, 2323 insertions, 364 deletions
diff --git a/source/blender/geometry/CMakeLists.txt b/source/blender/geometry/CMakeLists.txt index f0fb5c5c9af..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 @@ -24,9 +25,12 @@ set(SRC intern/realize_instances.cc intern/resample_curves.cc intern/reverse_uv_sampler.cc + intern/set_curve_type.cc + intern/subdivide_curves.cc 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 @@ -35,6 +39,8 @@ set(SRC GEO_realize_instances.hh GEO_resample_curves.hh GEO_reverse_uv_sampler.hh + GEO_set_curve_type.hh + GEO_subdivide_curves.hh GEO_uv_parametrizer.h ) diff --git a/source/blender/geometry/GEO_add_curves_on_mesh.hh b/source/blender/geometry/GEO_add_curves_on_mesh.hh index cf60a8e8ace..2d2ba89a18f 100644 --- a/source/blender/geometry/GEO_add_curves_on_mesh.hh +++ b/source/blender/geometry/GEO_add_curves_on_mesh.hh @@ -13,13 +13,13 @@ #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" +#include "GEO_reverse_uv_sampler.hh" + namespace blender::geometry { struct AddCurvesOnMeshInputs { - /** Information about the root points where new curves should be generated. */ - Span<float3> root_positions_cu; - Span<float3> bary_coords; - Span<int> looptri_indices; + /** UV Coordinates at which the new curves should be added. */ + Span<float2> uvs; /** Determines shape of new curves. */ bool interpolate_length = false; @@ -30,25 +30,32 @@ struct AddCurvesOnMeshInputs { /** Information about the surface that the new curves are attached to. */ const Mesh *surface = nullptr; - BVHTreeFromMesh *surface_bvh = nullptr; - Span<MLoopTri> surface_looptris; - Span<float2> surface_uv_map; + const ReverseUVSampler *reverse_uv_sampler = nullptr; Span<float3> corner_normals_su; - /** Transformation matrices. */ - float4x4 curves_to_surface_mat; - float4x4 surface_to_curves_normal_mat; + bke::CurvesSurfaceTransforms *transforms = nullptr; /** * KD-Tree that contains the root points of existing curves. This is only necessary when * interpolation is used. */ KDTree_3d *old_roots_kdtree = nullptr; + + bool r_uv_error = false; +}; + +struct AddCurvesOnMeshOutputs { + bool uv_error = false; }; /** * Generate new curves on a mesh surface with the given inputs. Existing curves stay intact. */ -void add_curves_on_mesh(bke::CurvesGeometry &curves, const AddCurvesOnMeshInputs &inputs); +AddCurvesOnMeshOutputs add_curves_on_mesh(bke::CurvesGeometry &curves, + const AddCurvesOnMeshInputs &inputs); + +float3 compute_surface_point_normal(const MLoopTri &looptri, + const float3 &bary_coord, + const Span<float3> corner_normals); } // namespace blender::geometry 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_point_merge_by_distance.hh b/source/blender/geometry/GEO_point_merge_by_distance.hh index 1e977cf3bdc..92d871c62ab 100644 --- a/source/blender/geometry/GEO_point_merge_by_distance.hh +++ b/source/blender/geometry/GEO_point_merge_by_distance.hh @@ -17,7 +17,7 @@ namespace blender::geometry { * Merge selected points into other selected points within the \a merge_distance. The merged * indices favor speed over accuracy, since the results will depend on the order of the points. */ -PointCloud *point_merge_by_distance(const PointCloudComponent &src_points, +PointCloud *point_merge_by_distance(const PointCloud &src_points, const float merge_distance, const IndexMask selection); diff --git a/source/blender/geometry/GEO_reverse_uv_sampler.hh b/source/blender/geometry/GEO_reverse_uv_sampler.hh index d392b65eaf4..ee91e0b0731 100644 --- a/source/blender/geometry/GEO_reverse_uv_sampler.hh +++ b/source/blender/geometry/GEO_reverse_uv_sampler.hh @@ -5,6 +5,7 @@ #include <optional> #include "BLI_math_vector.hh" +#include "BLI_multi_value_map.hh" #include "BLI_span.hh" #include "DNA_meshdata_types.h" @@ -20,6 +21,8 @@ class ReverseUVSampler { private: const Span<float2> uv_map_; const Span<MLoopTri> looptris_; + int resolution_; + MultiValueMap<int2, int> looptris_by_cell_; public: ReverseUVSampler(const Span<float2> uv_map, const Span<MLoopTri> looptris); @@ -37,6 +40,7 @@ class ReverseUVSampler { }; Result sample(const float2 &query_uv) const; + void sample_many(Span<float2> query_uvs, MutableSpan<Result> r_results) const; }; } // namespace blender::geometry diff --git a/source/blender/geometry/GEO_set_curve_type.hh b/source/blender/geometry/GEO_set_curve_type.hh new file mode 100644 index 00000000000..f38e63b1fc8 --- /dev/null +++ b/source/blender/geometry/GEO_set_curve_type.hh @@ -0,0 +1,32 @@ +/* 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 { + +/** + * Try the conversion to the #dst_type-- avoiding the majority of the work done in + * #convert_curves by modifying an existing object in place rather than creating a new one. + * + * \note This function is necessary because attributes do not have proper support for CoW. + * + * \param get_writable_curves_fn: Should return the write-able curves to change directly if + * possible. This is a function in order to avoid the cost of retrieval when unnecessary. + */ +bool try_curves_conversion_in_place(IndexMask selection, + CurveType dst_type, + FunctionRef<bke::CurvesGeometry &()> get_writable_curves_fn); + +/** + * Change the types of the selected curves, potentially changing the total point count. + */ +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 new file mode 100644 index 00000000000..ba55118baa4 --- /dev/null +++ b/source/blender/geometry/GEO_subdivide_curves.hh @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_function_ref.hh" +#include "BLI_index_mask.hh" +#include "BLI_virtual_array.hh" + +#include "BKE_curves.hh" + +namespace blender::geometry { + +/** + * Add more points along each segment, with the amount of points to add in each segment described + * by the #cuts input. The new points are equidistant in parameter space, but not in the actual + * distances. + * + * \param selection: A selection of curves to consider when subdividing. + */ +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..7184d774a22 100644 --- a/source/blender/geometry/intern/add_curves_on_mesh.cc +++ b/source/blender/geometry/intern/add_curves_on_mesh.cc @@ -1,9 +1,12 @@ /* 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" +#include "GEO_reverse_uv_sampler.hh" /** * The code below uses a suffix naming convention to indicate the coordinate space: @@ -25,9 +28,9 @@ struct NeighborCurve { static constexpr int max_neighbors = 5; using NeighborCurves = Vector<NeighborCurve, max_neighbors>; -static float3 compute_surface_point_normal(const MLoopTri &looptri, - const float3 &bary_coord, - const Span<float3> corner_normals) +float3 compute_surface_point_normal(const MLoopTri &looptri, + const float3 &bary_coord, + const Span<float3> corner_normals) { const int l0 = looptri.tri[0]; const int l1 = looptri.tri[1]; @@ -134,27 +137,26 @@ static void interpolate_position_with_interpolation(CurvesGeometry &curves, const int old_curves_num, const Span<float> new_lengths_cu, const Span<float3> new_normals_su, - const float4x4 &surface_to_curves_normal_mat, - const float4x4 &curves_to_surface_mat, - const BVHTreeFromMesh &surface_bvh, - const Span<MLoopTri> surface_looptris, - const Mesh &surface, + const bke::CurvesSurfaceTransforms &transforms, + const ReverseUVSampler &reverse_uv_sampler, const Span<float3> corner_normals_su) { MutableSpan<float3> positions_cu = curves.positions_for_write(); const int added_curves_num = root_positions_cu.size(); + const Span<float2> uv_coords = curves.surface_uv_coords(); + 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 float3 normal_cu = math::normalize(surface_to_curves_normal_mat * normal_su); + 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(transforms.surface_to_curves_normal * 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. */ @@ -167,26 +169,15 @@ static void interpolate_position_with_interpolation(CurvesGeometry &curves, for (const NeighborCurve &neighbor : neighbors) { const int neighbor_curve_i = neighbor.index; - const float3 &neighbor_first_pos_cu = positions_cu[curves.offsets()[neighbor_curve_i]]; - const float3 neighbor_first_pos_su = curves_to_surface_mat * neighbor_first_pos_cu; - - BVHTreeNearest nearest; - nearest.dist_sq = FLT_MAX; - BLI_bvhtree_find_nearest(surface_bvh.tree, - neighbor_first_pos_su, - &nearest, - surface_bvh.nearest_callback, - const_cast<BVHTreeFromMesh *>(&surface_bvh)); - const int neighbor_looptri_index = nearest.index; - const MLoopTri &neighbor_looptri = surface_looptris[neighbor_looptri_index]; - - const float3 neighbor_bary_coord = - bke::mesh_surface_sample::compute_bary_coord_in_triangle( - surface, neighbor_looptri, nearest.co); + const float2 neighbor_uv = uv_coords[neighbor_curve_i]; + const ReverseUVSampler::Result result = reverse_uv_sampler.sample(neighbor_uv); + if (result.type != ReverseUVSampler::ResultType::Ok) { + continue; + } const float3 neighbor_normal_su = compute_surface_point_normal( - surface_looptris[neighbor_looptri_index], neighbor_bary_coord, corner_normals_su); - const float3 neighbor_normal_cu = math::normalize(surface_to_curves_normal_mat * + *result.looptri, result.bary_weights, corner_normals_su); + const float3 neighbor_normal_cu = math::normalize(transforms.surface_to_curves_normal * neighbor_normal_su); /* The rotation matrix used to transform relative coordinates of the neighbor curve @@ -197,48 +188,87 @@ 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; } } } }); } -void add_curves_on_mesh(CurvesGeometry &curves, const AddCurvesOnMeshInputs &inputs) +AddCurvesOnMeshOutputs add_curves_on_mesh(CurvesGeometry &curves, + const AddCurvesOnMeshInputs &inputs) { + AddCurvesOnMeshOutputs outputs; + const bool use_interpolation = inputs.interpolate_length || inputs.interpolate_point_count || inputs.interpolate_shape; + Vector<float3> root_positions_cu; + Vector<float3> bary_coords; + Vector<const MLoopTri *> looptris; + Vector<float2> used_uvs; + + /* Find faces that the passed in uvs belong to. */ + for (const int i : inputs.uvs.index_range()) { + const float2 &uv = inputs.uvs[i]; + const ReverseUVSampler::Result result = inputs.reverse_uv_sampler->sample(uv); + if (result.type != ReverseUVSampler::ResultType::Ok) { + outputs.uv_error = true; + continue; + } + const MLoopTri &looptri = *result.looptri; + bary_coords.append(result.bary_weights); + looptris.append(&looptri); + const float3 root_position_su = attribute_math::mix3<float3>( + result.bary_weights, + inputs.surface->mvert[inputs.surface->mloop[looptri.tri[0]].v].co, + inputs.surface->mvert[inputs.surface->mloop[looptri.tri[1]].v].co, + inputs.surface->mvert[inputs.surface->mloop[looptri.tri[2]].v].co); + root_positions_cu.append(inputs.transforms->surface_to_curves * root_position_su); + used_uvs.append(uv); + } + Array<NeighborCurves> neighbors_per_curve; if (use_interpolation) { BLI_assert(inputs.old_roots_kdtree != nullptr); - neighbors_per_curve = find_curve_neighbors(inputs.root_positions_cu, *inputs.old_roots_kdtree); + neighbors_per_curve = find_curve_neighbors(root_positions_cu, *inputs.old_roots_kdtree); } - const int added_curves_num = inputs.root_positions_cu.size(); + const int added_curves_num = root_positions_cu.size(); const int old_points_num = curves.points_num(); const int old_curves_num = curves.curves_num(); const int new_curves_num = old_curves_num + added_curves_num; @@ -267,6 +297,10 @@ void add_curves_on_mesh(CurvesGeometry &curves, const AddCurvesOnMeshInputs &inp curves.resize(new_points_num, new_curves_num); MutableSpan<float3> positions_cu = curves.positions_for_write(); + /* Initialize attachment information. */ + MutableSpan<float2> surface_uv_coords = curves.surface_uv_coords_for_write(); + surface_uv_coords.take_back(added_curves_num).copy_from(used_uvs); + /* Determine length of new curves. */ Array<float> new_lengths_cu(added_curves_num); if (inputs.interpolate_length) { @@ -293,25 +327,11 @@ void add_curves_on_mesh(CurvesGeometry &curves, const AddCurvesOnMeshInputs &inp Array<float3> new_normals_su(added_curves_num); threading::parallel_for(IndexRange(added_curves_num), 256, [&](const IndexRange range) { for (const int i : range) { - const int looptri_index = inputs.looptri_indices[i]; - const float3 &bary_coord = inputs.bary_coords[i]; new_normals_su[i] = compute_surface_point_normal( - inputs.surface_looptris[looptri_index], bary_coord, inputs.corner_normals_su); + *looptris[i], bary_coords[i], inputs.corner_normals_su); } }); - /* Propagate attachment information. */ - if (!inputs.surface_uv_map.is_empty()) { - MutableSpan<float2> surface_uv_coords = curves.surface_uv_coords_for_write(); - bke::mesh_surface_sample::sample_corner_attribute( - *inputs.surface, - inputs.looptri_indices, - inputs.bary_coords, - GVArray::ForSpan(inputs.surface_uv_map), - IndexRange(added_curves_num), - surface_uv_coords.take_back(added_curves_num)); - } - /* Update selection arrays when available. */ const VArray<float> points_selection = curves.selection_point_float(); if (points_selection.is_span()) { @@ -327,28 +347,30 @@ void add_curves_on_mesh(CurvesGeometry &curves, const AddCurvesOnMeshInputs &inp /* Initialize position attribute. */ if (inputs.interpolate_shape) { interpolate_position_with_interpolation(curves, - inputs.root_positions_cu, + root_positions_cu, neighbors_per_curve, old_curves_num, new_lengths_cu, new_normals_su, - inputs.surface_to_curves_normal_mat, - inputs.curves_to_surface_mat, - *inputs.surface_bvh, - inputs.surface_looptris, - *inputs.surface, + *inputs.transforms, + *inputs.reverse_uv_sampler, inputs.corner_normals_su); } else { interpolate_position_without_interpolation(curves, old_curves_num, - inputs.root_positions_cu, + root_positions_cu, new_lengths_cu, new_normals_su, - inputs.surface_to_curves_normal_mat); + inputs.transforms->surface_to_curves_normal); } + /* Set curve types. */ + MutableSpan<int8_t> types_span = curves.curve_types_for_write(); + types_span.drop_front(old_curves_num).fill(CURVE_TYPE_CATMULL_ROM); curves.update_curve_types(); + + return outputs; } } // namespace blender::geometry diff --git a/source/blender/geometry/intern/fillet_curves.cc b/source/blender/geometry/intern/fillet_curves.cc new file mode 100644 index 00000000000..1bbbee6edef --- /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[src_points[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_primitive_cuboid.cc b/source/blender/geometry/intern/mesh_primitive_cuboid.cc index 07ac2419ad9..486d8adbf39 100644 --- a/source/blender/geometry/intern/mesh_primitive_cuboid.cc +++ b/source/blender/geometry/intern/mesh_primitive_cuboid.cc @@ -7,7 +7,6 @@ #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" -#include "BKE_attribute_access.hh" #include "BKE_geometry_set.hh" #include "BKE_mesh.h" @@ -322,11 +321,10 @@ static void calculate_polys(const CuboidConfig &config, static void calculate_uvs(const CuboidConfig &config, Mesh *mesh, const bke::AttributeIDRef &uv_id) { - MeshComponent mesh_component; - mesh_component.replace(mesh, GeometryOwnershipType::Editable); - bke::OutputAttribute_Typed<float2> uv_attribute = - mesh_component.attribute_try_get_for_output_only<float2>(uv_id, ATTR_DOMAIN_CORNER); - MutableSpan<float2> uvs = uv_attribute.as_span(); + bke::MutableAttributeAccessor attributes = bke::mesh_attributes_for_write(*mesh); + bke::SpanAttributeWriter<float2> uv_attribute = + attributes.lookup_or_add_for_write_only_span<float2>(uv_id, ATTR_DOMAIN_CORNER); + MutableSpan<float2> uvs = uv_attribute.span; int loop_index = 0; @@ -394,7 +392,7 @@ static void calculate_uvs(const CuboidConfig &config, Mesh *mesh, const bke::Att } } - uv_attribute.save(); + uv_attribute.finish(); } Mesh *create_cuboid_mesh(const float3 &size, diff --git a/source/blender/geometry/intern/mesh_to_curve_convert.cc b/source/blender/geometry/intern/mesh_to_curve_convert.cc index 38f9da2a60c..fdacb174462 100644 --- a/source/blender/geometry/intern/mesh_to_curve_convert.cc +++ b/source/blender/geometry/intern/mesh_to_curve_convert.cc @@ -8,7 +8,7 @@ #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" -#include "BKE_attribute_access.hh" +#include "BKE_attribute.hh" #include "BKE_attribute_math.hh" #include "BKE_curves.hh" #include "BKE_geometry_set.hh" @@ -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,14 +43,13 @@ 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); - Set<bke::AttributeIDRef> source_attribute_ids = mesh_component.attribute_ids(); + const bke::AttributeAccessor mesh_attributes = bke::mesh_attributes(mesh); + bke::MutableAttributeAccessor curves_attributes = curves.attributes_for_write(); - CurveComponent curves_component; - curves_component.replace(curves_id, GeometryOwnershipType::Editable); + Set<bke::AttributeIDRef> source_attribute_ids = mesh_attributes.all_ids(); for (const bke::AttributeIDRef &attribute_id : source_attribute_ids) { - if (mesh_component.attribute_is_builtin(attribute_id) && - !curves_component.attribute_is_builtin(attribute_id)) { + if (mesh_attributes.is_builtin(attribute_id) && !curves_attributes.is_builtin(attribute_id)) { /* Don't copy attributes that are built-in on meshes but not on curves. */ continue; } @@ -60,8 +58,7 @@ static Curves *create_curve_from_vert_indices(const MeshComponent &mesh_componen continue; } - const GVArray mesh_attribute = mesh_component.attribute_try_get_for_read(attribute_id, - ATTR_DOMAIN_POINT); + const GVArray mesh_attribute = mesh_attributes.lookup(attribute_id, ATTR_DOMAIN_POINT); /* Some attributes might not exist if they were builtin attribute on domains that don't * have any elements, i.e. a face attribute on the output of the line primitive node. */ if (!mesh_attribute) { @@ -71,14 +68,14 @@ static Curves *create_curve_from_vert_indices(const MeshComponent &mesh_componen /* Copy attribute based on the map for this curve. */ attribute_math::convert_to_static_type(mesh_attribute.type(), [&](auto dummy) { using T = decltype(dummy); - bke::OutputAttribute_Typed<T> attribute = - curves_component.attribute_try_get_for_output_only<T>(attribute_id, ATTR_DOMAIN_POINT); - copy_with_map<T>(mesh_attribute.typed<T>(), vert_indices, attribute.as_span()); - attribute.save(); + bke::SpanAttributeWriter<T> attribute = + curves_attributes.lookup_or_add_for_write_only_span<T>(attribute_id, ATTR_DOMAIN_POINT); + copy_with_map<T>(mesh_attribute.typed<T>(), vert_indices, attribute.span); + attribute.finish(); }); } - return curves_id; + return curves; } struct CurveFromEdgesOutput { @@ -222,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 6639ff650d3..42fac849667 100644 --- a/source/blender/geometry/intern/point_merge_by_distance.cc +++ b/source/blender/geometry/intern/point_merge_by_distance.cc @@ -13,13 +13,14 @@ namespace blender::geometry { -PointCloud *point_merge_by_distance(const PointCloudComponent &src_points, +PointCloud *point_merge_by_distance(const PointCloud &src_points, const float merge_distance, 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}; + const bke::AttributeAccessor src_attributes = bke::pointcloud_attributes(src_points); + VArraySpan<float3> positions = src_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. */ @@ -40,8 +41,8 @@ PointCloud *point_merge_by_distance(const PointCloudComponent &src_points, /* Create the new point cloud and add it to a temporary component for the attribute API. */ const int dst_size = src_size - duplicate_count; PointCloud *dst_pointcloud = BKE_pointcloud_new_nomain(dst_size); - PointCloudComponent dst_points; - dst_points.replace(dst_pointcloud, GeometryOwnershipType::Editable); + bke::MutableAttributeAccessor dst_attributes = bke::pointcloud_attributes_for_write( + *dst_pointcloud); /* By default, every point is just "merged" with itself. Then fill in the results of the merge * finding, converting from indices into the selection to indices into the full input point @@ -104,47 +105,44 @@ PointCloud *point_merge_by_distance(const PointCloudComponent &src_points, point_merge_counts[dst_index]++; } - Set<bke::AttributeIDRef> attributes = src_points.attribute_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")) { - VArray<int> src = src_points.attribute_get_for_read<int>("id", ATTR_DOMAIN_POINT, 0); - bke::OutputAttribute_Typed<int> dst = dst_points.attribute_try_get_for_output_only<int>( + 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); - Span<int> src_ids = src.get_internal_span(); - MutableSpan<int> dst_ids = dst.as_span(); threading::parallel_for(IndexRange(dst_size), 1024, [&](IndexRange range) { for (const int i_dst : range) { const IndexRange points(map_offsets[i_dst], map_offsets[i_dst + 1] - map_offsets[i_dst]); - dst_ids[i_dst] = src_ids[points.first()]; + dst.span[i_dst] = src[points.first()]; } }); - dst.save(); - attributes.remove_contained("id"); + dst.finish(); + 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; } - bke::ReadAttributeLookup src_attribute = src_points.attribute_try_get_for_read(id); + bke::GAttributeReader src_attribute = src_attributes.lookup(id); attribute_math::convert_to_static_type(src_attribute.varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { - bke::OutputAttribute_Typed<T> dst_attribute = - dst_points.attribute_try_get_for_output_only<T>(id, ATTR_DOMAIN_POINT); - Span<T> src = src_attribute.varray.get_internal_span().typed<T>(); - MutableSpan<T> dst = dst_attribute.as_span(); + bke::SpanAttributeWriter<T> dst_attribute = + dst_attributes.lookup_or_add_for_write_only_span<T>(id, ATTR_DOMAIN_POINT); + VArraySpan<T> src = src_attribute.varray.typed<T>(); threading::parallel_for(IndexRange(dst_size), 1024, [&](IndexRange range) { for (const int i_dst : range) { /* Create a separate mixer for every point to avoid allocating temporary buffers * in the mixer the size of the result point cloud and to improve memory locality. */ - attribute_math::DefaultMixer<T> mixer{dst.slice(i_dst, 1)}; + attribute_math::DefaultMixer<T> mixer{dst_attribute.span.slice(i_dst, 1)}; const IndexRange points(map_offsets[i_dst], map_offsets[i_dst + 1] - map_offsets[i_dst]); @@ -157,7 +155,7 @@ PointCloud *point_merge_by_distance(const PointCloudComponent &src_points, } }); - dst_attribute.save(); + dst_attribute.finish(); } }); } diff --git a/source/blender/geometry/intern/realize_instances.cc b/source/blender/geometry/intern/realize_instances.cc index bd4099d37f9..4b3b184536b 100644 --- a/source/blender/geometry/intern/realize_instances.cc +++ b/source/blender/geometry/intern/realize_instances.cc @@ -23,12 +23,13 @@ namespace blender::geometry { using blender::bke::AttributeIDRef; +using blender::bke::AttributeKind; +using blender::bke::AttributeMetaData; using blender::bke::custom_data_type_to_cpp_type; using blender::bke::CustomDataAttributes; +using blender::bke::GSpanAttributeWriter; using blender::bke::object_get_evaluated_geometry_set; -using blender::bke::OutputAttribute; -using blender::bke::OutputAttribute_Typed; -using blender::bke::ReadAttributeLookup; +using blender::bke::SpanAttributeWriter; /** * An ordered set of attribute ids. Attributes are ordered to avoid name lookups in many places. @@ -66,8 +67,9 @@ struct AttributeFallbacksArray { struct PointCloudRealizeInfo { const PointCloud *pointcloud = nullptr; /** Matches the order stored in #AllPointCloudsInfo.attributes. */ - Array<std::optional<GVArray_GSpan>> 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; }; @@ -96,7 +98,7 @@ struct MeshRealizeInfo { /** Maps old material indices to new material indices. */ Array<int> material_index_map; /** Matches the order in #AllMeshesInfo.attributes. */ - Array<std::optional<GVArray_GSpan>> attributes; + Array<std::optional<GVArraySpan>> attributes; /** Vertex ids stored on the mesh. If there are no ids, this #Span is empty. */ Span<int> stored_vertex_ids; }; @@ -116,7 +118,7 @@ struct RealizeCurveInfo { /** * Matches the order in #AllCurvesInfo.attributes. */ - Array<std::optional<GVArray_GSpan>> attributes; + Array<std::optional<GVArraySpan>> attributes; /** ID attribute on the curves. If there are no ids, this #Span is empty. */ Span<int> stored_ids; @@ -202,7 +204,8 @@ struct GatherTasks { /* Volumes only have very simple support currently. Only the first found volume is put into the * output. */ - UserCounter<VolumeComponent> first_volume; + UserCounter<const VolumeComponent> first_volume; + UserCounter<const GeometryComponentEditData> first_edit_data; }; /** Current offsets while during the gather operation. */ @@ -283,30 +286,31 @@ static void threaded_fill(const GPointer value, GMutableSpan dst) } static void copy_generic_attributes_to_result( - const Span<std::optional<GVArray_GSpan>> src_attributes, + const Span<std::optional<GVArraySpan>> src_attributes, const AttributeFallbacksArray &attribute_fallbacks, const OrderedAttributes &ordered_attributes, const FunctionRef<IndexRange(eAttrDomain)> &range_fn, - MutableSpan<GMutableSpan> dst_attributes) + MutableSpan<GSpanAttributeWriter> dst_attribute_writers) { - threading::parallel_for(dst_attributes.index_range(), 10, [&](const IndexRange attribute_range) { - for (const int attribute_index : attribute_range) { - const eAttrDomain domain = ordered_attributes.kinds[attribute_index].domain; - const IndexRange element_slice = range_fn(domain); - - GMutableSpan dst_span = dst_attributes[attribute_index].slice(element_slice); - if (src_attributes[attribute_index].has_value()) { - threaded_copy(*src_attributes[attribute_index], dst_span); - } - else { - const CPPType &cpp_type = dst_span.type(); - const void *fallback = attribute_fallbacks.array[attribute_index] == nullptr ? - cpp_type.default_value() : - attribute_fallbacks.array[attribute_index]; - threaded_fill({cpp_type, fallback}, dst_span); - } - } - }); + threading::parallel_for( + dst_attribute_writers.index_range(), 10, [&](const IndexRange attribute_range) { + for (const int attribute_index : attribute_range) { + const eAttrDomain domain = ordered_attributes.kinds[attribute_index].domain; + const IndexRange element_slice = range_fn(domain); + + GMutableSpan dst_span = dst_attribute_writers[attribute_index].span.slice(element_slice); + if (src_attributes[attribute_index].has_value()) { + threaded_copy(*src_attributes[attribute_index], dst_span); + } + else { + const CPPType &cpp_type = dst_span.type(); + const void *fallback = attribute_fallbacks.array[attribute_index] == nullptr ? + cpp_type.default_value() : + attribute_fallbacks.array[attribute_index]; + threaded_fill({cpp_type, fallback}, dst_span); + } + } + }); } static void create_result_ids(const RealizeInstancesOptions &options, @@ -361,7 +365,7 @@ static Vector<std::pair<int, GSpan>> prepare_attribute_fallbacks( const OrderedAttributes &ordered_attributes) { Vector<std::pair<int, GSpan>> attributes_to_override; - const CustomDataAttributes &attributes = instances_component.attributes(); + const CustomDataAttributes &attributes = instances_component.instance_attributes(); attributes.foreach_attribute( [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { const int attribute_index = ordered_attributes.ids.index_of_try(attribute_id); @@ -447,7 +451,7 @@ static void gather_realize_tasks_for_instances(GatherTasksInfo &gather_info, Span<int> stored_instance_ids; if (gather_info.create_id_attribute_on_any_component) { - std::optional<GSpan> ids = instances_component.attributes().get_for_read("id"); + std::optional<GSpan> ids = instances_component.instance_attributes().get_for_read("id"); if (ids.has_value()) { stored_instance_ids = ids->typed<int>(); } @@ -579,7 +583,16 @@ static void gather_realize_tasks_recursive(GatherTasksInfo &gather_info, const VolumeComponent *volume_component = static_cast<const VolumeComponent *>(component); if (!gather_info.r_tasks.first_volume) { volume_component->user_add(); - gather_info.r_tasks.first_volume = const_cast<VolumeComponent *>(volume_component); + gather_info.r_tasks.first_volume = volume_component; + } + break; + } + case GEO_COMPONENT_TYPE_EDIT: { + const GeometryComponentEditData *edit_component = + static_cast<const GeometryComponentEditData *>(component); + if (!gather_info.r_tasks.first_edit_data) { + edit_component->user_add(); + gather_info.r_tasks.first_edit_data = edit_component; } break; } @@ -646,43 +659,44 @@ static AllPointCloudsInfo preprocess_pointclouds(const GeometrySet &geometry_set pointcloud_info.pointcloud = pointcloud; /* Access attributes. */ - PointCloudComponent component; - component.replace(const_cast<PointCloud *>(pointcloud), GeometryOwnershipType::ReadOnly); + bke::AttributeAccessor attributes = bke::pointcloud_attributes(*pointcloud); pointcloud_info.attributes.reinitialize(info.attributes.size()); for (const int attribute_index : info.attributes.index_range()) { const AttributeIDRef &attribute_id = info.attributes.ids[attribute_index]; const eCustomDataType data_type = info.attributes.kinds[attribute_index].data_type; const eAttrDomain domain = info.attributes.kinds[attribute_index].domain; - if (component.attribute_exists(attribute_id)) { - GVArray attribute = component.attribute_get_for_read(attribute_id, domain, data_type); + if (attributes.contains(attribute_id)) { + GVArray attribute = attributes.lookup_or_default(attribute_id, domain, data_type); pointcloud_info.attributes[attribute_index].emplace(std::move(attribute)); } } if (info.create_id_attribute) { - ReadAttributeLookup ids_lookup = component.attribute_try_get_for_read("id"); - if (ids_lookup) { - pointcloud_info.stored_ids = ids_lookup.varray.get_internal_span().typed<int>(); + bke::GAttributeReader ids_attribute = attributes.lookup("id"); + if (ids_attribute) { + 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; } -static void execute_realize_pointcloud_task(const RealizeInstancesOptions &options, - const RealizePointCloudTask &task, - const OrderedAttributes &ordered_attributes, - PointCloud &dst_pointcloud, - MutableSpan<GMutableSpan> dst_attribute_spans, - MutableSpan<int> all_dst_ids) +static void execute_realize_pointcloud_task( + const RealizeInstancesOptions &options, + const RealizePointCloudTask &task, + const OrderedAttributes &ordered_attributes, + MutableSpan<GSpanAttributeWriter> dst_attribute_writers, + 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()) { @@ -699,7 +713,7 @@ static void execute_realize_pointcloud_task(const RealizeInstancesOptions &optio UNUSED_VARS_NDEBUG(domain); return point_slice; }, - dst_attribute_spans); + dst_attribute_writers); } static void execute_realize_pointcloud_tasks(const RealizeInstancesOptions &options, @@ -721,42 +735,47 @@ static void execute_realize_pointcloud_tasks(const RealizeInstancesOptions &opti PointCloudComponent &dst_component = r_realized_geometry.get_component_for_write<PointCloudComponent>(); dst_component.replace(dst_pointcloud); + 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. */ - OutputAttribute_Typed<int> point_ids; - MutableSpan<int> point_ids_span; + SpanAttributeWriter<int> point_ids; if (all_pointclouds_info.create_id_attribute) { - point_ids = dst_component.attribute_try_get_for_output_only<int>("id", ATTR_DOMAIN_POINT); - point_ids_span = point_ids.as_span(); + point_ids = dst_attributes.lookup_or_add_for_write_only_span<int>("id", ATTR_DOMAIN_POINT); } /* Prepare generic output attributes. */ - Vector<OutputAttribute> dst_attributes; - Vector<GMutableSpan> dst_attribute_spans; + Vector<GSpanAttributeWriter> dst_attribute_writers; for (const int attribute_index : ordered_attributes.index_range()) { const AttributeIDRef &attribute_id = ordered_attributes.ids[attribute_index]; const eCustomDataType data_type = ordered_attributes.kinds[attribute_index].data_type; - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - attribute_id, ATTR_DOMAIN_POINT, data_type); - dst_attribute_spans.append(dst_attribute.as_span()); - dst_attributes.append(std::move(dst_attribute)); + dst_attribute_writers.append(dst_attributes.lookup_or_add_for_write_only_span( + attribute_id, ATTR_DOMAIN_POINT, data_type)); } /* Actually execute all tasks. */ threading::parallel_for(tasks.index_range(), 100, [&](const IndexRange task_range) { for (const int task_index : task_range) { const RealizePointCloudTask &task = tasks[task_index]; - execute_realize_pointcloud_task( - options, task, ordered_attributes, *dst_pointcloud, dst_attribute_spans, point_ids_span); + execute_realize_pointcloud_task(options, + task, + ordered_attributes, + dst_attribute_writers, + point_ids.span, + positions.span); } }); - /* Save modified attributes. */ - for (OutputAttribute &dst_attribute : dst_attributes) { - dst_attribute.save(); + /* Tag modified attributes. */ + for (GSpanAttributeWriter &dst_attribute : dst_attribute_writers) { + dst_attribute.finish(); } + positions.finish(); if (point_ids) { - point_ids.save(); + point_ids.finish(); } } @@ -837,22 +856,21 @@ static AllMeshesInfo preprocess_meshes(const GeometrySet &geometry_set, } /* Access attributes. */ - MeshComponent component; - component.replace(const_cast<Mesh *>(mesh), GeometryOwnershipType::ReadOnly); + bke::AttributeAccessor attributes = bke::mesh_attributes(*mesh); mesh_info.attributes.reinitialize(info.attributes.size()); for (const int attribute_index : info.attributes.index_range()) { const AttributeIDRef &attribute_id = info.attributes.ids[attribute_index]; const eCustomDataType data_type = info.attributes.kinds[attribute_index].data_type; const eAttrDomain domain = info.attributes.kinds[attribute_index].domain; - if (component.attribute_exists(attribute_id)) { - GVArray attribute = component.attribute_get_for_read(attribute_id, domain, data_type); + if (attributes.contains(attribute_id)) { + GVArray attribute = attributes.lookup_or_default(attribute_id, domain, data_type); mesh_info.attributes[attribute_index].emplace(std::move(attribute)); } } if (info.create_id_attribute) { - ReadAttributeLookup ids_lookup = component.attribute_try_get_for_read("id"); - if (ids_lookup) { - mesh_info.stored_vertex_ids = ids_lookup.varray.get_internal_span().typed<int>(); + bke::GAttributeReader ids_attribute = attributes.lookup("id"); + if (ids_attribute) { + mesh_info.stored_vertex_ids = ids_attribute.varray.get_internal_span().typed<int>(); } } } @@ -863,7 +881,7 @@ static void execute_realize_mesh_task(const RealizeInstancesOptions &options, const RealizeMeshTask &task, const OrderedAttributes &ordered_attributes, Mesh &dst_mesh, - MutableSpan<GMutableSpan> dst_attribute_spans, + MutableSpan<GSpanAttributeWriter> dst_attribute_writers, MutableSpan<int> all_dst_vertex_ids) { const MeshRealizeInfo &mesh_info = *task.mesh_info; @@ -949,7 +967,7 @@ static void execute_realize_mesh_task(const RealizeInstancesOptions &options, return IndexRange(); } }, - dst_attribute_spans); + dst_attribute_writers); } static void execute_realize_mesh_tasks(const RealizeInstancesOptions &options, @@ -973,6 +991,7 @@ static void execute_realize_mesh_tasks(const RealizeInstancesOptions &options, Mesh *dst_mesh = BKE_mesh_new_nomain(tot_vertices, tot_edges, 0, tot_loops, tot_poly); MeshComponent &dst_component = r_realized_geometry.get_component_for_write<MeshComponent>(); dst_component.replace(dst_mesh); + bke::MutableAttributeAccessor dst_attributes = bke::mesh_attributes_for_write(*dst_mesh); /* Copy settings from the first input geometry set with a mesh. */ const RealizeMeshTask &first_task = tasks.first(); @@ -986,24 +1005,19 @@ static void execute_realize_mesh_tasks(const RealizeInstancesOptions &options, } /* Prepare id attribute. */ - OutputAttribute_Typed<int> vertex_ids; - MutableSpan<int> vertex_ids_span; + SpanAttributeWriter<int> vertex_ids; if (all_meshes_info.create_id_attribute) { - vertex_ids = dst_component.attribute_try_get_for_output_only<int>("id", ATTR_DOMAIN_POINT); - vertex_ids_span = vertex_ids.as_span(); + vertex_ids = dst_attributes.lookup_or_add_for_write_only_span<int>("id", ATTR_DOMAIN_POINT); } /* Prepare generic output attributes. */ - Vector<OutputAttribute> dst_attributes; - Vector<GMutableSpan> dst_attribute_spans; + Vector<GSpanAttributeWriter> dst_attribute_writers; for (const int attribute_index : ordered_attributes.index_range()) { const AttributeIDRef &attribute_id = ordered_attributes.ids[attribute_index]; const eAttrDomain domain = ordered_attributes.kinds[attribute_index].domain; const eCustomDataType data_type = ordered_attributes.kinds[attribute_index].data_type; - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - attribute_id, domain, data_type); - dst_attribute_spans.append(dst_attribute.as_span()); - dst_attributes.append(std::move(dst_attribute)); + dst_attribute_writers.append( + dst_attributes.lookup_or_add_for_write_only_span(attribute_id, domain, data_type)); } /* Actually execute all tasks. */ @@ -1011,16 +1025,16 @@ static void execute_realize_mesh_tasks(const RealizeInstancesOptions &options, for (const int task_index : task_range) { const RealizeMeshTask &task = tasks[task_index]; execute_realize_mesh_task( - options, task, ordered_attributes, *dst_mesh, dst_attribute_spans, vertex_ids_span); + options, task, ordered_attributes, *dst_mesh, dst_attribute_writers, vertex_ids.span); } }); - /* Save modified attributes. */ - for (OutputAttribute &dst_attribute : dst_attributes) { - dst_attribute.save(); + /* Tag modified attributes. */ + for (GSpanAttributeWriter &dst_attribute : dst_attribute_writers) { + dst_attribute.finish(); } if (vertex_ids) { - vertex_ids.save(); + vertex_ids.finish(); } } @@ -1088,49 +1102,43 @@ static AllCurvesInfo preprocess_curves(const GeometrySet &geometry_set, curve_info.curves = curves_id; /* Access attributes. */ - CurveComponent component; - component.replace(const_cast<Curves *>(curves_id), GeometryOwnershipType::ReadOnly); + bke::AttributeAccessor attributes = curves.attributes(); curve_info.attributes.reinitialize(info.attributes.size()); for (const int attribute_index : info.attributes.index_range()) { const eAttrDomain domain = info.attributes.kinds[attribute_index].domain; const AttributeIDRef &attribute_id = info.attributes.ids[attribute_index]; const eCustomDataType data_type = info.attributes.kinds[attribute_index].data_type; - if (component.attribute_exists(attribute_id)) { - GVArray attribute = component.attribute_get_for_read(attribute_id, domain, data_type); + if (attributes.contains(attribute_id)) { + GVArray attribute = attributes.lookup_or_default(attribute_id, domain, data_type); curve_info.attributes[attribute_index].emplace(std::move(attribute)); } } if (info.create_id_attribute) { - ReadAttributeLookup ids_lookup = component.attribute_try_get_for_read("id"); - if (ids_lookup) { - curve_info.stored_ids = ids_lookup.varray.get_internal_span().typed<int>(); + bke::GAttributeReader id_attribute = attributes.lookup("id"); + if (id_attribute) { + curve_info.stored_ids = id_attribute.varray.get_internal_span().typed<int>(); } } /* Retrieve the radius attribute, if it exists. */ - if (component.attribute_exists("radius")) { - curve_info.radius = component - .attribute_get_for_read<float>("radius", ATTR_DOMAIN_POINT, 0.0f) - .get_internal_span(); + if (attributes.contains("radius")) { + curve_info.radius = + attributes.lookup<float>("radius", ATTR_DOMAIN_POINT).get_internal_span(); info.create_radius_attribute = true; } /* Retrieve the resolution attribute, if it exists. */ curve_info.resolution = curves.resolution(); - if (component.attribute_exists("resolution")) { + if (attributes.contains("resolution")) { info.create_resolution_attribute = true; } /* Retrieve handle position attributes, if they exist. */ - if (component.attribute_exists("handle_right")) { - curve_info.handle_left = component - .attribute_get_for_read<float3>( - "handle_left", ATTR_DOMAIN_POINT, float3(0)) - .get_internal_span(); - curve_info.handle_right = component - .attribute_get_for_read<float3>( - "handle_right", ATTR_DOMAIN_POINT, float3(0)) - .get_internal_span(); + if (attributes.contains("handle_right")) { + curve_info.handle_left = + attributes.lookup<float3>("handle_left", ATTR_DOMAIN_POINT).get_internal_span(); + curve_info.handle_right = + attributes.lookup<float3>("handle_right", ATTR_DOMAIN_POINT).get_internal_span(); info.create_handle_postion_attributes = true; } } @@ -1142,7 +1150,7 @@ static void execute_realize_curve_task(const RealizeInstancesOptions &options, const RealizeCurveTask &task, const OrderedAttributes &ordered_attributes, bke::CurvesGeometry &dst_curves, - MutableSpan<GMutableSpan> dst_attribute_spans, + MutableSpan<GSpanAttributeWriter> dst_attribute_writers, MutableSpan<int> all_dst_ids, MutableSpan<float3> all_handle_left, MutableSpan<float3> all_handle_right, @@ -1220,7 +1228,7 @@ static void execute_realize_curve_task(const RealizeInstancesOptions &options, return IndexRange(); } }, - dst_attribute_spans); + dst_attribute_writers); } static void execute_realize_curve_tasks(const RealizeInstancesOptions &options, @@ -1244,57 +1252,50 @@ static void execute_realize_curve_tasks(const RealizeInstancesOptions &options, dst_curves.offsets_for_write().last() = points_num; CurveComponent &dst_component = r_realized_geometry.get_component_for_write<CurveComponent>(); 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. */ - OutputAttribute_Typed<int> point_ids; - MutableSpan<int> point_ids_span; + SpanAttributeWriter<int> point_ids; if (all_curves_info.create_id_attribute) { - point_ids = dst_component.attribute_try_get_for_output_only<int>("id", ATTR_DOMAIN_POINT); - point_ids_span = point_ids.as_span(); + point_ids = dst_attributes.lookup_or_add_for_write_only_span<int>("id", ATTR_DOMAIN_POINT); } /* Prepare generic output attributes. */ - Vector<OutputAttribute> dst_attributes; - Vector<GMutableSpan> dst_attribute_spans; + Vector<GSpanAttributeWriter> dst_attribute_writers; for (const int attribute_index : ordered_attributes.index_range()) { const AttributeIDRef &attribute_id = ordered_attributes.ids[attribute_index]; const eAttrDomain domain = ordered_attributes.kinds[attribute_index].domain; const eCustomDataType data_type = ordered_attributes.kinds[attribute_index].data_type; - OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - attribute_id, domain, data_type); - dst_attribute_spans.append(dst_attribute.as_span()); - dst_attributes.append(std::move(dst_attribute)); + dst_attribute_writers.append( + dst_attributes.lookup_or_add_for_write_only_span(attribute_id, domain, data_type)); } /* Prepare handle position attributes if necessary. */ - OutputAttribute_Typed<float3> handle_left; - OutputAttribute_Typed<float3> handle_right; - MutableSpan<float3> handle_left_span; - MutableSpan<float3> handle_right_span; + SpanAttributeWriter<float3> handle_left; + SpanAttributeWriter<float3> handle_right; if (all_curves_info.create_handle_postion_attributes) { - handle_left = dst_component.attribute_try_get_for_output_only<float3>("handle_left", - ATTR_DOMAIN_POINT); - handle_right = dst_component.attribute_try_get_for_output_only<float3>("handle_right", + handle_left = dst_attributes.lookup_or_add_for_write_only_span<float3>("handle_left", ATTR_DOMAIN_POINT); - handle_left_span = handle_left.as_span(); - handle_right_span = handle_right.as_span(); + handle_right = dst_attributes.lookup_or_add_for_write_only_span<float3>("handle_right", + ATTR_DOMAIN_POINT); } /* Prepare radius attribute if necessary. */ - OutputAttribute_Typed<float> radius; - MutableSpan<float> radius_span; + SpanAttributeWriter<float> radius; if (all_curves_info.create_radius_attribute) { - radius = dst_component.attribute_try_get_for_output_only<float>("radius", ATTR_DOMAIN_POINT); - radius_span = radius.as_span(); + radius = dst_attributes.lookup_or_add_for_write_only_span<float>("radius", ATTR_DOMAIN_POINT); } /* Prepare resolution attribute if necessary. */ - OutputAttribute_Typed<int> resolution; - MutableSpan<int> resolution_span; + SpanAttributeWriter<int> resolution; if (all_curves_info.create_resolution_attribute) { - resolution = dst_component.attribute_try_get_for_output_only<int>("resolution", - ATTR_DOMAIN_CURVE); - resolution_span = resolution.as_span(); + resolution = dst_attributes.lookup_or_add_for_write_only_span<int>("resolution", + ATTR_DOMAIN_CURVE); } /* Actually execute all tasks. */ @@ -1306,31 +1307,40 @@ static void execute_realize_curve_tasks(const RealizeInstancesOptions &options, task, ordered_attributes, dst_curves, - dst_attribute_spans, - point_ids_span, - handle_left_span, - handle_right_span, - radius_span, - resolution_span); + dst_attribute_writers, + point_ids.span, + handle_left.span, + handle_right.span, + radius.span, + resolution.span); } }); - /* Save modified attributes. */ - for (OutputAttribute &dst_attribute : dst_attributes) { - dst_attribute.save(); + /* 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(); } if (point_ids) { - point_ids.save(); + point_ids.finish(); } if (radius) { - radius.save(); + radius.finish(); } if (resolution) { - resolution.save(); + resolution.finish(); } if (all_curves_info.create_handle_postion_attributes) { - handle_left.save(); - handle_right.save(); + handle_left.finish(); + handle_right.finish(); } } @@ -1345,7 +1355,7 @@ static void remove_id_attribute_from_instances(GeometrySet &geometry_set) geometry_set.modify_geometry_sets([&](GeometrySet &sub_geometry) { if (sub_geometry.has<InstancesComponent>()) { InstancesComponent &component = sub_geometry.get_component_for_write<InstancesComponent>(); - component.attributes().remove("id"); + component.instance_attributes().remove("id"); } }); } @@ -1405,6 +1415,9 @@ GeometrySet realize_instances(GeometrySet geometry_set, const RealizeInstancesOp if (gather_info.r_tasks.first_volume) { new_geometry_set.add(*gather_info.r_tasks.first_volume); } + if (gather_info.r_tasks.first_edit_data) { + new_geometry_set.add(*gather_info.r_tasks.first_edit_data); + } return new_geometry_set; } diff --git a/source/blender/geometry/intern/resample_curves.cc b/source/blender/geometry/intern/resample_curves.cc index 36525e1bdf0..86c1980d9ee 100644 --- a/source/blender/geometry/intern/resample_curves.cc +++ b/source/blender/geometry/intern/resample_curves.cc @@ -77,8 +77,8 @@ static bool interpolate_attribute_to_poly_curve(const bke::AttributeIDRef &attri static const Set<StringRef> no_interpolation{{ "handle_type_left", "handle_type_right", - "handle_position_right", - "handle_position_left", + "handle_right", + "handle_left", "nurbs_weight", }}; return !(attribute_id.is_named() && no_interpolation.contains(attribute_id.name())); @@ -92,17 +92,18 @@ static void retrieve_attribute_spans(const Span<bke::AttributeIDRef> ids, CurveComponent &dst_component, Vector<GSpan> &src, Vector<GMutableSpan> &dst, - Vector<bke::OutputAttribute> &dst_attributes) + Vector<bke::GSpanAttributeWriter> &dst_attributes) { for (const int i : ids.index_range()) { - GVArray src_attribute = src_component.attribute_try_get_for_read(ids[i], ATTR_DOMAIN_POINT); + GVArray src_attribute = src_component.attributes()->lookup(ids[i], ATTR_DOMAIN_POINT); BLI_assert(src_attribute); src.append(src_attribute.get_internal_span()); const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(src_attribute.type()); - bke::OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( - ids[i], ATTR_DOMAIN_POINT, data_type); - dst.append(dst_attribute.as_span()); + bke::GSpanAttributeWriter dst_attribute = + dst_component.attributes_for_write()->lookup_or_add_for_write_only_span( + ids[i], ATTR_DOMAIN_POINT, data_type); + dst.append(dst_attribute.span); dst_attributes.append(std::move(dst_attribute)); } } @@ -111,7 +112,7 @@ struct AttributesForInterpolation : NonCopyable, NonMovable { Vector<GSpan> src; Vector<GMutableSpan> dst; - Vector<bke::OutputAttribute> dst_attributes; + Vector<bke::GSpanAttributeWriter> dst_attributes; Vector<GSpan> src_no_interpolation; Vector<GMutableSpan> dst_no_interpolation; @@ -129,8 +130,8 @@ static void gather_point_attributes_to_interpolate(const CurveComponent &src_com VectorSet<bke::AttributeIDRef> ids; VectorSet<bke::AttributeIDRef> ids_no_interpolation; - src_component.attribute_foreach( - [&](const bke::AttributeIDRef &id, const AttributeMetaData meta_data) { + src_component.attributes()->for_all( + [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) { if (meta_data.domain != ATTR_DOMAIN_POINT) { return true; } @@ -161,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, @@ -234,11 +233,18 @@ static Curves *resample_to_uniform(const CurveComponent &src_component, for (const int i_curve : sliced_selection) { const bool cyclic = curves_cyclic[i_curve]; const IndexRange dst_points = dst_curves.points_for_curve(i_curve); - length_parameterize::create_uniform_samples( - src_curves.evaluated_lengths_for_curve(i_curve, cyclic), - curves_cyclic[i_curve], - sample_indices.as_mutable_span().slice(dst_points), - sample_factors.as_mutable_span().slice(dst_points)); + const Span<float> lengths = src_curves.evaluated_lengths_for_curve(i_curve, cyclic); + if (lengths.is_empty()) { + /* Handle curves with only one evaluated point. */ + sample_indices.as_mutable_span().slice(dst_points).fill(0); + sample_factors.as_mutable_span().slice(dst_points).fill(0.0f); + } + else { + length_parameterize::sample_uniform(lengths, + !curves_cyclic[i_curve], + sample_indices.as_mutable_span().slice(dst_points), + sample_factors.as_mutable_span().slice(dst_points)); + } } /* For every attribute, evaluate attributes from every curve in the range in the original @@ -254,10 +260,10 @@ static Curves *resample_to_uniform(const CurveComponent &src_component, const IndexRange dst_points = dst_curves.points_for_curve(i_curve); if (curve_types[i_curve] == CURVE_TYPE_POLY) { - length_parameterize::linear_interpolation(src.slice(src_points), - sample_indices.as_span().slice(dst_points), - sample_factors.as_span().slice(dst_points), - dst.slice(dst_points)); + length_parameterize::interpolate(src.slice(src_points), + sample_indices.as_span().slice(dst_points), + sample_factors.as_span().slice(dst_points), + dst.slice(dst_points)); } else { const int evaluated_size = src_curves.evaluated_points_for_curve(i_curve).size(); @@ -266,10 +272,10 @@ static Curves *resample_to_uniform(const CurveComponent &src_component, MutableSpan<T> evaluated = evaluated_buffer.as_mutable_span().cast<T>(); src_curves.interpolate_to_evaluated(i_curve, src.slice(src_points), evaluated); - length_parameterize::linear_interpolation(evaluated.as_span(), - sample_indices.as_span().slice(dst_points), - sample_factors.as_span().slice(dst_points), - dst.slice(dst_points)); + length_parameterize::interpolate(evaluated.as_span(), + sample_indices.as_span().slice(dst_points), + sample_factors.as_span().slice(dst_points), + dst.slice(dst_points)); } } }); @@ -279,10 +285,10 @@ static Curves *resample_to_uniform(const CurveComponent &src_component, for (const int i_curve : sliced_selection) { const IndexRange src_points = src_curves.evaluated_points_for_curve(i_curve); const IndexRange dst_points = dst_curves.points_for_curve(i_curve); - length_parameterize::linear_interpolation(evaluated_positions.slice(src_points), - sample_indices.as_span().slice(dst_points), - sample_factors.as_span().slice(dst_points), - dst_positions.slice(dst_points)); + length_parameterize::interpolate(evaluated_positions.slice(src_points), + sample_indices.as_span().slice(dst_points), + sample_factors.as_span().slice(dst_points), + dst_positions.slice(dst_points)); } /* Fill the default value for non-interpolating attributes that still must be copied. */ @@ -312,8 +318,8 @@ static Curves *resample_to_uniform(const CurveComponent &src_component, bke::curves::copy_point_data( src_curves, dst_curves, unselected_ranges, src_positions, dst_positions); - for (bke::OutputAttribute &attribute : attributes.dst_attributes) { - attribute.save(); + for (bke::GSpanAttributeWriter &attribute : attributes.dst_attributes) { + attribute.finish(); } return dst_curves_id; @@ -434,8 +440,8 @@ Curves *resample_to_evaluated(const CurveComponent &src_component, bke::curves::copy_point_data( src_curves, dst_curves, unselected_ranges, src_positions, dst_positions); - for (bke::OutputAttribute &attribute : attributes.dst_attributes) { - attribute.save(); + for (bke::GSpanAttributeWriter &attribute : attributes.dst_attributes) { + attribute.finish(); } return dst_curves_id; diff --git a/source/blender/geometry/intern/reverse_uv_sampler.cc b/source/blender/geometry/intern/reverse_uv_sampler.cc index 9aa98895a86..39fec40333c 100644 --- a/source/blender/geometry/intern/reverse_uv_sampler.cc +++ b/source/blender/geometry/intern/reverse_uv_sampler.cc @@ -3,30 +3,99 @@ #include "GEO_reverse_uv_sampler.hh" #include "BLI_math_geom.h" +#include "BLI_math_vector.hh" +#include "BLI_task.hh" +#include "BLI_timeit.hh" namespace blender::geometry { +static int2 uv_to_cell_key(const float2 &uv, const int resolution) +{ + return int2{uv * resolution}; +} + ReverseUVSampler::ReverseUVSampler(const Span<float2> uv_map, const Span<MLoopTri> looptris) : uv_map_(uv_map), looptris_(looptris) { + resolution_ = std::max<int>(3, std::sqrt(looptris.size()) * 2); + + for (const int looptri_index : looptris.index_range()) { + const MLoopTri &looptri = looptris[looptri_index]; + const float2 &uv_0 = uv_map_[looptri.tri[0]]; + const float2 &uv_1 = uv_map_[looptri.tri[1]]; + const float2 &uv_2 = uv_map_[looptri.tri[2]]; + + const int2 key_0 = uv_to_cell_key(uv_0, resolution_); + const int2 key_1 = uv_to_cell_key(uv_1, resolution_); + const int2 key_2 = uv_to_cell_key(uv_2, resolution_); + + const int2 min_key = math::min(math::min(key_0, key_1), key_2); + const int2 max_key = math::max(math::max(key_0, key_1), key_2); + + for (int key_x = min_key.x; key_x <= max_key.x; key_x++) { + for (int key_y = min_key.y; key_y <= max_key.y; key_y++) { + const int2 key{key_x, key_y}; + looptris_by_cell_.add(key, looptri_index); + } + } + } } ReverseUVSampler::Result ReverseUVSampler::sample(const float2 &query_uv) const { - for (const MLoopTri &looptri : looptris_) { - const float2 &uv0 = uv_map_[looptri.tri[0]]; - const float2 &uv1 = uv_map_[looptri.tri[1]]; - const float2 &uv2 = uv_map_[looptri.tri[2]]; + const int2 cell_key = uv_to_cell_key(query_uv, resolution_); + const Span<int> looptri_indices = looptris_by_cell_.lookup(cell_key); + + float best_dist = FLT_MAX; + float3 best_bary_weights; + const MLoopTri *best_looptri; + + for (const int looptri_index : looptri_indices) { + const MLoopTri &looptri = looptris_[looptri_index]; + const float2 &uv_0 = uv_map_[looptri.tri[0]]; + const float2 &uv_1 = uv_map_[looptri.tri[1]]; + const float2 &uv_2 = uv_map_[looptri.tri[2]]; float3 bary_weights; - if (!barycentric_coords_v2(uv0, uv1, uv2, query_uv, bary_weights)) { + if (!barycentric_coords_v2(uv_0, uv_1, uv_2, query_uv, bary_weights)) { continue; } - if (IN_RANGE_INCL(bary_weights.x, 0.0f, 1.0f) && IN_RANGE_INCL(bary_weights.y, 0.0f, 1.0f) && - IN_RANGE_INCL(bary_weights.z, 0.0f, 1.0f)) { - return Result{ResultType::Ok, &looptri, bary_weights}; + + /* If #query_uv is in the triangle, the distance is <= 0. Otherwise, the larger the distance, + * the further away the uv is from the triangle. */ + const float x_dist = std::max(-bary_weights.x, bary_weights.x - 1.0f); + const float y_dist = std::max(-bary_weights.y, bary_weights.y - 1.0f); + const float z_dist = std::max(-bary_weights.z, bary_weights.z - 1.0f); + const float dist = MAX3(x_dist, y_dist, z_dist); + + if (dist <= 0.0f && best_dist <= 0.0f) { + /* The uv sample is in multiple triangles. */ + return Result{ResultType::Multiple}; + } + + if (dist < best_dist) { + best_dist = dist; + best_bary_weights = bary_weights; + best_looptri = &looptri; } } + + /* Allow for a small epsilon in case the uv is on th edge. */ + if (best_dist < 0.00001f) { + return Result{ResultType::Ok, best_looptri, math::clamp(best_bary_weights, 0.0f, 1.0f)}; + } + return Result{}; } +void ReverseUVSampler::sample_many(const Span<float2> query_uvs, + MutableSpan<Result> r_results) const +{ + BLI_assert(query_uvs.size() == r_results.size()); + threading::parallel_for(query_uvs.index_range(), 256, [&](const IndexRange range) { + for (const int i : range) { + r_results[i] = this->sample(query_uvs[i]); + } + }); +} + } // namespace blender::geometry diff --git a/source/blender/geometry/intern/set_curve_type.cc b/source/blender/geometry/intern/set_curve_type.cc new file mode 100644 index 00000000000..40ee2488a4b --- /dev/null +++ b/source/blender/geometry/intern/set_curve_type.cc @@ -0,0 +1,689 @@ +/* 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 "BLI_task.hh" + +#include "GEO_set_curve_type.hh" + +namespace blender::geometry { + +/** + * This function answers the question about possible conversion method for NURBS-to-Bezier. In + * general for 3rd degree NURBS curves there is one-to-one relation with 3rd degree Bezier curves + * that can be exploit for conversion - Bezier handles sit on NURBS hull segments and in the middle + * between those handles are Bezier anchor points. + */ +static bool is_nurbs_to_bezier_one_to_one(const KnotsMode knots_mode) +{ + if (ELEM(knots_mode, NURBS_KNOT_MODE_NORMAL, NURBS_KNOT_MODE_ENDPOINT)) { + return true; + } + return false; +} + +/** + * As an optimization, just change the types on a mutable curves data-block when the conversion is + * simple. This could be expanded to more cases where the number of points doesn't change in the + * future, though that might require properly initializing some attributes, or removing others. + */ +static bool conversion_can_change_point_num(const CurveType dst_type) +{ + if (ELEM(dst_type, CURVE_TYPE_CATMULL_ROM, CURVE_TYPE_POLY)) { + /* The conversion to Catmull Rom or Poly should never change the number of points, no matter + * the source type (Bezier to Catmull Rom conversion cannot maintain the same shape anyway). */ + return false; + } + return true; +} + +template<typename T> +static void scale_input_assign(const Span<T> src, + const int scale, + const int offset, + MutableSpan<T> dst) +{ + for (const int i : dst.index_range()) { + dst[i] = src[i * scale + offset]; + } +} + +/** + * The Bezier control point and its handles become three control points on the NURBS curve, + * so each attribute value is duplicated three times. + */ +template<typename T> static void bezier_generic_to_nurbs(const Span<T> src, MutableSpan<T> dst) +{ + for (const int i : src.index_range()) { + dst[i * 3] = src[i]; + dst[i * 3 + 1] = src[i]; + dst[i * 3 + 2] = src[i]; + } +} + +static void bezier_generic_to_nurbs(const GSpan src, GMutableSpan dst) +{ + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + bezier_generic_to_nurbs(src.typed<T>(), dst.typed<T>()); + }); +} + +static void bezier_positions_to_nurbs(const Span<float3> src_positions, + const Span<float3> src_handles_l, + const Span<float3> src_handles_r, + MutableSpan<float3> dst_positions) +{ + for (const int i : src_positions.index_range()) { + dst_positions[i * 3] = src_handles_l[i]; + dst_positions[i * 3 + 1] = src_positions[i]; + dst_positions[i * 3 + 2] = src_handles_r[i]; + } +} + +static void catmull_rom_to_bezier_handles(const Span<float3> src_positions, + const bool cyclic, + MutableSpan<float3> dst_handles_l, + MutableSpan<float3> dst_handles_r) +{ + /* Catmull Rom curves are the same as Bezier curves with automatically defined handle positions. + * This constant defines the portion of the distance between the next/previous points to use for + * the length of the handles. */ + constexpr float handle_scale = 1.0f / 6.0f; + + if (src_positions.size() == 1) { + dst_handles_l.first() = src_positions.first(); + dst_handles_r.first() = src_positions.first(); + return; + } + + const float3 first_offset = cyclic ? src_positions[1] - src_positions.last() : + src_positions[1] - src_positions[0]; + dst_handles_r.first() = src_positions.first() + first_offset * handle_scale; + dst_handles_l.first() = src_positions.first() - first_offset * handle_scale; + + const float3 last_offset = cyclic ? src_positions.first() - src_positions.last(1) : + src_positions.last() - src_positions.last(1); + dst_handles_l.last() = src_positions.last() - last_offset * handle_scale; + dst_handles_r.last() = src_positions.last() + last_offset * handle_scale; + + for (const int i : src_positions.index_range().drop_front(1).drop_back(1)) { + const float3 left_offset = src_positions[i - 1] - src_positions[i + 1]; + dst_handles_l[i] = src_positions[i] + left_offset * handle_scale; + + const float3 right_offset = src_positions[i + 1] - src_positions[i - 1]; + dst_handles_r[i] = src_positions[i] + right_offset * handle_scale; + } +} + +static void catmull_rom_to_nurbs_positions(const Span<float3> src_positions, + const bool cyclic, + MutableSpan<float3> dst_positions) +{ + /* Convert the Catmull Rom position data to Bezier handles in order to reuse the Bezier to + * NURBS positions assignment. If this becomes a bottleneck, this step could be avoided. */ + Array<float3, 32> bezier_handles_l(src_positions.size()); + Array<float3, 32> bezier_handles_r(src_positions.size()); + catmull_rom_to_bezier_handles(src_positions, cyclic, bezier_handles_l, bezier_handles_r); + bezier_positions_to_nurbs(src_positions, bezier_handles_l, bezier_handles_r, dst_positions); +} + +template<typename T> +static void nurbs_to_bezier_assign(const Span<T> src, + const MutableSpan<T> dst, + const KnotsMode knots_mode) +{ + switch (knots_mode) { + case NURBS_KNOT_MODE_NORMAL: + for (const int i : dst.index_range()) { + dst[i] = src[(i + 1) % src.size()]; + } + break; + case NURBS_KNOT_MODE_ENDPOINT: + for (const int i : dst.index_range().drop_back(1).drop_front(1)) { + dst[i] = src[i + 1]; + } + dst.first() = src.first(); + dst.last() = src.last(); + break; + default: + /* Every 3rd NURBS position (starting from index 1) should have its attributes transferred. + */ + scale_input_assign<T>(src, 3, 1, dst); + } +} + +static void nurbs_to_bezier_assign(const GSpan src, const KnotsMode knots_mode, GMutableSpan dst) +{ + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + nurbs_to_bezier_assign(src.typed<T>(), dst.typed<T>(), knots_mode); + }); +} + +static Vector<float3> create_nurbs_to_bezier_handles(const Span<float3> nurbs_positions, + const KnotsMode knots_mode) +{ + const int nurbs_positions_num = nurbs_positions.size(); + Vector<float3> handle_positions; + + if (is_nurbs_to_bezier_one_to_one(knots_mode)) { + const bool is_periodic = knots_mode == NURBS_KNOT_MODE_NORMAL; + if (is_periodic) { + handle_positions.append(nurbs_positions[1] + + ((nurbs_positions[0] - nurbs_positions[1]) / 3)); + } + else { + handle_positions.append(2 * nurbs_positions[0] - nurbs_positions[1]); + handle_positions.append(nurbs_positions[1]); + } + + /* Place Bezier handles on interior NURBS hull segments. Those handles can be either placed on + * endpoints, midpoints or 1/3 of the distance of a hull segment. */ + const int segments_num = nurbs_positions_num - 1; + const bool ignore_interior_segment = segments_num == 3 && is_periodic == false; + if (ignore_interior_segment == false) { + const float mid_offset = (float)(segments_num - 1) / 2.0f; + for (const int i : IndexRange(1, segments_num - 2)) { + /* Divisor can have values: 1, 2 or 3. */ + const int divisor = is_periodic ? + 3 : + std::min(3, (int)(-std::abs(i - mid_offset) + mid_offset + 1.0f)); + const float3 &p1 = nurbs_positions[i]; + const float3 &p2 = nurbs_positions[i + 1]; + const float3 displacement = (p2 - p1) / divisor; + const int num_handles_on_segment = divisor < 3 ? 1 : 2; + for (int j : IndexRange(1, num_handles_on_segment)) { + handle_positions.append(p1 + (displacement * j)); + } + } + } + + const int last_index = nurbs_positions_num - 1; + if (is_periodic) { + handle_positions.append( + nurbs_positions[last_index - 1] + + ((nurbs_positions[last_index] - nurbs_positions[last_index - 1]) / 3)); + } + else { + handle_positions.append(nurbs_positions[last_index - 1]); + handle_positions.append(2 * nurbs_positions[last_index] - nurbs_positions[last_index - 1]); + } + } + else { + for (const int i : IndexRange(nurbs_positions_num)) { + if (i % 3 == 1) { + continue; + } + handle_positions.append(nurbs_positions[i]); + } + if (nurbs_positions_num % 3 == 1) { + handle_positions.pop_last(); + } + else if (nurbs_positions_num % 3 == 2) { + const int last_index = nurbs_positions_num - 1; + handle_positions.append(2 * nurbs_positions[last_index] - nurbs_positions[last_index - 1]); + } + } + + return handle_positions; +} + +static void create_nurbs_to_bezier_positions(const Span<float3> nurbs_positions, + const Span<float3> handle_positions, + const KnotsMode knots_mode, + MutableSpan<float3> bezier_positions) +{ + if (is_nurbs_to_bezier_one_to_one(knots_mode)) { + for (const int i : bezier_positions.index_range()) { + bezier_positions[i] = math::interpolate( + handle_positions[i * 2], handle_positions[i * 2 + 1], 0.5f); + } + } + else { + /* Every 3rd NURBS position (starting from index 1) should be converted to Bezier position. */ + scale_input_assign(nurbs_positions, 3, 1, bezier_positions); + } +} + +static int to_bezier_size(const CurveType src_type, + const bool cyclic, + const KnotsMode knots_mode, + const int src_size) +{ + switch (src_type) { + case CURVE_TYPE_NURBS: { + if (is_nurbs_to_bezier_one_to_one(knots_mode)) { + return cyclic ? src_size : src_size - 2; + } + return (src_size + 1) / 3; + } + default: + return src_size; + } +} + +static int to_nurbs_size(const CurveType src_type, const int src_size) +{ + switch (src_type) { + case CURVE_TYPE_BEZIER: + case CURVE_TYPE_CATMULL_ROM: + return src_size * 3; + default: + return src_size; + } +} + +static void retrieve_curve_sizes(const bke::CurvesGeometry &curves, MutableSpan<int> sizes) +{ + threading::parallel_for(curves.curves_range(), 4096, [&](IndexRange range) { + for (const int i : range) { + sizes[i] = curves.points_for_curve(i).size(); + } + }); +} + +struct GenericAttributes : NonCopyable, NonMovable { + Vector<GSpan> src; + Vector<GMutableSpan> dst; + + Vector<bke::GSpanAttributeWriter> attributes; +}; + +static void retrieve_generic_point_attributes(const bke::AttributeAccessor &src_attributes, + bke::MutableAttributeAccessor &dst_attributes, + GenericAttributes &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 (src_attributes.is_builtin(id)) { + if (!(id.is_named() && ELEM(id, "tilt", "radius"))) { + return true; + } + } + + GVArray src_attribute = src_attributes.lookup(id, ATTR_DOMAIN_POINT); + BLI_assert(src_attribute); + attributes.src.append(src_attribute.get_internal_span()); + + bke::GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_span( + id, ATTR_DOMAIN_POINT, meta_data.data_type); + attributes.dst.append(dst_attribute.span); + attributes.attributes.append(std::move(dst_attribute)); + + return true; + }); +} + +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(); + + 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()); + threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) { + for (const int i : selection.slice(range)) { + dst_offsets[i] = to_bezier_size( + CurveType(src_types[i]), src_cyclic[i], KnotsMode(src_knot_modes[i]), dst_offsets[i]); + } + }); + bke::curves::accumulate_counts_to_offsets(dst_offsets); + dst_curves.resize(dst_offsets.last(), dst_curves.curves_num()); + + 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); + + MutableSpan<float3> dst_positions = dst_curves.positions_for_write(); + MutableSpan<float3> dst_handles_l = dst_curves.handle_positions_left_for_write(); + MutableSpan<float3> dst_handles_r = dst_curves.handle_positions_right_for_write(); + MutableSpan<int8_t> dst_types_l = dst_curves.handle_types_left_for_write(); + MutableSpan<int8_t> dst_types_r = dst_curves.handle_types_right_for_write(); + MutableSpan<float> dst_weights = dst_curves.nurbs_weights_for_write(); + + auto catmull_rom_to_bezier = [&](IndexMask selection) { + bke::curves::fill_points<int8_t>(dst_curves, selection, BEZIER_HANDLE_ALIGN, dst_types_l); + bke::curves::fill_points<int8_t>(dst_curves, selection, BEZIER_HANDLE_ALIGN, dst_types_r); + bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions); + + threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) { + for (const int i : selection.slice(range)) { + const IndexRange src_points = src_curves.points_for_curve(i); + const IndexRange dst_points = dst_curves.points_for_curve(i); + catmull_rom_to_bezier_handles(src_positions.slice(src_points), + src_cyclic[i], + dst_handles_l.slice(dst_points), + dst_handles_r.slice(dst_points)); + } + }); + + for (const int i : attributes.src.index_range()) { + bke::curves::copy_point_data( + src_curves, dst_curves, selection, attributes.src[i], attributes.dst[i]); + } + }; + + auto poly_to_bezier = [&](IndexMask selection) { + bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions); + bke::curves::fill_points<int8_t>(dst_curves, selection, BEZIER_HANDLE_VECTOR, dst_types_l); + bke::curves::fill_points<int8_t>(dst_curves, selection, BEZIER_HANDLE_VECTOR, dst_types_r); + dst_curves.calculate_bezier_auto_handles(); + for (const int i : attributes.src.index_range()) { + bke::curves::copy_point_data( + src_curves, dst_curves, selection, attributes.src[i], attributes.dst[i]); + } + }; + + auto bezier_to_bezier = [&](IndexMask selection) { + const VArraySpan<int8_t> src_types_l = src_curves.handle_types_left(); + const VArraySpan<int8_t> src_types_r = src_curves.handle_types_right(); + const Span<float3> src_handles_l = src_curves.handle_positions_left(); + const Span<float3> src_handles_r = src_curves.handle_positions_right(); + + bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions); + bke::curves::copy_point_data(src_curves, dst_curves, selection, src_handles_l, dst_handles_l); + bke::curves::copy_point_data(src_curves, dst_curves, selection, src_handles_r, dst_handles_r); + bke::curves::copy_point_data(src_curves, dst_curves, selection, src_types_l, dst_types_l); + bke::curves::copy_point_data(src_curves, dst_curves, selection, src_types_r, dst_types_r); + + dst_curves.calculate_bezier_auto_handles(); + + for (const int i : attributes.src.index_range()) { + bke::curves::copy_point_data( + src_curves, dst_curves, selection, attributes.src[i], attributes.dst[i]); + } + }; + + auto nurbs_to_bezier = [&](IndexMask selection) { + bke::curves::fill_points<int8_t>(dst_curves, selection, BEZIER_HANDLE_ALIGN, dst_types_l); + bke::curves::fill_points<int8_t>(dst_curves, selection, BEZIER_HANDLE_ALIGN, dst_types_r); + bke::curves::fill_points<float>(dst_curves, selection, 0.0f, dst_weights); + + threading::parallel_for(selection.index_range(), 64, [&](IndexRange range) { + for (const int i : selection.slice(range)) { + const IndexRange src_points = src_curves.points_for_curve(i); + const IndexRange dst_points = dst_curves.points_for_curve(i); + const Span<float3> src_curve_positions = src_positions.slice(src_points); + + KnotsMode knots_mode = KnotsMode(src_knot_modes[i]); + Span<float3> nurbs_positions = src_curve_positions; + Vector<float3> nurbs_positions_vector; + if (src_cyclic[i] && is_nurbs_to_bezier_one_to_one(knots_mode)) { + /* For conversion treat this as periodic closed curve. Extend NURBS hull to first and + * second point which will act as a skeleton for placing Bezier handles. */ + nurbs_positions_vector.extend(src_curve_positions); + nurbs_positions_vector.append(src_curve_positions[0]); + nurbs_positions_vector.append(src_curve_positions[1]); + nurbs_positions = nurbs_positions_vector; + knots_mode = NURBS_KNOT_MODE_NORMAL; + } + + const Vector<float3> handle_positions = create_nurbs_to_bezier_handles(nurbs_positions, + knots_mode); + + scale_input_assign(handle_positions.as_span(), 2, 0, dst_handles_l.slice(dst_points)); + scale_input_assign(handle_positions.as_span(), 2, 1, dst_handles_r.slice(dst_points)); + + create_nurbs_to_bezier_positions( + nurbs_positions, handle_positions, knots_mode, dst_positions.slice(dst_points)); + } + }); + + for (const int i_attribute : attributes.src.index_range()) { + threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) { + for (const int i : selection.slice(range)) { + const IndexRange src_points = src_curves.points_for_curve(i); + const IndexRange dst_points = dst_curves.points_for_curve(i); + nurbs_to_bezier_assign(attributes.src[i_attribute].slice(src_points), + KnotsMode(src_knot_modes[i]), + attributes.dst[i_attribute].slice(dst_points)); + } + }); + } + }; + + bke::curves::foreach_curve_by_type(src_curves.curve_types(), + src_curves.curve_type_counts(), + selection, + catmull_rom_to_bezier, + poly_to_bezier, + bezier_to_bezier, + nurbs_to_bezier); + + const Vector<IndexRange> unselected_ranges = selection.extract_ranges_invert( + src_curves.curves_range()); + + for (const int i : attributes.src.index_range()) { + bke::curves::copy_point_data( + src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]); + } + + for (bke::GSpanAttributeWriter &attribute : attributes.attributes) { + attribute.finish(); + } + + return dst_curves; +} + +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(); + + 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()); + threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) { + for (const int i : selection.slice(range)) { + dst_offsets[i] = to_nurbs_size(CurveType(src_types[i]), dst_offsets[i]); + } + }); + bke::curves::accumulate_counts_to_offsets(dst_offsets); + dst_curves.resize(dst_offsets.last(), dst_curves.curves_num()); + + 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); + + MutableSpan<float3> dst_positions = dst_curves.positions_for_write(); + + auto fill_weights_if_necessary = [&](const IndexMask selection) { + if (!src_curves.nurbs_weights().is_empty()) { + bke::curves::fill_points(dst_curves, selection, 1.0f, dst_curves.nurbs_weights_for_write()); + } + }; + + auto catmull_rom_to_nurbs = [&](IndexMask selection) { + dst_curves.nurbs_orders_for_write().fill_indices(selection, 4); + dst_curves.nurbs_knots_modes_for_write().fill_indices(selection, NURBS_KNOT_MODE_BEZIER); + fill_weights_if_necessary(selection); + + threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) { + for (const int i : selection.slice(range)) { + const IndexRange src_points = src_curves.points_for_curve(i); + const IndexRange dst_points = dst_curves.points_for_curve(i); + catmull_rom_to_nurbs_positions( + src_positions.slice(src_points), src_cyclic[i], dst_positions.slice(dst_points)); + } + }); + + for (const int i_attribute : attributes.src.index_range()) { + threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) { + for (const int i : selection.slice(range)) { + const IndexRange src_points = src_curves.points_for_curve(i); + const IndexRange dst_points = dst_curves.points_for_curve(i); + bezier_generic_to_nurbs(attributes.src[i_attribute].slice(src_points), + attributes.dst[i_attribute].slice(dst_points)); + } + }); + } + }; + + auto poly_to_nurbs = [&](IndexMask selection) { + dst_curves.nurbs_orders_for_write().fill_indices(selection, 4); + bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions); + fill_weights_if_necessary(selection); + + /* Avoid using "Endpoint" knots modes for cyclic curves, since it adds a sharp point at the + * start/end. */ + if (src_cyclic.is_single()) { + dst_curves.nurbs_knots_modes_for_write().fill_indices( + selection, + src_cyclic.get_internal_single() ? NURBS_KNOT_MODE_NORMAL : NURBS_KNOT_MODE_ENDPOINT); + } + else { + VArraySpan<bool> cyclic{src_cyclic}; + MutableSpan<int8_t> knots_modes = dst_curves.nurbs_knots_modes_for_write(); + threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) { + for (const int i : selection.slice(range)) { + knots_modes[i] = cyclic[i] ? NURBS_KNOT_MODE_NORMAL : NURBS_KNOT_MODE_ENDPOINT; + } + }); + } + + for (const int i_attribute : attributes.src.index_range()) { + bke::curves::copy_point_data(src_curves, + dst_curves, + selection, + attributes.src[i_attribute], + attributes.dst[i_attribute]); + } + }; + + auto bezier_to_nurbs = [&](IndexMask selection) { + const Span<float3> src_handles_l = src_curves.handle_positions_left(); + const Span<float3> src_handles_r = src_curves.handle_positions_right(); + + dst_curves.nurbs_orders_for_write().fill_indices(selection, 4); + dst_curves.nurbs_knots_modes_for_write().fill_indices(selection, NURBS_KNOT_MODE_BEZIER); + fill_weights_if_necessary(selection); + + threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) { + for (const int i : selection.slice(range)) { + const IndexRange src_points = src_curves.points_for_curve(i); + const IndexRange dst_points = dst_curves.points_for_curve(i); + bezier_positions_to_nurbs(src_positions.slice(src_points), + src_handles_l.slice(src_points), + src_handles_r.slice(src_points), + dst_positions.slice(dst_points)); + } + }); + + for (const int i_attribute : attributes.src.index_range()) { + threading::parallel_for(selection.index_range(), 512, [&](IndexRange range) { + for (const int i : selection.slice(range)) { + const IndexRange src_points = src_curves.points_for_curve(i); + const IndexRange dst_points = dst_curves.points_for_curve(i); + bezier_generic_to_nurbs(attributes.src[i_attribute].slice(src_points), + attributes.dst[i_attribute].slice(dst_points)); + } + }); + } + }; + + auto nurbs_to_nurbs = [&](IndexMask selection) { + bke::curves::copy_point_data(src_curves, dst_curves, selection, src_positions, dst_positions); + + if (!src_curves.nurbs_weights().is_empty()) { + bke::curves::copy_point_data(src_curves, + dst_curves, + selection, + src_curves.nurbs_weights(), + dst_curves.nurbs_weights_for_write()); + } + + for (const int i_attribute : attributes.src.index_range()) { + bke::curves::copy_point_data(src_curves, + dst_curves, + selection, + attributes.src[i_attribute], + attributes.dst[i_attribute]); + } + }; + + bke::curves::foreach_curve_by_type(src_curves.curve_types(), + src_curves.curve_type_counts(), + selection, + catmull_rom_to_nurbs, + poly_to_nurbs, + bezier_to_nurbs, + nurbs_to_nurbs); + + const Vector<IndexRange> unselected_ranges = selection.extract_ranges_invert( + src_curves.curves_range()); + + for (const int i : attributes.src.index_range()) { + bke::curves::copy_point_data( + src_curves, dst_curves, unselected_ranges, attributes.src[i], attributes.dst[i]); + } + + for (bke::GSpanAttributeWriter &attribute : attributes.attributes) { + attribute.finish(); + } + + return dst_curves; +} + +static bke::CurvesGeometry convert_curves_trivial(const bke::CurvesGeometry &src_curves, + const IndexMask selection, + const CurveType dst_type) +{ + bke::CurvesGeometry dst_curves(src_curves); + dst_curves.fill_curve_types(selection, dst_type); + dst_curves.remove_attributes_based_on_types(); + return dst_curves; +} + +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 convert_curves_trivial(src_curves, selection, dst_type); + case CURVE_TYPE_BEZIER: + return convert_curves_to_bezier(src_curves, selection); + case CURVE_TYPE_NURBS: + return convert_curves_to_nurbs(src_curves, selection); + } + BLI_assert_unreachable(); + return {}; +} + +bool try_curves_conversion_in_place(const IndexMask selection, + const CurveType dst_type, + FunctionRef<bke::CurvesGeometry &()> get_writable_curves_fn) +{ + if (conversion_can_change_point_num(dst_type)) { + return false; + } + bke::CurvesGeometry &curves = get_writable_curves_fn(); + curves.fill_curve_types(selection, dst_type); + curves.remove_attributes_based_on_types(); + return true; +} + +} // namespace blender::geometry diff --git a/source/blender/geometry/intern/subdivide_curves.cc b/source/blender/geometry/intern/subdivide_curves.cc new file mode 100644 index 00000000000..8057f029e73 --- /dev/null +++ b/source/blender/geometry/intern/subdivide_curves.cc @@ -0,0 +1,429 @@ +/* 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_task.hh" + +#include "GEO_subdivide_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}; +} + +static void calculate_result_offsets(const bke::CurvesGeometry &src_curves, + const IndexMask selection, + const Span<IndexRange> unselected_ranges, + const VArray<int> &cuts, + const Span<bool> cyclic, + MutableSpan<int> dst_curve_offsets, + MutableSpan<int> dst_point_offsets) +{ + /* Fill the array with each curve's point count, then accumulate them to the offsets. */ + bke::curves::fill_curve_counts(src_curves, unselected_ranges, dst_curve_offsets); + threading::parallel_for(selection.index_range(), 1024, [&](IndexRange range) { + for (const int curve_i : selection.slice(range)) { + const IndexRange src_points = src_curves.points_for_curve(curve_i); + const IndexRange src_segments = curve_dst_offsets(src_points, curve_i); + + MutableSpan<int> point_offsets = dst_point_offsets.slice(src_segments); + + MutableSpan<int> point_counts = point_offsets.drop_back(1); + cuts.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]) { + /* The last point only has a segment to be subdivided if the curve isn't cyclic. */ + point_counts.last() = 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); +} + +template<typename T> +static inline void linear_interpolation(const T &a, const T &b, MutableSpan<T> dst) +{ + dst.first() = a; + const float step = 1.0f / dst.size(); + for (const int i : dst.index_range().drop_front(1)) { + dst[i] = attribute_math::mix2(i * step, a, b); + } +} + +template<typename T> +static void subdivide_attribute_linear(const bke::CurvesGeometry &src_curves, + const bke::CurvesGeometry &dst_curves, + const IndexMask selection, + const Span<int> point_offsets, + const Span<T> src, + MutableSpan<T> dst) +{ + threading::parallel_for(selection.index_range(), 512, [&](IndexRange selection_range) { + for (const int curve_i : selection.slice(selection_range)) { + const IndexRange src_points = src_curves.points_for_curve(curve_i); + const IndexRange src_segments = curve_dst_offsets(src_points, curve_i); + const Span<int> offsets = point_offsets.slice(src_segments); + + const IndexRange dst_points = dst_curves.points_for_curve(curve_i); + const Span<T> curve_src = src.slice(src_points); + MutableSpan<T> curve_dst = dst.slice(dst_points); + + threading::parallel_for(curve_src.index_range().drop_back(1), 1024, [&](IndexRange range) { + for (const int i : range) { + const IndexRange segment_points = bke::offsets_to_range(offsets, i); + linear_interpolation(curve_src[i], curve_src[i + 1], curve_dst.slice(segment_points)); + } + }); + + const IndexRange dst_last_segment = dst_points.slice( + bke::offsets_to_range(offsets, src_points.size() - 1)); + linear_interpolation(curve_src.last(), curve_src.first(), dst.slice(dst_last_segment)); + } + }); +} + +static void subdivide_attribute_linear(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); + subdivide_attribute_linear( + src_curves, dst_curves, selection, point_offsets, src.typed<T>(), dst.typed<T>()); + }); +} + +template<typename T> +static void subdivide_attribute_catmull_rom(const bke::CurvesGeometry &src_curves, + const bke::CurvesGeometry &dst_curves, + const IndexMask selection, + const Span<int> point_offsets, + const Span<bool> cyclic, + const Span<T> src, + MutableSpan<T> dst) +{ + threading::parallel_for(selection.index_range(), 512, [&](IndexRange selection_range) { + for (const int curve_i : selection.slice(selection_range)) { + const IndexRange src_points = src_curves.points_for_curve(curve_i); + const IndexRange src_segments = curve_dst_offsets(src_points, curve_i); + const IndexRange dst_points = dst_curves.points_for_curve(curve_i); + + bke::curves::catmull_rom::interpolate_to_evaluated(src.slice(src_points), + cyclic[curve_i], + point_offsets.slice(src_segments), + dst.slice(dst_points)); + } + }); +} + +static void subdivide_attribute_catmull_rom(const bke::CurvesGeometry &src_curves, + const bke::CurvesGeometry &dst_curves, + const IndexMask selection, + const Span<int> point_offsets, + const Span<bool> cyclic, + const GSpan src, + GMutableSpan dst) +{ + attribute_math::convert_to_static_type(dst.type(), [&](auto dummy) { + using T = decltype(dummy); + subdivide_attribute_catmull_rom( + src_curves, dst_curves, selection, point_offsets, cyclic, src.typed<T>(), dst.typed<T>()); + }); +} + +static void subdivide_bezier_segment(const float3 &position_prev, + const float3 &handle_prev, + const float3 &handle_next, + const float3 &position_next, + const HandleType type_prev, + const HandleType type_next, + const IndexRange segment_points, + MutableSpan<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 bool is_last_cyclic_segment) +{ + auto fill_segment_handle_types = [&](const HandleType type) { + /* Also change the left handle of the control point following the segment's points. And don't + * change the left handle of the first point, since that is part of the previous segment. */ + dst_types_l.slice(segment_points.shift(1)).fill(type); + dst_types_r.slice(segment_points).fill(type); + }; + + if (bke::curves::bezier::segment_is_vector(type_prev, type_next)) { + linear_interpolation(position_prev, position_next, dst_positions.slice(segment_points)); + fill_segment_handle_types(BEZIER_HANDLE_VECTOR); + } + else { + /* The first point in the segment is always copied. */ + dst_positions[segment_points.first()] = position_prev; + + /* Non-vector segments in the result curve 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. */ + fill_segment_handle_types(BEZIER_HANDLE_FREE); + + /* In order to generate a Bezier curve with the same shape as the input curve, 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. */ + float3 segment_start = position_prev; + float3 segment_handle_prev = handle_prev; + float3 segment_handle_next = handle_next; + const float3 segment_end = position_next; + + for (const int i : IndexRange(segment_points.size() - 1)) { + const float parameter = 1.0f / (segment_points.size() - i); + const int point_i = segment_points[i]; + bke::curves::bezier::Insertion insert = bke::curves::bezier::insert( + segment_start, segment_handle_prev, segment_handle_next, segment_end, parameter); + + /* Copy relevant temporary data to the result. */ + dst_handles_r[point_i] = insert.handle_prev; + dst_handles_l[point_i + 1] = insert.left_handle; + dst_positions[point_i + 1] = insert.position; + + /* Update the segment to prepare it for the next subdivision. */ + segment_start = insert.position; + segment_handle_prev = insert.right_handle; + segment_handle_next = insert.handle_next; + } + + /* Copy the handles for the last segment from the working variables. */ + const int i_segment_last = is_last_cyclic_segment ? 0 : segment_points.one_after_last(); + dst_handles_r[segment_points.last()] = segment_handle_prev; + dst_handles_l[i_segment_last] = segment_handle_next; + } +} + +static void subdivide_bezier_positions(const Span<float3> src_positions, + const Span<int8_t> src_types_l, + const Span<int8_t> src_types_r, + const Span<float3> src_handles_l, + const Span<float3> src_handles_r, + const Span<int> evaluated_offsets, + const bool cyclic, + MutableSpan<float3> dst_positions, + MutableSpan<int8_t> dst_types_l, + MutableSpan<int8_t> dst_types_r, + MutableSpan<float3> dst_handles_l, + MutableSpan<float3> dst_handles_r) +{ + threading::parallel_for(src_positions.index_range().drop_back(1), 512, [&](IndexRange range) { + for (const int segment_i : range) { + const IndexRange segment = bke::offsets_to_range(evaluated_offsets, segment_i); + subdivide_bezier_segment(src_positions[segment_i], + src_handles_r[segment_i], + src_handles_l[segment_i + 1], + src_positions[segment_i + 1], + HandleType(src_types_r[segment_i]), + HandleType(src_types_l[segment_i + 1]), + segment, + dst_positions, + dst_handles_l, + dst_handles_r, + dst_types_l, + dst_types_r, + false); + } + }); + + if (cyclic) { + const int last_index = src_positions.index_range().last(); + const IndexRange segment = bke::offsets_to_range(evaluated_offsets, last_index); + const HandleType type_prev = HandleType(src_types_r.last()); + const HandleType type_next = HandleType(src_types_l.first()); + subdivide_bezier_segment(src_positions.last(), + src_handles_r.last(), + src_handles_l.first(), + src_positions.first(), + type_prev, + type_next, + segment, + dst_positions, + dst_handles_l, + dst_handles_r, + dst_types_l, + dst_types_r, + true); + + if (bke::curves::bezier::segment_is_vector(type_prev, type_next)) { + dst_types_l.first() = BEZIER_HANDLE_VECTOR; + dst_types_r.last() = BEZIER_HANDLE_VECTOR; + } + else { + dst_types_l.first() = BEZIER_HANDLE_FREE; + dst_types_r.last() = BEZIER_HANDLE_FREE; + } + } + else { + dst_positions.last() = src_positions.last(); + dst_types_l.first() = src_types_l.first(); + dst_types_r.last() = src_types_r.last(); + dst_handles_l.first() = src_handles_l.first(); + dst_handles_r.last() = src_handles_r.last(); + } + + /* TODO: It would be possible to avoid calling this for all segments besides vector segments. */ + bke::curves::bezier::calculate_auto_handles( + cyclic, dst_types_l, dst_types_r, dst_positions, dst_handles_l, dst_handles_r); +} + +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()); + + /* Cyclic is accessed a lot, it's probably worth it to make sure it's a span. */ + const VArraySpan<bool> cyclic{src_curves.cyclic()}; + + 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 + * look like this: + * + * | | Curve 0 | Curve 1 | + * | ------------------- |---|---|---|---|---|---|---|---|---|----| + * | Cuts | 0 | 3 | 0 | 0 | - | 2 | 0 | 0 | 4 | - | + * | New Point Count | 1 | 4 | 1 | 1 | - | 3 | 1 | 1 | 5 | - | + * | Accumulated Offsets | 0 | 1 | 5 | 6 | 7 | 0 | 3 | 4 | 5 | 10 | + * + * Storing the leading zero is unnecessary but makes the array a bit simpler to use by avoiding + * a check for the first segment, and because some existing utilities also use leading zeros. */ + Array<int> dst_point_offsets(src_curves.points_num() + src_curves.curves_num()); +#ifdef DEBUG + dst_point_offsets.fill(-1); +#endif + calculate_result_offsets(src_curves, + selection, + unselected_ranges, + cuts, + 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()); + + 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 : bke::retrieve_attributes_for_transfer( + src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) { + subdivide_attribute_catmull_rom(src_curves, + dst_curves, + selection, + point_offsets, + cyclic, + attribute.src, + attribute.dst.span); + attribute.dst.finish(); + } + }; + + auto subdivide_poly = [&](IndexMask selection) { + 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(); + } + }; + + auto subdivide_bezier = [&](IndexMask selection) { + const Span<float3> src_positions = src_curves.positions(); + const VArraySpan<int8_t> src_types_l{src_curves.handle_types_left()}; + const VArraySpan<int8_t> src_types_r{src_curves.handle_types_right()}; + const Span<float3> src_handles_l = src_curves.handle_positions_left(); + const Span<float3> src_handles_r = src_curves.handle_positions_right(); + + MutableSpan<float3> dst_positions = dst_curves.positions_for_write(); + MutableSpan<int8_t> dst_types_l = dst_curves.handle_types_left_for_write(); + MutableSpan<int8_t> dst_types_r = dst_curves.handle_types_right_for_write(); + MutableSpan<float3> dst_handles_l = dst_curves.handle_positions_left_for_write(); + MutableSpan<float3> dst_handles_r = dst_curves.handle_positions_right_for_write(); + + 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 src_segments = curve_dst_offsets(src_points, curve_i); + + const IndexRange dst_points = dst_curves.points_for_curve(curve_i); + subdivide_bezier_positions(src_positions.slice(src_points), + src_types_l.slice(src_points), + src_types_r.slice(src_points), + src_handles_l.slice(src_points), + src_handles_r.slice(src_points), + point_offsets.slice(src_segments), + cyclic[curve_i], + dst_positions.slice(dst_points), + dst_types_l.slice(dst_points), + dst_types_r.slice(dst_points), + dst_handles_l.slice(dst_points), + dst_handles_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"})) { + subdivide_attribute_linear( + src_curves, dst_curves, selection, point_offsets, attribute.src, attribute.dst.span); + attribute.dst.finish(); + } + }; + + /* NURBS curves are just treated as poly curves. NURBS subdivision that maintains + * their shape may be possible, but probably wouldn't work with the "cuts" input. */ + auto subdivide_nurbs = subdivide_poly; + + bke::curves::foreach_curve_by_type(src_curves.curve_types(), + src_curves.curve_type_counts(), + selection, + subdivide_catmull_rom, + subdivide_poly, + subdivide_bezier, + subdivide_nurbs); + + 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; +} + +} // 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); } } |