Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/geometry')
-rw-r--r--source/blender/geometry/CMakeLists.txt6
-rw-r--r--source/blender/geometry/GEO_add_curves_on_mesh.hh29
-rw-r--r--source/blender/geometry/GEO_fillet_curves.hh23
-rw-r--r--source/blender/geometry/GEO_mesh_to_curve.hh12
-rw-r--r--source/blender/geometry/GEO_point_merge_by_distance.hh2
-rw-r--r--source/blender/geometry/GEO_reverse_uv_sampler.hh4
-rw-r--r--source/blender/geometry/GEO_set_curve_type.hh32
-rw-r--r--source/blender/geometry/GEO_subdivide_curves.hh24
-rw-r--r--source/blender/geometry/GEO_uv_parametrizer.h5
-rw-r--r--source/blender/geometry/intern/add_curves_on_mesh.cc182
-rw-r--r--source/blender/geometry/intern/fillet_curves.cc561
-rw-r--r--source/blender/geometry/intern/mesh_primitive_cuboid.cc12
-rw-r--r--source/blender/geometry/intern/mesh_to_curve_convert.cc43
-rw-r--r--source/blender/geometry/intern/point_merge_by_distance.cc44
-rw-r--r--source/blender/geometry/intern/realize_instances.cc321
-rw-r--r--source/blender/geometry/intern/resample_curves.cc72
-rw-r--r--source/blender/geometry/intern/reverse_uv_sampler.cc85
-rw-r--r--source/blender/geometry/intern/set_curve_type.cc689
-rw-r--r--source/blender/geometry/intern/subdivide_curves.cc429
-rw-r--r--source/blender/geometry/intern/uv_parametrizer.c112
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);
}
}