Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,529 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_vector.hh"
#include "BLI_kdtree.hh"
#include "BLI_length_parameterize.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_matrix.hh"
#include "BLI_math_rotation.h"
#include "BLI_task.hh"
#include "BKE_attribute_math.hh"
#include "BKE_mesh.hh"
#include "BKE_mesh_sample.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:
* cu: Local space of the curves object that is being edited.
* su: Local space of the surface object.
*/
namespace blender::geometry {
using bke::CurvesGeometry;
struct NeighborCurve {
/* Curve index of the neighbor. */
int index;
/* The weights of all neighbors of a new curve add up to 1. */
float weight;
};
static constexpr int max_neighbors = 5;
using NeighborCurves = Vector<NeighborCurve, max_neighbors>;
float3 compute_surface_point_normal(const int3 &corner_tri,
const float3 &bary_coord,
const Span<float3> corner_normals)
{
const float3 value = bke::mesh_surface_sample::sample_corner_attribute_with_bary_coords(
bary_coord, corner_tri, corner_normals);
return math::normalize(value);
}
static void calc_straight_curve_positions(const float3 &a,
const float3 &b,
MutableSpan<float3> dst)
{
const float step = math::rcp(float(dst.size() - 1));
for (const int i : dst.index_range()) {
dst[i] = bke::attribute_math::mix2(i * step, a, b);
}
}
static void find_curve_neighbors(const Span<float3> root_positions,
const KDTree<float3> &old_roots_kdtree,
Vector<int> &offset_data,
Vector<int> &index_data,
Vector<float> &weight_data)
{
PRF_scope(ProfileCategory::Default);
const int tot_added_curves = root_positions.size();
Array<NeighborCurves> neighbors_per_curve(tot_added_curves);
threading::parallel_for(IndexRange(tot_added_curves), 128, [&](const IndexRange range) {
for (const int i : range) {
const float3 root = root_positions[i];
std::array<KDTreeNearest<float3>, max_neighbors> nearest_n;
const int found_neighbors = kdtree_find_nearest_n<float3>(
&old_roots_kdtree, root, nearest_n.data(), max_neighbors);
float tot_weight = 0.0f;
for (const int neighbor_i : IndexRange(found_neighbors)) {
KDTreeNearest<float3> &nearest = nearest_n[neighbor_i];
const float weight = 1.0f / std::max(nearest.dist, 0.00001f);
tot_weight += weight;
neighbors_per_curve[i].append({nearest.index, weight});
}
/* Normalize weights. */
for (NeighborCurve &neighbor : neighbors_per_curve[i]) {
neighbor.weight /= tot_weight;
}
}
});
offset_data.resize(tot_added_curves + 1);
threading::parallel_for(neighbors_per_curve.index_range(), 4096, [&](const IndexRange range) {
for (const int i : range) {
offset_data[i] = neighbors_per_curve[i].size();
}
});
const OffsetIndices offsets = offset_indices::accumulate_counts_to_offsets(offset_data);
index_data.resize(offsets.total_size());
weight_data.resize(offsets.total_size());
threading::parallel_for(offsets.index_range(), 2048, [&](const IndexRange range) {
for (const int dst_i : range) {
const IndexRange neighbor_range = offsets[dst_i];
for (const int i : neighbor_range.index_range()) {
index_data[neighbor_range[i]] = neighbors_per_curve[dst_i][i].index;
weight_data[neighbor_range[i]] = neighbors_per_curve[dst_i][i].weight;
}
}
});
}
static void calc_position_without_interpolation(CurvesGeometry &curves,
const int old_curves_num,
const Span<float3> root_positions_cu,
const Span<float> new_lengths_cu,
const Span<float3> new_normals_su,
const float4x4 &surface_to_curves_normal_mat)
{
PRF_scope(ProfileCategory::Default);
const int added_curves_num = root_positions_cu.size();
const OffsetIndices points_by_curve = curves.points_by_curve();
MutableSpan<float3> positions_cu = curves.positions_for_write();
threading::parallel_for(IndexRange(added_curves_num), 256, [&](const IndexRange range) {
for (const int i : range) {
const int curve_i = old_curves_num + i;
const IndexRange points = points_by_curve[curve_i];
const float3 &root_cu = root_positions_cu[i];
const float length = new_lengths_cu[i];
const float3 &normal_su = new_normals_su[i];
const float3 normal_cu = math::normalize(
math::transform_direction(surface_to_curves_normal_mat, normal_su));
const float3 tip_cu = root_cu + length * normal_cu;
calc_straight_curve_positions(root_cu, tip_cu, positions_cu.slice(points));
}
});
}
static void calc_position_with_interpolation(CurvesGeometry &curves,
const Span<float3> root_positions_cu,
const OffsetIndices<int> neighbor_offsets,
const Span<int> neighbor_indices,
const Span<float> neighbor_weights,
const int old_curves_num,
const Span<float> new_lengths_cu,
const Span<float3> new_normals_su,
const bke::CurvesSurfaceTransforms &transforms,
const Span<int3> corner_tris,
const ReverseUVSampler &reverse_uv_sampler,
const Span<float3> corner_normals_su)
{
PRF_scope(ProfileCategory::Default);
MutableSpan<float3> positions_cu = curves.positions_for_write();
const int added_curves_num = root_positions_cu.size();
const OffsetIndices points_by_curve = curves.points_by_curve();
const Span<float2> uv_coords = *curves.surface_uv_coords();
threading::parallel_for(IndexRange(added_curves_num), 256, [&](const IndexRange range) {
for (const int added_curve_i : range) {
const IndexRange neighbors = neighbor_offsets[added_curve_i];
const int curve_i = old_curves_num + added_curve_i;
const IndexRange points = points_by_curve[curve_i];
const float length_cu = new_lengths_cu[added_curve_i];
const float3 &normal_su = new_normals_su[added_curve_i];
const float3 normal_cu = math::normalize(
math::transform_direction(transforms.surface_to_curves_normal, normal_su));
const float3 &root_cu = root_positions_cu[added_curve_i];
if (neighbors.is_empty()) {
/* If there are no neighbors, just make a straight line. */
const float3 tip_cu = root_cu + length_cu * normal_cu;
calc_straight_curve_positions(root_cu, tip_cu, positions_cu.slice(points));
continue;
}
positions_cu.slice(points).fill(root_cu);
for (const int neighbor_i : neighbors) {
const int neighbor_curve_i = neighbor_indices[neighbor_i];
const float neighbor_weight = neighbor_weights[neighbor_i];
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(
corner_tris[result.tri_index], result.bary_weights, corner_normals_su);
const float3 neighbor_normal_cu = math::normalize(
math::transform_direction(transforms.surface_to_curves_normal, neighbor_normal_su));
/* The rotation matrix used to transform relative coordinates of the neighbor curve
* to the new curve. */
float normal_rotation_cu[3][3];
rotation_between_vecs_to_mat3(normal_rotation_cu, neighbor_normal_cu, normal_cu);
const IndexRange neighbor_points = points_by_curve[neighbor_curve_i];
const float3 &neighbor_root_cu = positions_cu[neighbor_points[0]];
/* 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();
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 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[i]] += neighbor_weight * rotated_relative_coord;
}
}
}
});
}
static void calc_radius_without_interpolation(CurvesGeometry &curves,
const IndexRange new_points_range,
const float radius)
{
const VArray<float> radii = curves.radius();
if (const std::optional<float> single = radii.get_if_single()) {
if (compare_ff_relative(*single, radius, FLT_EPSILON, 16)) {
return;
}
}
curves.radius_for_write().slice(new_points_range).fill(radius);
curves.tag_radii_changed();
}
static void calc_radius_with_interpolation(CurvesGeometry &curves,
const int old_curves_num,
const float radius,
const Span<float> new_lengths_cu,
const OffsetIndices<int> neighbor_offsets,
const Span<int> neighbor_indices,
const Span<float> neighbor_weights)
{
const int added_curves_num = new_lengths_cu.size();
const OffsetIndices points_by_curve = curves.points_by_curve();
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
bke::SpanAttributeWriter radius_attr = attributes.lookup_for_write_span<float>("radius");
if (!radius_attr) {
return;
}
MutableSpan<float3> positions_cu = curves.positions_for_write();
MutableSpan<float> radii_cu = radius_attr.span;
threading::parallel_for(IndexRange(added_curves_num), 256, [&](const IndexRange range) {
for (const int i : range) {
const IndexRange neighbors = neighbor_offsets[i];
const float length_cu = new_lengths_cu[i];
const int curve_i = old_curves_num + i;
const IndexRange points = points_by_curve[curve_i];
if (neighbors.is_empty()) {
/* If there are no neighbors, just using uniform radius. */
radii_cu.slice(points).fill(radius);
continue;
}
radii_cu.slice(points).fill(0.0f);
for (const int neighbor_i : neighbors) {
const int neighbor_curve_i = neighbor_indices[neighbor_i];
const float neighbor_weight = neighbor_weights[neighbor_i];
const IndexRange neighbor_points = points_by_curve[neighbor_curve_i];
const Span<float3> neighbor_positions_cu = positions_cu.slice(neighbor_points);
const Span<float> neighbor_radii_cu = radius_attr.span.slice(neighbor_points);
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();
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 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 float sample_cu = math::interpolate(
neighbor_radii_cu[indices[i]], neighbor_radii_cu[indices[i] + 1], factors[i]);
radii_cu[points[i]] += neighbor_weight * sample_cu;
}
}
}
});
radius_attr.finish();
}
AddCurvesOnMeshOutputs add_curves_on_mesh(CurvesGeometry &curves,
const AddCurvesOnMeshInputs &inputs)
{
PRF_scope(ProfileCategory::Default);
AddCurvesOnMeshOutputs outputs;
const bool use_interpolation = inputs.interpolate_length || inputs.interpolate_point_count ||
inputs.interpolate_radius || inputs.interpolate_shape ||
inputs.interpolate_resolution;
Vector<float3> root_positions_cu;
Vector<float3> bary_coords;
Vector<int> tri_indices;
Vector<float2> used_uvs;
/* Find faces that the passed in uvs belong to. */
const Span<float3> surface_positions = inputs.surface->vert_positions();
const Span<int> surface_corner_verts = inputs.surface->corner_verts();
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 int3 &tri = inputs.surface_corner_tris[result.tri_index];
bary_coords.append(result.bary_weights);
tri_indices.append(result.tri_index);
const float3 root_position_su = bke::attribute_math::mix3<float3>(
result.bary_weights,
surface_positions[surface_corner_verts[tri[0]]],
surface_positions[surface_corner_verts[tri[1]]],
surface_positions[surface_corner_verts[tri[2]]]);
root_positions_cu.append(
math::transform_point(inputs.transforms->surface_to_curves, root_position_su));
used_uvs.append(uv);
}
Vector<int> curve_neighbor_offset_data;
Vector<int> curve_neighbor_index_data;
Vector<float> curve_neighbor_weight_data;
if (use_interpolation) {
BLI_assert(inputs.old_roots_kdtree != nullptr);
find_curve_neighbors(root_positions_cu,
*inputs.old_roots_kdtree,
curve_neighbor_offset_data,
curve_neighbor_index_data,
curve_neighbor_weight_data);
}
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;
/* Grow number of curves first, so that the offsets array can be filled. */
curves.resize(old_points_num, new_curves_num);
if (new_curves_num == 0) {
return outputs;
}
/* Compute new curve offsets. */
MutableSpan<int> curve_offsets = curves.offsets_for_write();
Array<int> new_point_counts_per_curve(added_curves_num);
if (inputs.interpolate_point_count && old_curves_num > 0) {
const OffsetIndices<int> old_points_by_curve{curve_offsets.take_front(old_curves_num + 1)};
Array<int> sizes(old_curves_num);
offset_indices::copy_group_sizes(old_points_by_curve, sizes.index_range(), sizes);
bke::attribute_math::mix_groups(sizes.as_span(),
OffsetIndices(curve_neighbor_offset_data.as_span()),
curve_neighbor_index_data.as_span(),
curve_neighbor_weight_data.as_span(),
new_point_counts_per_curve.as_mutable_span());
}
else {
new_point_counts_per_curve.fill(inputs.fallback_point_count);
}
curve_offsets[old_curves_num] = old_points_num;
int offset = old_points_num;
for (const int i : new_point_counts_per_curve.index_range()) {
const int point_count_in_curve = new_point_counts_per_curve[i];
curve_offsets[old_curves_num + i + 1] = offset + point_count_in_curve;
offset += point_count_in_curve;
}
const int new_points_num = curves.offsets().last();
curves.resize(new_points_num, new_curves_num);
const OffsetIndices points_by_curve = curves.points_by_curve();
/* The new elements are added at the end of the arrays. */
outputs.new_points_range = curves.points_range().drop_front(old_points_num);
outputs.new_curves_range = curves.curves_range().drop_front(old_curves_num);
/* 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. */
Span<float3> positions_cu = curves.positions();
Array<float> new_lengths_cu(added_curves_num);
if (inputs.interpolate_length) {
Array<float> lengths(old_curves_num);
threading::parallel_for(IndexRange(old_curves_num), 256, [&](const IndexRange range) {
for (const int curve_i : range) {
const IndexRange points = points_by_curve[curve_i];
float length = 0.0f;
for (const int segment_i : points.drop_back(1)) {
const float3 &p1 = positions_cu[segment_i];
const float3 &p2 = positions_cu[segment_i + 1];
length += math::distance(p1, p2);
}
lengths[curve_i] = length;
}
});
bke::attribute_math::mix_groups(lengths.as_span(),
OffsetIndices(curve_neighbor_offset_data.as_span()),
curve_neighbor_index_data.as_span(),
curve_neighbor_weight_data.as_span(),
new_lengths_cu.as_mutable_span());
}
else {
new_lengths_cu.fill(inputs.fallback_curve_length);
}
/* Find surface normal at root points. */
Array<float3> new_normals_su(added_curves_num);
bke::mesh_surface_sample::sample_corner_normals(inputs.surface_corner_tris,
tri_indices,
bary_coords,
inputs.corner_normals_su,
IndexMask(added_curves_num),
new_normals_su);
/* Initialize position attribute. */
if (inputs.interpolate_shape) {
calc_position_with_interpolation(curves,
root_positions_cu,
OffsetIndices(curve_neighbor_offset_data.as_span()),
curve_neighbor_index_data.as_span(),
curve_neighbor_weight_data.as_span(),
old_curves_num,
new_lengths_cu,
new_normals_su,
*inputs.transforms,
inputs.surface_corner_tris,
*inputs.reverse_uv_sampler,
inputs.corner_normals_su);
}
else {
calc_position_without_interpolation(curves,
old_curves_num,
root_positions_cu,
new_lengths_cu,
new_normals_su,
inputs.transforms->surface_to_curves_normal);
}
/* Initialize radius attribute */
if (inputs.interpolate_radius) {
calc_radius_with_interpolation(curves,
old_curves_num,
inputs.fallback_curve_radius,
new_lengths_cu,
OffsetIndices(curve_neighbor_offset_data.as_span()),
curve_neighbor_index_data.as_span(),
curve_neighbor_weight_data.as_span());
}
else {
calc_radius_without_interpolation(
curves, outputs.new_points_range, inputs.fallback_curve_radius);
}
curves.fill_curve_types(outputs.new_curves_range, CURVE_TYPE_CATMULL_ROM);
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
if (bke::SpanAttributeWriter<int> resolution = attributes.lookup_for_write_span<int>(
"resolution"))
{
if (inputs.interpolate_resolution) {
bke::attribute_math::mix_groups(resolution.span,
OffsetIndices(curve_neighbor_offset_data.as_span()),
curve_neighbor_index_data.as_span(),
curve_neighbor_weight_data.as_span(),
resolution.span.take_back(added_curves_num));
resolution.finish();
}
else {
resolution.span.take_back(added_curves_num).fill(12);
}
}
/* Explicitly set all other attributes besides those processed above to default values. */
bke::fill_attribute_range_default(attributes,
bke::AttrDomain::Point,
bke::attribute_filter_from_skip_ref({"position", "radius"}),
outputs.new_points_range);
bke::fill_attribute_range_default(
attributes,
bke::AttrDomain::Curve,
bke::attribute_filter_from_skip_ref({"curve_type", "surface_uv_coordinate", "resolution"}),
outputs.new_curves_range);
return outputs;
}
} // namespace blender::geometry

View File

@@ -0,0 +1,188 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_matrix.hh"
#include "BLI_task.hh"
#include "DNA_mesh_types.h"
#include "GEO_curve_constraints.hh"
#include "BKE_bvhutils.hh"
/**
* The code below uses a prefix naming convention to indicate the coordinate space:
* `cu`: Local space of the curves object that is being edited.
* `su`: Local space of the surface object.
* `wo`: World space.
*/
namespace blender::geometry::curve_constraints {
void compute_segment_lengths(const OffsetIndices<int> points_by_curve,
const Span<float3> positions,
const IndexMask &curve_selection,
MutableSpan<float> r_segment_lengths)
{
BLI_assert(r_segment_lengths.size() == points_by_curve.total_size());
curve_selection.foreach_segment(
[&](const IndexMaskSegment segment) {
for (const int curve_i : segment) {
const IndexRange points = points_by_curve[curve_i].drop_back(1);
for (const int point_i : points) {
const float3 &p1 = positions[point_i];
const float3 &p2 = positions[point_i + 1];
const float length = math::distance(p1, p2);
r_segment_lengths[point_i] = length;
}
}
},
exec_mode::grain_size(256));
}
void solve_length_constraints(const OffsetIndices<int> points_by_curve,
const IndexMask &curve_selection,
const Span<float> segment_lenghts,
MutableSpan<float3> positions)
{
BLI_assert(segment_lenghts.size() == points_by_curve.total_size());
curve_selection.foreach_segment(
[&](const IndexMaskSegment segment) {
for (const int curve_i : segment) {
const IndexRange points = points_by_curve[curve_i].drop_back(1);
for (const int point_i : points) {
const float3 &p1 = positions[point_i];
float3 &p2 = positions[point_i + 1];
const float3 direction = math::normalize(p2 - p1);
const float goal_length = segment_lenghts[point_i];
p2 = p1 + direction * goal_length;
}
}
},
exec_mode::grain_size(256));
}
void solve_length_and_collision_constraints(const OffsetIndices<int> points_by_curve,
const IndexMask &curve_selection,
const Span<float> segment_lengths_cu,
const Span<float3> start_positions_cu,
const Mesh &surface,
const bke::CurvesSurfaceTransforms &transforms,
MutableSpan<float3> positions_cu,
const float surface_collision_distance)
{
solve_length_constraints(points_by_curve, curve_selection, segment_lengths_cu, positions_cu);
bke::BVHTreeFromMesh surface_bvh = surface.bvh_corner_tris();
const int max_collisions = 5;
curve_selection.foreach_segment(
[&](const IndexMaskSegment segment) {
for (const int curve_i : segment) {
const IndexRange points = points_by_curve[curve_i];
/* Sometimes not all collisions can be handled. This happens relatively rarely, but if it
* happens it's better to just not to move the curve instead of going into the surface.
*/
bool revert_curve = false;
for (const int point_i : points.drop_front(1)) {
const float goal_segment_length_cu = segment_lengths_cu[point_i - 1];
const float3 &prev_pos_cu = positions_cu[point_i - 1];
const float3 &start_pos_cu = start_positions_cu[point_i];
int used_iterations = 0;
for ([[maybe_unused]] const int iteration : IndexRange(max_collisions)) {
used_iterations++;
const float3 &old_pos_cu = positions_cu[point_i];
if (start_pos_cu == old_pos_cu) {
/* The point did not move, done. */
break;
}
/* Check if the point moved through a surface. */
const float3 start_pos_su = math::transform_point(transforms.curves_to_surface,
start_pos_cu);
const float3 old_pos_su = math::transform_point(transforms.curves_to_surface,
old_pos_cu);
const float3 pos_diff_su = old_pos_su - start_pos_su;
float max_ray_length_su;
const float3 ray_direction_su = math::normalize_and_get_length(pos_diff_su,
max_ray_length_su);
BVHTreeRayHit hit;
hit.index = -1;
hit.dist = max_ray_length_su + surface_collision_distance;
BLI_bvhtree_ray_cast(surface_bvh.tree,
start_pos_su,
ray_direction_su,
surface_collision_distance,
&hit,
surface_bvh.raycast_callback,
&surface_bvh);
if (hit.index == -1) {
break;
}
const float3 hit_pos_su = hit.co;
const float3 hit_normal_su = hit.no;
if (math::dot(hit_normal_su, ray_direction_su) > 0.0f) {
/* Moving from the inside to the outside is ok. */
break;
}
/* The point was moved through a surface. Now put it back on the correct side of the
* surface and slide it on the surface to keep the length the same. */
const float3 hit_pos_cu = math::transform_point(transforms.surface_to_curves,
hit_pos_su);
const float3 hit_normal_cu = math::normalize(
math::transform_direction(transforms.surface_to_curves_normal, hit_normal_su));
/* Slide on a plane that is slightly above the surface. */
const float3 plane_pos_cu = hit_pos_cu + hit_normal_cu * surface_collision_distance;
const float3 plane_normal_cu = hit_normal_cu;
/* Decompose the current segment into the part normal and tangent to the collision
* surface. */
const float3 collided_segment_cu = plane_pos_cu - prev_pos_cu;
const float3 slide_normal_cu = plane_normal_cu *
math::dot(collided_segment_cu, plane_normal_cu);
const float3 slide_direction_cu = collided_segment_cu - slide_normal_cu;
float slide_direction_length_cu;
const float3 normalized_slide_direction_cu = math::normalize_and_get_length(
slide_direction_cu, slide_direction_length_cu);
const float slide_normal_length_sq_cu = math::length_squared(slide_normal_cu);
if (pow2f(goal_segment_length_cu) > slide_normal_length_sq_cu) {
/* Use Pythagorean theorem to determine how far to slide. */
const float slide_distance_cu = std::sqrt(pow2f(goal_segment_length_cu) -
slide_normal_length_sq_cu) -
slide_direction_length_cu;
positions_cu[point_i] = plane_pos_cu +
normalized_slide_direction_cu * slide_distance_cu;
}
else {
/* Minimum distance is larger than allowed segment length.
* The unilateral collision constraint is satisfied by just clamping segment
* length. */
positions_cu[point_i] = prev_pos_cu + math::normalize(old_pos_su - prev_pos_cu) *
goal_segment_length_cu;
}
}
if (used_iterations == max_collisions) {
revert_curve = true;
break;
}
}
if (revert_curve) {
positions_cu.slice(points).copy_from(start_positions_cu.slice(points));
}
}
},
exec_mode::grain_size(64));
}
} // namespace blender::geometry::curve_constraints

View File

@@ -0,0 +1,187 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "BKE_attribute.hh"
#include "BKE_curves.hh"
#include "BKE_curves_utils.hh"
#include "BKE_deform.hh"
#include "BKE_grease_pencil_fills.hh"
#include "GEO_curves_remove_and_split.hh"
namespace blender::geometry {
bke::CurvesGeometry remove_points_and_split(const bke::CurvesGeometry &curves,
const IndexMask &mask)
{
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
const VArray<bool> src_cyclic = curves.cyclic();
Array<bool> points_to_delete(curves.points_num());
mask.to_bools(points_to_delete.as_mutable_span());
const int total_points = points_to_delete.as_span().count(false);
/* Return if deleting everything. */
if (total_points == 0) {
return {};
}
int curr_dst_point_id = 0;
Array<int> dst_to_src_point(total_points);
Vector<int> dst_curve_counts;
Vector<int> dst_to_src_curve;
Vector<bool> dst_cyclic;
for (const int curve_i : curves.curves_range()) {
const IndexRange points = points_by_curve[curve_i];
const Span<bool> curve_points_to_delete = points_to_delete.as_span().slice(points);
const bool curve_cyclic = src_cyclic[curve_i];
/* Note, these ranges start at zero and needed to be shifted by `points.first()` */
const Vector<IndexRange> ranges_to_keep = array_utils::find_all_ranges(curve_points_to_delete,
false);
if (ranges_to_keep.is_empty()) {
continue;
}
const bool is_last_segment_selected = curve_cyclic && ranges_to_keep.first().first() == 0 &&
ranges_to_keep.last().last() == points.size() - 1;
const bool is_curve_self_joined = is_last_segment_selected && ranges_to_keep.size() != 1;
const bool is_cyclic = ranges_to_keep.size() == 1 && is_last_segment_selected;
IndexRange range_ids = ranges_to_keep.index_range();
/* Skip the first range because it is joined to the end of the last range. */
for (const int range_i : ranges_to_keep.index_range().drop_front(is_curve_self_joined)) {
const IndexRange range = ranges_to_keep[range_i];
int count = range.size();
for (const int src_point : range.shift(points.first())) {
dst_to_src_point[curr_dst_point_id++] = src_point;
}
/* Join the first range to the end of the last range. */
if (is_curve_self_joined && range_i == range_ids.last()) {
const IndexRange first_range = ranges_to_keep[range_ids.first()];
for (const int src_point : first_range.shift(points.first())) {
dst_to_src_point[curr_dst_point_id++] = src_point;
}
count += first_range.size();
}
dst_curve_counts.append(count);
dst_to_src_curve.append(curve_i);
dst_cyclic.append(is_cyclic);
}
}
const int total_curves = dst_to_src_curve.size();
bke::CurvesGeometry dst_curves(total_points, total_curves);
BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &curves.vertex_group_names);
MutableSpan<int> new_curve_offsets = dst_curves.offsets_for_write();
array_utils::copy(dst_curve_counts.as_span(), new_curve_offsets.drop_back(1));
offset_indices::accumulate_counts_to_offsets(new_curve_offsets);
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
const bke::AttributeAccessor src_attributes = curves.attributes();
/* Transfer curve attributes. */
gather_attributes(src_attributes,
bke::AttrDomain::Curve,
bke::AttrDomain::Curve,
bke::attribute_filter_from_skip_ref({"cyclic"}),
dst_to_src_curve,
dst_attributes);
array_utils::copy(dst_cyclic.as_span(), dst_curves.cyclic_for_write());
/* Transfer point attributes. */
gather_attributes(src_attributes,
bke::AttrDomain::Point,
bke::AttrDomain::Point,
{},
dst_to_src_point,
dst_attributes);
dst_curves.update_curve_types();
dst_curves.remove_attributes_based_on_types();
if (curves.nurbs_has_custom_knots()) {
bke::curves::nurbs::update_custom_knot_modes(
dst_curves.curves_range(), NURBS_KNOT_MODE_NORMAL, NURBS_KNOT_MODE_NORMAL, dst_curves);
}
return dst_curves;
}
bke::CurvesGeometry grease_pencil_remove_points_and_split(const bke::CurvesGeometry &curves,
const IndexMask &mask)
{
bke::CurvesGeometry dst_curves = remove_points_and_split(curves, mask);
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
if (bke::SpanAttributeWriter<int> dst_fill_ids = dst_attributes.lookup_for_write_span<int>(
"fill_id"))
{
Array<bool> points_to_delete(curves.points_num());
mask.to_bools(points_to_delete.as_mutable_span());
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
const VArray<bool> src_cyclic = curves.cyclic();
Array<Vector<int>> src_to_dst_curve(curves.curves_num());
Vector<int> dst_to_src_curve;
for (const int curve_i : curves.curves_range()) {
const IndexRange points = points_by_curve[curve_i];
const Span<bool> curve_points_to_delete = points_to_delete.as_span().slice(points);
const bool curve_cyclic = src_cyclic[curve_i];
/* Note: These ranges start at zero and need to be shifted by `points.first()` */
const Vector<IndexRange> ranges_to_keep = array_utils::find_all_ranges(
curve_points_to_delete, false);
if (ranges_to_keep.is_empty()) {
continue;
}
const bool is_last_segment_selected = curve_cyclic && ranges_to_keep.first().first() == 0 &&
ranges_to_keep.last().last() == points.size() - 1;
const bool is_curve_self_joined = is_last_segment_selected && ranges_to_keep.size() != 1;
/* Skip the first range because it is joined to the end of the last range. */
const int num_dst_curves = ranges_to_keep.size() - int(is_curve_self_joined);
src_to_dst_curve[curve_i].resize(num_dst_curves);
array_utils::fill_index_range<int>(src_to_dst_curve[curve_i].as_mutable_span(),
dst_to_src_curve.size());
dst_to_src_curve.append_n_times(curve_i, num_dst_curves);
}
IndexMaskMemory memory;
/* Get all the curves that were split off of the original geometry. */
const IndexMask non_original_curves = IndexMask::from_predicate(
dst_to_src_curve.index_range(), memory, [&](const int64_t dst_curve_index) {
/* Skip non-filled curves. */
if (dst_fill_ids.span[dst_curve_index] == 0) {
return false;
}
const int src_curve_index = dst_to_src_curve[dst_curve_index];
return src_to_dst_curve[src_curve_index].first() != dst_curve_index;
});
bke::greasepencil::gather_next_available_fill_ids(
dst_fill_ids.span.varray(), non_original_curves, dst_fill_ids.span);
dst_fill_ids.finish();
}
return dst_curves;
}
} // namespace blender::geometry

View File

@@ -0,0 +1,370 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include "BLI_math_axis_angle.hh"
#include "BLI_math_matrix.hh"
#include "BLI_math_quaternion.hh"
#include "BLI_math_rotation.hh"
#include "BLI_math_vector.hh"
#include "BKE_attribute.hh"
#include "BKE_curves.hh"
#include "BKE_curves_utils.hh"
#include "BKE_geometry_set.hh"
#include "GEO_extend_curves.hh"
namespace blender::geometry {
static void extend_curve_straight(const float used_percent_length,
const float new_size,
const Span<int> start_points,
const Span<int> end_points,
const int curve,
const IndexRange new_curve,
const Span<float> use_start_lengths,
const Span<float> use_end_lengths,
MutableSpan<float3> positions)
{
float overshoot_point_param = used_percent_length * (new_size - 1);
if (start_points[curve]) {
/** Here we use the vector between two adjacent points around #overshoot_point_param as
* our reference for the direction of extension, however to have better tolerance for jitter,
* using the vector (a_few_points_back - end_point) might be a better solution in the future.
*/
int index1 = math::floor(overshoot_point_param);
int index2 = math::ceil(overshoot_point_param);
/* When #overshoot_point_param is zero */
if (index2 == 0) {
index2 = 1;
}
float3 result = math::interpolate(positions[new_curve[index1]],
positions[new_curve[index2]],
fmodf(overshoot_point_param, 1.0f));
result -= positions[new_curve.first()];
if (UNLIKELY(math::is_zero(result))) {
result = positions[new_curve[1]] - positions[new_curve[0]];
}
positions[new_curve[0]] += result * (-use_start_lengths[curve] / math::length(result));
}
if (end_points[curve]) {
int index1 = new_size - 1 - math::floor(overshoot_point_param);
int index2 = new_size - 1 - math::ceil(overshoot_point_param);
float3 result = math::interpolate(positions[new_curve[index1]],
positions[new_curve[index2]],
fmodf(overshoot_point_param, 1.0f));
result -= positions[new_curve.last()];
if (UNLIKELY(math::is_zero(result))) {
result = positions[new_curve[new_size - 2]] - positions[new_curve[new_size - 1]];
}
positions[new_curve[new_size - 1]] += result *
(-use_end_lengths[curve] / math::length(result));
}
}
static void extend_curve_curved(const float used_percent_length,
const Span<int> start_points,
const Span<int> end_points,
const OffsetIndices<int> points_by_curve,
const int curve,
const IndexRange new_curve,
const Span<float> use_start_lengths,
const Span<float> use_end_lengths,
const float max_angle,
const float segment_influence,
const bool invert_curvature,
MutableSpan<float3> positions)
{
/* Curvature calculation. */
const int first_old_index = start_points[curve] ? start_points[curve] : 0;
const int last_old_index = points_by_curve[curve].size() - 1 + first_old_index;
const int orig_totpoints = points_by_curve[curve].size();
/* The fractional amount of points to query when calculating the average curvature of the
* strokes. */
const float overshoot_parameter = used_percent_length * (orig_totpoints - 2);
int overshoot_pointcount = math::ceil(overshoot_parameter);
overshoot_pointcount = math::clamp(overshoot_pointcount, 1, orig_totpoints - 2);
/* Do for both sides without code duplication. */
float3 vec1, total_angle;
for (int k = 0; k < 2; k++) {
if ((k == 0 && !start_points[curve]) || (k == 1 && !end_points[curve])) {
continue;
}
const int start_i = k == 0 ? first_old_index : last_old_index;
const int dir_i = 1 - k * 2;
vec1 = positions[new_curve[start_i + dir_i]] - positions[new_curve[start_i]];
total_angle = float3({0, 0, 0});
float segment_length;
vec1 = math::normalize_and_get_length(vec1, segment_length);
float overshoot_length = 0.0f;
/* Accumulate rotation angle and length. */
int j = 0;
float3 no, vec2;
for (int i = start_i; j < overshoot_pointcount; i += dir_i, j++) {
/* Don't fully add last segment to get continuity in overshoot_fac. */
float fac = math::min(overshoot_parameter - j, 1.0f);
/* Read segments. */
vec2 = vec1;
vec1 = positions[new_curve[i + dir_i * 2]] - positions[new_curve[i + dir_i]];
float len;
vec1 = math::normalize_and_get_length(vec1, len);
float angle = math::angle_between(vec1, vec2).radian() * fac;
/* Add half of both adjacent legs of the current angle. */
const float added_len = (segment_length + len) * 0.5f * fac;
overshoot_length += added_len;
segment_length = len;
if (angle > max_angle) {
continue;
}
if (angle > M_PI * 0.995f) {
continue;
}
angle *= math::pow(added_len, segment_influence);
no = math::cross(vec1, vec2);
no = math::normalize(no) * angle;
total_angle += no;
}
if (UNLIKELY(overshoot_length == 0.0f)) {
/* Don't do a proper extension if the used points are all in the same position. */
continue;
}
vec1 = positions[new_curve[start_i]] - positions[new_curve[start_i + dir_i]];
/* In general curvature = 1/radius. For the case without the
* weights introduced by #segment_influence, the calculation is:
* `curvature = delta angle/delta arclength = len_v3(total_angle) / overshoot_length` */
float curvature = normalize_v3(total_angle) / overshoot_length;
/* Compensate for the weights powf(added_len, segment_influence). */
curvature /= math::pow(overshoot_length / math::min(overshoot_parameter, float(j)),
segment_influence);
if (invert_curvature) {
curvature = -curvature;
}
const float dist = k == 0 ? use_start_lengths[curve] : use_end_lengths[curve];
const int extra_point_count = k == 0 ? start_points[curve] : end_points[curve];
const float angle_step = curvature * dist / extra_point_count;
float step_length = dist / extra_point_count;
if (math::abs(angle_step) > FLT_EPSILON) {
/* Make a direct step length from the assigned arc step length. */
step_length *= sin(angle_step * 0.5f) / (angle_step * 0.5f);
}
else {
total_angle = float3({0, 0, 0});
}
float prev_length;
vec1 = math::normalize_and_get_length(vec1, prev_length);
vec1 *= step_length;
/* Build rotation matrix here to get best performance. */
math::AxisAngle axis_base(total_angle, angle_step);
math::Quaternion q = math::to_quaternion(axis_base);
float3x3 rot = math::from_rotation<float3x3>(q);
/* Rotate the starting direction to account for change in edge lengths. */
math::AxisAngle step_base(total_angle,
math::max(0.0f, 1.0f - math::abs(segment_influence)) *
(curvature * prev_length - angle_step) / 2.0f);
q = math::to_quaternion(step_base);
vec1 = math::transform_point(q, vec1);
/* Now iteratively accumulate the segments with a rotating added direction. */
for (int i = start_i - dir_i, j = 0; j < extra_point_count; i -= dir_i, j++) {
vec1 = rot * vec1;
positions[new_curve[i]] = vec1 + positions[new_curve[i + dir_i]];
}
}
}
bke::CurvesGeometry extend_curves(bke::CurvesGeometry &src_curves,
const IndexMask &selection,
const VArray<float> &start_lengths,
const VArray<float> &end_lengths,
const float overshoot_fac,
const bool follow_curvature,
const float point_density,
const float segment_influence,
const float max_angle,
const bool invert_curvature,
const GeometryNodeCurveSampleMode sample_mode,
const bke::AttributeFilter &attribute_filter)
{
if (src_curves.points_num() < 2) {
return src_curves;
}
if (selection.is_empty()) {
return src_curves;
}
const int src_curves_num = src_curves.curves_num();
/* Extra point count at the start/end of extended strokes. For straight extension, or for strokes
* with only 2 points (thus unable to curve), the value of their respective index should be set
* to 1 to allow #extend_curves_straight() to identify strokes to work on. */
Array<int> start_points(src_curves_num, 0);
Array<int> end_points(src_curves_num, 0);
Array<float> use_start_lengths(src_curves_num);
Array<float> use_end_lengths(src_curves_num);
const OffsetIndices<int> points_by_curve = src_curves.points_by_curve();
src_curves.ensure_evaluated_lengths();
selection.foreach_index([&](const int curve) {
use_start_lengths[curve] = start_lengths[curve];
use_end_lengths[curve] = end_lengths[curve];
if (sample_mode == GEO_NODE_CURVE_SAMPLE_FACTOR) {
float total_length = src_curves.evaluated_length_total_for_curve(curve, false);
use_start_lengths[curve] *= total_length;
use_end_lengths[curve] *= total_length;
}
});
bke::CurvesGeometry dst_curves;
if (!follow_curvature) {
/* Use the old curves when extending straight when no new points are added. */
dst_curves = std::move(src_curves);
/* Enable affected curves for #extend_curves_straight(). */
index_mask::masked_fill<int>(start_points, 1, selection);
index_mask::masked_fill<int>(end_points, 1, selection);
}
else {
/* Copy only curves domain since we are not changing the number of curves here. */
dst_curves = bke::curves::copy_only_curve_domain(src_curves);
MutableSpan<int> dst_points_by_curve = dst_curves.offsets_for_write();
offset_indices::copy_group_sizes(
src_curves.points_by_curve(), src_curves.curves_range(), dst_points_by_curve);
/* Count how many points we need. */
selection.foreach_index([&](const int curve) {
const int point_count = dst_points_by_curve[curve];
if (point_count <= 2) {
/* Can't make a curve, set start/end points to 1 to allow straight extension. */
start_points[curve] = 1;
end_points[curve] = 1;
return;
}
const int count_start = (use_start_lengths[curve] > 0) ?
math::ceil(use_start_lengths[curve] * point_density) :
0;
const int count_end = (use_end_lengths[curve] > 0) ?
math::ceil(use_end_lengths[curve] * point_density) :
0;
dst_points_by_curve[curve] += count_start;
dst_points_by_curve[curve] += count_end;
start_points[curve] = count_start;
end_points[curve] = count_end;
});
OffsetIndices dst_indices = offset_indices::accumulate_counts_to_offsets(dst_points_by_curve);
int target_point_count = dst_points_by_curve.last();
/* Make destination to source map for points. */
Array<int> dst_to_src_point(target_point_count);
for (const int curve : src_curves.curves_range()) {
const int point_count = points_by_curve[curve].size();
int local_front = 0;
MutableSpan<int> new_points_by_curve = dst_to_src_point.as_mutable_span().slice(
dst_indices[curve]);
if (point_count <= 2) {
for (const int point_i : new_points_by_curve.index_range()) {
new_points_by_curve[point_i] = points_by_curve[curve][point_i];
}
continue;
}
if (follow_curvature) {
MutableSpan<int> starts = new_points_by_curve.slice(0, start_points[curve]);
starts.fill(points_by_curve[curve].first());
local_front = start_points[curve];
MutableSpan<int> ends = new_points_by_curve.slice(
new_points_by_curve.size() - end_points[curve], end_points[curve]);
ends.fill(points_by_curve[curve].last());
}
MutableSpan<int> original_points = new_points_by_curve.slice(local_front, point_count);
for (const int point_i : original_points.index_range()) {
original_points[point_i] = points_by_curve[curve][point_i];
}
}
dst_curves.resize(target_point_count, src_curves_num);
const bke::AttributeAccessor src_attributes = src_curves.attributes();
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
/* Transfer point attributes. */
gather_attributes(src_attributes,
bke::AttrDomain::Point,
bke::AttrDomain::Point,
attribute_filter,
dst_to_src_point,
dst_attributes);
}
MutableSpan<float3> positions = dst_curves.positions_for_write();
const OffsetIndices<int> new_points_by_curve = dst_curves.points_by_curve();
threading::parallel_for(dst_curves.curves_range(), 512, [&](const IndexRange curves_range) {
for (const int curve : curves_range) {
const IndexRange new_curve = new_points_by_curve[curve];
int new_size = new_curve.size();
/* #used_percent_length must always be finite and non-zero. */
const float used_percent_length = math::clamp(
isfinite(overshoot_fac) ? overshoot_fac : 0.1f, 1e-4f, 1.0f);
if (!follow_curvature || new_size == 2) {
extend_curve_straight(used_percent_length,
new_size,
start_points.as_span(),
end_points.as_span(),
curve,
new_curve,
use_start_lengths.as_span(),
use_end_lengths.as_span(),
positions);
}
else if (start_points[curve] > 0 || end_points[curve] > 0) {
extend_curve_curved(used_percent_length,
start_points.as_span(),
end_points.as_span(),
points_by_curve,
curve,
new_curve,
use_start_lengths.as_span(),
use_end_lengths.as_span(),
max_angle,
segment_influence,
invert_curvature,
positions);
}
}
});
if (src_curves.nurbs_has_custom_knots()) {
bke::curves::nurbs::update_custom_knot_modes(
dst_curves.curves_range(), NURBS_KNOT_MODE_NORMAL, NURBS_KNOT_MODE_NORMAL, dst_curves);
}
return dst_curves;
}
} // namespace blender::geometry

View File

@@ -0,0 +1,549 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "GEO_extract_elements.hh"
#include "BLI_index_mask.hh"
#include "BKE_attribute.hh"
#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_instances.hh"
#include "BKE_mesh.hh"
#include "BKE_pointcloud.hh"
namespace blender::geometry {
using bke::AttrDomain;
struct PropagationAttribute {
StringRef name;
bke::AttrType data_type;
AttrDomain domain;
GVArray data;
};
Array<Mesh *> extract_mesh_vertices(const Mesh &mesh,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
BLI_assert(mask.min_array_size() <= mesh.verts_num);
Array<Mesh *> elements(mask.size(), nullptr);
const bke::AttributeAccessor src_attributes = mesh.attributes();
Vector<PropagationAttribute> propagation_attributes;
src_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
if (iter.data_type == bke::AttrType::String) {
return;
}
if (attribute_filter.allow_skip(iter.name)) {
return;
}
const bke::GAttributeReader src_attribute = iter.get(AttrDomain::Point);
if (!src_attribute) {
return;
}
propagation_attributes.append({iter.name, iter.data_type, AttrDomain::Point, *src_attribute});
});
mask.foreach_index(
[&](const int vert_i, const int element_i) {
Mesh *element = BKE_mesh_new_nomain(1, 0, 0, 0);
BKE_mesh_copy_parameters_for_eval(element, &mesh);
bke::MutableAttributeAccessor element_attributes = element->attributes_for_write();
for (const PropagationAttribute &src_attribute : propagation_attributes) {
bke::GSpanAttributeWriter dst = element_attributes.lookup_or_add_for_write_only_span(
src_attribute.name, AttrDomain::Point, src_attribute.data_type);
if (!dst) {
continue;
}
src_attribute.data.get(vert_i, dst.span[0]);
dst.finish();
}
elements[element_i] = element;
},
exec_mode::grain_size(32));
return elements;
}
Array<Mesh *> extract_mesh_edges(const Mesh &mesh,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
BLI_assert(mask.min_array_size() <= mesh.edges_num);
Array<Mesh *> elements(mask.size(), nullptr);
const Span<int2> src_edges = mesh.edges();
const bke::AttributeAccessor src_attributes = mesh.attributes();
Vector<PropagationAttribute> propagation_attributes;
src_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
if (iter.data_type == bke::AttrType::String) {
return;
}
if (iter.name == ".edge_verts") {
return;
}
if (attribute_filter.allow_skip(iter.name)) {
return;
}
const bke::GAttributeReader src_attribute = iter.get();
if (ELEM(src_attribute.domain, AttrDomain::Point, AttrDomain::Edge)) {
propagation_attributes.append(
{iter.name, iter.data_type, src_attribute.domain, *src_attribute});
}
else if (src_attribute.domain == AttrDomain::Corner) {
if (GVArray adapted_attribute = src_attributes.adapt_domain(
*src_attribute, src_attribute.domain, AttrDomain::Point))
{
propagation_attributes.append(
{iter.name, iter.data_type, AttrDomain::Point, adapted_attribute});
}
}
else if (src_attribute.domain == AttrDomain::Face) {
if (GVArray adapted_attribute = src_attributes.adapt_domain(
*src_attribute, src_attribute.domain, AttrDomain::Edge))
{
propagation_attributes.append(
{iter.name, iter.data_type, AttrDomain::Edge, adapted_attribute});
}
}
});
mask.foreach_index(
[&](const int edge_i, const int element_i) {
Mesh *element = BKE_mesh_new_nomain(2, 1, 0, 0);
BKE_mesh_copy_parameters_for_eval(element, &mesh);
MutableSpan<int2> element_edges = element->edges_for_write();
element_edges[0] = {0, 1};
const int2 &src_edge = src_edges[edge_i];
bke::MutableAttributeAccessor element_attributes = element->attributes_for_write();
for (const PropagationAttribute &src_attribute : propagation_attributes) {
bke::GSpanAttributeWriter dst = element_attributes.lookup_or_add_for_write_only_span(
src_attribute.name, src_attribute.domain, src_attribute.data_type);
if (!dst) {
continue;
}
if (src_attribute.domain == AttrDomain::Point) {
src_attribute.data.get(src_edge[0], dst.span[0]);
src_attribute.data.get(src_edge[1], dst.span[1]);
}
else {
src_attribute.data.get(edge_i, dst.span[0]);
}
dst.finish();
}
elements[element_i] = element;
},
exec_mode::grain_size(32));
return elements;
}
Array<Mesh *> extract_mesh_faces(const Mesh &mesh,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
BLI_assert(mask.min_array_size() <= mesh.faces_num);
Array<Mesh *> elements(mask.size(), nullptr);
const Span<int> src_corner_verts = mesh.corner_verts();
const Span<int> src_corner_edges = mesh.corner_edges();
const OffsetIndices<int> src_faces = mesh.faces();
const bke::AttributeAccessor src_attributes = mesh.attributes();
Vector<PropagationAttribute> propagation_attributes;
src_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
if (iter.data_type == bke::AttrType::String) {
return;
}
if (ELEM(iter.name, ".edge_verts", ".corner_edge", ".corner_vert")) {
return;
}
if (attribute_filter.allow_skip(iter.name)) {
return;
}
const bke::GAttributeReader src_attribute = iter.get();
if (!src_attribute) {
return;
}
propagation_attributes.append(
{iter.name, iter.data_type, src_attribute.domain, *src_attribute});
});
mask.foreach_index(
[&](const int face_i, const int element_i) {
const IndexRange src_face = src_faces[face_i];
const int verts_num = src_face.size();
Mesh *element = BKE_mesh_new_nomain(verts_num, verts_num, 1, verts_num);
BKE_mesh_copy_parameters_for_eval(element, &mesh);
MutableSpan<int2> element_edges = element->edges_for_write();
MutableSpan<int> element_corner_verts = element->corner_verts_for_write();
MutableSpan<int> element_corner_edges = element->corner_edges_for_write();
MutableSpan<int> element_face_offsets = element->face_offsets_for_write();
for (const int i : IndexRange(verts_num)) {
element_edges[i] = {i, i + 1};
element_corner_verts[i] = i;
element_corner_edges[i] = i;
}
element_edges.last()[1] = 0;
element_face_offsets[0] = 0;
element_face_offsets[1] = verts_num;
bke::MutableAttributeAccessor element_attributes = element->attributes_for_write();
for (const PropagationAttribute &src_attribute : propagation_attributes) {
bke::GSpanAttributeWriter dst = element_attributes.lookup_or_add_for_write_only_span(
src_attribute.name, src_attribute.domain, src_attribute.data_type);
if (!dst) {
continue;
}
switch (src_attribute.domain) {
case AttrDomain::Point: {
for (const int i : IndexRange(verts_num)) {
const int src_corner_i = src_face[i];
const int src_vert_i = src_corner_verts[src_corner_i];
src_attribute.data.get(src_vert_i, dst.span[i]);
}
break;
}
case AttrDomain::Edge: {
for (const int i : IndexRange(verts_num)) {
const int src_corner_i = src_face[i];
const int src_edge_i = src_corner_edges[src_corner_i];
src_attribute.data.get(src_edge_i, dst.span[i]);
}
break;
}
case AttrDomain::Corner: {
src_attribute.data.materialize_compressed(src_face, dst.span.data());
break;
}
case AttrDomain::Face: {
src_attribute.data.get(face_i, dst.span[0]);
break;
}
default:
BLI_assert_unreachable();
break;
}
dst.finish();
}
elements[element_i] = element;
},
exec_mode::grain_size(32));
return elements;
}
Array<PointCloud *> extract_pointcloud_points(const PointCloud &pointcloud,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
BLI_assert(mask.min_array_size() <= pointcloud.totpoint);
Array<PointCloud *> elements(mask.size(), nullptr);
const bke::AttributeAccessor src_attributes = pointcloud.attributes();
mask.foreach_index(
[&](const int point_i, const int element_i) {
PointCloud *element = BKE_pointcloud_new_nomain(1);
element->totcol = pointcloud.totcol;
element->mat = MEM_dupalloc(pointcloud.mat);
bke::gather_attributes(src_attributes,
AttrDomain::Point,
AttrDomain::Point,
attribute_filter,
Span<int>{point_i},
element->attributes_for_write());
elements[element_i] = element;
},
exec_mode::grain_size(32));
return elements;
}
Array<Curves *> extract_curves_points(const Curves &curves,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
BLI_assert(mask.min_array_size() <= curves.geometry.point_num);
Array<Curves *> elements(mask.size(), nullptr);
const bke::CurvesGeometry &src_curves = curves.geometry.wrap();
const bke::AttributeAccessor src_attributes = src_curves.attributes();
const Array<int> point_to_curve_map = src_curves.point_to_curve_map();
mask.foreach_index(
[&](const int point_i, const int element_i) {
const int curve_i = point_to_curve_map[point_i];
/* Actual curve type is propagated below. */
Curves *element = bke::curves_new_nomain_single(1, CURVE_TYPE_POLY);
bke::curves_copy_parameters(curves, *element);
bke::MutableAttributeAccessor element_attributes =
element->geometry.wrap().attributes_for_write();
bke::gather_attributes(src_attributes,
AttrDomain::Point,
AttrDomain::Point,
attribute_filter,
Span<int>{point_i},
element_attributes);
bke::gather_attributes(src_attributes,
AttrDomain::Curve,
AttrDomain::Curve,
attribute_filter,
Span<int>{curve_i},
element_attributes);
elements[element_i] = element;
},
exec_mode::grain_size(32));
return elements;
}
Array<Curves *> extract_curves(const Curves &curves,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
BLI_assert(mask.min_array_size() <= curves.geometry.curve_num);
Array<Curves *> elements(mask.size(), nullptr);
const bke::CurvesGeometry &src_curves = curves.geometry.wrap();
const bke::AttributeAccessor src_attributes = src_curves.attributes();
const OffsetIndices<int> src_points_by_curve = src_curves.points_by_curve();
mask.foreach_index(
[&](const int curve_i, const int element_i) {
const IndexRange src_points = src_points_by_curve[curve_i];
const int points_num = src_points.size();
Curves *element = bke::curves_new_nomain(points_num, 1);
bke::MutableAttributeAccessor element_attributes =
element->geometry.wrap().attributes_for_write();
bke::curves_copy_parameters(curves, *element);
bke::gather_attributes(src_attributes,
AttrDomain::Point,
AttrDomain::Point,
attribute_filter,
src_points,
element_attributes);
bke::gather_attributes(src_attributes,
AttrDomain::Curve,
AttrDomain::Curve,
attribute_filter,
Span<int>{curve_i},
element_attributes);
element->geometry.wrap().update_curve_types();
elements[element_i] = element;
},
exec_mode::grain_size(32));
return elements;
}
Array<bke::Instances *> extract_instances(const bke::Instances &instances,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
using bke::Instances;
BLI_assert(mask.min_array_size() <= instances.instances_num());
Array<Instances *> elements(mask.size(), nullptr);
const bke::AttributeAccessor src_attributes = instances.attributes();
const Span<bke::InstanceReference> src_references = instances.references();
const Span<int> src_reference_handles = instances.reference_handles();
const Span<float4x4> src_transforms = instances.transforms();
mask.foreach_index(
[&](const int instance_i, const int element_i) {
const int old_handle = src_reference_handles[instance_i];
const bke::InstanceReference &old_reference = src_references[old_handle];
const float4x4 &old_transform = src_transforms[instance_i];
Instances *element = new Instances(1);
element->reference_handles_for_write().first() = element->add_new_reference(old_reference);
element->transforms_for_write().first() = old_transform;
bke::gather_attributes(src_attributes,
AttrDomain::Instance,
AttrDomain::Instance,
bke::attribute_filter_with_skip_ref(
attribute_filter, {".reference_index", "instance_transform"}),
Span<int>{instance_i},
element->attributes_for_write());
elements[element_i] = element;
},
exec_mode::grain_size(32));
return elements;
}
Array<GreasePencil *> extract_greasepencil_layers(const GreasePencil &grease_pencil,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
using namespace bke::greasepencil;
BLI_assert(mask.min_array_size() <= grease_pencil.layers().size());
Array<GreasePencil *> elements(mask.size(), nullptr);
const bke::AttributeAccessor src_attributes = grease_pencil.attributes();
const Span<const Layer *> src_layers = grease_pencil.layers();
mask.foreach_index(
[&](const int layer_i, const int element_i) {
GreasePencil *element = BKE_grease_pencil_new_nomain();
element->material_array = MEM_dupalloc(grease_pencil.material_array);
element->material_array_num = grease_pencil.material_array_num;
const Layer &src_layer = *src_layers[layer_i];
const Drawing *src_drawing = grease_pencil.get_eval_drawing(src_layer);
if (src_drawing) {
Layer &new_layer = element->add_layer(src_layer.name());
Drawing &drawing = *element->insert_frame(new_layer, element->runtime->eval_frame);
drawing.strokes_for_write() = src_drawing->strokes();
bke::gather_attributes(src_attributes,
AttrDomain::Layer,
AttrDomain::Layer,
attribute_filter,
Span<int>{layer_i},
element->attributes_for_write());
}
elements[element_i] = element;
},
exec_mode::grain_size(32));
return elements;
}
Array<GreasePencil *> extract_greasepencil_layer_points(
const GreasePencil &grease_pencil,
int layer_i,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
using namespace bke::greasepencil;
const Layer &src_layer = grease_pencil.layer(layer_i);
const Drawing &src_drawing = *grease_pencil.get_eval_drawing(src_layer);
const bke::CurvesGeometry &src_curves = src_drawing.strokes();
const bke::AttributeAccessor src_layer_attributes = grease_pencil.attributes();
const bke::AttributeAccessor src_curves_attributes = src_curves.attributes();
const Array<int> point_to_curve_map = src_curves.point_to_curve_map();
Array<GreasePencil *> elements(mask.size(), nullptr);
mask.foreach_index(
[&](const int point_i, const int element_i) {
const int curve_i = point_to_curve_map[point_i];
GreasePencil *element = BKE_grease_pencil_new_nomain();
element->material_array = MEM_dupalloc(grease_pencil.material_array);
element->material_array_num = grease_pencil.material_array_num;
Layer &new_layer = element->add_layer(src_layer.name());
Drawing &drawing = *element->insert_frame(new_layer, element->runtime->eval_frame);
bke::CurvesGeometry &new_curves = drawing.strokes_for_write();
new_curves.resize(1, 1);
new_curves.offsets_for_write().last() = 1;
bke::gather_attributes(src_curves_attributes,
AttrDomain::Point,
AttrDomain::Point,
attribute_filter,
Span<int>{point_i},
new_curves.attributes_for_write());
bke::gather_attributes(src_curves_attributes,
AttrDomain::Curve,
AttrDomain::Curve,
attribute_filter,
Span<int>{curve_i},
new_curves.attributes_for_write());
bke::gather_attributes(src_layer_attributes,
AttrDomain::Layer,
AttrDomain::Layer,
attribute_filter,
Span<int>{layer_i},
element->attributes_for_write());
new_curves.update_curve_types();
elements[element_i] = element;
},
exec_mode::grain_size(32));
return elements;
}
Array<GreasePencil *> extract_greasepencil_layer_curves(
const GreasePencil &grease_pencil,
const int layer_i,
const IndexMask &mask,
const bke::AttributeFilter &attribute_filter)
{
using namespace bke::greasepencil;
const Layer &src_layer = grease_pencil.layer(layer_i);
const Drawing &src_drawing = *grease_pencil.get_eval_drawing(src_layer);
const bke::CurvesGeometry &src_curves = src_drawing.strokes();
const bke::AttributeAccessor src_layer_attributes = grease_pencil.attributes();
const bke::AttributeAccessor src_curves_attributes = src_curves.attributes();
const OffsetIndices<int> src_points_by_curve = src_curves.points_by_curve();
Array<GreasePencil *> elements(mask.size(), nullptr);
mask.foreach_index(
[&](const int curve_i, const int element_i) {
const IndexRange src_points = src_points_by_curve[curve_i];
const int points_num = src_points.size();
GreasePencil *element = BKE_grease_pencil_new_nomain();
element->material_array = MEM_dupalloc(grease_pencil.material_array);
element->material_array_num = grease_pencil.material_array_num;
Layer &new_layer = element->add_layer(src_layer.name());
Drawing &drawing = *element->insert_frame(new_layer, element->runtime->eval_frame);
bke::CurvesGeometry &new_curves = drawing.strokes_for_write();
new_curves.resize(points_num, 1);
bke::gather_attributes(src_curves_attributes,
AttrDomain::Point,
AttrDomain::Point,
attribute_filter,
src_points,
new_curves.attributes_for_write());
bke::gather_attributes(src_curves_attributes,
AttrDomain::Curve,
AttrDomain::Curve,
attribute_filter,
Span<int>{curve_i},
new_curves.attributes_for_write());
bke::gather_attributes(src_layer_attributes,
AttrDomain::Layer,
AttrDomain::Layer,
attribute_filter,
Span<int>{layer_i},
element->attributes_for_write());
new_curves.update_curve_types();
elements[element_i] = element;
},
exec_mode::grain_size(32));
return elements;
}
} // namespace blender::geometry

View File

@@ -0,0 +1,560 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_curves_utils.hh"
#include "BLI_math_rotation_legacy.hh"
#include "BLI_task.hh"
#include "GEO_fillet_curves.hh"
namespace blender::geometry {
static void duplicate_fillet_point_data(const OffsetIndices<int> src_points_by_curve,
const OffsetIndices<int> dst_points_by_curve,
const IndexMask &curve_selection,
const Span<int> all_point_offsets,
const GSpan src,
GMutableSpan dst)
{
curve_selection.foreach_index(
[&](const int curve_i) {
const IndexRange src_points = src_points_by_curve[curve_i];
const IndexRange dst_points = dst_points_by_curve[curve_i];
const IndexRange offsets_range = bke::curves::per_curve_point_offsets_range(src_points,
curve_i);
bke::attribute_math::gather_to_groups(all_point_offsets.slice(offsets_range),
IndexRange(src_points.size()),
src.slice(src_points),
dst.slice(dst_points));
},
exec_mode::grain_size(512));
}
static void calculate_result_offsets(const OffsetIndices<int> src_points_by_curve,
const IndexMask &selection,
const IndexMask &unselected,
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. */
offset_indices::copy_group_sizes(src_points_by_curve, unselected, dst_curve_offsets);
selection.foreach_index(
[&](const int curve_i) {
const IndexRange src_points = src_points_by_curve[curve_i];
const IndexRange offsets_range = bke::curves::per_curve_point_offsets_range(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;
}
}
});
offset_indices::accumulate_counts_to_offsets(point_offsets);
dst_curve_offsets[curve_i] = point_offsets.last();
},
exec_mode::grain_size(512));
offset_indices::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(
math::safe_divide(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(
math::safe_divide(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 OffsetIndices<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 = 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 OffsetIndices<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 = 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 OffsetIndices<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 = 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 bke::AttributeFilter &attribute_filter)
{
if (src_curves.is_empty()) {
return src_curves;
}
const OffsetIndices src_points_by_curve = src_curves.points_by_curve();
const Span<float3> positions = src_curves.positions();
const VArraySpan<bool> cyclic{src_curves.cyclic()};
const bke::AttributeAccessor src_attributes = src_curves.attributes();
IndexMaskMemory memory;
const IndexMask unselected = curve_selection.complement(src_curves.curves_range(), memory);
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_points_by_curve,
curve_selection,
unselected,
radius_input,
counts,
cyclic,
dst_curves.offsets_for_write(),
dst_point_offsets);
const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve();
const Span<int> all_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();
}
curve_selection.foreach_segment(
[&](const IndexMaskSegment segment) {
Array<float3> directions;
Array<float> angles;
Array<float> radii;
Array<float> input_radii_buffer;
for (const int curve_i : segment) {
const IndexRange src_points = src_points_by_curve[curve_i];
const IndexRange offsets_range = bke::curves::per_curve_point_offsets_range(src_points,
curve_i);
const OffsetIndices<int> offsets(all_point_offsets.slice(offsets_range));
const IndexRange dst_points = dst_points_by_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));
}
}
}
},
exec_mode::grain_size(512));
for (auto &attribute : bke::retrieve_attributes_for_transfer(
src_attributes,
dst_attributes,
{bke::AttrDomain::Point},
bke::attribute_filter_with_skip_ref(attribute_filter,
{"position",
"handle_type_left",
"handle_type_right",
"handle_right",
"handle_left"})))
{
duplicate_fillet_point_data(src_points_by_curve,
dst_points_by_curve,
curve_selection,
all_point_offsets,
attribute.src,
attribute.dst.span);
attribute.dst.finish();
}
bke::copy_attributes_group_to_group(src_attributes,
bke::AttrDomain::Point,
bke::AttrDomain::Point,
attribute_filter,
src_points_by_curve,
dst_points_by_curve,
unselected,
dst_attributes);
if (src_curves.nurbs_has_custom_knots()) {
bke::curves::nurbs::update_custom_knot_modes(
dst_curves.curves_range(), NURBS_KNOT_MODE_NORMAL, NURBS_KNOT_MODE_NORMAL, dst_curves);
}
dst_curves.calculate_bezier_auto_handles();
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,
const bke::AttributeFilter &attribute_filter)
{
return fillet_curves(
src_curves, curve_selection, radius, count, limit_radius, false, attribute_filter);
}
bke::CurvesGeometry fillet_curves_bezier(const bke::CurvesGeometry &src_curves,
const IndexMask &curve_selection,
const VArray<float> &radius,
const bool limit_radius,
const bke::AttributeFilter &attribute_filter)
{
return fillet_curves(src_curves,
curve_selection,
radius,
VArray<int>::from_single(1, src_curves.points_num()),
limit_radius,
true,
attribute_filter);
}
} // namespace blender::geometry

View File

@@ -0,0 +1,292 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "BLI_task.hh"
#include "BKE_curves_utils.hh"
#include "BKE_deform.hh"
#include "GEO_fit_curves.hh"
namespace blender {
extern "C" {
#include "curve_fit_nd.h"
}
namespace geometry {
bke::CurvesGeometry fit_poly_to_bezier_curves(const bke::CurvesGeometry &src_curves,
const IndexMask &curve_selection,
const VArray<float> &thresholds,
const VArray<bool> &corners,
const FitMethod method,
const bke::AttributeFilter &attribute_filter)
{
if (curve_selection.is_empty()) {
return src_curves;
}
BLI_assert(thresholds.size() == src_curves.curves_num());
BLI_assert(corners.size() == src_curves.points_num());
const OffsetIndices src_points_by_curve = src_curves.offsets();
const Span<float3> src_positions = src_curves.positions();
const VArray<bool> src_cyclic = src_curves.cyclic();
bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves);
BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &src_curves.vertex_group_names);
IndexMaskMemory memory;
const IndexMask unselected_curves = curve_selection.complement(src_curves.curves_range(),
memory);
/* Write the new sizes to the dst_curve_sizes, they will be accumulated later to offsets. */
MutableSpan<int> dst_curve_sizes = dst_curves.offsets_for_write();
offset_indices::copy_group_sizes(src_points_by_curve, unselected_curves, dst_curve_sizes);
MutableSpan<int8_t> dst_curve_types = dst_curves.curve_types_for_write();
/* NOTE: These spans own the data from the curve fit C-API. */
Array<MutableSpan<float3>> cubic_array_per_curve(curve_selection.size());
Array<MutableSpan<int>> corner_indices_per_curve(curve_selection.size());
Array<MutableSpan<int>> original_indices_per_curve(curve_selection.size());
std::atomic<bool> success = false;
curve_selection.foreach_index(
[&](const int64_t curve_i, const int64_t pos) {
const IndexRange points = src_points_by_curve[curve_i];
if (points.size() < 2) {
dst_curve_sizes[curve_i] = points.size();
dst_curve_types[curve_i] = CURVE_TYPE_POLY;
return;
}
const Span<float3> curve_positions = src_positions.slice(points);
const bool is_cyclic = src_cyclic[curve_i];
const float epsilon = thresholds[curve_i];
/* Both curve fitting algorithms expect the first and last points for non-cyclic curves to
* be treated as if they were corners. */
const bool use_first_as_corner = !is_cyclic && !corners[points.first()];
const bool use_last_as_corner = !is_cyclic && !corners[points.last()];
Vector<int, 32> src_corners;
if (use_first_as_corner) {
src_corners.append(0);
}
if (points.size() > 2) {
for (const int i : IndexRange::from_begin_end(1, points.size() - 1)) {
if (corners[points[i]]) {
src_corners.append(i);
}
}
}
if (use_last_as_corner) {
src_corners.append(points.last());
}
const uint *src_corners_ptr = src_corners.is_empty() ?
nullptr :
reinterpret_cast<uint *>(src_corners.data());
const uint8_t flag = CURVE_FIT_CALC_HIGH_QUALITY |
((is_cyclic) ? CURVE_FIT_CALC_CYCLIC : 0);
float *cubic_array = nullptr;
uint32_t *orig_index_map = nullptr;
uint32_t cubic_array_size = 0;
uint32_t *corner_index_array = nullptr;
uint32_t corner_index_array_size = 0;
int error = 1;
if (method == FitMethod::Split) {
error = curve_fit_cubic_to_points_fl(curve_positions.cast<float>().data(),
curve_positions.size(),
3,
epsilon,
flag,
src_corners_ptr,
src_corners.size(),
&cubic_array,
&cubic_array_size,
&orig_index_map,
&corner_index_array,
&corner_index_array_size);
}
else if (method == FitMethod::Refit) {
error = curve_fit_cubic_to_points_refit_fl(curve_positions.cast<float>().data(),
curve_positions.size(),
3,
epsilon,
flag,
src_corners_ptr,
src_corners.size(),
/* Don't use automatic corner detection. */
FLT_MAX,
&cubic_array,
&cubic_array_size,
&orig_index_map,
&corner_index_array,
&corner_index_array_size);
}
if (error) {
/* Some error occurred. Fall back to using the input positions as the (poly) curve. */
dst_curve_sizes[curve_i] = points.size();
dst_curve_types[curve_i] = CURVE_TYPE_POLY;
return;
}
success.store(true, std::memory_order_relaxed);
const int dst_points_num = cubic_array_size;
BLI_assert(dst_points_num > 0);
dst_curve_sizes[curve_i] = dst_points_num;
dst_curve_types[curve_i] = CURVE_TYPE_BEZIER;
cubic_array_per_curve[pos] = MutableSpan<float3>(reinterpret_cast<float3 *>(cubic_array),
dst_points_num * 3);
corner_indices_per_curve[pos] = MutableSpan<int>(
reinterpret_cast<int *>(corner_index_array), corner_index_array_size);
original_indices_per_curve[pos] = MutableSpan<int>(reinterpret_cast<int *>(orig_index_map),
dst_points_num);
},
exec_mode::grain_size(32));
if (!success) {
/* None of the curve fittings succeeded. */
return src_curves;
}
const OffsetIndices dst_points_by_curve = offset_indices::accumulate_counts_to_offsets(
dst_curve_sizes);
dst_curves.resize(dst_curves.offsets().last(), dst_curves.curves_num());
const std::optional<Span<float3>> src_handles_left = src_curves.handle_positions_left();
const std::optional<Span<float3>> src_handles_right = src_curves.handle_positions_right();
const VArraySpan<int8_t> src_handle_types_left = src_curves.handle_types_left();
const VArraySpan<int8_t> src_handle_types_right = src_curves.handle_types_right();
MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
MutableSpan<float3> dst_handles_left = dst_curves.handle_positions_left_for_write();
MutableSpan<float3> dst_handles_right = dst_curves.handle_positions_right_for_write();
MutableSpan<int8_t> dst_handle_types_left = dst_curves.handle_types_left_for_write();
MutableSpan<int8_t> dst_handle_types_right = dst_curves.handle_types_right_for_write();
/* First handle the unselected curves. */
if (src_handles_left) {
array_utils::copy_group_to_group(src_points_by_curve,
dst_points_by_curve,
unselected_curves,
*src_handles_left,
dst_handles_left);
}
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, unselected_curves, src_positions, dst_positions);
if (src_handles_right) {
array_utils::copy_group_to_group(src_points_by_curve,
dst_points_by_curve,
unselected_curves,
*src_handles_right,
dst_handles_right);
}
if (!src_handle_types_left.is_empty()) {
array_utils::copy_group_to_group(src_points_by_curve,
dst_points_by_curve,
unselected_curves,
src_handle_types_left,
dst_handle_types_left);
}
if (!src_handle_types_right.is_empty()) {
array_utils::copy_group_to_group(src_points_by_curve,
dst_points_by_curve,
unselected_curves,
src_handle_types_right,
dst_handle_types_right);
}
Array<int> old_by_new_map(dst_curves.points_num());
unselected_curves.foreach_index(
[&](const int64_t curve_i) {
const IndexRange src_points = src_points_by_curve[curve_i];
const IndexRange dst_points = dst_points_by_curve[curve_i];
array_utils::fill_index_range<int>(old_by_new_map.as_mutable_span().slice(dst_points),
src_points.start());
},
exec_mode::grain_size(1024));
/* Now copy the data of the newly fitted curves. */
curve_selection.foreach_index(
[&](const int64_t curve_i, const int64_t pos) {
const IndexRange src_points = src_points_by_curve[curve_i];
const IndexRange dst_points = dst_points_by_curve[curve_i];
MutableSpan<float3> positions = dst_positions.slice(dst_points);
MutableSpan<int> old_by_new = old_by_new_map.as_mutable_span().slice(dst_points);
if (dst_curve_types[curve_i] == CURVE_TYPE_POLY) {
/* Handle the curves for which the curve fitting has failed. */
BLI_assert(src_points.size() == dst_points.size());
positions.copy_from(src_positions.slice(src_points));
dst_handles_left.slice(dst_points).copy_from(src_positions.slice(src_points));
dst_handles_right.slice(dst_points).copy_from(src_positions.slice(src_points));
dst_handle_types_left.slice(dst_points).fill(BEZIER_HANDLE_FREE);
dst_handle_types_right.slice(dst_points).fill(BEZIER_HANDLE_FREE);
array_utils::fill_index_range<int>(old_by_new, src_points.start());
return;
}
const Span<float3> cubic_array = cubic_array_per_curve[pos];
BLI_assert(dst_points.size() * 3 == cubic_array.size());
MutableSpan<float3> left_handles = dst_handles_left.slice(dst_points);
MutableSpan<float3> right_handles = dst_handles_right.slice(dst_points);
threading::parallel_for(dst_points.index_range(), 8192, [&](const IndexRange range) {
for (const int i : range) {
const int index = i * 3;
positions[i] = cubic_array[index + 1];
left_handles[i] = cubic_array[index];
right_handles[i] = cubic_array[index + 2];
}
});
const Span<int> corner_indices = corner_indices_per_curve[pos];
dst_handle_types_left.slice(dst_points).fill(BEZIER_HANDLE_ALIGN);
dst_handle_types_right.slice(dst_points).fill(BEZIER_HANDLE_ALIGN);
dst_handle_types_left.slice(dst_points).fill_indices(corner_indices, BEZIER_HANDLE_FREE);
dst_handle_types_right.slice(dst_points).fill_indices(corner_indices, BEZIER_HANDLE_FREE);
const Span<int> original_indices = original_indices_per_curve[pos];
threading::parallel_for(dst_points.index_range(), 8192, [&](const IndexRange range) {
for (const int i : range) {
old_by_new[i] = src_points[original_indices[i]];
}
});
},
exec_mode::grain_size(1024));
dst_curves.update_curve_types();
bke::gather_attributes(
src_curves.attributes(),
bke::AttrDomain::Point,
bke::AttrDomain::Point,
bke::attribute_filter_with_skip_ref(
attribute_filter,
{"position", "handle_left", "handle_right", "handle_type_left", "handle_type_right"}),
old_by_new_map,
dst_curves.attributes_for_write());
/* Free all the data from the C-API. */
for (MutableSpan<float3> cubic_array : cubic_array_per_curve) {
free(cubic_array.data());
}
for (MutableSpan<int> corner_indices : corner_indices_per_curve) {
free(corner_indices.data());
}
for (MutableSpan<int> original_indices : original_indices_per_curve) {
free(original_indices.data());
}
return dst_curves;
}
} // namespace geometry
} // namespace blender

View File

@@ -0,0 +1,105 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_geometry_set.hh"
#include "BKE_instances.hh"
#include "GEO_foreach_geometry.hh"
#include "GEO_join_geometries.hh"
namespace blender::geometry {
static void extract_real_geometries_recursive(
bke::GeometrySet &geometry,
Vector<int> &path,
Map<bke::GeometrySet, Vector<Vector<int>>> &r_real_geometries)
{
bke::GeometrySet real_geometry = geometry;
real_geometry.remove(bke::GeometryComponent::Type::Instance);
geometry.keep_only({bke::GeometryComponent::Type::Instance});
r_real_geometries.lookup_or_add_default(std::move(real_geometry)).append(path);
bke::Instances *instances = geometry.get_instances_for_write();
if (!instances) {
return;
}
instances->ensure_geometry_instances();
MutableSpan<bke::InstanceReference> references = instances->references_for_write();
for (const int i : references.index_range()) {
bke::InstanceReference &reference = references[i];
if (reference.type() == bke::InstanceReference::Type::GeometrySet) {
bke::GeometrySet &sub_geometry = reference.geometry_set();
path.append(i);
extract_real_geometries_recursive(sub_geometry, path, r_real_geometries);
path.pop_last();
}
}
}
static void reinsert_modified_geometry_recursive(bke::GeometrySet &geometry,
const bke::GeometrySet &geometry_to_insert,
const Span<int> path)
{
if (path.is_empty()) {
/* Instance references must not be merged here as that could invalidate the paths. */
const bool allow_merging_instance_references = false;
/* Important to pass the old geometry first, so that the instance reference paths stay
* valid. */
geometry = join_geometries(
{geometry, geometry_to_insert}, {}, {}, allow_merging_instance_references);
return;
}
bke::Instances *instances = geometry.get_instances_for_write();
BLI_assert(instances);
const int reference_i = path.first();
const MutableSpan<bke::InstanceReference> references = instances->references_for_write();
BLI_assert(reference_i < references.size());
bke::InstanceReference &reference = references[reference_i];
BLI_assert(reference.type() == bke::InstanceReference::Type::GeometrySet);
bke::GeometrySet &sub_geometry = reference.geometry_set();
reinsert_modified_geometry_recursive(sub_geometry, geometry_to_insert, path.drop_front(1));
}
struct GeometryWithPaths {
bke::GeometrySet geometry;
Vector<Vector<int>> paths;
};
void foreach_real_geometry(bke::GeometrySet &geometry,
FunctionRef<void(bke::GeometrySet &geometry_set)> fn)
{
/* Afterwards the geometry does not have realized geometry anymore. It has been extracted and
* will be reinserted afterwards. */
Map<bke::GeometrySet, Vector<Vector<int>>> real_geometries;
{
Vector<int> path;
extract_real_geometries_recursive(geometry, path, real_geometries);
}
/* Take the geometries out of the map so that they can be edited in-place. As keys in the #Map
* the geometries are const and thus can't be modified. */
Vector<GeometryWithPaths> geometries_with_paths;
for (auto &&item : real_geometries.items()) {
geometries_with_paths.append({item.key, std::move(item.value)});
}
/* Clear to avoid extra references to the geometries which prohibit editing them in-place. */
real_geometries.clear();
/* Actually modify the geometries in parallel. */
threading::parallel_for(geometries_with_paths.index_range(), 1, [&](const IndexRange range) {
for (const int i : range) {
bke::GeometrySet &geometry_to_modify = geometries_with_paths[i].geometry;
fn(geometry_to_modify);
}
});
/* Reinsert modified geometries. */
for (GeometryWithPaths &geometry_with_paths : geometries_with_paths) {
for (const Span<int> path : geometry_with_paths.paths) {
reinsert_modified_geometry_recursive(geometry, geometry_with_paths.geometry, path);
}
}
}
} // namespace blender::geometry

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,278 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "GEO_join_geometries.hh"
#include "GEO_realize_instances.hh"
#include "BKE_instances.hh"
namespace blender::geometry {
using bke::AttributeDomainAndType;
using bke::GeometryComponent;
using bke::GeometrySet;
static GeometrySet::GatheredAttributes get_final_attribute_info(
const Span<const GeometryComponent *> components, const Span<StringRef> ignored_attributes)
{
GeometrySet::GatheredAttributes info;
for (const GeometryComponent *component : components) {
component->attributes()->foreach_attribute([&](const bke::AttributeIter &iter) {
if (ignored_attributes.contains(iter.name)) {
return;
}
if (iter.data_type == bke::AttrType::String) {
return;
}
info.add(iter.name, AttributeDomainAndType{iter.domain, iter.data_type});
});
}
return info;
}
static void fill_new_attribute(const Span<const GeometryComponent *> src_components,
const StringRef name,
const bke::AttrType data_type,
const bke::AttrDomain domain,
GMutableSpan dst_span)
{
const CPPType &cpp_type = bke::attribute_type_to_cpp_type(data_type);
int offset = 0;
for (const GeometryComponent *component : src_components) {
const int domain_num = component->attribute_domain_size(domain);
if (domain_num == 0) {
continue;
}
GVArray read_attribute = *component->attributes()->lookup_or_default(
name, domain, data_type, nullptr);
GVArraySpan src_span{read_attribute};
const void *src_buffer = src_span.data();
void *dst_buffer = dst_span[offset];
cpp_type.copy_construct_n(src_buffer, dst_buffer, domain_num);
offset += domain_num;
}
}
static bool try_join_single_value_attribute(const Span<const GeometryComponent *> src_components,
const StringRef name,
const bke::AttrDomain domain,
const bke::AttrType data_type,
bke::MutableAttributeAccessor dst_attributes)
{
const auto get_single_value = [&](const GeometryComponent &component) {
const bke::AttributeAccessor attributes = *component.attributes();
const GVArray src = *attributes.lookup_or_default(name, domain, data_type);
const CommonVArrayInfo info = src.common_info();
if (info.type != CommonVArrayInfo::Type::Single) {
return GPointer();
}
return GPointer(src.type(), info.data);
};
const GPointer first_value = get_single_value(*src_components.first());
if (!first_value) {
return false;
}
const bool all_equal = threading::parallel_reduce(
src_components.index_range().drop_front(1),
64,
true,
[&](const IndexRange range, bool value) {
if (!value) {
return false;
}
for (const int i : range) {
const GPointer value = get_single_value(*src_components[i]);
if (!value) {
return false;
}
if (!value.type()->is_equal(value.get(), first_value.get())) {
return false;
}
}
return true;
},
std::logical_and<bool>());
if (!all_equal) {
return false;
}
return dst_attributes.add(name, domain, data_type, bke::AttributeInitValue(first_value));
}
static void join_attributes(const Span<const GeometryComponent *> src_components,
GeometryComponent &result,
const Span<StringRef> ignored_attributes)
{
const GeometrySet::GatheredAttributes info = get_final_attribute_info(src_components,
ignored_attributes);
bke::MutableAttributeAccessor dst_attributes = *result.attributes_for_write();
for (const int i : info.names.index_range()) {
const StringRef name = info.names[i];
const AttributeDomainAndType &meta_data = info.kinds[i];
if (try_join_single_value_attribute(
src_components, name, meta_data.domain, meta_data.data_type, dst_attributes))
{
continue;
}
bke::GSpanAttributeWriter write_attribute = dst_attributes.lookup_or_add_for_write_only_span(
name, meta_data.domain, meta_data.data_type);
if (!write_attribute) {
continue;
}
fill_new_attribute(
src_components, name, meta_data.data_type, meta_data.domain, write_attribute.span);
write_attribute.finish();
}
}
static void join_instances(const Span<const GeometryComponent *> src_components,
const bool allow_merging_instance_references,
GeometrySet &result)
{
Array<int> offsets_data(src_components.size() + 1);
for (const int i : src_components.index_range()) {
const auto &src_component = static_cast<const bke::InstancesComponent &>(*src_components[i]);
offsets_data[i] = src_component.get()->instances_num();
}
const OffsetIndices offsets = offset_indices::accumulate_counts_to_offsets(offsets_data);
auto dst_instances = std::make_unique<bke::Instances>(offsets.total_size());
MutableSpan<int> all_handles = dst_instances->reference_handles_for_write();
Map<std::reference_wrapper<const bke::InstanceReference>, int> new_handle_by_src_reference_cache;
for (const int i : src_components.index_range()) {
const auto &src_component = static_cast<const bke::InstancesComponent &>(*src_components[i]);
const bke::Instances &src_instances = *src_component.get();
const Span<bke::InstanceReference> src_references = src_instances.references();
Array<int> handle_map(src_references.size());
for (const int src_handle : src_references.index_range()) {
const bke::InstanceReference &src_reference = src_references[src_handle];
if (allow_merging_instance_references) {
handle_map[src_handle] = new_handle_by_src_reference_cache.lookup_or_add_cb(
src_reference, [&]() { return dst_instances->add_new_reference(src_reference); });
}
else {
handle_map[src_handle] = dst_instances->add_new_reference(src_reference);
}
}
const IndexRange dst_range = offsets[i];
const Span<int> src_handles = src_instances.reference_handles();
array_utils::gather(handle_map.as_span(), src_handles, all_handles.slice(dst_range));
}
result.replace_instances(dst_instances.release());
auto &dst_component = result.get_component_for_write<bke::InstancesComponent>();
join_attributes(src_components, dst_component, {".reference_index"});
}
static void join_volumes(const Span<const GeometryComponent *> /*src_components*/,
GeometrySet & /*result*/)
{
/* Not yet supported. Joining volume grids with the same name requires resampling of at least one
* of the grids. The cell size of the resulting volume has to be determined somehow. */
}
static void join_component_type(const bke::GeometryComponent::Type component_type,
const Span<GeometrySet> src_geometry_sets,
const bke::AttributeFilter &attribute_filter,
const bool allow_merging_instance_references,
GeometrySet &result)
{
Vector<const GeometryComponent *> components;
for (const GeometrySet &geometry_set : src_geometry_sets) {
const GeometryComponent *component = geometry_set.get_component(component_type);
if (component != nullptr && !component->is_empty()) {
components.append(component);
}
}
if (components.is_empty()) {
return;
}
if (components.size() == 1) {
result.add(*components.first());
return;
}
switch (component_type) {
case bke::GeometryComponent::Type::Instance:
join_instances(components, allow_merging_instance_references, result);
return;
case bke::GeometryComponent::Type::Volume:
join_volumes(components, result);
return;
default:
break;
}
auto instances = std::make_unique<bke::Instances>(components.size());
instances->transforms_for_write().fill(float4x4::identity());
MutableSpan<int> handles = instances->reference_handles_for_write();
Map<const GeometryComponent *, int> handle_by_component;
for (const int i : components.index_range()) {
const GeometryComponent *component = components[i];
handles[i] = handle_by_component.lookup_or_add_cb(component, [&]() {
GeometrySet tmp_geo;
tmp_geo.add(*components[i]);
return instances->add_new_reference(bke::InstanceReference{tmp_geo});
});
}
RealizeInstancesOptions options;
options.keep_original_ids = true;
options.realize_instance_attributes = false;
options.attribute_filter = attribute_filter;
GeometrySet joined_components =
realize_instances(GeometrySet::from_instances(std::move(instances)), options).geometry;
result.add(joined_components.get_component_for_write(component_type));
}
GeometrySet join_geometries(
const Span<GeometrySet> geometries,
const bke::AttributeFilter &attribute_filter,
const std::optional<Span<GeometryComponent::Type>> &component_types_to_join,
const bool allow_merging_instance_references)
{
GeometrySet result;
result.set_name(geometries.is_empty() ? "" : geometries[0].name());
for (const GeometrySet &geometry_set : geometries) {
result.merge_bundle_from(geometry_set);
}
static const Array<GeometryComponent::Type> supported_types(
{GeometryComponent::Type::Mesh,
GeometryComponent::Type::PointCloud,
GeometryComponent::Type::Instance,
GeometryComponent::Type::Volume,
GeometryComponent::Type::Curve,
GeometryComponent::Type::GreasePencil,
GeometryComponent::Type::Edit});
const Span<GeometryComponent::Type> types_to_join = component_types_to_join.has_value() ?
*component_types_to_join :
Span<GeometryComponent::Type>(
supported_types);
for (const GeometryComponent::Type type : types_to_join) {
join_component_type(
type, geometries, attribute_filter, allow_merging_instance_references, result);
}
return result;
}
} // namespace blender::geometry

View File

@@ -0,0 +1,329 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "BLI_stack.hh"
#include "BKE_curves_utils.hh"
#include "BKE_deform.hh"
#include "GEO_merge_curves.hh"
namespace blender::geometry {
enum Flag {
OnStack = 1,
Inserted = 2,
};
template<typename Fn>
static void foreach_connected_curve(const Span<int> connect_to_curve,
MutableSpan<uint8_t> flags,
const int start,
Fn fn)
{
const IndexRange range = connect_to_curve.index_range();
Stack<int> stack;
bool has_cycle = false;
auto push_curve = [&](const int curve_i) -> bool {
if ((flags[curve_i] & Inserted) != 0) {
return false;
}
if ((flags[curve_i] & OnStack) != 0) {
has_cycle = true;
return false;
}
stack.push(curve_i);
flags[curve_i] |= OnStack;
fn(curve_i);
return true;
};
push_curve(start);
while (!stack.is_empty()) {
const int current = stack.peek();
const int next = connect_to_curve[current];
if (range.contains(next)) {
if (push_curve(next)) {
continue;
}
}
flags[current] |= Inserted;
stack.pop();
}
UNUSED_VARS(has_cycle);
}
/* Topological sorting that puts connected curves into contiguous ranges. */
static Vector<int> toposort_connected_curves(const Span<int> connect_to_curve)
{
const IndexRange range = connect_to_curve.index_range();
Array<uint8_t> flags(connect_to_curve.size());
/* First add all open chains by finding curves without a connection. */
Array<bool> is_start_curve(range.size(), true);
for (const int curve_i : range) {
const int next = connect_to_curve[curve_i];
if (range.contains(next)) {
is_start_curve[next] = false;
}
}
/* Mark all curves that can be reached from a start curve. These must not be added before the
* start curve, or it can lead to gaps in curve ranges. */
flags.fill(0);
Array<bool> is_reachable(range.size(), false);
for (const int curve_i : range) {
if (is_start_curve[curve_i]) {
foreach_connected_curve(
connect_to_curve, flags, curve_i, [&](const int index) { is_reachable[index] = true; });
}
}
Vector<int> sorted_curves;
sorted_curves.reserve(connect_to_curve.size());
flags.fill(0);
for (const int curve_i : range) {
if (is_start_curve[curve_i] || !is_reachable[curve_i]) {
foreach_connected_curve(
connect_to_curve, flags, curve_i, [&](const int index) { sorted_curves.append(index); });
}
}
BLI_assert(sorted_curves.size() == range.size());
return sorted_curves;
}
/* TODO Add an optimized function for reversing the order of spans. */
static void reverse_order(GMutableSpan span)
{
const CPPType &cpptype = span.type();
BUFFER_FOR_CPP_TYPE_VALUE(cpptype, buffer);
cpptype.default_construct(buffer);
for (const int i : IndexRange(span.size() / 2)) {
const int mirror_i = span.size() - 1 - i;
/* Swap. */
cpptype.move_assign(span[i], buffer);
cpptype.move_assign(span[mirror_i], span[i]);
cpptype.move_assign(buffer, span[mirror_i]);
}
cpptype.destruct(buffer);
}
static void reorder_and_flip_attributes_group_to_group(
const bke::AttributeAccessor src_attributes,
const bke::AttrDomain domain,
const OffsetIndices<int> src_offsets,
const OffsetIndices<int> dst_offsets,
const Span<int> old_by_new_map,
const Span<bool> flip_direction,
bke::MutableAttributeAccessor dst_attributes)
{
src_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
if (iter.domain != domain) {
return;
}
if (iter.data_type == bke::AttrType::String) {
return;
}
const GVArray src = *iter.get(domain);
const CommonVArrayInfo info = src.common_info();
if (info.type == CommonVArrayInfo::Type::Single) {
const bke::AttributeInitValue init(GPointer(src.type(), info.data));
if (dst_attributes.add(iter.name, iter.domain, iter.data_type, init)) {
return;
}
}
bke::GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
iter.name, domain, iter.data_type);
if (!dst) {
return;
}
threading::parallel_for(old_by_new_map.index_range(), 1024, [&](const IndexRange range) {
for (const int new_i : range) {
const int old_i = old_by_new_map[new_i];
const bool flip = flip_direction[old_i];
GMutableSpan dst_span = dst.span.slice(dst_offsets[new_i]);
array_utils::copy(src.slice(src_offsets[old_i]), dst_span);
if (flip) {
reverse_order(dst_span);
}
}
});
dst.finish();
});
}
static bke::CurvesGeometry reorder_and_flip_curves(const bke::CurvesGeometry &src_curves,
const Span<int> old_by_new_map,
const Span<bool> flip_direction)
{
bke::CurvesGeometry dst_curves = bke::CurvesGeometry(src_curves);
bke::gather_attributes(src_curves.attributes(),
bke::AttrDomain::Curve,
bke::AttrDomain::Curve,
{},
old_by_new_map,
dst_curves.attributes_for_write());
const Span<int> old_offsets = src_curves.offsets();
MutableSpan<int> new_offsets = dst_curves.offsets_for_write();
offset_indices::gather_group_sizes(old_offsets, old_by_new_map, new_offsets);
offset_indices::accumulate_counts_to_offsets(new_offsets);
reorder_and_flip_attributes_group_to_group(src_curves.attributes(),
bke::AttrDomain::Point,
old_offsets,
new_offsets.as_span(),
old_by_new_map,
flip_direction,
dst_curves.attributes_for_write());
dst_curves.tag_topology_changed();
return dst_curves;
}
/* Build new offsets array for connected ranges. */
static void find_connected_ranges(const bke::CurvesGeometry &src_curves,
const Span<int> old_by_new_map,
Span<int> connect_to_curve,
Span<bool> cyclic,
Vector<int> &r_joined_curve_offsets,
Vector<bool> &r_joined_cyclic)
{
const IndexRange curves_range = src_curves.curves_range();
Array<int> new_by_old_map(old_by_new_map.size());
for (const int dst_i : old_by_new_map.index_range()) {
const int src_i = old_by_new_map[dst_i];
new_by_old_map[src_i] = dst_i;
}
r_joined_curve_offsets.reserve(curves_range.size() + 1);
r_joined_cyclic.reserve(curves_range.size());
int start_index = -1;
for (const int dst_i : curves_range) {
const int src_i = old_by_new_map[dst_i];
/* Strokes are cyclic if they are not connected and the original stroke is cyclic, or if the
* the last stroke of a chain is merged with the first stroke. */
const bool src_cyclic = cyclic[src_i];
if (start_index < 0) {
r_joined_curve_offsets.append(0);
r_joined_cyclic.append(src_cyclic);
start_index = dst_i;
}
++r_joined_curve_offsets.last();
const int src_connect_to = connect_to_curve[src_i];
const bool is_connected = curves_range.contains(src_connect_to);
const int dst_connect_to = is_connected ? new_by_old_map[src_connect_to] : -1;
/* Check for end of chain. */
if (dst_connect_to != dst_i + 1) {
/* Set cyclic state for connected curves.
* Becomes cyclic if connected to the start. */
const bool is_chain = (is_connected || dst_i != start_index);
if (is_chain) {
r_joined_cyclic.last() = (dst_connect_to == start_index);
}
/* Start new curve. */
start_index = -1;
}
}
/* Offsets has one more entry for the overall size. */
r_joined_curve_offsets.append(0);
offset_indices::accumulate_counts_to_offsets(r_joined_curve_offsets);
}
static bke::CurvesGeometry join_curves_ranges(const bke::CurvesGeometry &src_curves,
const OffsetIndices<int> old_curves_by_new)
{
bke::CurvesGeometry dst_curves = bke::CurvesGeometry(src_curves.points_num(),
old_curves_by_new.size());
/* Copy vertex group names. */
BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &src_curves.vertex_group_names);
dst_curves.attributes_active_index = src_curves.attributes_active_index;
/* NOTE: using the offsets as an index map means the first curve of each range is used for
* attributes. */
const Span<int> old_by_new_map = old_curves_by_new.data().drop_back(1);
bke::gather_attributes(src_curves.attributes(),
bke::AttrDomain::Curve,
bke::AttrDomain::Curve,
bke::attribute_filter_from_skip_ref({"cyclic"}),
old_by_new_map,
dst_curves.attributes_for_write());
const OffsetIndices old_points_by_curve = src_curves.points_by_curve();
MutableSpan<int> new_offsets = dst_curves.offsets_for_write();
new_offsets.fill(0);
for (const int new_i : new_offsets.index_range().drop_back(1)) {
const IndexRange old_curves = old_curves_by_new[new_i];
new_offsets[new_i] = offset_indices::sum_group_sizes(old_points_by_curve, old_curves);
}
offset_indices::accumulate_counts_to_offsets(new_offsets);
/* Point attributes copied without changes. */
bke::copy_attributes(src_curves.attributes(),
bke::AttrDomain::Point,
bke::AttrDomain::Point,
{},
dst_curves.attributes_for_write());
dst_curves.tag_topology_changed();
return dst_curves;
}
bke::CurvesGeometry curves_merge_endpoints(const bke::CurvesGeometry &src_curves,
Span<int> connect_to_curve,
Span<bool> flip_direction,
const bke::AttributeFilter & /*attribute_filter*/)
{
BLI_assert(connect_to_curve.size() == src_curves.curves_num());
const VArraySpan<bool> src_cyclic = src_curves.cyclic();
Vector<int> old_by_new_map = toposort_connected_curves(connect_to_curve);
Vector<int> joined_curve_offsets;
Vector<bool> cyclic;
find_connected_ranges(
src_curves, old_by_new_map, connect_to_curve, src_cyclic, joined_curve_offsets, cyclic);
bke::CurvesGeometry ordered_curves = reorder_and_flip_curves(
src_curves, old_by_new_map, flip_direction);
OffsetIndices joined_curves_by_new = OffsetIndices<int>(joined_curve_offsets);
bke::CurvesGeometry merged_curves = join_curves_ranges(ordered_curves, joined_curves_by_new);
merged_curves.cyclic_for_write().copy_from(cyclic);
/**
* `curves_merge_endpoints` seems to be working only with CURVE_TYPE_POLY, still adding this here
* in advance.
*/
if (src_curves.nurbs_has_custom_knots()) {
bke::curves::nurbs::update_custom_knot_modes(merged_curves.curves_range(),
NURBS_KNOT_MODE_NORMAL,
NURBS_KNOT_MODE_NORMAL,
merged_curves);
}
return merged_curves;
}
} // namespace blender::geometry

View File

@@ -0,0 +1,157 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "GEO_merge_layers.hh"
#include "BLI_math_matrix.hh"
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "GEO_join_geometries.hh"
namespace blender::geometry {
static bke::CurvesGeometry join_curves(const GreasePencil &src_grease_pencil,
const Span<const bke::CurvesGeometry *> all_src_curves,
const Span<float4x4> transforms_to_apply,
const bke::AttributeFilter &attribute_filter)
{
BLI_assert(all_src_curves.size() == transforms_to_apply.size());
Vector<bke::GeometrySet> src_geometries(all_src_curves.size());
for (const int src_curves_i : all_src_curves.index_range()) {
bke::CurvesGeometry src_curves = *all_src_curves[src_curves_i];
if (src_curves.is_empty()) {
continue;
}
const float4x4 &transform = transforms_to_apply[src_curves_i];
src_curves.transform(transform);
Curves *src_curves_id = bke::curves_new_nomain(std::move(src_curves));
src_curves_id->mat = MEM_dupalloc(src_grease_pencil.material_array);
src_curves_id->totcol = src_grease_pencil.material_array_num;
src_geometries[src_curves_i].replace_curves(src_curves_id);
}
bke::GeometrySet joined_geometry = join_geometries(src_geometries, attribute_filter);
if (joined_geometry.has_curves()) {
return joined_geometry.get_curves()->geometry.wrap();
}
return {};
}
GreasePencil *merge_layers(const GreasePencil &src_grease_pencil,
const GroupedSpan<int> layers_to_merge,
const bke::AttributeFilter &attribute_filter)
{
using namespace bke::greasepencil;
GreasePencil *new_grease_pencil = BKE_grease_pencil_new_nomain();
BKE_grease_pencil_copy_parameters(src_grease_pencil, *new_grease_pencil);
new_grease_pencil->runtime->eval_frame = src_grease_pencil.runtime->eval_frame;
const int new_layers_num = layers_to_merge.size();
new_grease_pencil->add_layers_with_empty_drawings_for_eval(new_layers_num);
Vector<bke::CurvesGeometry *> curves_by_new_layer(new_layers_num);
for (const int new_layer_i : IndexRange(new_layers_num)) {
Layer &layer = new_grease_pencil->layer(new_layer_i);
const Span<int> src_layer_indices = layers_to_merge[new_layer_i];
BLI_assert(!src_layer_indices.is_empty());
const int first_src_layer_i = src_layer_indices[0];
const Layer &first_src_layer = src_grease_pencil.layer(first_src_layer_i);
layer.set_name(first_src_layer.name());
layer.opacity = first_src_layer.opacity;
Drawing *drawing = new_grease_pencil->get_eval_drawing(layer);
BLI_assert(drawing != nullptr);
curves_by_new_layer[new_layer_i] = &drawing->strokes_for_write();
}
threading::parallel_for(IndexRange(new_layers_num), 32, [&](const IndexRange new_layers_range) {
for (const int new_layer_i : new_layers_range) {
Layer &new_layer = new_grease_pencil->layer(new_layer_i);
const Span<int> src_layer_indices = layers_to_merge[new_layer_i];
const int first_src_layer_i = src_layer_indices[0];
const Layer &first_src_layer = src_grease_pencil.layer(first_src_layer_i);
const float4x4 new_layer_transform = first_src_layer.local_transform();
new_layer.set_local_transform(new_layer_transform);
bke::CurvesGeometry &new_curves = *curves_by_new_layer[new_layer_i];
if (src_layer_indices.size() == 1) {
/* Optimization for the case if the new layer corresponds to exactly one source layer. */
if (const Drawing *src_drawing = src_grease_pencil.get_eval_drawing(first_src_layer)) {
const bke::CurvesGeometry &src_curves = src_drawing->strokes();
new_curves = src_curves;
}
continue;
}
/* Needed to transform the positions from all spaces into the same space. */
const float4x4 new_layer_transform_inv = math::invert(new_layer_transform);
Vector<const bke::CurvesGeometry *> all_src_curves;
Vector<float4x4> transforms_to_apply;
for (const int i : src_layer_indices.index_range()) {
const int src_layer_i = src_layer_indices[i];
const Layer &src_layer = src_grease_pencil.layer(src_layer_i);
if (const Drawing *src_drawing = src_grease_pencil.get_eval_drawing(src_layer)) {
const bke::CurvesGeometry &src_curves = src_drawing->strokes();
all_src_curves.append(&src_curves);
transforms_to_apply.append(new_layer_transform_inv * src_layer.local_transform());
}
}
new_curves = join_curves(
src_grease_pencil, all_src_curves, transforms_to_apply, attribute_filter);
}
});
const bke::AttributeAccessor src_attributes = src_grease_pencil.attributes();
bke::MutableAttributeAccessor new_attributes = new_grease_pencil->attributes_for_write();
src_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
if (attribute_filter.allow_skip(iter.name)) {
return;
}
bke::GAttributeReader src_attribute = iter.get();
bke::GSpanAttributeWriter new_attribute = new_attributes.lookup_or_add_for_write_only_span(
iter.name, bke::AttrDomain::Layer, iter.data_type);
bke::attribute_math::mix_groups(
GVArraySpan(src_attribute.varray), layers_to_merge, new_attribute.span);
new_attribute.finish();
});
return new_grease_pencil;
}
GreasePencil *merge_layers_by_name(const GreasePencil &src_grease_pencil,
const VArray<bool> &selection,
const bke::AttributeFilter &attribute_filter)
{
using namespace bke::greasepencil;
const int old_layers_num = src_grease_pencil.layers().size();
Array<int> layer_to_group(old_layers_num);
Map<StringRef, int> name_to_group_index;
int groups_num = 0;
for (const int i : IndexRange(old_layers_num)) {
if (selection[i]) {
const Layer &layer = src_grease_pencil.layer(i);
layer_to_group[i] = name_to_group_index.lookup_or_add_cb(layer.name(),
[&]() { return groups_num++; });
}
else {
layer_to_group[i] = groups_num++;
}
}
Array<int> offset_data;
Array<int> index_data;
const GroupedSpan<int> src_groups = offset_indices::build_groups_from_indices(
layer_to_group, groups_num, offset_data, index_data);
return merge_layers(src_grease_pencil, src_groups, attribute_filter);
}
} // namespace blender::geometry

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,81 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "DNA_mesh_types.h"
#include "BKE_attribute.hh"
#include "BKE_geometry_set.hh"
#include "BLI_span.hh"
namespace blender {
struct Mesh;
namespace geometry::boolean {
/**
* Holds cumulative offsets for the given elements of a number
* of concatenated Meshes. The sizes are one greater than the
* number of meshes, so that the last value of each gives the
* total number of elements.
*/
struct MeshOffsets : NonCopyable, NonMovable {
Array<int> vert_start;
Array<int> face_start;
Array<int> edge_start;
Array<int> corner_start;
OffsetIndices<int> vert_offsets;
OffsetIndices<int> face_offsets;
OffsetIndices<int> edge_offsets;
OffsetIndices<int> corner_offsets;
MeshOffsets() = default;
explicit MeshOffsets(Span<const Mesh *> meshes);
};
/**
* Copy attributes on the face corner domain to the output mesh, and for output corners that values
* that don't have an explicit mapping defined (the maps contain -1 for that element), interpolate
* the values across the face .
*/
void interpolate_corner_attributes(bke::MutableAttributeAccessor output_attrs,
bke::AttributeAccessor input_attrs,
Mesh *output_mesh,
const Mesh *input_mesh,
Span<int> out_to_in_corner_map,
Span<int> out_to_in_face_map);
/** Similar to #attribute_math::gather, but for -1 values in the map, store the default value. */
void copy_attribute_using_map(GSpan src, Span<int> out_to_in_map, GMutableSpan dst);
/**
* The \a dst span should be the material_index property of the result.
* Rather than using the attribute from the joined mesh, we want to take
* the original face and map it using \a material_remaps.
*/
void set_material_from_map(Span<int> out_to_in_map,
Span<Array<short>> material_remaps,
Span<const Mesh *> meshes,
const MeshOffsets &mesh_offsets,
MutableSpan<int> dst);
bke::GeometrySet join_meshes_with_transforms(Span<const Mesh *> meshes, Span<float4x4> transforms);
/**
* What mesh_id corresponds to a given face_id, assuming that the face_id
* is in one of the ranges of mesh_offsets.face_offsets.
*/
int mesh_id_for_face(int face_id, const MeshOffsets &mesh_offsets);
/**
* What is the vertex index range for the face \a face_id, assuming that face_id is one of the
* ranges of mesh_offsets.face_offsets.
*/
IndexRange vertex_range_for_face(int face_id, const MeshOffsets &mesh_offsets);
} // namespace geometry::boolean
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "GEO_mesh_boolean.hh"
namespace blender::geometry::boolean {
Mesh *mesh_boolean_manifold(Span<const Mesh *> meshes,
Span<float4x4> transforms,
Span<Array<short>> material_remaps,
BooleanOpParameters op_params,
Vector<int> *r_intersecting_edges,
BooleanError *r_error);
}

View File

@@ -0,0 +1,482 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "DNA_object_types.h"
#include "BLI_enumerable_thread_specific.hh"
#include "BLI_index_mask.hh"
#include "BLI_listbase.h"
#include "PRF_profile.hh"
#include "BKE_attribute.hh"
#include "BKE_deform.hh"
#include "BKE_mesh.hh"
#include "GEO_mesh_copy_selection.hh"
#include "GEO_mesh_selection.hh"
namespace blender::geometry {
static void remap_verts(const OffsetIndices<int> src_faces,
const OffsetIndices<int> dst_faces,
const int src_verts_num,
const IndexMask &vert_mask,
const IndexMask &edge_mask,
const IndexMask &face_mask,
const Span<int2> src_edges,
const Span<int> src_corner_verts,
MutableSpan<int2> dst_edges,
MutableSpan<int> dst_corner_verts)
{
Array<int> map(src_verts_num);
index_mask::build_reverse_map<int>(vert_mask, map);
threading::parallel_invoke(
vert_mask.size() > 1024,
[&]() {
face_mask.foreach_index(
[&](const int64_t src_i, const int64_t dst_i) {
const IndexRange src_face = src_faces[src_i];
const IndexRange dst_face = dst_faces[dst_i];
for (const int i : src_face.index_range()) {
dst_corner_verts[dst_face[i]] = map[src_corner_verts[src_face[i]]];
}
},
exec_mode::grain_size(512));
},
[&]() {
edge_mask.foreach_index(
[&](const int64_t src_i, const int64_t dst_i) {
dst_edges[dst_i][0] = map[src_edges[src_i][0]];
dst_edges[dst_i][1] = map[src_edges[src_i][1]];
},
exec_mode::grain_size(512));
});
}
static void remap_edges(const OffsetIndices<int> src_faces,
const OffsetIndices<int> dst_faces,
const int src_edges_num,
const IndexMask &edge_mask,
const IndexMask &face_mask,
const Span<int> src_corner_edges,
MutableSpan<int> dst_corner_edges)
{
Array<int> map(src_edges_num);
index_mask::build_reverse_map<int>(edge_mask, map);
face_mask.foreach_index(
[&](const int64_t src_i, const int64_t dst_i) {
const IndexRange src_face = src_faces[src_i];
const IndexRange dst_face = dst_faces[dst_i];
for (const int i : src_face.index_range()) {
dst_corner_edges[dst_face[i]] = map[src_corner_edges[src_face[i]]];
}
},
exec_mode::grain_size(512));
}
static void copy_loose_vert_hint(const Mesh &src, Mesh &dst)
{
const auto &src_cache = src.runtime->loose_verts_cache;
if (src_cache.is_cached() && src_cache.data().mask.is_empty()) {
dst.tag_loose_verts_none();
}
}
static void copy_loose_edge_hint(const Mesh &src, Mesh &dst)
{
const auto &src_cache = src.runtime->loose_edges_cache;
if (src_cache.is_cached() && src_cache.data().mask.is_empty()) {
dst.tag_loose_edges_none();
}
}
static void copy_overlapping_hint(const Mesh &src, Mesh &dst)
{
if (src.no_overlapping_topology()) {
dst.tag_overlapping_none();
}
}
/** Gather vertex group data and array attributes in separate loops. */
static void gather_vert_attributes(const Mesh &mesh_src,
const bke::AttributeFilter &attribute_filter,
const IndexMask &vert_mask,
Mesh &mesh_dst)
{
Set<std::string> vertex_group_names;
for (bDeformGroup &group : mesh_src.vertex_group_names) {
vertex_group_names.add(group.name);
}
const Span<MDeformVert> src = mesh_src.deform_verts();
if (!vertex_group_names.is_empty() && !src.is_empty()) {
MutableSpan<MDeformVert> dst = mesh_dst.deform_verts_for_write();
bke::gather_deform_verts(src, vert_mask, dst);
}
bke::gather_attributes(mesh_src.attributes(),
bke::AttrDomain::Point,
bke::AttrDomain::Point,
bke::attribute_filter_with_skip_ref(attribute_filter, vertex_group_names),
vert_mask,
mesh_dst.attributes_for_write());
}
std::optional<Mesh *> mesh_copy_selection(const Mesh &src_mesh,
const VArray<bool> &selection,
const bke::AttrDomain selection_domain,
const bke::AttributeFilter &attribute_filter)
{
PRF_scope(ProfileCategory::Default);
const Span<int2> src_edges = src_mesh.edges();
const OffsetIndices src_faces = src_mesh.faces();
const Span<int> src_corner_verts = src_mesh.corner_verts();
const Span<int> src_corner_edges = src_mesh.corner_edges();
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
if (selection.is_empty()) {
return std::nullopt;
}
if (const std::optional<bool> single = selection.get_if_single()) {
return *single ? std::nullopt : std::make_optional<Mesh *>(nullptr);
}
threading::EnumerableThreadSpecific<IndexMaskMemory> memory;
IndexMask vert_mask;
IndexMask edge_mask;
IndexMask face_mask;
switch (selection_domain) {
case bke::AttrDomain::Point: {
const VArraySpan<bool> span(selection);
threading::parallel_invoke(
src_mesh.verts_num > 1024,
[&]() { vert_mask = IndexMask::from_bools(span, memory.local()); },
[&]() { edge_mask = edge_selection_from_vert(src_edges, span, memory.local()); },
[&]() {
face_mask = face_selection_from_vert(
src_faces, src_corner_verts, span, memory.local());
});
break;
}
case bke::AttrDomain::Edge: {
const VArraySpan<bool> span(selection);
threading::parallel_invoke(
src_edges.size() > 1024,
[&]() {
edge_mask = IndexMask::from_bools(span, memory.local());
vert_mask = vert_selection_from_edge(
src_edges, edge_mask, src_mesh.verts_num, memory.local());
},
[&]() {
face_mask = face_selection_from_edge(
src_faces, src_corner_edges, span, memory.local());
});
break;
}
case bke::AttrDomain::Face: {
const VArraySpan<bool> span(selection);
face_mask = IndexMask::from_bools(span, memory.local());
threading::parallel_invoke(
face_mask.size() > 1024,
[&]() {
vert_mask = vert_selection_from_face(
src_faces, face_mask, src_corner_verts, src_mesh.verts_num, memory.local());
},
[&]() {
edge_mask = edge_selection_from_face(
src_faces, face_mask, src_corner_edges, src_mesh.edges_num, memory.local());
});
break;
}
default:
BLI_assert_unreachable();
break;
}
if (vert_mask.is_empty()) {
return nullptr;
}
const bool same_verts = vert_mask.size() == src_mesh.verts_num;
const bool same_edges = edge_mask.size() == src_mesh.edges_num;
const bool same_faces = face_mask.size() == src_mesh.faces_num;
if (same_verts && same_edges && same_faces) {
return std::nullopt;
}
Mesh *dst_mesh = bke::mesh_new_no_attributes(
vert_mask.size(), edge_mask.size(), face_mask.size(), 0);
BKE_mesh_copy_parameters_for_eval(dst_mesh, &src_mesh);
bke::MutableAttributeAccessor dst_attributes = dst_mesh->attributes_for_write();
dst_attributes.add<int2>(".edge_verts", bke::AttrDomain::Edge, bke::AttributeInitConstruct());
MutableSpan<int2> dst_edges = dst_mesh->edges_for_write();
const OffsetIndices<int> dst_faces = offset_indices::gather_selected_offsets(
src_faces, face_mask, dst_mesh->face_offsets_for_write());
dst_mesh->corners_num = dst_faces.total_size();
dst_attributes.add<int>(".corner_vert", bke::AttrDomain::Corner, bke::AttributeInitConstruct());
dst_attributes.add<int>(".corner_edge", bke::AttrDomain::Corner, bke::AttributeInitConstruct());
MutableSpan<int> dst_corner_verts = dst_mesh->corner_verts_for_write();
MutableSpan<int> dst_corner_edges = dst_mesh->corner_edges_for_write();
threading::parallel_invoke(
vert_mask.size() > 1024,
[&]() {
remap_verts(src_faces,
dst_faces,
src_mesh.verts_num,
vert_mask,
edge_mask,
face_mask,
src_edges,
src_corner_verts,
dst_edges,
dst_corner_verts);
},
[&]() {
remap_edges(src_faces,
dst_faces,
src_edges.size(),
edge_mask,
face_mask,
src_corner_edges,
dst_corner_edges);
},
[&]() {
gather_vert_attributes(src_mesh, attribute_filter, vert_mask, *dst_mesh);
bke::gather_attributes(
src_attributes,
bke::AttrDomain::Edge,
bke::AttrDomain::Edge,
bke::attribute_filter_with_skip_ref(attribute_filter, {".edge_verts"}),
edge_mask,
dst_attributes);
bke::gather_attributes(src_attributes,
bke::AttrDomain::Face,
bke::AttrDomain::Face,
attribute_filter,
face_mask,
dst_attributes);
bke::gather_attributes_group_to_group(
src_attributes,
bke::AttrDomain::Corner,
bke::AttrDomain::Corner,
bke::attribute_filter_with_skip_ref(attribute_filter,
{".corner_edge", ".corner_vert"}),
src_faces,
dst_faces,
face_mask,
dst_attributes);
});
if (selection_domain == bke::AttrDomain::Edge) {
copy_loose_vert_hint(src_mesh, *dst_mesh);
}
else if (selection_domain == bke::AttrDomain::Face) {
copy_loose_vert_hint(src_mesh, *dst_mesh);
copy_loose_edge_hint(src_mesh, *dst_mesh);
}
copy_overlapping_hint(src_mesh, *dst_mesh);
return dst_mesh;
}
std::optional<Mesh *> mesh_copy_selection_keep_verts(const Mesh &src_mesh,
const VArray<bool> &selection,
const bke::AttrDomain selection_domain,
const bke::AttributeFilter &attribute_filter)
{
const Span<int2> src_edges = src_mesh.edges();
const OffsetIndices src_faces = src_mesh.faces();
const Span<int> src_corner_verts = src_mesh.corner_verts();
const Span<int> src_corner_edges = src_mesh.corner_edges();
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
if (selection.is_empty()) {
return std::nullopt;
}
threading::EnumerableThreadSpecific<IndexMaskMemory> memory;
IndexMask edge_mask;
IndexMask face_mask;
switch (selection_domain) {
case bke::AttrDomain::Point: {
const VArraySpan<bool> span(selection);
threading::parallel_invoke(
src_edges.size() > 1024,
[&]() { edge_mask = edge_selection_from_vert(src_edges, span, memory.local()); },
[&]() {
face_mask = face_selection_from_vert(
src_faces, src_corner_verts, span, memory.local());
});
break;
}
case bke::AttrDomain::Edge: {
const VArraySpan<bool> span(selection);
threading::parallel_invoke(
src_edges.size() > 1024,
[&]() { edge_mask = IndexMask::from_bools(span, memory.local()); },
[&]() {
face_mask = face_selection_from_edge(
src_faces, src_corner_edges, span, memory.local());
});
break;
}
case bke::AttrDomain::Face: {
const VArraySpan<bool> span(selection);
face_mask = IndexMask::from_bools(span, memory.local());
edge_mask = edge_selection_from_face(
src_faces, face_mask, src_corner_edges, src_edges.size(), memory.local());
break;
}
default:
BLI_assert_unreachable();
break;
}
const bool same_edges = edge_mask.size() == src_mesh.edges_num;
const bool same_faces = face_mask.size() == src_mesh.faces_num;
if (same_edges && same_faces) {
return std::nullopt;
}
Mesh *dst_mesh = bke::mesh_new_no_attributes(
src_mesh.verts_num, edge_mask.size(), face_mask.size(), 0);
BKE_mesh_copy_parameters_for_eval(dst_mesh, &src_mesh);
bke::MutableAttributeAccessor dst_attributes = dst_mesh->attributes_for_write();
const OffsetIndices<int> dst_faces = offset_indices::gather_selected_offsets(
src_faces, face_mask, dst_mesh->face_offsets_for_write());
dst_mesh->corners_num = dst_faces.total_size();
dst_attributes.add<int>(".corner_edge", bke::AttrDomain::Corner, bke::AttributeInitConstruct());
MutableSpan<int> dst_corner_edges = dst_mesh->corner_edges_for_write();
threading::parallel_invoke(
[&]() {
remap_edges(src_faces,
dst_faces,
src_edges.size(),
edge_mask,
face_mask,
src_corner_edges,
dst_corner_edges);
},
[&]() {
bke::copy_attributes(src_attributes,
bke::AttrDomain::Point,
bke::AttrDomain::Point,
attribute_filter,
dst_attributes);
bke::gather_attributes(src_attributes,
bke::AttrDomain::Edge,
bke::AttrDomain::Edge,
attribute_filter,
edge_mask,
dst_attributes);
bke::gather_attributes(src_attributes,
bke::AttrDomain::Face,
bke::AttrDomain::Face,
attribute_filter,
face_mask,
dst_attributes);
bke::gather_attributes_group_to_group(
src_attributes,
bke::AttrDomain::Corner,
bke::AttrDomain::Corner,
bke::attribute_filter_with_skip_ref(attribute_filter, {".corner_edge"}),
src_faces,
dst_faces,
face_mask,
dst_attributes);
});
/* Positions are not changed by the operation, so the bounds are the same. */
dst_mesh->runtime->bounds_cache = src_mesh.runtime->bounds_cache;
if (selection_domain == bke::AttrDomain::Face) {
copy_loose_edge_hint(src_mesh, *dst_mesh);
}
copy_overlapping_hint(src_mesh, *dst_mesh);
return dst_mesh;
}
std::optional<Mesh *> mesh_copy_selection_keep_edges(const Mesh &src_mesh,
const VArray<bool> &selection,
const bke::AttrDomain selection_domain,
const bke::AttributeFilter &attribute_filter)
{
const OffsetIndices src_faces = src_mesh.faces();
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
if (selection.is_empty()) {
return std::nullopt;
}
IndexMaskMemory memory;
IndexMask face_mask;
switch (selection_domain) {
case bke::AttrDomain::Point:
face_mask = face_selection_from_vert(
src_faces, src_mesh.corner_verts(), VArraySpan(selection), memory);
break;
case bke::AttrDomain::Edge:
face_mask = face_selection_from_edge(
src_faces, src_mesh.corner_edges(), VArraySpan(selection), memory);
break;
case bke::AttrDomain::Face:
face_mask = IndexMask::from_bools(selection, memory);
break;
default:
BLI_assert_unreachable();
break;
}
const bool same_faces = face_mask.size() == src_mesh.faces_num;
if (same_faces) {
return std::nullopt;
}
Mesh *dst_mesh = bke::mesh_new_no_attributes(
src_mesh.verts_num, src_mesh.edges_num, face_mask.size(), 0);
BKE_mesh_copy_parameters_for_eval(dst_mesh, &src_mesh);
bke::MutableAttributeAccessor dst_attributes = dst_mesh->attributes_for_write();
const OffsetIndices<int> dst_faces = offset_indices::gather_selected_offsets(
src_faces, face_mask, dst_mesh->face_offsets_for_write());
dst_mesh->corners_num = dst_faces.total_size();
dst_attributes.add<int>(".corner_vert", bke::AttrDomain::Corner, bke::AttributeInitConstruct());
dst_attributes.add<int>(".corner_edge", bke::AttrDomain::Corner, bke::AttributeInitConstruct());
bke::copy_attributes(src_attributes,
bke::AttrDomain::Point,
bke::AttrDomain::Point,
attribute_filter,
dst_attributes);
bke::copy_attributes(src_attributes,
bke::AttrDomain::Edge,
bke::AttrDomain::Edge,
attribute_filter,
dst_attributes);
bke::gather_attributes(src_attributes,
bke::AttrDomain::Face,
bke::AttrDomain::Face,
attribute_filter,
face_mask,
dst_attributes);
bke::gather_attributes_group_to_group(src_attributes,
bke::AttrDomain::Corner,
bke::AttrDomain::Corner,
attribute_filter,
src_faces,
dst_faces,
face_mask,
dst_attributes);
/* Positions are not changed by the operation, so the bounds are the same. */
dst_mesh->runtime->bounds_cache = src_mesh.runtime->bounds_cache;
copy_loose_vert_hint(src_mesh, *dst_mesh);
copy_overlapping_hint(src_mesh, *dst_mesh);
return dst_mesh;
}
} // namespace blender::geometry

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,406 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_attribute.hh"
#include "BKE_mesh.hh"
#include "GEO_mesh_primitive_cuboid.hh"
namespace blender::geometry {
struct CuboidConfig {
float3 size;
int verts_x;
int verts_y;
int verts_z;
int edges_x;
int edges_y;
int edges_z;
int vertex_count;
int face_count;
int loop_count;
CuboidConfig(float3 size, int verts_x, int verts_y, int verts_z)
: size(size),
verts_x(verts_x),
verts_y(verts_y),
verts_z(verts_z),
edges_x(verts_x - 1),
edges_y(verts_y - 1),
edges_z(verts_z - 1)
{
BLI_assert(edges_x > 0 && edges_y > 0 && edges_z > 0);
this->vertex_count = this->get_vertex_count();
this->face_count = this->get_face_count();
this->loop_count = this->face_count * 4;
}
private:
int get_vertex_count()
{
const int inner_position_count = (verts_x - 2) * (verts_y - 2) * (verts_z - 2);
return verts_x * verts_y * verts_z - inner_position_count;
}
int get_face_count()
{
return 2 * (edges_x * edges_y + edges_y * edges_z + edges_z * edges_x);
}
};
static void calculate_positions(const CuboidConfig &config, MutableSpan<float3> positions)
{
const float z_bottom = -config.size.z / 2.0f;
const float z_delta = config.size.z / config.edges_z;
const float x_left = -config.size.x / 2.0f;
const float x_delta = config.size.x / config.edges_x;
const float y_front = -config.size.y / 2.0f;
const float y_delta = config.size.y / config.edges_y;
int vert_index = 0;
for (const int z : IndexRange(config.verts_z)) {
if (ELEM(z, 0, config.edges_z)) {
/* Fill bottom and top. */
const float z_pos = z_bottom + z_delta * z;
for (const int y : IndexRange(config.verts_y)) {
const float y_pos = y_front + y_delta * y;
for (const int x : IndexRange(config.verts_x)) {
const float x_pos = x_left + x_delta * x;
positions[vert_index++] = float3(x_pos, y_pos, z_pos);
}
}
}
else {
for (const int y : IndexRange(config.verts_y)) {
if (ELEM(y, 0, config.edges_y)) {
/* Fill y-sides. */
const float y_pos = y_front + y_delta * y;
const float z_pos = z_bottom + z_delta * z;
for (const int x : IndexRange(config.verts_x)) {
const float x_pos = x_left + x_delta * x;
positions[vert_index++] = float3(x_pos, y_pos, z_pos);
}
}
else {
/* Fill x-sides. */
const float x_pos = x_left;
const float y_pos = y_front + y_delta * y;
const float z_pos = z_bottom + z_delta * z;
positions[vert_index++] = float3(x_pos, y_pos, z_pos);
const float x_pos2 = x_left + x_delta * config.edges_x;
positions[vert_index++] = float3(x_pos2, y_pos, z_pos);
}
}
}
}
}
/* vert_1 = bottom left, vert_2 = bottom right, vert_3 = top right, vert_4 = top left.
* Hence they are passed as 1,4,3,2 when calculating faces clockwise, and 1,2,3,4 for
* anti-clockwise.
*/
static void define_quad(MutableSpan<int> corner_verts,
const int corner,
const int vert_1,
const int vert_2,
const int vert_3,
const int vert_4)
{
corner_verts[corner] = vert_1;
corner_verts[corner + 1] = vert_2;
corner_verts[corner + 2] = vert_3;
corner_verts[corner + 3] = vert_4;
}
static void calculate_corner_verts(const CuboidConfig &config, MutableSpan<int> corner_verts)
{
int corner = 0;
/* Number of vertices in an XY cross-section of the cube (barring top and bottom faces). */
const int xy_cross_section_vert_count = config.verts_x * config.verts_y -
(config.verts_x - 2) * (config.verts_y - 2);
/* Calculate faces for Bottom faces. */
int vert_1_start = 0;
for ([[maybe_unused]] const int y : IndexRange(config.edges_y)) {
for (const int x : IndexRange(config.edges_x)) {
const int vert_1 = vert_1_start + x;
const int vert_2 = vert_1_start + config.verts_x + x;
const int vert_3 = vert_2 + 1;
const int vert_4 = vert_1 + 1;
define_quad(corner_verts, corner, vert_1, vert_2, vert_3, vert_4);
corner += 4;
}
vert_1_start += config.verts_x;
}
/* Calculate faces for Front faces. */
vert_1_start = 0;
int vert_2_start = config.verts_x * config.verts_y;
for ([[maybe_unused]] const int z : IndexRange(config.edges_z)) {
for (const int x : IndexRange(config.edges_x)) {
define_quad(corner_verts,
corner,
vert_1_start + x,
vert_1_start + x + 1,
vert_2_start + x + 1,
vert_2_start + x);
corner += 4;
}
vert_1_start = vert_2_start;
vert_2_start += config.verts_x * config.verts_y - (config.verts_x - 2) * (config.verts_y - 2);
}
/* Calculate faces for Top faces. */
vert_1_start = config.verts_x * config.verts_y +
(config.verts_z - 2) * (config.verts_x * config.verts_y -
(config.verts_x - 2) * (config.verts_y - 2));
vert_2_start = vert_1_start + config.verts_x;
for ([[maybe_unused]] const int y : IndexRange(config.edges_y)) {
for (const int x : IndexRange(config.edges_x)) {
define_quad(corner_verts,
corner,
vert_1_start + x,
vert_1_start + x + 1,
vert_2_start + x + 1,
vert_2_start + x);
corner += 4;
}
vert_2_start += config.verts_x;
vert_1_start += config.verts_x;
}
/* Calculate faces for Back faces. */
vert_1_start = config.verts_x * config.edges_y;
vert_2_start = vert_1_start + xy_cross_section_vert_count;
for (const int z : IndexRange(config.edges_z)) {
if (z == (config.edges_z - 1)) {
vert_2_start += (config.verts_x - 2) * (config.verts_y - 2);
}
for (const int x : IndexRange(config.edges_x)) {
define_quad(corner_verts,
corner,
vert_1_start + x,
vert_2_start + x,
vert_2_start + x + 1,
vert_1_start + x + 1);
corner += 4;
}
vert_2_start += xy_cross_section_vert_count;
vert_1_start += xy_cross_section_vert_count;
}
/* Calculate faces for Left faces. */
vert_1_start = 0;
vert_2_start = config.verts_x * config.verts_y;
for (const int z : IndexRange(config.edges_z)) {
for (const int y : IndexRange(config.edges_y)) {
int vert_1;
int vert_2;
int vert_3;
int vert_4;
if (z == 0 || y == 0) {
vert_1 = vert_1_start + config.verts_x * y;
vert_4 = vert_1 + config.verts_x;
}
else {
vert_1 = vert_1_start + 2 * y;
vert_1 += config.verts_x - 2;
vert_4 = vert_1 + 2;
}
if (y == 0 || z == (config.edges_z - 1)) {
vert_2 = vert_2_start + config.verts_x * y;
vert_3 = vert_2 + config.verts_x;
}
else {
vert_2 = vert_2_start + 2 * y;
vert_2 += config.verts_x - 2;
vert_3 = vert_2 + 2;
}
define_quad(corner_verts, corner, vert_1, vert_2, vert_3, vert_4);
corner += 4;
}
if (z == 0) {
vert_1_start += config.verts_x * config.verts_y;
}
else {
vert_1_start += xy_cross_section_vert_count;
}
vert_2_start += xy_cross_section_vert_count;
}
/* Calculate faces for Right faces. */
vert_1_start = config.edges_x;
vert_2_start = vert_1_start + config.verts_x * config.verts_y;
for (const int z : IndexRange(config.edges_z)) {
for (const int y : IndexRange(config.edges_y)) {
int vert_1 = vert_1_start;
int vert_2 = vert_2_start;
int vert_3 = vert_2_start + 2;
int vert_4 = vert_1 + config.verts_x;
if (z == 0) {
vert_1 = vert_1_start + config.verts_x * y;
vert_4 = vert_1 + config.verts_x;
}
else {
vert_1 = vert_1_start + 2 * y;
vert_4 = vert_1 + 2;
}
if (z == (config.edges_z - 1)) {
vert_2 = vert_2_start + config.verts_x * y;
vert_3 = vert_2 + config.verts_x;
}
else {
vert_2 = vert_2_start + 2 * y;
vert_3 = vert_2 + 2;
}
if (y == (config.edges_y - 1)) {
vert_3 = vert_2 + config.verts_x;
vert_4 = vert_1 + config.verts_x;
}
define_quad(corner_verts, corner, vert_1, vert_4, vert_3, vert_2);
corner += 4;
}
if (z == 0) {
vert_1_start += config.verts_x * config.verts_y;
}
else {
vert_1_start += xy_cross_section_vert_count;
}
vert_2_start += xy_cross_section_vert_count;
}
}
static void calculate_uvs(const CuboidConfig &config, Mesh *mesh, const StringRef uv_id)
{
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
bke::SpanAttributeWriter uv_attribute = attributes.lookup_or_add_for_write_only_span<float2>(
uv_id, bke::AttrDomain::Corner);
MutableSpan<float2> uvs = uv_attribute.span;
int corner = 0;
const float x_delta = 0.25f / float(config.edges_x);
const float y_delta = 0.25f / float(config.edges_y);
const float z_delta = 0.25f / float(config.edges_z);
/* Calculate bottom face UVs. */
for (const int y : IndexRange(config.edges_y)) {
for (const int x : IndexRange(config.edges_x)) {
uvs[corner++] = float2(0.25f + x * x_delta, 0.375f - y * y_delta);
uvs[corner++] = float2(0.25f + x * x_delta, 0.375f - (y + 1) * y_delta);
uvs[corner++] = float2(0.25f + (x + 1) * x_delta, 0.375f - (y + 1) * y_delta);
uvs[corner++] = float2(0.25f + (x + 1) * x_delta, 0.375f - y * y_delta);
}
}
/* Calculate front face UVs. */
for (const int z : IndexRange(config.edges_z)) {
for (const int x : IndexRange(config.edges_x)) {
uvs[corner++] = float2(0.25f + x * x_delta, 0.375f + z * z_delta);
uvs[corner++] = float2(0.25f + (x + 1) * x_delta, 0.375f + z * z_delta);
uvs[corner++] = float2(0.25f + (x + 1) * x_delta, 0.375f + (z + 1) * z_delta);
uvs[corner++] = float2(0.25f + x * x_delta, 0.375f + (z + 1) * z_delta);
}
}
/* Calculate top face UVs. */
for (const int y : IndexRange(config.edges_y)) {
for (const int x : IndexRange(config.edges_x)) {
uvs[corner++] = float2(0.25f + x * x_delta, 0.625f + y * y_delta);
uvs[corner++] = float2(0.25f + (x + 1) * x_delta, 0.625f + y * y_delta);
uvs[corner++] = float2(0.25f + (x + 1) * x_delta, 0.625f + (y + 1) * y_delta);
uvs[corner++] = float2(0.25f + x * x_delta, 0.625f + (y + 1) * y_delta);
}
}
/* Calculate back face UVs. */
for (const int z : IndexRange(config.edges_z)) {
for (const int x : IndexRange(config.edges_x)) {
uvs[corner++] = float2(1.0f - x * x_delta, 0.375f + z * z_delta);
uvs[corner++] = float2(1.0f - x * x_delta, 0.375f + (z + 1) * z_delta);
uvs[corner++] = float2(1.0f - (x + 1) * x_delta, 0.375f + (z + 1) * z_delta);
uvs[corner++] = float2(1.0f - (x + 1) * x_delta, 0.375f + z * z_delta);
}
}
/* Calculate left face UVs. */
for (const int z : IndexRange(config.edges_z)) {
for (const int y : IndexRange(config.edges_y)) {
uvs[corner++] = float2(0.25f - y * y_delta, 0.375f + z * z_delta);
uvs[corner++] = float2(0.25f - y * y_delta, 0.375f + (z + 1) * z_delta);
uvs[corner++] = float2(0.25f - (y + 1) * y_delta, 0.375f + (z + 1) * z_delta);
uvs[corner++] = float2(0.25f - (y + 1) * y_delta, 0.375f + z * z_delta);
}
}
/* Calculate right face UVs. */
for (const int z : IndexRange(config.edges_z)) {
for (const int y : IndexRange(config.edges_y)) {
uvs[corner++] = float2(0.50f + y * y_delta, 0.375f + z * z_delta);
uvs[corner++] = float2(0.50f + (y + 1) * y_delta, 0.375f + z * z_delta);
uvs[corner++] = float2(0.50f + (y + 1) * y_delta, 0.375f + (z + 1) * z_delta);
uvs[corner++] = float2(0.50f + y * y_delta, 0.375f + (z + 1) * z_delta);
}
}
uv_attribute.finish();
}
Mesh *create_cuboid_mesh(const float3 &size,
const int verts_x,
const int verts_y,
const int verts_z,
const std::optional<StringRef> uv_id)
{
const CuboidConfig config(size, verts_x, verts_y, verts_z);
Mesh *mesh = BKE_mesh_new_nomain(config.vertex_count, 0, config.face_count, config.loop_count);
MutableSpan<float3> positions = mesh->vert_positions_for_write();
MutableSpan<int> corner_verts = mesh->corner_verts_for_write();
bke::mesh_smooth_set(*mesh, false);
calculate_positions(config, positions);
offset_indices::fill_constant_group_size(4, 0, mesh->face_offsets_for_write());
calculate_corner_verts(config, corner_verts);
bke::mesh_calc_edges(*mesh, false, false);
if (uv_id) {
calculate_uvs(config, mesh, *uv_id);
}
const float3 bounds = size * 0.5f;
mesh->bounds_set_eager({-bounds, bounds});
mesh->tag_loose_verts_none();
mesh->tag_overlapping_none();
return mesh;
}
Mesh *create_cuboid_mesh(const float3 &size,
const int verts_x,
const int verts_y,
const int verts_z)
{
return create_cuboid_mesh(size, verts_x, verts_y, verts_z, {});
}
} // namespace blender::geometry

View File

@@ -0,0 +1,709 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <cmath>
#include <numbers>
#include "BKE_attribute.hh"
#include "BKE_mesh.hh"
#include "BLI_array_utils.hh"
#include "GEO_mesh_primitive_cylinder_cone.hh"
#include "GEO_mesh_primitive_line.hh"
#include "GEO_mesh_primitive_uv_sphere.hh"
namespace blender::geometry {
struct ConeConfig {
float radius_top;
float radius_bottom;
float height;
int circle_segments;
int side_segments;
int fill_segments;
ConeFillType fill_type;
bool top_is_point;
bool bottom_is_point;
/* The cone tip and a triangle fan filling are topologically identical.
* This simplifies the logic in some cases. */
bool top_has_center_vert;
bool bottom_has_center_vert;
/* Helpful quantities. */
int tot_quad_rings;
int tot_edge_rings;
int tot_verts;
int tot_edges;
int tot_corners;
int tot_faces;
/* Helpful vertex indices. */
int first_vert;
int first_ring_verts_start;
int last_ring_verts_start;
int last_vert;
/* Helpful edge indices. */
int first_ring_edges_start;
int last_ring_edges_start;
int last_fan_edges_start;
int last_edge;
/* Helpful face indices. */
int top_faces_start;
int top_faces_len;
int side_faces_start;
int side_faces_len;
int bottom_faces_start;
int bottom_faces_len;
ConeConfig(float radius_top,
float radius_bottom,
float depth,
int circle_segments,
int side_segments,
int fill_segments,
ConeFillType fill_type)
: radius_top(radius_top),
radius_bottom(radius_bottom),
height(0.5f * depth),
circle_segments(circle_segments),
side_segments(side_segments),
fill_segments(fill_segments),
fill_type(fill_type)
{
this->top_is_point = this->radius_top == 0.0f;
this->bottom_is_point = this->radius_bottom == 0.0f;
this->top_has_center_vert = this->top_is_point || this->fill_type == ConeFillType::Triangles;
this->bottom_has_center_vert = this->bottom_is_point ||
this->fill_type == ConeFillType::Triangles;
this->tot_quad_rings = this->calculate_total_quad_rings();
this->tot_edge_rings = this->calculate_total_edge_rings();
this->tot_verts = this->calculate_total_verts();
this->tot_edges = this->calculate_total_edges();
this->tot_corners = this->calculate_total_corners();
this->first_vert = 0;
this->first_ring_verts_start = this->top_has_center_vert ? 1 : first_vert;
this->last_vert = this->tot_verts - 1;
this->last_ring_verts_start = this->last_vert - this->circle_segments;
this->first_ring_edges_start = this->top_has_center_vert ? this->circle_segments : 0;
this->last_ring_edges_start = this->first_ring_edges_start +
this->tot_quad_rings * this->circle_segments * 2;
this->last_fan_edges_start = this->tot_edges - this->circle_segments;
this->last_edge = this->tot_edges - 1;
this->top_faces_start = 0;
if (!this->top_is_point) {
this->top_faces_len = (fill_segments - 1) * circle_segments;
this->top_faces_len += this->top_has_center_vert ? circle_segments : 0;
this->top_faces_len += this->fill_type == ConeFillType::NGon ? 1 : 0;
}
else {
this->top_faces_len = 0;
}
this->side_faces_start = this->top_faces_len;
if (this->top_is_point && this->bottom_is_point) {
this->side_faces_len = 0;
}
else {
this->side_faces_len = side_segments * circle_segments;
}
if (!this->bottom_is_point) {
this->bottom_faces_len = (fill_segments - 1) * circle_segments;
this->bottom_faces_len += this->bottom_has_center_vert ? circle_segments : 0;
this->bottom_faces_len += this->fill_type == ConeFillType::NGon ? 1 : 0;
}
else {
this->bottom_faces_len = 0;
}
this->bottom_faces_start = this->side_faces_start + this->side_faces_len;
this->tot_faces = this->top_faces_len + this->side_faces_len + this->bottom_faces_len;
}
private:
int calculate_total_quad_rings();
int calculate_total_edge_rings();
int calculate_total_verts();
int calculate_total_edges();
int calculate_total_corners();
};
int ConeConfig::calculate_total_quad_rings()
{
if (top_is_point && bottom_is_point) {
return 0;
}
int quad_rings = 0;
if (!top_is_point) {
quad_rings += fill_segments - 1;
}
quad_rings += (!top_is_point && !bottom_is_point) ? side_segments : (side_segments - 1);
if (!bottom_is_point) {
quad_rings += fill_segments - 1;
}
return quad_rings;
}
int ConeConfig::calculate_total_edge_rings()
{
if (top_is_point && bottom_is_point) {
return 0;
}
int edge_rings = 0;
if (!top_is_point) {
edge_rings += fill_segments;
}
edge_rings += side_segments - 1;
if (!bottom_is_point) {
edge_rings += fill_segments;
}
return edge_rings;
}
int ConeConfig::calculate_total_verts()
{
if (top_is_point && bottom_is_point) {
return side_segments + 1;
}
int vert_total = 0;
if (top_has_center_vert) {
vert_total++;
}
if (!top_is_point) {
vert_total += circle_segments * fill_segments;
}
vert_total += circle_segments * (side_segments - 1);
if (!bottom_is_point) {
vert_total += circle_segments * fill_segments;
}
if (bottom_has_center_vert) {
vert_total++;
}
return vert_total;
}
int ConeConfig::calculate_total_edges()
{
if (top_is_point && bottom_is_point) {
return side_segments;
}
int edge_total = 0;
if (top_has_center_vert) {
edge_total += circle_segments;
}
edge_total += circle_segments * (tot_quad_rings * 2 + 1);
if (bottom_has_center_vert) {
edge_total += circle_segments;
}
return edge_total;
}
int ConeConfig::calculate_total_corners()
{
if (top_is_point && bottom_is_point) {
return 0;
}
int corner_total = 0;
if (top_has_center_vert) {
corner_total += (circle_segments * 3);
}
else if (!top_is_point && fill_type == ConeFillType::NGon) {
corner_total += circle_segments;
}
corner_total += tot_quad_rings * (circle_segments * 4);
if (bottom_has_center_vert) {
corner_total += (circle_segments * 3);
}
else if (!bottom_is_point && fill_type == ConeFillType::NGon) {
corner_total += circle_segments;
}
return corner_total;
}
static void calculate_cone_verts(const ConeConfig &config, MutableSpan<float3> positions)
{
Array<float2> circle(config.circle_segments);
const float angle_delta = 2.0f * (std::numbers::pi / float(config.circle_segments));
float angle = 0.0f;
for (const int i : IndexRange(config.circle_segments)) {
circle[i].x = std::cos(angle);
circle[i].y = std::sin(angle);
angle += angle_delta;
}
int vert_index = 0;
/* Top cone tip or triangle fan center. */
if (config.top_has_center_vert) {
positions[vert_index++] = {0.0f, 0.0f, config.height};
}
/* Top fill including the outer edge of the fill. */
if (!config.top_is_point) {
const float top_fill_radius_delta = config.radius_top / float(config.fill_segments);
for (const int i : IndexRange(config.fill_segments)) {
const float top_fill_radius = top_fill_radius_delta * (i + 1);
for (const int j : IndexRange(config.circle_segments)) {
const float x = circle[j].x * top_fill_radius;
const float y = circle[j].y * top_fill_radius;
positions[vert_index++] = {x, y, config.height};
}
}
}
/* Rings along the side. */
const float side_radius_delta = (config.radius_bottom - config.radius_top) /
float(config.side_segments);
const float height_delta = 2.0f * config.height / float(config.side_segments);
for (const int i : IndexRange(config.side_segments - 1)) {
const float ring_radius = config.radius_top + (side_radius_delta * (i + 1));
const float ring_height = config.height - (height_delta * (i + 1));
for (const int j : IndexRange(config.circle_segments)) {
const float x = circle[j].x * ring_radius;
const float y = circle[j].y * ring_radius;
positions[vert_index++] = {x, y, ring_height};
}
}
/* Bottom fill including the outer edge of the fill. */
if (!config.bottom_is_point) {
const float bottom_fill_radius_delta = config.radius_bottom / float(config.fill_segments);
for (const int i : IndexRange(config.fill_segments)) {
const float bottom_fill_radius = config.radius_bottom - (i * bottom_fill_radius_delta);
for (const int j : IndexRange(config.circle_segments)) {
const float x = circle[j].x * bottom_fill_radius;
const float y = circle[j].y * bottom_fill_radius;
positions[vert_index++] = {x, y, -config.height};
}
}
}
/* Bottom cone tip or triangle fan center. */
if (config.bottom_has_center_vert) {
positions[vert_index++] = {0.0f, 0.0f, -config.height};
}
}
static void calculate_cone_edges(const ConeConfig &config, MutableSpan<int2> edges)
{
int edge_index = 0;
/* Edges for top cone tip or triangle fan */
if (config.top_has_center_vert) {
for (const int i : IndexRange(config.circle_segments)) {
int2 &edge = edges[edge_index++];
edge[0] = config.first_vert;
edge[1] = config.first_ring_verts_start + i;
}
}
/* Rings and connecting edges between the rings. */
for (const int i : IndexRange(config.tot_edge_rings)) {
const int this_ring_vert_start = config.first_ring_verts_start + (i * config.circle_segments);
const int next_ring_vert_start = this_ring_vert_start + config.circle_segments;
/* Edge rings. */
for (const int j : IndexRange(config.circle_segments)) {
int2 &edge = edges[edge_index++];
edge[0] = this_ring_vert_start + j;
edge[1] = this_ring_vert_start + ((j + 1) % config.circle_segments);
}
if (i == config.tot_edge_rings - 1) {
/* There is one fewer ring of connecting edges. */
break;
}
/* Connecting edges. */
for (const int j : IndexRange(config.circle_segments)) {
int2 &edge = edges[edge_index++];
edge[0] = this_ring_vert_start + j;
edge[1] = next_ring_vert_start + j;
}
}
/* Edges for bottom triangle fan or tip. */
if (config.bottom_has_center_vert) {
for (const int i : IndexRange(config.circle_segments)) {
int2 &edge = edges[edge_index++];
edge[0] = config.last_ring_verts_start + i;
edge[1] = config.last_vert;
}
}
}
static void calculate_cone_faces(const ConeConfig &config,
MutableSpan<int> corner_verts,
MutableSpan<int> corner_edges,
MutableSpan<int> face_sizes)
{
int rings_face_start = 0;
int rings_loop_start = 0;
if (config.top_has_center_vert) {
rings_face_start = config.circle_segments;
rings_loop_start = config.circle_segments * 3;
face_sizes.take_front(config.circle_segments).fill(3);
/* Top cone tip or center triangle fan in the fill. */
const int top_center_vert = 0;
const int top_fan_edges_start = 0;
for (const int i : IndexRange(config.circle_segments)) {
const int loop_start = i * 3;
corner_verts[loop_start + 0] = config.first_ring_verts_start + i;
corner_edges[loop_start + 0] = config.first_ring_edges_start + i;
corner_verts[loop_start + 1] = config.first_ring_verts_start +
((i + 1) % config.circle_segments);
corner_edges[loop_start + 1] = top_fan_edges_start + ((i + 1) % config.circle_segments);
corner_verts[loop_start + 2] = top_center_vert;
corner_edges[loop_start + 2] = top_fan_edges_start + i;
}
}
else if (config.fill_type == ConeFillType::NGon) {
rings_face_start = 1;
rings_loop_start = config.circle_segments;
/* Center n-gon in the fill. */
face_sizes.first() = config.circle_segments;
array_utils::fill_index_range(corner_verts.take_front(config.circle_segments));
array_utils::fill_index_range(corner_edges.take_front(config.circle_segments));
}
/* Quads connect one edge ring to the next one. */
const int ring_faces_num = config.tot_quad_rings * config.circle_segments;
face_sizes.slice(rings_face_start, ring_faces_num).fill(4);
for (const int i : IndexRange(config.tot_quad_rings)) {
const int this_ring_loop_start = rings_loop_start + i * config.circle_segments * 4;
const int this_ring_vert_start = config.first_ring_verts_start + (i * config.circle_segments);
const int next_ring_vert_start = this_ring_vert_start + config.circle_segments;
const int this_ring_edges_start = config.first_ring_edges_start +
(i * 2 * config.circle_segments);
const int next_ring_edges_start = this_ring_edges_start + (2 * config.circle_segments);
const int ring_connections_start = this_ring_edges_start + config.circle_segments;
for (const int j : IndexRange(config.circle_segments)) {
const int loop_start = this_ring_loop_start + j * 4;
corner_verts[loop_start + 0] = this_ring_vert_start + j;
corner_edges[loop_start + 0] = ring_connections_start + j;
corner_verts[loop_start + 1] = next_ring_vert_start + j;
corner_edges[loop_start + 1] = next_ring_edges_start + j;
corner_verts[loop_start + 2] = next_ring_vert_start + ((j + 1) % config.circle_segments);
corner_edges[loop_start + 2] = ring_connections_start + ((j + 1) % config.circle_segments);
corner_verts[loop_start + 3] = this_ring_vert_start + ((j + 1) % config.circle_segments);
corner_edges[loop_start + 3] = this_ring_edges_start + j;
}
}
const int bottom_face_start = rings_face_start + ring_faces_num;
const int bottom_loop_start = rings_loop_start + ring_faces_num * 4;
if (config.bottom_has_center_vert) {
face_sizes.slice(bottom_face_start, config.circle_segments).fill(3);
/* Bottom cone tip or center triangle fan in the fill. */
for (const int i : IndexRange(config.circle_segments)) {
const int loop_start = bottom_loop_start + i * 3;
corner_verts[loop_start + 0] = config.last_ring_verts_start + i;
corner_edges[loop_start + 0] = config.last_fan_edges_start + i;
corner_verts[loop_start + 1] = config.last_vert;
corner_edges[loop_start + 1] = config.last_fan_edges_start +
(i + 1) % config.circle_segments;
corner_verts[loop_start + 2] = config.last_ring_verts_start +
(i + 1) % config.circle_segments;
corner_edges[loop_start + 2] = config.last_ring_edges_start + i;
}
}
else if (config.fill_type == ConeFillType::NGon) {
/* Center n-gon in the fill. */
face_sizes[bottom_face_start] = config.circle_segments;
for (const int i : IndexRange(config.circle_segments)) {
/* Go backwards to reverse surface normal. */
corner_verts[bottom_loop_start + i] = config.last_vert - i;
corner_edges[bottom_loop_start + i] = config.last_edge - ((i + 1) % config.circle_segments);
}
}
}
static void calculate_selection_outputs(const ConeConfig &config,
const ConeAttributeOutputs &attribute_outputs,
bke::MutableAttributeAccessor attributes)
{
/* Populate "Top" selection output. */
if (attribute_outputs.top_id) {
const bool face = !config.top_is_point && config.fill_type != ConeFillType::None;
bke::SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
*attribute_outputs.top_id, face ? bke::AttrDomain::Face : bke::AttrDomain::Point);
if (config.top_is_point) {
selection.span[config.first_vert] = true;
}
else {
selection.span.slice(0, face ? config.top_faces_len : config.circle_segments).fill(true);
}
selection.finish();
}
/* Populate "Bottom" selection output. */
if (attribute_outputs.bottom_id) {
const bool face = !config.bottom_is_point && config.fill_type != ConeFillType::None;
bke::SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
*attribute_outputs.bottom_id, face ? bke::AttrDomain::Face : bke::AttrDomain::Point);
if (config.bottom_is_point) {
selection.span[config.last_vert] = true;
}
else if (face) {
selection.span.slice(config.bottom_faces_start, config.bottom_faces_len).fill(true);
}
else {
selection.span.slice(config.last_ring_verts_start + 1, config.circle_segments).fill(true);
}
selection.finish();
}
/* Populate "Side" selection output. */
if (attribute_outputs.side_id) {
bke::SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
*attribute_outputs.side_id, bke::AttrDomain::Face);
selection.span.slice(config.side_faces_start, config.side_faces_len).fill(true);
selection.finish();
}
}
/**
* If the top is the cone tip or has a fill, it is unwrapped into a circle in the
* lower left quadrant of the UV.
* Likewise, if the bottom is the cone tip or has a fill, it is unwrapped into a circle
* in the lower right quadrant of the UV.
* If the mesh is a truncated cone or a cylinder, the side faces are unwrapped into
* a rectangle that fills the top half of the UV (or the entire UV, if there are no fills).
*/
static void calculate_cone_uvs(const ConeConfig &config, Mesh *mesh, const StringRef uv_map_id)
{
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
bke::SpanAttributeWriter<float2> uv_attribute =
attributes.lookup_or_add_for_write_only_span<float2>(uv_map_id, bke::AttrDomain::Corner);
MutableSpan<float2> uvs = uv_attribute.span;
Array<float2> circle(config.circle_segments);
float angle = 0.0f;
const float angle_delta = 2.0f * std::numbers::pi / float(config.circle_segments);
for (const int i : IndexRange(config.circle_segments)) {
circle[i].x = std::cos(angle) * 0.225f;
circle[i].y = std::sin(angle) * 0.225f;
angle += angle_delta;
}
int corner = 0;
/* Left circle of the UV representing the top fill or top cone tip. */
if (config.top_is_point || config.fill_type != ConeFillType::None) {
const float2 center_left(0.25f, 0.25f);
const float radius_factor_delta = 1.0f / (config.top_is_point ? float(config.side_segments) :
float(config.fill_segments));
const int left_circle_segment_count = config.top_is_point ? config.side_segments :
config.fill_segments;
if (config.top_has_center_vert) {
/* Cone tip itself or triangle fan center of the fill. */
for (const int i : IndexRange(config.circle_segments)) {
uvs[corner++] = radius_factor_delta * circle[i] + center_left;
uvs[corner++] = radius_factor_delta * circle[(i + 1) % config.circle_segments] +
center_left;
uvs[corner++] = center_left;
}
}
else if (!config.top_is_point && config.fill_type == ConeFillType::NGon) {
/* N-gon at the center of the fill. */
for (const int i : IndexRange(config.circle_segments)) {
uvs[corner++] = radius_factor_delta * circle[i] + center_left;
}
}
/* The rest of the top fill is made out of quad rings. */
for (const int i : IndexRange(1, left_circle_segment_count - 1)) {
const float inner_radius_factor = i * radius_factor_delta;
const float outer_radius_factor = (i + 1) * radius_factor_delta;
for (const int j : IndexRange(config.circle_segments)) {
uvs[corner++] = inner_radius_factor * circle[j] + center_left;
uvs[corner++] = outer_radius_factor * circle[j] + center_left;
uvs[corner++] = outer_radius_factor * circle[(j + 1) % config.circle_segments] +
center_left;
uvs[corner++] = inner_radius_factor * circle[(j + 1) % config.circle_segments] +
center_left;
}
}
}
if (!config.top_is_point && !config.bottom_is_point) {
/* Mesh is a truncated cone or cylinder. The sides are unwrapped into a rectangle. */
const float bottom = (config.fill_type == ConeFillType::None) ? 0.0f : 0.5f;
const float x_delta = 1.0f / float(config.circle_segments);
const float y_delta = (1.0f - bottom) / float(config.side_segments);
for (const int i : IndexRange(config.side_segments)) {
for (const int j : IndexRange(config.circle_segments)) {
uvs[corner++] = float2(j * x_delta, i * y_delta + bottom);
uvs[corner++] = float2(j * x_delta, (i + 1) * y_delta + bottom);
uvs[corner++] = float2((j + 1) * x_delta, (i + 1) * y_delta + bottom);
uvs[corner++] = float2((j + 1) * x_delta, i * y_delta + bottom);
}
}
}
/* Right circle of the UV representing the bottom fill or bottom cone tip. */
if (config.bottom_is_point || config.fill_type != ConeFillType::None) {
const float2 center_right(0.75f, 0.25f);
const float radius_factor_delta = 1.0f / (config.bottom_is_point ?
float(config.side_segments) :
float(config.fill_segments));
const int right_circle_segment_count = config.bottom_is_point ? config.side_segments :
config.fill_segments;
/* The bottom circle has to be created outside in to match the loop order. */
for (const int i : IndexRange(right_circle_segment_count - 1)) {
const float outer_radius_factor = 1.0f - i * radius_factor_delta;
const float inner_radius_factor = 1.0f - (i + 1) * radius_factor_delta;
for (const int j : IndexRange(config.circle_segments)) {
uvs[corner++] = outer_radius_factor * circle[j] + center_right;
uvs[corner++] = inner_radius_factor * circle[j] + center_right;
uvs[corner++] = inner_radius_factor * circle[(j + 1) % config.circle_segments] +
center_right;
uvs[corner++] = outer_radius_factor * circle[(j + 1) % config.circle_segments] +
center_right;
}
}
if (config.bottom_has_center_vert) {
/* Cone tip itself or triangle fan center of the fill. */
for (const int i : IndexRange(config.circle_segments)) {
uvs[corner++] = radius_factor_delta * circle[i] + center_right;
uvs[corner++] = center_right;
uvs[corner++] = radius_factor_delta * circle[(i + 1) % config.circle_segments] +
center_right;
}
}
else if (!config.bottom_is_point && config.fill_type == ConeFillType::NGon) {
/* N-gon at the center of the fill. */
for (const int i : IndexRange(config.circle_segments)) {
/* Go backwards because of reversed face normal. */
uvs[corner++] = radius_factor_delta * circle[config.circle_segments - 1 - i] +
center_right;
}
}
}
uv_attribute.finish();
}
static Mesh *create_vertex_mesh()
{
/* Returns a mesh with a single vertex at the origin. */
Mesh *mesh = BKE_mesh_new_nomain(1, 0, 0, 0);
mesh->vert_positions_for_write().first() = float3(0);
return mesh;
}
static Bounds<float3> calculate_bounds_cylinder(const ConeConfig &config)
{
return geometry::calculate_bounds_radial_primitive(
config.radius_top, config.radius_bottom, config.circle_segments, config.height);
}
Mesh *create_cylinder_or_cone_mesh(const float radius_top,
const float radius_bottom,
const float depth,
const int circle_segments,
const int side_segments,
const int fill_segments,
const ConeFillType fill_type,
ConeAttributeOutputs &attribute_outputs)
{
const ConeConfig config(
radius_top, radius_bottom, depth, circle_segments, side_segments, fill_segments, fill_type);
/* Handle the case of a line / single point before everything else to avoid
* the need to check for it later. */
if (config.top_is_point && config.bottom_is_point) {
if (config.height == 0.0f) {
return create_vertex_mesh();
}
const float z_delta = -2.0f * config.height / float(config.side_segments);
const float3 start(0.0f, 0.0f, config.height);
const float3 delta(0.0f, 0.0f, z_delta);
return create_line_mesh(start, delta, config.tot_verts);
}
Mesh *mesh = BKE_mesh_new_nomain(
config.tot_verts, config.tot_edges, config.tot_faces, config.tot_corners);
MutableSpan<float3> positions = mesh->vert_positions_for_write();
MutableSpan<int2> edges = mesh->edges_for_write();
MutableSpan<int> face_offsets = mesh->face_offsets_for_write();
MutableSpan<int> corner_verts = mesh->corner_verts_for_write();
MutableSpan<int> corner_edges = mesh->corner_edges_for_write();
bke::mesh_smooth_set(*mesh, false);
calculate_cone_verts(config, positions);
calculate_cone_edges(config, edges);
calculate_cone_faces(config, corner_verts, corner_edges, face_offsets.drop_back(1));
offset_indices::accumulate_counts_to_offsets(face_offsets);
if (attribute_outputs.uv_map_id) {
calculate_cone_uvs(config, mesh, *attribute_outputs.uv_map_id);
}
calculate_selection_outputs(config, attribute_outputs, mesh->attributes_for_write());
mesh->tag_loose_verts_none();
mesh->tag_loose_edges_none();
mesh->tag_overlapping_none();
mesh->bounds_set_eager(calculate_bounds_cylinder(config));
return mesh;
}
} // namespace blender::geometry

View File

@@ -0,0 +1,163 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_attribute.hh"
#include "BKE_mesh.hh"
#include "GEO_mesh_primitive_grid.hh"
namespace blender::geometry {
static void calculate_uvs(Mesh *mesh,
const Span<float3> positions,
const Span<int> corner_verts,
const float size_x,
const float size_y,
const StringRef uv_map_id)
{
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
bke::SpanAttributeWriter uv_attribute = attributes.lookup_or_add_for_write_only_span<float2>(
uv_map_id, bke::AttrDomain::Corner);
const float dx = (size_x == 0.0f) ? 0.0f : 1.0f / size_x;
const float dy = (size_y == 0.0f) ? 0.0f : 1.0f / size_y;
threading::memory_bandwidth_bound_task(
uv_attribute.span.size_in_bytes() + positions.size_in_bytes() + corner_verts.size_in_bytes(),
[&]() {
threading::parallel_for(corner_verts.index_range(), 1024, [&](IndexRange range) {
for (const int i : range) {
const float3 &co = positions[corner_verts[i]];
uv_attribute.span[i].x = (co.x + size_x * 0.5f) * dx;
uv_attribute.span[i].y = (co.y + size_y * 0.5f) * dy;
}
});
});
uv_attribute.finish();
}
Mesh *create_grid_mesh(const int verts_x,
const int verts_y,
const float size_x,
const float size_y,
const std::optional<StringRef> uv_map_id)
{
BLI_assert(verts_x > 0 && verts_y > 0);
const int edges_x = verts_x - 1;
const int edges_y = verts_y - 1;
Mesh *mesh = BKE_mesh_new_nomain(verts_x * verts_y,
edges_x * verts_y + edges_y * verts_x,
edges_x * edges_y,
edges_x * edges_y * 4);
MutableSpan<float3> positions = mesh->vert_positions_for_write();
MutableSpan<int2> edges = mesh->edges_for_write();
MutableSpan<int> corner_verts = mesh->corner_verts_for_write();
MutableSpan<int> corner_edges = mesh->corner_edges_for_write();
bke::mesh_smooth_set(*mesh, false);
offset_indices::fill_constant_group_size(4, 0, mesh->face_offsets_for_write());
{
const float dx = edges_x == 0 ? 0.0f : size_x / edges_x;
const float dy = edges_y == 0 ? 0.0f : size_y / edges_y;
const float x_shift = edges_x / 2.0f;
const float y_shift = edges_y / 2.0f;
threading::memory_bandwidth_bound_task(positions.size_in_bytes(), [&]() {
threading::parallel_for(IndexRange(verts_x), 512, [&](IndexRange x_range) {
for (const int x : x_range) {
const int y_offset = x * verts_y;
threading::parallel_for(IndexRange(verts_y), 512, [&](IndexRange y_range) {
for (const int y : y_range) {
const int vert = y_offset + y;
positions[vert].x = (x - x_shift) * dx;
positions[vert].y = (y - y_shift) * dy;
positions[vert].z = 0.0f;
}
});
}
});
});
}
const int y_edges_start = 0;
const int x_edges_start = verts_x * edges_y;
/* Build the horizontal edges in the X direction. */
threading::memory_bandwidth_bound_task(edges.size_in_bytes(), [&]() {
threading::parallel_for(IndexRange(verts_x), 512, [&](IndexRange x_range) {
for (const int x : x_range) {
const int y_vert_offset = x * verts_y;
const int y_edge_offset = y_edges_start + x * edges_y;
threading::parallel_for(IndexRange(edges_y), 512, [&](IndexRange y_range) {
for (const int y : y_range) {
const int vert = y_vert_offset + y;
edges[y_edge_offset + y] = int2(vert, vert + 1);
}
});
}
});
});
/* Build the vertical edges in the Y direction. */
threading::memory_bandwidth_bound_task(edges.size_in_bytes(), [&]() {
threading::parallel_for(IndexRange(verts_y), 512, [&](IndexRange y_range) {
for (const int y : y_range) {
const int x_edge_offset = x_edges_start + y * edges_x;
threading::parallel_for(IndexRange(edges_x), 512, [&](IndexRange x_range) {
for (const int x : x_range) {
const int vert = x * verts_y + y;
edges[x_edge_offset + x] = int2(vert, vert + verts_y);
}
});
}
});
});
threading::memory_bandwidth_bound_task(
corner_edges.size_in_bytes() + corner_verts.size_in_bytes(), [&]() {
threading::parallel_for(IndexRange(edges_x), 512, [&](IndexRange x_range) {
for (const int x : x_range) {
const int y_offset = x * edges_y;
threading::parallel_for(IndexRange(edges_y), 512, [&](IndexRange y_range) {
for (const int y : y_range) {
const int face = y_offset + y;
const int corner = face * 4;
const int vert = x * verts_y + y;
corner_verts[corner] = vert;
corner_edges[corner] = x_edges_start + edges_x * y + x;
corner_verts[corner + 1] = vert + verts_y;
corner_edges[corner + 1] = y_edges_start + edges_y * (x + 1) + y;
corner_verts[corner + 2] = vert + verts_y + 1;
corner_edges[corner + 2] = x_edges_start + edges_x * (y + 1) + x;
corner_verts[corner + 3] = vert + 1;
corner_edges[corner + 3] = y_edges_start + edges_y * x + y;
}
});
}
});
});
if (uv_map_id && mesh->faces_num != 0) {
calculate_uvs(mesh, positions, corner_verts, size_x, size_y, *uv_map_id);
}
if (verts_x > 1 || verts_y > 1) {
mesh->tag_loose_verts_none();
}
if (verts_x > 1 && verts_y > 1) {
mesh->tag_loose_edges_none();
}
mesh->tag_overlapping_none();
const float3 bounds = float3(size_x * 0.5f, size_y * 0.5f, 0.0f);
mesh->bounds_set_eager({-bounds, bounds});
return mesh;
}
} // namespace blender::geometry

View File

@@ -0,0 +1,51 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_bounds.hh"
#include "BKE_mesh.hh"
#include "GEO_mesh_primitive_line.hh"
namespace blender::geometry {
Mesh *create_line_mesh(const float3 start, const float3 delta, const int count)
{
if (count < 1) {
return nullptr;
}
const int edges_num = count - 1;
Mesh *mesh = BKE_mesh_new_nomain(count, edges_num, 0, 0);
MutableSpan<float3> positions = mesh->vert_positions_for_write();
MutableSpan<int2> edges = mesh->edges_for_write();
threading::memory_bandwidth_bound_task(positions.size_in_bytes() + edges.size_in_bytes(), [&]() {
threading::parallel_invoke(
1024 < count,
[&]() {
threading::parallel_for(positions.index_range(), 4096, [&](IndexRange range) {
for (const int i : range) {
positions[i] = start + delta * i;
}
});
},
[&]() {
threading::parallel_for(edges.index_range(), 4096, [&](IndexRange range) {
for (const int i : range) {
edges[i][0] = i;
edges[i][1] = i + 1;
}
});
});
});
mesh->tag_loose_verts_none();
mesh->tag_overlapping_none();
mesh->bounds_set_eager(*bounds::min_max<float3>({start, start + delta * edges_num}));
return mesh;
}
} // namespace blender::geometry

View File

@@ -0,0 +1,338 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <cmath>
#include "BLI_math_base.hh"
#include "BKE_attribute.hh"
#include "BKE_mesh.hh"
#include "GEO_mesh_primitive_uv_sphere.hh"
namespace blender::geometry {
Bounds<float3> calculate_bounds_radial_primitive(const float radius_top,
const float radius_bottom,
const int segments,
const float height)
{
const float radius = std::max(radius_top, radius_bottom);
const float delta_phi = (2.0f * std::numbers::pi) / float(segments);
const float x_max = radius;
const float x_min = std::cos(std::round(0.5f * segments) * delta_phi) * radius;
const float y_max = std::sin(std::round(0.25f * segments) * delta_phi) * radius;
const float y_min = -y_max;
const float3 bounds_min(x_min, y_min, -height);
const float3 bounds_max(x_max, y_max, height);
return {bounds_min, bounds_max};
}
static int sphere_vert_total(const int segments, const int rings)
{
return segments * (rings - 1) + 2;
}
static int sphere_edge_total(const int segments, const int rings)
{
return segments * (rings * 2 - 1);
}
static int sphere_corner_total(const int segments, const int rings)
{
const int quad_corners = 4 * segments * (rings - 2);
const int tri_corners = 3 * segments * 2;
return quad_corners + tri_corners;
}
static int sphere_face_total(const int segments, const int rings)
{
const int quads = segments * (rings - 2);
const int triangles = segments * 2;
return quads + triangles;
}
/**
* Also calculate vertex normals here, since the calculation is trivial, and it allows avoiding the
* calculation later, if it's necessary. The vertex normals are just the normalized positions.
*/
BLI_NOINLINE static void calculate_sphere_vertex_data(MutableSpan<float3> positions,
MutableSpan<float3> vert_normals,
const float radius,
const int segments,
const int rings)
{
const float delta_theta = std::numbers::pi / rings;
const float delta_phi = (2.0f * std::numbers::pi) / segments;
Array<float, 64> segment_cosines(segments + 1);
for (const int segment : IndexRange(1, segments)) {
const float phi = segment * delta_phi;
segment_cosines[segment] = std::cos(phi);
}
Array<float, 64> segment_sines(segments + 1);
for (const int segment : IndexRange(1, segments)) {
const float phi = segment * delta_phi;
segment_sines[segment] = std::sin(phi);
}
positions[0] = float3(0.0f, 0.0f, radius);
vert_normals.first() = float3(0.0f, 0.0f, 1.0f);
int vert_index = 1;
for (const int ring : IndexRange(1, rings - 1)) {
const float theta = ring * delta_theta;
const float sin_theta = std::sin(theta);
const float z = std::cos(theta);
for (const int segment : IndexRange(1, segments)) {
const float x = sin_theta * segment_cosines[segment];
const float y = sin_theta * segment_sines[segment];
positions[vert_index] = float3(x, y, z) * radius;
vert_normals[vert_index] = float3(x, y, z);
vert_index++;
}
}
positions.last() = float3(0.0f, 0.0f, -radius);
vert_normals.last() = float3(0.0f, 0.0f, -1.0f);
}
BLI_NOINLINE static void calculate_sphere_edge_indices(MutableSpan<int2> edges,
const int segments,
const int rings)
{
int edge_index = 0;
/* Add the edges connecting the top vertex to the first ring. */
const int first_vert_ring_index_start = 1;
for (const int segment : IndexRange(segments)) {
int2 &edge = edges[edge_index++];
edge[0] = 0;
edge[1] = first_vert_ring_index_start + segment;
}
int ring_vert_index_start = 1;
for (const int ring : IndexRange(rings - 1)) {
const int next_ring_vert_index_start = ring_vert_index_start + segments;
/* Add the edges running along each ring. */
for (const int segment : IndexRange(segments)) {
int2 &edge = edges[edge_index++];
edge[0] = ring_vert_index_start + segment;
edge[1] = ring_vert_index_start + ((segment + 1) % segments);
}
/* Add the edges connecting to the next ring. */
if (ring < rings - 2) {
for (const int segment : IndexRange(segments)) {
int2 &edge = edges[edge_index++];
edge[0] = ring_vert_index_start + segment;
edge[1] = next_ring_vert_index_start + segment;
}
}
ring_vert_index_start += segments;
}
/* Add the edges connecting the last ring to the bottom vertex. */
const int last_vert_index = sphere_vert_total(segments, rings) - 1;
const int last_vert_ring_start = last_vert_index - segments;
for (const int segment : IndexRange(segments)) {
int2 &edge = edges[edge_index++];
edge[0] = last_vert_index;
edge[1] = last_vert_ring_start + segment;
}
}
BLI_NOINLINE static void calculate_sphere_faces(MutableSpan<int> face_offsets, const int segments)
{
MutableSpan<int> face_sizes = face_offsets.drop_back(1);
/* Add the triangles connected to the top vertex. */
face_sizes.take_front(segments).fill(3);
/* Add the middle quads. */
face_sizes.drop_front(segments).drop_back(segments).fill(4);
/* Add the triangles connected to the bottom vertex. */
face_sizes.take_back(segments).fill(3);
offset_indices::accumulate_counts_to_offsets(face_offsets);
}
BLI_NOINLINE static void calculate_sphere_corners(MutableSpan<int> corner_verts,
MutableSpan<int> corner_edges,
const int segments,
const int rings)
{
auto segment_next_or_first = [&](const int segment) {
return segment == segments - 1 ? 0 : segment + 1;
};
/* Add the triangles connected to the top vertex. */
const int first_vert_ring_start = 1;
for (const int segment : IndexRange(segments)) {
const int loop_start = segment * 3;
const int segment_next = segment_next_or_first(segment);
corner_verts[loop_start + 0] = 0;
corner_edges[loop_start + 0] = segment;
corner_verts[loop_start + 1] = first_vert_ring_start + segment;
corner_edges[loop_start + 1] = segments + segment;
corner_verts[loop_start + 2] = first_vert_ring_start + segment_next;
corner_edges[loop_start + 2] = segment_next;
}
const int rings_vert_start = 1;
const int rings_edge_start = segments;
const int rings_loop_start = segments * 3;
for (const int ring : IndexRange(1, rings - 2)) {
const int ring_vert_start = rings_vert_start + (ring - 1) * segments;
const int ring_edge_start = rings_edge_start + (ring - 1) * segments * 2;
const int ring_loop_start = rings_loop_start + (ring - 1) * segments * 4;
const int next_ring_vert_start = ring_vert_start + segments;
const int next_ring_edge_start = ring_edge_start + segments * 2;
const int ring_vertical_edge_start = ring_edge_start + segments;
for (const int segment : IndexRange(segments)) {
const int loop_start = ring_loop_start + segment * 4;
const int segment_next = segment_next_or_first(segment);
corner_verts[loop_start + 0] = ring_vert_start + segment;
corner_edges[loop_start + 0] = ring_vertical_edge_start + segment;
corner_verts[loop_start + 1] = next_ring_vert_start + segment;
corner_edges[loop_start + 1] = next_ring_edge_start + segment;
corner_verts[loop_start + 2] = next_ring_vert_start + segment_next;
corner_edges[loop_start + 2] = ring_vertical_edge_start + segment_next;
corner_verts[loop_start + 3] = ring_vert_start + segment_next;
corner_edges[loop_start + 3] = ring_edge_start + segment;
}
}
/* Add the triangles connected to the bottom vertex. */
const int bottom_loop_start = rings_loop_start + segments * (rings - 2) * 4;
const int last_edge_ring_start = segments * (rings - 2) * 2 + segments;
const int bottom_edge_fan_start = last_edge_ring_start + segments;
const int last_vert_index = sphere_vert_total(segments, rings) - 1;
const int last_vert_ring_start = last_vert_index - segments;
for (const int segment : IndexRange(segments)) {
const int loop_start = bottom_loop_start + segment * 3;
const int segment_next = segment_next_or_first(segment);
corner_verts[loop_start + 0] = last_vert_index;
corner_edges[loop_start + 0] = bottom_edge_fan_start + segment_next;
corner_verts[loop_start + 1] = last_vert_ring_start + segment_next;
corner_edges[loop_start + 1] = last_edge_ring_start + segment;
corner_verts[loop_start + 2] = last_vert_ring_start + segment;
corner_edges[loop_start + 2] = bottom_edge_fan_start + segment;
}
}
BLI_NOINLINE static void calculate_sphere_uvs(Mesh *mesh,
const float segments,
const float rings,
const StringRef uv_map_id)
{
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
bke::SpanAttributeWriter<float2> uv_attribute =
attributes.lookup_or_add_for_write_only_span<float2>(uv_map_id, bke::AttrDomain::Corner);
MutableSpan<float2> uvs = uv_attribute.span;
const float dy = 1.0f / rings;
const float segments_inv = 1.0f / segments;
for (const int i_segment : IndexRange(segments)) {
const int loop_start = i_segment * 3;
const float segment = float(i_segment);
uvs[loop_start + 0] = float2((segment + 0.5f) * segments_inv, 0.0f);
uvs[loop_start + 1] = float2(segment * segments_inv, dy);
uvs[loop_start + 2] = float2((segment + 1.0f) * segments_inv, dy);
}
const int rings_loop_start = segments * 3;
for (const int i_ring : IndexRange(1, rings - 2)) {
const int ring_loop_start = rings_loop_start + (i_ring - 1) * segments * 4;
const float ring = float(i_ring);
for (const int i_segment : IndexRange(segments)) {
const int loop_start = ring_loop_start + i_segment * 4;
const float segment = float(i_segment);
uvs[loop_start + 0] = float2(segment * segments_inv, ring / rings);
uvs[loop_start + 1] = float2(segment * segments_inv, (ring + 1.0f) / rings);
uvs[loop_start + 2] = float2((segment + 1.0f) * segments_inv, (ring + 1.0f) / rings);
uvs[loop_start + 3] = float2((segment + 1.0f) * segments_inv, ring / rings);
}
}
const int bottom_loop_start = rings_loop_start + segments * (rings - 2) * 4;
for (const int i_segment : IndexRange(segments)) {
const int loop_start = bottom_loop_start + i_segment * 3;
const float segment = float(i_segment);
uvs[loop_start + 0] = float2((segment + 0.5f) * segments_inv, 1.0f);
uvs[loop_start + 1] = float2((segment + 1.0f) * segments_inv, 1.0f - dy);
uvs[loop_start + 2] = float2(segment * segments_inv, 1.0f - dy);
}
uv_attribute.finish();
}
static Bounds<float3> calculate_bounds_uv_sphere(const float radius,
const int segments,
const int rings)
{
const float delta_theta = std::numbers::pi / float(rings);
const float sin_equator = std::sin(std::round(0.5f * rings) * delta_theta);
return calculate_bounds_radial_primitive(0.0f, radius * sin_equator, segments, radius);
}
Mesh *create_uv_sphere_mesh(const float radius,
const int segments,
const int rings,
const std::optional<StringRef> uv_map_id)
{
Mesh *mesh = BKE_mesh_new_nomain(sphere_vert_total(segments, rings),
sphere_edge_total(segments, rings),
sphere_face_total(segments, rings),
sphere_corner_total(segments, rings));
MutableSpan<float3> positions = mesh->vert_positions_for_write();
MutableSpan<int2> edges = mesh->edges_for_write();
MutableSpan<int> face_offsets = mesh->face_offsets_for_write();
MutableSpan<int> corner_verts = mesh->corner_verts_for_write();
MutableSpan<int> corner_edges = mesh->corner_edges_for_write();
bke::mesh_smooth_set(*mesh, false);
threading::parallel_invoke(
1024 < segments * rings,
[&]() {
Vector<float3> vert_normals(mesh->verts_num);
calculate_sphere_vertex_data(positions, vert_normals, radius, segments, rings);
bke::mesh_vert_normals_assign(*mesh, std::move(vert_normals));
},
[&]() { calculate_sphere_edge_indices(edges, segments, rings); },
[&]() { calculate_sphere_faces(face_offsets, segments); },
[&]() { calculate_sphere_corners(corner_verts, corner_edges, segments, rings); },
[&]() {
if (uv_map_id) {
calculate_sphere_uvs(mesh, segments, rings, *uv_map_id);
}
});
mesh->tag_loose_verts_none();
mesh->tag_loose_edges_none();
mesh->tag_overlapping_none();
mesh->bounds_set_eager(calculate_bounds_uv_sphere(radius, segments, rings));
return mesh;
}
} // namespace blender::geometry

View File

@@ -0,0 +1,103 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_index_mask.hh"
#include "PRF_profile.hh"
#include "GEO_mesh_selection.hh"
namespace blender::geometry {
IndexMask vert_selection_from_edge(const Span<int2> edges,
const IndexMask &edge_mask,
const int verts_num,
IndexMaskMemory &memory)
{
PRF_scope(ProfileCategory::Default);
Array<bool> array(verts_num, false);
edge_mask.foreach_index_optimized<int>(
[&](const int i) {
array[edges[i][0]] = true;
array[edges[i][1]] = true;
},
exec_mode::grain_size(4096));
return IndexMask::from_bools(array, memory);
}
static IndexMask mapped_corner_selection_from_face(const OffsetIndices<int> faces,
const IndexMask &face_mask,
const Span<int> corner_verts_or_edges,
const int verts_or_edges_num,
IndexMaskMemory &memory)
{
PRF_scope(ProfileCategory::Default);
Array<bool> array(verts_or_edges_num, false);
face_mask.foreach_index(
[&](const int64_t i) {
array.as_mutable_span().fill_indices(corner_verts_or_edges.slice(faces[i]), true);
},
exec_mode::grain_size(512));
return IndexMask::from_bools(array, memory);
}
IndexMask vert_selection_from_face(const OffsetIndices<int> faces,
const IndexMask &face_mask,
const Span<int> corner_verts,
const int verts_num,
IndexMaskMemory &memory)
{
return mapped_corner_selection_from_face(faces, face_mask, corner_verts, verts_num, memory);
}
IndexMask edge_selection_from_face(const OffsetIndices<int> faces,
const IndexMask &face_mask,
const Span<int> corner_edges,
const int edges_num,
IndexMaskMemory &memory)
{
return mapped_corner_selection_from_face(faces, face_mask, corner_edges, edges_num, memory);
}
IndexMask edge_selection_from_vert(const Span<int2> edges,
const Span<bool> vert_selection,
IndexMaskMemory &memory)
{
PRF_scope(ProfileCategory::Default);
return IndexMask::from_predicate(edges.index_range(), memory, [&](const int64_t i) {
const int2 edge = edges[i];
return vert_selection[edge[0]] && vert_selection[edge[1]];
});
}
static IndexMask face_selection_from_mapped_corner(const OffsetIndices<int> faces,
const Span<int> corner_verts_or_edges,
const Span<bool> vert_or_edge_selection,
IndexMaskMemory &memory)
{
PRF_scope(ProfileCategory::Default);
return IndexMask::from_predicate(faces.index_range(), memory, [&](const int64_t i) {
const Span<int> indices = corner_verts_or_edges.slice(faces[i]);
return std::all_of(
indices.begin(), indices.end(), [&](const int i) { return vert_or_edge_selection[i]; });
});
}
IndexMask face_selection_from_vert(const OffsetIndices<int> faces,
const Span<int> corner_verts,
const Span<bool> vert_selection,
IndexMaskMemory &memory)
{
return face_selection_from_mapped_corner(faces, corner_verts, vert_selection, memory);
}
IndexMask face_selection_from_edge(const OffsetIndices<int> faces,
const Span<int> corner_edges,
const Span<bool> edge_mask,
IndexMaskMemory &memory)
{
return face_selection_from_mapped_corner(faces, corner_edges, edge_mask, memory);
}
} // namespace blender::geometry

View File

@@ -0,0 +1,636 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "BLI_index_mask.hh"
#include "BLI_listbase_iterator.hh"
#include "BLI_ordered_edge.hh"
#include "BKE_attribute.hh"
#include "BKE_attribute_math.hh"
#include "BKE_attribute_storage.hh"
#include "BKE_customdata.hh"
#include "BKE_deform.hh"
#include "BKE_mesh.hh"
#include "BKE_mesh_mapping.hh"
#include "DNA_object_types.h"
#include "GEO_mesh_selection.hh"
#include "GEO_mesh_split_edges.hh"
#include "GEO_randomize.hh"
namespace blender::geometry {
static void propagate_vert_attributes(Mesh &mesh, const Span<int> new_to_old_verts_map)
{
/* These types aren't supported for interpolation below. */
CustomData_free_layers(&mesh.vert_data, CD_SHAPEKEY);
CustomData_free_layers(&mesh.vert_data, CD_CLOTH_ORCO);
CustomData_free_layers(&mesh.vert_data, CD_MVERT_SKIN);
CustomData_realloc(
&mesh.vert_data, mesh.verts_num, mesh.verts_num + new_to_old_verts_map.size());
mesh.verts_num += new_to_old_verts_map.size();
mesh.attribute_storage.wrap().resize(bke::AttrDomain::Point, mesh.verts_num);
Set<StringRef> vertex_group_names;
for (bDeformGroup &group : mesh.vertex_group_names) {
vertex_group_names.add(group.name);
}
if (!vertex_group_names.is_empty() && !mesh.deform_verts().is_empty()) {
MutableSpan<MDeformVert> dverts = mesh.deform_verts_for_write();
bke::gather_deform_verts(
dverts, new_to_old_verts_map, dverts.take_back(new_to_old_verts_map.size()));
}
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
if (iter.storage_type == bke::AttrStorageType::Single) {
return;
}
if (iter.domain != bke::AttrDomain::Point) {
return;
}
if (iter.data_type == bke::AttrType::String) {
return;
}
if (vertex_group_names.contains(iter.name)) {
return;
}
bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(iter.name);
if (!attribute) {
return;
}
bke::attribute_math::gather(attribute.span,
new_to_old_verts_map,
attribute.span.take_back(new_to_old_verts_map.size()));
attribute.finish();
});
if (float3 *orco = static_cast<float3 *>(
CustomData_get_layer_for_write(&mesh.vert_data, CD_ORCO, mesh.verts_num)))
{
array_utils::gather(Span(orco, mesh.verts_num),
new_to_old_verts_map,
MutableSpan(orco, mesh.verts_num).take_back(new_to_old_verts_map.size()));
}
if (int *orig_indices = static_cast<int *>(
CustomData_get_layer_for_write(&mesh.vert_data, CD_ORIGINDEX, mesh.verts_num)))
{
array_utils::gather(
Span(orig_indices, mesh.verts_num),
new_to_old_verts_map,
MutableSpan(orig_indices, mesh.verts_num).take_back(new_to_old_verts_map.size()));
}
}
static void propagate_edge_attributes(Mesh &mesh, const Span<int> new_to_old_edge_map)
{
CustomData_realloc(&mesh.edge_data, mesh.edges_num, mesh.edges_num + new_to_old_edge_map.size());
mesh.edges_num += new_to_old_edge_map.size();
mesh.attribute_storage.wrap().resize(bke::AttrDomain::Edge, mesh.edges_num);
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
if (iter.storage_type == bke::AttrStorageType::Single) {
return;
}
if (iter.domain != bke::AttrDomain::Edge) {
return;
}
if (iter.data_type == bke::AttrType::String) {
return;
}
if (iter.name == ".edge_verts") {
/* Edge vertices are updated and combined with new edges separately. */
return;
}
bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(iter.name);
if (!attribute) {
return;
}
bke::attribute_math::gather(
attribute.span, new_to_old_edge_map, attribute.span.take_back(new_to_old_edge_map.size()));
attribute.finish();
});
if (int *orig_indices = static_cast<int *>(
CustomData_get_layer_for_write(&mesh.edge_data, CD_ORIGINDEX, mesh.edges_num)))
{
array_utils::gather(
Span(orig_indices, mesh.edges_num),
new_to_old_edge_map,
MutableSpan(orig_indices, mesh.edges_num).take_back(new_to_old_edge_map.size()));
}
}
/**
* Used for fanning around the corners connected to a vertex.
*
* Depending on the winding direction of neighboring faces, traveling from a corner across an edge
* to a different face can give a corner that uses a different vertex than the original. To find
* the face's corner that uses the original vertex, we may have to use the next corner instead.
*/
static int corner_on_edge_connected_to_vert(const Span<int> corner_verts,
const int corner,
const IndexRange face,
const int vert)
{
if (corner_verts[corner] == vert) {
return corner;
}
const int other = bke::mesh::face_corner_next(face, corner);
BLI_assert(corner_verts[other] == vert);
return other;
}
using CornerGroup = Vector<int>;
/**
* Collect groups of corners connected by edges bordered by boundary edges or split edges. We store
* corner indices instead of edge indices because later on in the algorithm we only relink the
* `corner_vert` array to each group's new vertex.
*
* The corners are not ordered in winding order, since we only need to group connected faces into
* each group.
*/
static Vector<CornerGroup> calc_corner_groups_for_vertex(const OffsetIndices<int> faces,
const Span<int> corner_verts,
const Span<int> corner_edges,
const GroupedSpan<int> edge_to_corner_map,
const Span<int> corner_to_face_map,
const BitSpan split_edges,
const Span<int> connected_corners,
const int vert)
{
Vector<CornerGroup> groups;
/* Each corner should only be added to a single group. */
BitVector<> used_corners(connected_corners.size());
for (const int start_corner : connected_corners) {
CornerGroup group;
Vector<int> corner_stack({start_corner});
while (!corner_stack.is_empty()) {
const int corner = corner_stack.pop_last();
const int i = connected_corners.first_index(corner);
if (used_corners[i]) {
continue;
}
used_corners[i].set();
group.append(corner);
const int face = corner_to_face_map[corner];
const int prev_corner = bke::mesh::face_corner_prev(faces[face], corner);
/* Travel across the two edges neighboring this vertex, if they aren't split. */
for (const int edge : {corner_edges[corner], corner_edges[prev_corner]}) {
if (split_edges[edge]) {
continue;
}
for (const int other_corner : edge_to_corner_map[edge]) {
const int other_face = corner_to_face_map[other_corner];
if (other_face == face) {
/* Avoid continuing back to the same face. */
continue;
}
const int neighbor_corner = corner_on_edge_connected_to_vert(
corner_verts, other_corner, faces[other_face], vert);
corner_stack.append(neighbor_corner);
}
}
}
if (!group.is_empty()) {
groups.append(std::move(group));
}
}
return groups;
}
/* Calculate groups of corners that are contiguously connected to each input vertex.
* BLI_NOINLINE because MSVC 17.7 has a codegen bug here, given there is only a single call to this
* function, not inlining it for all platforms won't affect performance. See
* https://developercommunity.visualstudio.com/t/10448291 for details. */
BLI_NOINLINE static Array<Vector<CornerGroup>> calc_all_corner_groups(
const OffsetIndices<int> faces,
const Span<int> corner_verts,
const Span<int> corner_edges,
const GroupedSpan<int> vert_to_corner_map,
const GroupedSpan<int> edge_to_corner_map,
const Span<int> corner_to_face_map,
const BitSpan split_edges,
const IndexMask &affected_verts)
{
Array<Vector<CornerGroup>> corner_groups(affected_verts.size(), NoInitialization());
affected_verts.foreach_index(
[&](const int vert, const int mask) {
new (&corner_groups[mask])
Vector<CornerGroup>(calc_corner_groups_for_vertex(faces,
corner_verts,
corner_edges,
edge_to_corner_map,
corner_to_face_map,
split_edges,
vert_to_corner_map[vert],
vert));
},
exec_mode::grain_size(512));
return corner_groups;
}
/** Selected and unselected loose edges attached to a vertex. */
struct VertLooseEdges {
Vector<int> selected;
Vector<int> unselected;
};
/** Find selected and non-selected loose edges connected to a vertex. */
static VertLooseEdges calc_vert_loose_edges(const GroupedSpan<int> vert_to_edge_map,
const BitSpan loose_edges,
const BitSpan split_edges,
const int vert)
{
VertLooseEdges info;
for (const int edge : vert_to_edge_map[vert]) {
if (loose_edges[edge]) {
if (split_edges[edge]) {
info.selected.append(edge);
}
else {
info.unselected.append(edge);
}
}
}
return info;
}
/**
* Every affected vertex maps to potentially multiple output vertices. Create a mapping from
* affected vertex index to the group of output vertex indices (indices are within those groups,
* not indices in arrays of _all_ vertices). For every original vertex, reuse the original vertex
* for the first of:
* 1. The last face corner group
* 2. The last selected loose edge
* 3. The group of non-selected loose edges
* Using this order prioritizes the simplicity of the no-loose-edge case, which we assume is
* more common.
*/
static OffsetIndices<int> calc_vert_ranges_per_old_vert(
const IndexMask &affected_verts,
const Span<Vector<CornerGroup>> corner_groups,
const GroupedSpan<int> vert_to_edge_map,
const BitSpan loose_edges,
const BitSpan split_edges,
Array<int> &offset_data)
{
offset_data.reinitialize(affected_verts.size() + 1);
MutableSpan<int> new_verts_nums = offset_data;
threading::parallel_for(affected_verts.index_range(), 2048, [&](const IndexRange range) {
/* Start with -1 for the reused vertex. None of the final sizes should be negative. */
new_verts_nums.slice(range).fill(-1);
for (const int i : range) {
new_verts_nums[i] += corner_groups[i].size();
}
});
if (!loose_edges.is_empty()) {
affected_verts.foreach_index(
[&](const int vert, const int mask) {
const VertLooseEdges info = calc_vert_loose_edges(
vert_to_edge_map, loose_edges, split_edges, vert);
new_verts_nums[mask] += info.selected.size();
if (corner_groups[mask].is_empty()) {
/* Loose edges share their vertex with a corner group if possible. */
new_verts_nums[mask] += info.unselected.size() > 0;
}
},
exec_mode::grain_size(512));
}
return offset_indices::accumulate_counts_to_offsets(offset_data);
}
/**
* Update corner verts so that each group of corners gets its own vertex. For the last "new vertex"
* we can reuse the original vertex, which would otherwise become unused by any faces. The loose
* edge case will have to deal with this later.
*/
static void update_corner_verts(const int orig_verts_num,
const Span<Vector<CornerGroup>> corner_groups,
const OffsetIndices<int> new_verts_by_affected_vert,
MutableSpan<int> new_corner_verts)
{
threading::parallel_for(corner_groups.index_range(), 512, [&](const IndexRange range) {
for (const int new_vert : range) {
const Span<CornerGroup> groups = corner_groups[new_vert];
const IndexRange new_verts = new_verts_by_affected_vert[new_vert];
for (const int group : groups.index_range().drop_back(1)) {
const int new_vert = orig_verts_num + new_verts[group];
new_corner_verts.fill_indices(groups[group].as_span(), new_vert);
}
}
});
}
static OrderedEdge edge_from_corner(const OffsetIndices<int> faces,
const Span<int> corner_verts,
const Span<int> corner_to_face_map,
const int corner)
{
const int face = corner_to_face_map[corner];
const int corner_next = bke::mesh::face_corner_next(faces[face], corner);
return OrderedEdge(corner_verts[corner], corner_verts[corner_next]);
}
/**
* Based on updated corner vertex indices, update the edges in each face. This includes updating
* corner edge indices, adding new edges, and reusing original edges for the first "split" edge.
* The main complexity comes from the fact that in the case of single isolated split edges, no new
* edges are created because they all end up identical. We need to handle this case, but since it's
* rare, we optimize for the case that it doesn't happen first.
*/
static Array<int2> calc_new_edges(const OffsetIndices<int> faces,
const Span<int> corner_verts,
const GroupedSpan<int> edge_to_corner_map,
const Span<int> corner_to_face_map,
const IndexMask &selected_edges,
MutableSpan<int2> edges,
MutableSpan<int> corner_edges,
MutableSpan<int> r_new_edge_offsets)
{
/* Calculate the offset of new edges assuming no new edges are identical and are merged. */
selected_edges.foreach_index_optimized<int>(
[&](const int edge, const int mask) {
r_new_edge_offsets[mask] = std::max<int>(edge_to_corner_map[edge].size() - 1, 0);
},
exec_mode::grain_size(4096));
const OffsetIndices offsets = offset_indices::accumulate_counts_to_offsets(r_new_edge_offsets);
Array<int2> new_edges(offsets.total_size());
/* Count the number of final new edges per edge, to use as offsets if there are duplicates. */
Array<int> num_edges_per_edge_merged(r_new_edge_offsets.size());
std::atomic<bool> found_duplicate = false;
/* The first new edge for each selected edge is reused-- we modify the existing edge in
* place. Simply reusing the first new edge isn't enough because deduplication might make
* multiple new edges reuse the original. */
Array<bool> is_reused(corner_verts.size(), false);
/* Calculate per-original split edge deduplication of new edges, which are stored by the
* corner vertices of connected faces. Update corner verts to store the updated indices. */
selected_edges.foreach_index(
[&](const int edge, const int mask) {
if (edge_to_corner_map[edge].is_empty()) {
/* Handle loose edges. */
num_edges_per_edge_merged[mask] = 0;
return;
}
const int new_edges_start = offsets[mask].start();
Vector<OrderedEdge> deduplication;
for (const int corner : edge_to_corner_map[edge]) {
const OrderedEdge edge = edge_from_corner(
faces, corner_verts, corner_to_face_map, corner);
int index = deduplication.first_index_of_try(edge);
if (UNLIKELY(index != -1)) {
found_duplicate.store(true, std::memory_order_relaxed);
}
else {
index = deduplication.append_and_get_index(edge);
}
if (index == 0) {
is_reused[corner] = true;
}
else {
corner_edges[corner] = edges.size() + new_edges_start + index - 1;
}
}
const int new_edges_num = deduplication.size() - 1;
edges[edge] = int2(deduplication.first().v_low, deduplication.first().v_high);
new_edges.as_mutable_span()
.slice(new_edges_start, new_edges_num)
.copy_from(deduplication.as_span().drop_front(1).cast<int2>());
num_edges_per_edge_merged[mask] = new_edges_num;
},
exec_mode::grain_size(1024));
if (!found_duplicate) {
/* No edges were merged, we can use the existing output array and offsets. */
return new_edges;
}
/* Update corner edges to remove the "holes" left by merged new edges. */
const OffsetIndices offsets_merged = offset_indices::accumulate_counts_to_offsets(
num_edges_per_edge_merged);
selected_edges.foreach_index(
[&](const int edge, const int mask) {
const int difference = offsets[mask].start() - offsets_merged[mask].start();
for (const int corner : edge_to_corner_map[edge]) {
if (!is_reused[corner]) {
corner_edges[corner] -= difference;
}
}
},
exec_mode::grain_size(2048));
/* Create new edges without the empty slots for the duplicates */
Array<int2> new_edges_merged(offsets_merged.total_size());
threading::parallel_for(offsets_merged.index_range(), 1024, [&](const IndexRange range) {
for (const int i : range) {
new_edges_merged.as_mutable_span()
.slice(offsets_merged[i])
.copy_from(new_edges.as_span().slice(offsets[i].start(), offsets_merged[i].size()));
}
});
r_new_edge_offsets.copy_from(num_edges_per_edge_merged);
return new_edges_merged;
}
static void update_unselected_edges(const OffsetIndices<int> faces,
const Span<int> corner_verts,
const GroupedSpan<int> edge_to_corner_map,
const Span<int> corner_to_face_map,
const IndexMask &unselected_edges,
MutableSpan<int2> edges)
{
unselected_edges.foreach_index(
[&](const int edge) {
const Span<int> edge_corners = edge_to_corner_map[edge];
if (edge_corners.is_empty()) {
return;
}
const int corner = edge_corners.first();
const OrderedEdge new_edge = edge_from_corner(
faces, corner_verts, corner_to_face_map, corner);
edges[edge] = int2(new_edge.v_low, new_edge.v_high);
},
exec_mode::grain_size(1024));
}
static void swap_edge_vert(int2 &edge, const int old_vert, const int new_vert)
{
if (edge[0] == old_vert) {
edge[0] = new_vert;
}
else if (edge[1] == old_vert) {
edge[1] = new_vert;
}
}
/**
* Assign the newly created vertex duplicates to the loose edges around this vertex. Every split
* loose edge is reattached to a newly created vertex. If there are non-split loose edges attached
* to the vertex, they all reuse the original vertex.
*/
static void reassign_loose_edge_verts(const int orig_verts_num,
const IndexMask &affected_verts,
const GroupedSpan<int> vert_to_edge_map,
const BitSpan loose_edges,
const BitSpan split_edges,
const Span<Vector<CornerGroup>> corner_groups,
const OffsetIndices<int> new_verts_by_affected_vert,
MutableSpan<int2> edges)
{
affected_verts.foreach_index(
[&](const int vert, const int mask) {
const IndexRange new_verts = new_verts_by_affected_vert[mask];
/* Account for the reuse of the original vertex by non-loose corner groups. In practice
* this means using the new vertices for each split loose edge until we run out of new
* vertices. We then expect the count to match up with the number of new vertices reserved
* by #calc_vert_ranges_per_old_vert. */
int new_vert_i = std::max<int>(corner_groups[mask].size() - 1, 0);
if (new_vert_i == new_verts.size()) {
return;
}
const VertLooseEdges vert_info = calc_vert_loose_edges(
vert_to_edge_map, loose_edges, split_edges, vert);
for (const int edge : vert_info.selected) {
const int new_vert = orig_verts_num + new_verts[new_vert_i];
swap_edge_vert(edges[edge], vert, new_vert);
new_vert_i++;
if (new_vert_i == new_verts.size()) {
return;
}
}
const int new_vert = orig_verts_num + new_verts[new_vert_i];
for (const int orig_edge : vert_info.unselected) {
swap_edge_vert(edges[orig_edge], vert, new_vert);
}
},
exec_mode::grain_size(1024));
}
/**
* Transform the #OffsetIndices storage of new elements per source element into a more
* standard index map which can be used with existing utilities to copy attributes.
*/
static Array<int> offsets_to_map(const IndexMask &mask, const OffsetIndices<int> offsets)
{
Array<int> map(offsets.total_size());
mask.foreach_index(
[&](const int i, const int mask) { map.as_mutable_span().slice(offsets[mask]).fill(i); },
exec_mode::grain_size(1024));
return map;
}
void split_edges(Mesh &mesh,
const IndexMask &selected_edges,
const bke::AttributeFilter & /*attribute_filter*/)
{
const int orig_verts_num = mesh.verts_num;
const Span<int2> orig_edges = mesh.edges();
const OffsetIndices faces = mesh.faces();
IndexMaskMemory memory;
const IndexMask affected_verts = vert_selection_from_edge(
orig_edges, selected_edges, orig_verts_num, memory);
BitVector<> selection_bits(orig_edges.size());
selected_edges.to_bits(selection_bits);
const IndexMask &loose_edges = mesh.loose_edges();
BitVector<> loose_edge_bits;
if (!loose_edges.is_empty()) {
loose_edge_bits.resize(orig_edges.size());
loose_edges.to_bits(loose_edge_bits);
}
const GroupedSpan<int> vert_to_corner_map = mesh.vert_to_corner_map();
Array<int> edge_to_corner_offsets;
Array<int> edge_to_corner_indices;
const GroupedSpan<int> edge_to_corner_map = bke::mesh::build_edge_to_corner_map(
mesh.corner_edges(), orig_edges.size(), edge_to_corner_offsets, edge_to_corner_indices);
Array<int> vert_to_edge_offsets;
Array<int> vert_to_edge_indices;
GroupedSpan<int> vert_to_edge_map;
if (!loose_edges.is_empty()) {
vert_to_edge_map = bke::mesh::build_vert_to_edge_map(
orig_edges, orig_verts_num, vert_to_edge_offsets, vert_to_edge_indices);
}
const Array<int> corner_to_face_map = mesh.corner_to_face_map();
const Array<Vector<CornerGroup>> corner_groups = calc_all_corner_groups(faces,
mesh.corner_verts(),
mesh.corner_edges(),
vert_to_corner_map,
edge_to_corner_map,
corner_to_face_map,
selection_bits,
affected_verts);
Array<int> vert_new_vert_offset_data;
const OffsetIndices new_verts_by_affected_vert = calc_vert_ranges_per_old_vert(
affected_verts,
corner_groups,
vert_to_edge_map,
loose_edge_bits,
selection_bits,
vert_new_vert_offset_data);
MutableSpan<int> corner_verts = mesh.corner_verts_for_write();
update_corner_verts(orig_verts_num, corner_groups, new_verts_by_affected_vert, corner_verts);
Array<int> new_edge_offsets(selected_edges.size() + 1);
Array<int2> new_edges = calc_new_edges(faces,
corner_verts,
edge_to_corner_map,
corner_to_face_map,
selected_edges,
mesh.edges_for_write(),
mesh.corner_edges_for_write(),
new_edge_offsets);
const IndexMask unselected_edges = selected_edges.complement(orig_edges.index_range(), memory);
update_unselected_edges(faces,
corner_verts,
edge_to_corner_map,
corner_to_face_map,
unselected_edges,
mesh.edges_for_write());
if (!loose_edges.is_empty()) {
reassign_loose_edge_verts(orig_verts_num,
affected_verts,
vert_to_edge_map,
loose_edge_bits,
selection_bits,
corner_groups,
new_verts_by_affected_vert,
mesh.edges_for_write());
}
const Array<int> edge_map = offsets_to_map(selected_edges, new_edge_offsets.as_span());
propagate_edge_attributes(mesh, edge_map);
mesh.edges_for_write().take_back(new_edges.size()).copy_from(new_edges);
const Array<int> vert_map = offsets_to_map(affected_verts, new_verts_by_affected_vert);
propagate_vert_attributes(mesh, vert_map);
mesh.tag_edges_split();
debug_randomize_vert_order(&mesh);
debug_randomize_edge_order(&mesh);
}
} // namespace blender::geometry

View File

@@ -0,0 +1,356 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_deform.hh"
#include "BLI_array.hh"
#include "BLI_array_utils.hh"
#include "BLI_set.hh"
#include "BKE_attribute.hh"
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_mesh.hh"
#include "GEO_mesh_to_curve.hh"
#include "GEO_randomize.hh"
namespace blender::geometry {
/* Don't copy attributes that are built-in on meshes but not on curves. */
static auto filter_builtin_attributes(const bke::AttributeAccessor &mesh_attributes,
const bke::AttributeAccessor &curves_attributes,
Set<StringRef> &storage,
const bke::AttributeFilter &attribute_filter)
{
for (const StringRef name : mesh_attributes.all_names()) {
if (mesh_attributes.is_builtin(name) && !curves_attributes.is_builtin(name)) {
storage.add(name);
}
}
return bke::attribute_filter_with_skip_ref(attribute_filter, storage);
}
BLI_NOINLINE bke::CurvesGeometry create_curve_from_vert_indices(
const bke::AttributeAccessor &mesh_attributes,
const Span<int> vert_indices,
const Span<int> curve_offsets,
const IndexRange cyclic_curves,
const bke::AttributeFilter &attribute_filter)
{
PRF_scope(ProfileCategory::Default);
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);
bke::MutableAttributeAccessor curves_attributes = curves.attributes_for_write();
if (!cyclic_curves.is_empty()) {
curves.cyclic_for_write().slice(cyclic_curves).fill(true);
}
Set<StringRef> skip_storage;
const auto attribute_filter_with_skip = filter_builtin_attributes(
mesh_attributes, curves_attributes, skip_storage, attribute_filter);
bke::gather_attributes(mesh_attributes,
bke::AttrDomain::Point,
bke::AttrDomain::Point,
attribute_filter_with_skip,
vert_indices,
curves_attributes);
/* Transfer attributes from edge, face, and corner domains to curve points. */
mesh_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
if (iter.domain == bke::AttrDomain::Point) {
return;
}
if (iter.data_type == bke::AttrType::String) {
return;
}
if (attribute_filter_with_skip.allow_skip(iter.name)) {
return;
}
const bke::GAttributeReader src = iter.get(bke::AttrDomain::Point);
/* Some attributes might not exist if they were builtin on domains that don't have
* any elements, i.e. a face attribute on the output of the line primitive node. */
if (!src) {
return;
}
const CommonVArrayInfo info = src.varray.common_info();
if (info.type == CommonVArrayInfo::Type::Single) {
const bke::AttributeInitValue init(GPointer(src.varray.type(), info.data));
if (curves_attributes.add(iter.name, bke::AttrDomain::Point, iter.data_type, init)) {
return;
}
}
bke::GSpanAttributeWriter dst = curves_attributes.lookup_or_add_for_write_only_span(
iter.name, bke::AttrDomain::Point, iter.data_type);
if (!dst) {
return;
}
bke::attribute_math::gather(*src, vert_indices, dst.span);
dst.finish();
});
debug_randomize_curve_order(&curves);
return curves;
}
struct CurveFromEdgesOutput {
/** The indices in the mesh for each control point of each result curves. */
Vector<int> vert_indices;
/** The first index of each curve in the result. */
Vector<int> curve_offsets;
/** A subset of curves that should be set cyclic. */
IndexRange cyclic_curves;
};
BLI_NOINLINE static CurveFromEdgesOutput edges_to_curve_point_indices(const int verts_num,
const Span<int2> edges)
{
PRF_scope(ProfileCategory::Default);
/* Compute the number of edges connecting to each vertex. */
Array<int> neighbor_offsets_data(verts_num + 1, 0);
const OffsetIndices<int> neighbor_offsets = offset_indices::build_reverse_offsets(
edges.cast<int>(), neighbor_offsets_data);
/* Use as an index into the "neighbor group" for each vertex. */
Array<int> used_slots(verts_num, 0);
/* Calculate the indices of each vertex's neighboring edges. */
Array<int> neighbors(edges.size() * 2);
for (const int i : edges.index_range()) {
const int v1 = edges[i][0];
const int v2 = edges[i][1];
neighbors[neighbor_offsets[v1].start() + used_slots[v1]] = v2;
neighbors[neighbor_offsets[v2].start() + used_slots[v2]] = v1;
used_slots[v1]++;
used_slots[v2]++;
}
Vector<int> vert_indices;
vert_indices.reserve(edges.size());
Vector<int> curve_offsets;
/* Now use the neighbor group offsets calculated above to count used edges at each vertex. */
Array<int> unused_edges = std::move(used_slots);
for (const int start_vert : IndexRange(verts_num)) {
/* Don't start at vertices with two neighbors, which may become part of cyclic curves. */
if (neighbor_offsets[start_vert].size() == 2) {
continue;
}
/* The vertex has no connected edges, or they were already used. */
if (unused_edges[start_vert] == 0) {
continue;
}
for (const int neighbor : neighbors.as_span().slice(neighbor_offsets[start_vert])) {
int current_vert = start_vert;
int next_vert = neighbor;
if (unused_edges[next_vert] == 0) {
continue;
}
/* Start a new curve in the output. */
curve_offsets.append(vert_indices.size());
vert_indices.append(current_vert);
/* Follow connected edges until we read a vertex with more than two connected edges. */
while (true) {
int last_vert = current_vert;
current_vert = next_vert;
vert_indices.append(current_vert);
unused_edges[current_vert]--;
unused_edges[last_vert]--;
if (neighbor_offsets[current_vert].size() != 2) {
break;
}
const int offset = neighbor_offsets[current_vert].start();
const int next_a = neighbors[offset];
const int next_b = neighbors[offset + 1];
next_vert = (last_vert == next_a) ? next_b : next_a;
}
}
}
/* All curves added after this are cyclic. */
const int cyclic_start = curve_offsets.size();
/* All remaining edges are part of cyclic curves because
* we skipped starting at vertices with two edges before. */
for (const int start_vert : IndexRange(verts_num)) {
if (unused_edges[start_vert] != 2) {
continue;
}
int current_vert = start_vert;
int next_vert = neighbors[neighbor_offsets[current_vert].start()];
curve_offsets.append(vert_indices.size());
vert_indices.append(current_vert);
/* Follow connected edges until we loop back to the start vertex. */
while (next_vert != start_vert) {
const int last_vert = current_vert;
current_vert = next_vert;
vert_indices.append(current_vert);
unused_edges[current_vert]--;
unused_edges[last_vert]--;
const int offset = neighbor_offsets[current_vert].start();
const int next_a = neighbors[offset];
const int next_b = neighbors[offset + 1];
next_vert = (last_vert == next_a) ? next_b : next_a;
}
}
const IndexRange cyclic_curves = curve_offsets.index_range().drop_front(cyclic_start);
return {std::move(vert_indices), std::move(curve_offsets), cyclic_curves};
}
BLI_NOINLINE static bke::CurvesGeometry edges_to_curves_convert(
const Mesh &mesh, const Span<int2> edges, const bke::AttributeFilter &attribute_filter)
{
CurveFromEdgesOutput output = edges_to_curve_point_indices(mesh.verts_num, edges);
return create_curve_from_vert_indices(mesh.attributes(),
output.vert_indices,
output.curve_offsets,
output.cyclic_curves,
attribute_filter);
}
bke::CurvesGeometry mesh_edges_to_curves_convert(const Mesh &mesh,
const IndexMask &selection,
const bke::AttributeFilter &attribute_filter)
{
PRF_scope(ProfileCategory::Default);
const Span<int2> edges = mesh.edges();
if (selection.size() == edges.size()) {
return edges_to_curves_convert(mesh, edges, attribute_filter);
}
Array<int2> selected_edges(selection.size());
array_utils::gather(edges, selection, selected_edges.as_mutable_span());
return edges_to_curves_convert(mesh, selected_edges, attribute_filter);
}
static bke::CurvesGeometry create_curves_for_faces(const Mesh &mesh,
const OffsetIndices<int> faces,
const IndexMask &selection)
{
bke::CurvesGeometry curves;
if (selection.size() == faces.size()) {
implicit_sharing::copy_shared_pointer(mesh.face_offset_indices,
mesh.runtime->face_offsets_sharing_info,
&curves.curve_offsets,
&curves.runtime->curve_offsets_sharing_info);
curves.curve_num = faces.size();
curves.resize(mesh.corners_num, faces.size());
}
else {
curves.resize(0, selection.size());
offset_indices::gather_selected_offsets(faces, selection, curves.offsets_for_write());
curves.resize(curves.offsets().last(), curves.curves_num());
}
BKE_defgroup_copy_list(&curves.vertex_group_names, &mesh.vertex_group_names);
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
attributes.add<bool>("cyclic", bke::AttrDomain::Curve, bke::AttributeInitValue(true));
curves.fill_curve_types(CURVE_TYPE_POLY);
return curves;
}
static Span<int> create_point_to_vert_map(const Mesh &mesh,
const OffsetIndices<int> faces,
const OffsetIndices<int> points_by_curve,
const IndexMask &selection,
Array<int> &map_data)
{
if (selection.size() == faces.size()) {
return mesh.corner_verts();
}
map_data.reinitialize(points_by_curve.total_size());
array_utils::gather_group_to_group(
faces, points_by_curve, selection, mesh.corner_verts(), map_data.as_mutable_span());
return map_data;
}
bke::CurvesGeometry mesh_faces_to_curves_convert(const Mesh &mesh,
const IndexMask &selection,
const bke::AttributeFilter &attribute_filter)
{
PRF_scope(ProfileCategory::Default);
const OffsetIndices faces = mesh.faces();
const bke::AttributeAccessor src_attributes = mesh.attributes();
bke::CurvesGeometry curves = create_curves_for_faces(mesh, faces, selection);
const OffsetIndices points_by_curve = curves.points_by_curve();
bke::MutableAttributeAccessor dst_attributes = curves.attributes_for_write();
Array<int> point_to_vert_data;
const Span<int> point_to_vert_map = create_point_to_vert_map(
mesh, faces, points_by_curve, selection, point_to_vert_data);
Set<StringRef> skip_storage;
const auto attribute_filter_with_skip = filter_builtin_attributes(
src_attributes, dst_attributes, skip_storage, attribute_filter);
bke::gather_attributes(src_attributes,
bke::AttrDomain::Point,
bke::AttrDomain::Point,
attribute_filter_with_skip,
point_to_vert_map,
dst_attributes);
src_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
if (iter.domain != bke::AttrDomain::Edge) {
return;
}
if (iter.data_type == bke::AttrType::String) {
return;
}
if (attribute_filter_with_skip.allow_skip(iter.name)) {
return;
}
const GVArray src = *iter.get(bke::AttrDomain::Point);
bke::GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
iter.name, bke::AttrDomain::Point, iter.data_type);
if (!dst) {
return;
}
bke::attribute_math::gather(src, point_to_vert_map, dst.span);
dst.finish();
});
bke::gather_attributes(src_attributes,
bke::AttrDomain::Face,
bke::AttrDomain::Curve,
attribute_filter_with_skip,
selection,
dst_attributes);
bke::gather_attributes_group_to_group(src_attributes,
bke::AttrDomain::Corner,
bke::AttrDomain::Point,
attribute_filter_with_skip,
faces,
points_by_curve,
selection,
dst_attributes);
return curves;
}
} // namespace blender::geometry

View File

@@ -0,0 +1,226 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_matrix.hh"
#include "BLI_task.hh"
#include "BKE_volume.hh"
#include "BKE_volume_grid.hh"
#include "BKE_volume_openvdb.hh"
#include "GEO_mesh_to_volume.hh"
#ifdef WITH_OPENVDB
# include <algorithm>
# include <openvdb/openvdb.h>
# include <openvdb/tools/GridTransformer.h>
# include <openvdb/tools/LevelSetUtil.h>
# include <openvdb/tools/VolumeToMesh.h>
namespace blender::geometry {
/* This class follows the MeshDataAdapter interface from openvdb. */
class OpenVDBMeshAdapter {
private:
Span<float3> positions_;
Span<int> corner_verts_;
Span<int3> corner_tris_;
float4x4 transform_;
public:
OpenVDBMeshAdapter(const Span<float3> positions,
const Span<int> corner_verts,
const Span<int3> corner_tris,
const float4x4 &transform);
size_t polygonCount() const;
size_t pointCount() const;
size_t vertexCount(size_t /*polygon_index*/) const;
void getIndexSpacePoint(size_t polygon_index, size_t vertex_index, openvdb::Vec3d &pos) const;
};
OpenVDBMeshAdapter::OpenVDBMeshAdapter(const Span<float3> positions,
const Span<int> corner_verts,
const Span<int3> corner_tris,
const float4x4 &transform)
: positions_(positions),
corner_verts_(corner_verts),
corner_tris_(corner_tris),
transform_(transform)
{
}
size_t OpenVDBMeshAdapter::polygonCount() const
{
return size_t(corner_tris_.size());
}
size_t OpenVDBMeshAdapter::pointCount() const
{
return size_t(positions_.size());
}
size_t OpenVDBMeshAdapter::vertexCount(size_t /*polygon_index*/) const
{
/* All polygons are triangles. */
return 3;
}
void OpenVDBMeshAdapter::getIndexSpacePoint(size_t polygon_index,
size_t vertex_index,
openvdb::Vec3d &pos) const
{
const int3 &tri = corner_tris_[polygon_index];
const float3 transformed_co = math::transform_point(
transform_, positions_[corner_verts_[tri[vertex_index]]]);
pos = &transformed_co.x;
}
float volume_compute_voxel_size(const Depsgraph *depsgraph,
const FunctionRef<Bounds<float3>()> bounds_fn,
const MeshToVolumeResolution res,
const float exterior_band_width,
const float4x4 &transform)
{
const float volume_simplify = BKE_volume_simplify_factor(depsgraph);
if (volume_simplify == 0.0f) {
return 0.0f;
}
if (res.mode == MESH_TO_VOLUME_RESOLUTION_MODE_VOXEL_SIZE) {
return res.settings.voxel_size / volume_simplify;
}
if (res.settings.voxel_amount <= 0) {
return 0;
}
const Bounds<float3> bounds = bounds_fn();
/* Compute the diagonal of the bounding box. This is used because
* it will always be bigger than the widest side of the mesh. */
const float diagonal = math::distance(math::transform_point(transform, bounds.min),
math::transform_point(transform, bounds.max));
/* To get the approximate size per voxel, first subtract the exterior band from the requested
* voxel amount, then divide the diagonal with this value if it's bigger than 1. */
const float voxel_size =
(diagonal / std::max(1.0f, float(res.settings.voxel_amount) - 2.0f * exterior_band_width));
/* Return the simplified voxel size. */
return voxel_size / volume_simplify;
}
static openvdb::FloatGrid::Ptr mesh_to_density_grid_impl(
const Span<float3> positions,
const Span<int> corner_verts,
const Span<int3> corner_tris,
const float4x4 &mesh_to_volume_space_transform,
const float voxel_size,
const float interior_band_width,
const float density)
{
if (!BKE_volume_voxel_size_valid(float3(voxel_size))) {
return nullptr;
}
float4x4 mesh_to_index_space_transform = math::from_scale<float4x4>(float3(1.0f / voxel_size));
mesh_to_index_space_transform *= mesh_to_volume_space_transform;
OpenVDBMeshAdapter mesh_adapter{
positions, corner_verts, corner_tris, mesh_to_index_space_transform};
const float interior = std::max(1.0f, interior_band_width / voxel_size);
openvdb::math::Transform::Ptr transform = openvdb::math::Transform::createLinearTransform(
voxel_size);
openvdb::FloatGrid::Ptr new_grid = openvdb::tools::meshToVolume<openvdb::FloatGrid>(
mesh_adapter, *transform, 1.0f, interior);
openvdb::tools::sdfToFogVolume(*new_grid);
if (density != 1.0f) {
openvdb::tools::foreach(new_grid->beginValueOn(),
[&](const openvdb::FloatGrid::ValueOnIter &iter) {
iter.modifyValue([&](float &value) { value *= density; });
});
}
return new_grid;
}
bke::VolumeGrid<float> mesh_to_density_grid(const Span<float3> positions,
const Span<int> corner_verts,
const Span<int3> corner_tris,
const float voxel_size,
const float interior_band_width,
const float density)
{
openvdb::FloatGrid::Ptr grid = mesh_to_density_grid_impl(positions,
corner_verts,
corner_tris,
float4x4::identity(),
voxel_size,
interior_band_width,
density);
if (!grid) {
return {};
}
return bke::VolumeGrid<float>(std::move(grid));
}
bke::VolumeGrid<float> mesh_to_sdf_grid(const Span<float3> positions,
const Span<int> corner_verts,
const Span<int3> corner_tris,
const float voxel_size,
const float half_band_width)
{
if (!BKE_volume_voxel_size_valid(float3(voxel_size)) || half_band_width <= 0.0f) {
return {};
}
std::vector<openvdb::Vec3s> points(positions.size());
std::vector<openvdb::Vec3I> triangles(corner_tris.size());
threading::parallel_for(positions.index_range(), 2048, [&](const IndexRange range) {
for (const int i : range) {
const float3 &co = positions[i];
points[i] = openvdb::Vec3s(co.x, co.y, co.z);
}
});
threading::parallel_for(corner_tris.index_range(), 2048, [&](const IndexRange range) {
for (const int i : range) {
const int3 &tri = corner_tris[i];
triangles[i] = openvdb::Vec3I(
corner_verts[tri[0]], corner_verts[tri[1]], corner_verts[tri[2]]);
}
});
openvdb::math::Transform::Ptr transform = openvdb::math::Transform::createLinearTransform(
voxel_size);
openvdb::FloatGrid::Ptr new_grid = openvdb::tools::meshToLevelSet<openvdb::FloatGrid>(
*transform, points, triangles, half_band_width);
return bke::VolumeGrid<float>(std::move(new_grid));
}
bke::VolumeGridData *fog_volume_grid_add_from_mesh(Volume *volume,
const StringRefNull name,
const Span<float3> positions,
const Span<int> corner_verts,
const Span<int3> corner_tris,
const float4x4 &mesh_to_volume_space_transform,
const float voxel_size,
const float interior_band_width,
const float density)
{
openvdb::FloatGrid::Ptr mesh_grid = mesh_to_density_grid_impl(positions,
corner_verts,
corner_tris,
mesh_to_volume_space_transform,
voxel_size,
interior_band_width,
density);
return mesh_grid ? BKE_volume_grid_add_vdb(*volume, name, std::move(mesh_grid)) : nullptr;
}
} // namespace blender::geometry
#endif

View File

@@ -0,0 +1,726 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "BLI_enumerable_thread_specific.hh"
#include "BLI_index_mask.hh"
#include "BLI_index_mask_expression.hh"
#include "BLI_index_ranges_builder.hh"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_polyfill_2d.h"
#include "BLI_polyfill_2d_beautify.h"
#include "BLI_vector_set.hh"
#include "BLI_heap.h"
#include "BLI_memarena.h"
#include "BKE_attribute.hh"
#include "BKE_attribute_math.hh"
#include "BKE_attribute_storage.hh"
#include "BKE_customdata.hh"
#include "BKE_mesh.hh"
#include "GEO_mesh_triangulate.hh"
namespace blender::geometry {
static void gather(const Span<int> src, const Span<int16_t> indices, MutableSpan<int> dst)
{
for (const int i : indices.index_range()) {
dst[i] = src[indices[i]];
}
}
static Span<int> gather_or_reference(const Span<int> src,
const Span<int16_t> indices,
Vector<int> &dst)
{
if (unique_sorted_indices::non_empty_is_range(indices)) {
return src.slice(indices[0], indices.size());
}
dst.reinitialize(indices.size());
gather(src, indices, dst);
return dst.as_span();
}
static Span<int> gather_or_reference(const Span<int> src,
const IndexMaskSegment mask,
Vector<int> &dst)
{
return gather_or_reference(src.drop_front(mask.offset()), mask.base_span(), dst);
}
/**
* If a significant number of Ngons are selected (> 25% of the faces), then use the
* face normals cache, in case the cache is persistent (or already calculated).
*/
static Span<float3> face_normals_if_worthwhile(const Mesh &src_mesh, const int selection_size)
{
if (src_mesh.runtime->face_normals_cache.is_cached()) {
return src_mesh.face_normals();
}
if (selection_size > src_mesh.faces_num / 4) {
return src_mesh.face_normals();
}
return {};
}
static void copy_loose_vert_hint(const Mesh &src, Mesh &dst)
{
const auto &src_cache = src.runtime->loose_verts_cache;
if (src_cache.is_cached() && src_cache.data().mask.is_empty()) {
dst.tag_loose_verts_none();
}
}
namespace quad {
/**
* #Edge_0_2 #Edge_1_3
* 3 ------- 2 3 ------- 2
* | 1 / | | \ 1 |
* | / | | \ |
* | / | | \ |
* | / 0 | | 0 \ |
* 0 ------- 1 0 ------- 1
*/
enum class QuadDirection : int8_t {
Edge_0_2 = 0,
Edge_1_3 = 1,
};
/**
* \note This behavior is meant to be the same as #BM_verts_calc_rotate_beauty.
* The order of vertices requires special attention.
*/
static QuadDirection calc_quad_direction_beauty(const float3 &v0,
const float3 &v1,
const float3 &v2,
const float3 &v3)
{
const int flip_flag = is_quad_flip_v3(v1, v2, v3, v0);
if (UNLIKELY(flip_flag & (1 << 0))) {
return QuadDirection::Edge_0_2;
}
if (UNLIKELY(flip_flag & (1 << 1))) {
return QuadDirection::Edge_1_3;
}
return BLI_polyfill_edge_calc_rotate_beauty__area(v1, v2, v3, v0, false) > 0.0f ?
QuadDirection::Edge_0_2 :
QuadDirection::Edge_1_3;
}
static void calc_quad_directions(const Span<float3> positions,
const Span<int> face_offsets,
const Span<int> corner_verts,
const TriangulateQuadMode quad_mode,
MutableSpan<QuadDirection> directions)
{
switch (quad_mode) {
case TriangulateQuadMode::Fixed: {
directions.fill(QuadDirection::Edge_0_2);
break;
}
case TriangulateQuadMode::Alternate: {
directions.fill(QuadDirection::Edge_1_3);
break;
}
case TriangulateQuadMode::ShortEdge: {
for (const int i : face_offsets.index_range()) {
const Span<int> verts = corner_verts.slice(face_offsets[i], 4);
const float dist_0_2 = math::distance_squared(positions[verts[0]], positions[verts[2]]);
const float dist_1_3 = math::distance_squared(positions[verts[1]], positions[verts[3]]);
directions[i] = dist_0_2 < dist_1_3 ? QuadDirection::Edge_0_2 : QuadDirection::Edge_1_3;
}
break;
}
case TriangulateQuadMode::LongEdge: {
for (const int i : face_offsets.index_range()) {
const Span<int> verts = corner_verts.slice(face_offsets[i], 4);
const float dist_0_2 = math::distance_squared(positions[verts[0]], positions[verts[2]]);
const float dist_1_3 = math::distance_squared(positions[verts[1]], positions[verts[3]]);
directions[i] = dist_0_2 > dist_1_3 ? QuadDirection::Edge_0_2 : QuadDirection::Edge_1_3;
}
break;
}
case TriangulateQuadMode::Beauty: {
for (const int i : face_offsets.index_range()) {
const Span<int> verts = corner_verts.slice(face_offsets[i], 4);
directions[i] = calc_quad_direction_beauty(
positions[verts[0]], positions[verts[1]], positions[verts[2]], positions[verts[3]]);
}
break;
}
}
}
static void calc_corner_tris(const Span<int> face_offsets,
const Span<QuadDirection> directions,
MutableSpan<int3> corner_tris)
{
for (const int i : face_offsets.index_range()) {
MutableSpan<int> quad_map = corner_tris.slice(2 * i, 2).cast<int>();
/* These corner orders give new edges based on the first vertex of each triangle. */
switch (directions[i]) {
case QuadDirection::Edge_0_2:
quad_map.copy_from({2, 0, 1, 0, 2, 3});
break;
case QuadDirection::Edge_1_3:
quad_map.copy_from({1, 3, 0, 3, 1, 2});
break;
}
const int src_face_start = face_offsets[i];
for (int &i : quad_map) {
i += src_face_start;
}
}
}
static void calc_corner_tris(const Span<float3> positions,
const OffsetIndices<int> src_faces,
const Span<int> src_corner_verts,
const IndexMask &quads,
const TriangulateQuadMode quad_mode,
MutableSpan<int3> corner_tris)
{
struct TLS {
Vector<int> offsets;
Vector<QuadDirection> directions;
};
threading::EnumerableThreadSpecific<TLS> tls;
quads.foreach_segment(
[&](const IndexMaskSegment quads, const int64_t pos) {
TLS &data = tls.local();
data.directions.reinitialize(quads.size());
/* Find the offsets of each face in the local selection. We can gather them together even
* if they aren't contiguous because we only need to know the start of each face; the size
* is just 4. */
const Span<int> offsets = gather_or_reference(src_faces.data(), quads, data.offsets);
calc_quad_directions(positions, offsets, src_corner_verts, quad_mode, data.directions);
const IndexRange tris_range(pos * 2, offsets.size() * 2);
quad::calc_corner_tris(offsets, data.directions, corner_tris.slice(tris_range));
},
exec_mode::grain_size(1024));
}
} // namespace quad
static OffsetIndices<int> gather_selected_offsets(const OffsetIndices<int> src_offsets,
const IndexMaskSegment selection,
MutableSpan<int> dst_offsets)
{
int offset = 0;
for (const int64_t i : selection.index_range()) {
dst_offsets[i] = offset;
offset += src_offsets[selection[i]].size();
}
dst_offsets.last() = offset;
return OffsetIndices<int>(dst_offsets);
}
namespace ngon {
static OffsetIndices<int> calc_tris_by_ngon(const OffsetIndices<int> src_faces,
const IndexMask &ngons,
MutableSpan<int> face_offset_data)
{
ngons.foreach_index(
[&](const int face, const int mask) {
face_offset_data[mask] = bke::mesh::face_triangles_num(src_faces[face].size());
},
exec_mode::grain_size(2048));
return offset_indices::accumulate_counts_to_offsets(face_offset_data);
}
static void calc_corner_tris(const Span<float3> positions,
const OffsetIndices<int> src_faces,
const Span<int> src_corner_verts,
const Span<float3> face_normals,
const IndexMask &ngons,
const OffsetIndices<int> tris_by_ngon,
const TriangulateNGonMode ngon_mode,
MutableSpan<int3> corner_tris)
{
struct LocalData {
Vector<float3x3> projections;
Array<int> offset_data;
Vector<float2> projected_positions;
/* Only used for the "Beauty" method. */
MemArena *arena = nullptr;
Heap *heap = nullptr;
~LocalData()
{
if (arena) {
BLI_memarena_free(arena);
}
if (heap) {
BLI_heap_free(heap, nullptr);
}
}
};
threading::EnumerableThreadSpecific<LocalData> tls;
ngons.foreach_segment(
[&](const IndexMaskSegment ngons, const int pos) {
LocalData &data = tls.local();
/* In order to simplify and "parallelize" the next loops, gather offsets used to group an
* array large enough for all the local face corners. */
data.offset_data.reinitialize(ngons.size() + 1);
const OffsetIndices local_corner_offsets = gather_selected_offsets(
src_faces, ngons, data.offset_data);
/* Use face normals to build projection matrices to make the face positions 2D. */
data.projections.reinitialize(ngons.size());
MutableSpan<float3x3> projections = data.projections;
if (face_normals.is_empty()) {
for (const int i : ngons.index_range()) {
const IndexRange src_face = src_faces[ngons[i]];
const Span<int> face_verts = src_corner_verts.slice(src_face);
const float3 normal = bke::mesh::face_normal_calc(positions, face_verts);
axis_dominant_v3_to_m3_negate(projections[i].ptr(), normal);
}
}
else {
for (const int i : ngons.index_range()) {
axis_dominant_v3_to_m3_negate(projections[i].ptr(), face_normals[ngons[i]]);
}
}
/* Project the face positions into 2D using the matrices calculated above. */
data.projected_positions.reinitialize(local_corner_offsets.total_size());
MutableSpan<float2> projected_positions = data.projected_positions;
for (const int i : ngons.index_range()) {
const IndexRange src_face = src_faces[ngons[i]];
const Span<int> face_verts = src_corner_verts.slice(src_face);
const float3x3 &matrix = projections[i];
MutableSpan<float2> positions_2d = projected_positions.slice(local_corner_offsets[i]);
for (const int i : face_verts.index_range()) {
mul_v2_m3v3(positions_2d[i], matrix.ptr(), positions[face_verts[i]]);
}
}
if (ngon_mode == TriangulateNGonMode::Beauty) {
if (!data.arena) {
data.arena = BLI_memarena_new(BLI_POLYFILL_ARENA_SIZE, __func__);
}
if (!data.heap) {
data.heap = BLI_heap_new_ex(BLI_POLYFILL_ALLOC_NGON_RESERVE);
}
}
/* Calculate the triangulation of corners indices local to each face. */
for (const int i : ngons.index_range()) {
const Span<float2> positions_2d = projected_positions.slice(local_corner_offsets[i]);
const IndexRange tris_range = tris_by_ngon[pos + i];
MutableSpan<int> map = corner_tris.slice(tris_range).cast<int>();
BLI_polyfill_calc(reinterpret_cast<const float (*)[2]>(positions_2d.data()),
positions_2d.size(),
1,
reinterpret_cast<uint(*)[3]>(map.data()));
if (ngon_mode == TriangulateNGonMode::Beauty) {
BLI_polyfill_beautify(reinterpret_cast<const float (*)[2]>(positions_2d.data()),
positions_2d.size(),
reinterpret_cast<uint(*)[3]>(map.data()),
data.arena,
data.heap);
BLI_memarena_clear(data.arena);
}
}
/* "Globalize" the triangulation created above so the map source indices reference _all_ of
* the source vertices, not just within the source face. */
for (const int i : ngons.index_range()) {
const IndexRange tris_range = tris_by_ngon[pos + i];
const int src_face_start = src_faces[ngons[i]].start();
MutableSpan<int> map = corner_tris.slice(tris_range).cast<int>();
for (int &vert : map) {
vert += src_face_start;
}
}
},
exec_mode::grain_size(128));
}
} // namespace ngon
struct TriKey {
int tri_index;
/* The lowest vertex index in the face is used as a hash value and a way to compare face keys to
* avoid memory lookup in all false cases. */
int tri_lower_vert;
TriKey(const int tri_index, Span<int3> tris)
: tri_index(tri_index), tri_lower_vert(tris[tri_index][0])
{
[[maybe_unused]] const int3 &tri_verts = tris[tri_index];
BLI_assert(std::is_sorted(&tri_verts[0], &tri_verts[0] + 3));
}
};
struct FaceHash {
uint64_t operator()(const TriKey value) const
{
return uint64_t(value.tri_lower_vert);
}
uint64_t operator()(const int3 value) const
{
BLI_assert(std::is_sorted(&value[0], &value[0] + 3));
return uint64_t(value[0]);
}
};
struct FacesEquality {
Span<int3> tris;
bool operator()(const TriKey a, const TriKey b) const
{
return a.tri_lower_vert == b.tri_lower_vert && tris[a.tri_index] == tris[b.tri_index];
}
bool operator()(const int3 a, const TriKey b) const
{
BLI_assert(std::is_sorted(&a[0], &a[0] + 3));
return b.tri_lower_vert == a[0] && tris[b.tri_index] == a;
}
};
static int3 tri_to_ordered(const int3 tri)
{
int3 res;
res[0] = std::min({tri[0], tri[1], tri[2]});
res[2] = std::max({tri[0], tri[1], tri[2]});
res[1] = (tri[0] - res[0]) + (tri[2] - res[2]) + tri[1];
return res;
}
static Span<int3> tri_to_ordered_tri(MutableSpan<int3> tris)
{
threading::parallel_for(tris.index_range(), 4096, [&](const IndexRange range) {
for (int3 &tri : tris.slice(range)) {
tri = tri_to_ordered(tri);
}
});
return tris;
}
static IndexMask face_tris_mask(const OffsetIndices<int> src_faces,
const IndexMask &mask,
IndexMaskMemory &memory)
{
return IndexMask::from_batch_predicate(
mask,
memory,
[&](const IndexMaskSegment universe_segment, IndexRangesBuilder<int16_t> &builder) {
if (unique_sorted_indices::non_empty_is_range(universe_segment.base_span())) {
const IndexRange universe_as_range = unique_sorted_indices::non_empty_as_range(
universe_segment.base_span());
const IndexRange segment_range = universe_as_range.shift(universe_segment.offset());
const OffsetIndices segment_faces = src_faces.slice(segment_range);
if (segment_faces.total_size() == segment_faces.size() * 3) {
/* All faces in segment are triangles. */
builder.add_range(universe_as_range.start(), universe_as_range.one_after_last());
return universe_segment.offset();
}
}
for (const int16_t i : universe_segment.base_span()) {
const int face = int(universe_segment.offset() + i);
if (src_faces[face].size() == 3) {
builder.add(i);
}
}
return universe_segment.offset();
});
}
static IndexMask tris_in_set(const IndexMask &tri_mask,
const OffsetIndices<int> faces,
const Span<int> corner_verts,
const VectorSet<TriKey,
4,
DefaultProbingStrategy,
FaceHash,
FacesEquality,
SimpleVectorSetSlot<TriKey, int>> &unique_tris,
IndexMaskMemory &memory)
{
return IndexMask::from_predicate(tri_mask, memory, [&](const int face_i) {
BLI_assert(faces[face_i].size() == 3);
const int3 corner_tri(&corner_verts[faces[face_i].start()]);
return unique_tris.contains_as(tri_to_ordered(corner_tri));
});
}
static void face_keys_to_face_indices(const Span<TriKey> faces, MutableSpan<int> indices)
{
BLI_assert(faces.size() == indices.size());
threading::parallel_for(faces.index_range(), 4096, [&](const IndexRange range) {
for (const int face_i : range) {
indices[face_i] = faces[face_i].tri_index;
}
});
}
static void quad_indices_of_tris(const IndexMask &quads, MutableSpan<int> indices)
{
BLI_assert(quads.size() * 2 == indices.size());
quads.foreach_index_optimized<int>(
[&](const int index, const int pos) {
indices[2 * pos + 0] = index;
indices[2 * pos + 1] = index;
},
exec_mode::grain_size(4096));
}
static void ngon_indices_of_tris(const IndexMask &ngons,
const OffsetIndices<int> tris_by_ngon,
MutableSpan<int> indices)
{
BLI_assert(tris_by_ngon.size() == ngons.size());
BLI_assert(tris_by_ngon.total_size() == indices.size());
ngons.foreach_index_optimized<int>(
[&](const int index, const int pos) { indices.slice(tris_by_ngon[pos]).fill(index); },
exec_mode::grain_size(4096));
}
std::optional<Mesh *> mesh_triangulate(const Mesh &src_mesh,
const IndexMask &selection_with_tris,
const TriangulateNGonMode ngon_mode,
const TriangulateQuadMode quad_mode,
const bke::AttributeFilter &attribute_filter)
{
const Span<float3> positions = src_mesh.vert_positions();
const OffsetIndices src_faces = src_mesh.faces();
const Span<int> src_corner_verts = src_mesh.corner_verts();
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
IndexMaskMemory memory;
/* If there are a lot of triangles, they can be skipped quickly for filtering. */
const IndexMask src_tris = face_tris_mask(src_faces, src_faces.index_range(), memory);
const IndexMask selection = IndexMask::from_difference(selection_with_tris, src_tris, memory);
/* Divide the input selection into separate selections for each face type. This isn't necessary
* for correctness, but considering groups of each face type separately simplifies optimizing
* for each type. For example, quad triangulation is much simpler than Ngon triangulation. */
const IndexMask quads = IndexMask::from_predicate(
selection, memory, [&](const int i) { return src_faces[i].size() == 4; });
const IndexMask ngons = IndexMask::from_predicate(
selection, memory, [&](const int i) { return src_faces[i].size() > 4; });
if (quads.is_empty() && ngons.is_empty()) {
/* All selected faces are already triangles. */
return std::nullopt;
}
/* Calculate group of triangle indices for each selected Ngon to facilitate calculating them in
* parallel later. */
Array<int> tris_by_ngon_data(ngons.size() + 1);
const OffsetIndices tris_by_ngon = ngon::calc_tris_by_ngon(src_faces, ngons, tris_by_ngon_data);
const int ngon_tris_num = tris_by_ngon.total_size();
const int quad_tris_num = quads.size() * 2;
const IndexRange tris_range(ngon_tris_num + quad_tris_num);
const IndexRange ngon_tris_range = tris_range.take_front(ngon_tris_num);
const IndexRange quad_tris_range = tris_range.take_back(quad_tris_num);
Array<int3> corner_tris(ngon_tris_num + quad_tris_num);
if (!ngons.is_empty()) {
ngon::calc_corner_tris(positions,
src_faces,
src_corner_verts,
face_normals_if_worthwhile(src_mesh, ngons.size()),
ngons,
tris_by_ngon,
ngon_mode,
corner_tris.as_mutable_span().slice(ngon_tris_range));
}
if (!quads.is_empty()) {
quad::calc_corner_tris(positions,
src_faces,
src_corner_verts,
quads,
quad_mode,
corner_tris.as_mutable_span().slice(quad_tris_range));
}
/* There are 3 separate sets of triangles: original mesh triangles, new triangles from quads,
* and triangles from n-gons. Deduplication can result in a mix of parts of multiple quads,
* multiple quads, original triangle, and even concatenation of parts of multiple n-gons.
* So we have to deduplicate all triangles together. */
Array<int3> vert_tris(ngon_tris_num + quad_tris_num);
array_utils::gather(src_corner_verts,
corner_tris.as_span().cast<int>(),
vert_tris.as_mutable_span().cast<int>());
const Span<int3> ordered_vert_tris = tri_to_ordered_tri(vert_tris.as_mutable_span());
/* Use ordered vertex triplets (a < b < c) to represent all new triangles.
* #TriKey knows indices of the face and points into #ordered_vert_tris, but probe can be done
* without #TriKey but directly with a triplet so probe not necessary to be a part of
* #ordered_vert_tris. */
VectorSet<TriKey,
4,
DefaultProbingStrategy,
FaceHash,
FacesEquality,
SimpleVectorSetSlot<TriKey, int>>
unique_tris(FaceHash{}, FacesEquality{ordered_vert_tris});
/* Could be done parallel using grouping of faces by their lowest vertex and the next linear
* deduplication, but right now this is just a sequential hash-set. */
for (const int face_i : ordered_vert_tris.index_range()) {
const TriKey face_key(face_i, ordered_vert_tris);
unique_tris.add(face_key);
}
const int unique_tri_num = unique_tris.size();
/* Since currently deduplication is greedy, there is no mix of data of deduplicated triangles,
* instead some of them are removed. Priority: Original triangles removed if any of new triangles
* are the same. For all new triangles here is direct order dependency. */
const IndexMask src_tris_duplicated = tris_in_set(
src_tris, src_faces, src_corner_verts, unique_tris, memory);
index_mask::ExprBuilder mask_builder;
const IndexMask unique_src_faces = index_mask::evaluate_expression(
mask_builder.subtract(src_faces.index_range(), {&quads, &ngons, &src_tris_duplicated}),
memory);
const IndexRange unique_faces_range(unique_tri_num + unique_src_faces.size());
const IndexRange unique_tri_range = unique_faces_range.take_front(unique_tri_num);
const IndexRange unique_src_faces_range = unique_faces_range.take_back(unique_src_faces.size());
/* Create a mesh with no face corners.
* - We haven't yet counted the number of corners from unselected faces. Creating the final face
* offsets will give us that number anyway, so wait to create the edges.
* - Don't create attributes to facilitate implicit sharing of the positions array. */
Mesh *mesh = bke::mesh_new_no_attributes(
src_mesh.verts_num, src_mesh.edges_num, unique_faces_range.size(), 0);
BKE_mesh_copy_parameters_for_eval(mesh, &src_mesh);
MutableSpan<int> dst_offsets = mesh->face_offsets_for_write();
offset_indices::fill_constant_group_size(
3, 0, dst_offsets.take_front(unique_tri_range.size() + 1));
const int total_new_tri_corners = unique_tri_range.size() * 3;
offset_indices::gather_selected_offsets(
src_faces,
unique_src_faces,
total_new_tri_corners,
dst_offsets.take_back(unique_src_faces_range.size() + 1));
const OffsetIndices<int> faces(dst_offsets);
mesh->corners_num = faces.total_size();
/* Vertex attributes are totally unaffected and can be shared with implicit sharing.
* Use the #CustomData API for simpler support for vertex groups. Edge attributes are the same
* for original edges. New edges will be generated by #bke::mesh_calc_edges later.*/
CustomData_merge(&src_mesh.vert_data, &mesh->vert_data, CD_MASK_MESH.vmask, mesh->verts_num);
CustomData_merge(&src_mesh.edge_data, &mesh->edge_data, CD_MASK_MESH.emask, mesh->edges_num);
for (const bke::Attribute &attr : src_mesh.attribute_storage.wrap()) {
if (!ELEM(attr.domain(), bke::AttrDomain::Point, bke::AttrDomain::Edge)) {
continue;
}
mesh->attribute_storage.wrap().add(attr.name(), attr.domain(), attr.data_type(), attr.data());
}
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
const bool has_duplicate_faces = unique_tri_num != (ngon_tris_num + quad_tris_num);
Array<int> dst_tri_to_src_face(unique_tri_num);
face_keys_to_face_indices(unique_tris.as_span(), dst_tri_to_src_face.as_mutable_span());
Array<int3> unique_corner_tris_data;
if (has_duplicate_faces) {
unique_corner_tris_data.reinitialize(unique_tri_num);
array_utils::gather(corner_tris.as_span(),
dst_tri_to_src_face.as_span(),
unique_corner_tris_data.as_mutable_span());
}
{
Array<int> src_to_unique_map(ngon_tris_num + quad_tris_num);
quad_indices_of_tris(quads, src_to_unique_map.as_mutable_span().slice(quad_tris_range));
ngon_indices_of_tris(
ngons, tris_by_ngon, src_to_unique_map.as_mutable_span().slice(ngon_tris_range));
array_utils::gather(src_to_unique_map.as_span(),
dst_tri_to_src_face.as_span(),
dst_tri_to_src_face.as_mutable_span());
}
const Span<int3> unique_corner_tris = has_duplicate_faces ? unique_corner_tris_data.as_span() :
corner_tris.as_span();
for (auto &attribute : bke::retrieve_attributes_for_transfer(
src_attributes, attributes, {bke::AttrDomain::Face}, attribute_filter))
{
bke::attribute_math::gather(
attribute.src, dst_tri_to_src_face.as_span(), attribute.dst.span.slice(unique_tri_range));
array_utils::gather(
attribute.src, unique_src_faces, attribute.dst.span.slice(unique_src_faces_range));
attribute.dst.finish();
}
if (CustomData_has_layer(&src_mesh.face_data, CD_ORIGINDEX)) {
const Span src(
static_cast<const int *>(CustomData_get_layer(&src_mesh.face_data, CD_ORIGINDEX)),
src_mesh.faces_num);
MutableSpan dst(static_cast<int *>(CustomData_add_layer(
&mesh->face_data, CD_ORIGINDEX, CD_CONSTRUCT, mesh->faces_num)),
mesh->faces_num);
array_utils::gather(src, dst_tri_to_src_face.as_span(), dst.slice(unique_tri_range));
array_utils::gather(src, unique_src_faces, dst.slice(unique_src_faces_range));
}
attributes.add<int>(".corner_vert", bke::AttrDomain::Corner, bke::AttributeInitConstruct());
MutableSpan<int> corner_verts = mesh->corner_verts_for_write();
array_utils::gather_group_to_group(src_faces,
faces.slice(unique_src_faces_range),
unique_src_faces,
src_corner_verts,
corner_verts);
array_utils::gather(src_corner_verts,
unique_corner_tris.cast<int>(),
corner_verts.take_front(total_new_tri_corners));
for (auto &attribute : bke::retrieve_attributes_for_transfer(
src_attributes,
attributes,
{bke::AttrDomain::Corner},
bke::attribute_filter_with_skip_ref(attribute_filter,
{".corner_vert", ".corner_edge"})))
{
bke::attribute_math::gather_group_to_group(
src_faces,
faces.slice(IndexRange(unique_tri_num, unique_src_faces.size())),
unique_src_faces,
attribute.src,
attribute.dst.span);
bke::attribute_math::gather(attribute.src,
unique_corner_tris.cast<int>(),
attribute.dst.span.slice(0, unique_tri_num * 3));
attribute.dst.finish();
}
/* Automatically generate new edges between new triangles, with necessary deduplication. */
bke::mesh_calc_edges(*mesh, true, false, attribute_filter);
mesh->runtime->bounds_cache = src_mesh.runtime->bounds_cache;
copy_loose_vert_hint(src_mesh, *mesh);
if (src_mesh.no_overlapping_topology()) {
mesh->tag_overlapping_none();
}
BLI_assert(bke::mesh_is_valid(*mesh));
return mesh;
}
} // namespace blender::geometry

View File

@@ -0,0 +1,314 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "GEO_mix_geometries.hh"
#include "BKE_attribute.hh"
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_instances.hh"
#include "DNA_mesh_types.h"
#include "DNA_pointcloud_types.h"
#include "NOD_geometry_nodes_bundle.hh"
#include "NOD_geometry_nodes_list.hh"
namespace blender::geometry {
static bool sharing_info_equal(const ImplicitSharingInfo *a, const ImplicitSharingInfo *b)
{
if (!a || !b) {
return false;
}
return a == b;
}
template<typename T>
void mix_with_indices(MutableSpan<T> a,
const VArray<T> &b,
const Span<int> index_map,
const float factor)
{
threading::parallel_for(a.index_range(), 1024, [&](const IndexRange range) {
devirtualize_varray(b, [&](const auto b) {
for (const int i : range) {
if (index_map[i] != -1) {
a[i] = bke::attribute_math::mix2(factor, a[i], b[index_map[i]]);
}
}
});
});
}
static void mix_with_indices(GMutableSpan a,
const GVArray &b,
const Span<int> index_map,
const float factor)
{
bke::attribute_math::to_static_type(a.type(), [&]<typename T>() {
if constexpr (!std::is_same_v<T, std::string>) {
mix_with_indices(a.typed<T>(), b.typed<T>(), index_map, factor);
}
});
}
template<typename T> static void mix(MutableSpan<T> a, const VArray<T> &b, const float factor)
{
threading::parallel_for(a.index_range(), 1024, [&](const IndexRange range) {
devirtualize_varray(b, [&](const auto b) {
for (const int i : range) {
a[i] = bke::attribute_math::mix2(factor, a[i], b[i]);
}
});
});
}
static void mix(GMutableSpan a, const GVArray &b, const float factor)
{
bke::attribute_math::to_static_type(a.type(), [&]<typename T>() {
if constexpr (!std::is_same_v<T, std::string>) {
mix(a.typed<T>(), b.typed<T>(), factor);
}
});
}
static void mix_attributes(bke::MutableAttributeAccessor attributes_a,
const bke::AttributeAccessor b_attributes,
const Span<int> index_map,
const bke::AttrDomain mix_domain,
const float factor,
const Set<std::string> &names_to_skip = {})
{
Set<StringRefNull> names = attributes_a.all_names();
names.remove("id");
for (const StringRef name : names_to_skip) {
names.remove_as(name);
}
for (const StringRef name : names) {
const bke::GAttributeReader attribute_a = attributes_a.lookup(name);
const bke::AttrDomain domain = attribute_a.domain;
if (domain != mix_domain) {
continue;
}
const bke::AttrType type = bke::cpp_type_to_attribute_type(attribute_a.varray.type());
if (ELEM(type, bke::AttrType::String, bke::AttrType::Bool)) {
/* String attributes can't be mixed, and there's no point in mixing boolean attributes. */
continue;
}
const bke::GAttributeReader attribute_b = b_attributes.lookup(name, attribute_a.domain, type);
if (sharing_info_equal(attribute_a.sharing_info, attribute_b.sharing_info)) {
continue;
}
if (!index_map.is_empty()) {
bke::GSpanAttributeWriter dst = attributes_a.lookup_for_write_span(name);
/* If there's an ID attribute, use its values to mix with potentially changed indices. */
mix_with_indices(dst.span, *attribute_b, index_map, factor);
dst.finish();
}
else if (attributes_a.domain_size(domain) == b_attributes.domain_size(domain)) {
bke::GSpanAttributeWriter dst = attributes_a.lookup_for_write_span(name);
/* With no ID attribute to find matching elements, we can only support mixing when the domain
* size (topology) is the same. Other options like mixing just the start of arrays might work
* too, but give bad results too. */
mix(dst.span, attribute_b.varray, factor);
dst.finish();
}
}
}
static Array<int> create_id_index_map(const bke::AttributeAccessor attributes_a,
const bke::AttributeAccessor b_attributes,
const bke::AttrDomain id_domain)
{
const bke::GAttributeReader ids_a = attributes_a.lookup("id");
const bke::GAttributeReader ids_b = b_attributes.lookup("id");
if (!ids_a || !ids_b) {
return {};
}
if (!ids_a.varray.type().is<int>() || !ids_b.varray.type().is<int>()) {
return {};
}
if (ids_a.domain != id_domain || ids_b.domain != id_domain) {
return {};
}
if (sharing_info_equal(ids_a.sharing_info, ids_b.sharing_info)) {
return {};
}
const VArraySpan ids_span_a(ids_a.varray.typed<int>());
const VArraySpan ids_span_b(ids_b.varray.typed<int>());
/* Use #int instead of the default #int64_t for internal indices. */
const VectorSet<int,
16,
DefaultProbingStrategy,
DefaultHash<int>,
DefaultEquality<int>,
SimpleVectorSetSlot<int, int>,
GuardedAllocator>
id_map_b(ids_span_b);
Array<int> index_map(ids_span_a.size());
threading::parallel_for(ids_span_a.index_range(), 1024, [&](const IndexRange range) {
for (const int i : range) {
index_map[i] = id_map_b.index_of_try(ids_span_a[i]);
}
});
return index_map;
}
static void mix_socket_values_same_type(bke::SocketValueVariant &a,
const bke::SocketValueVariant &b,
const float factor)
{
BLI_assert(a.socket_type() == b.socket_type());
if (a.is_single() && b.is_single()) {
GMutablePointer a_ptr = a.get_single_ptr();
const GPointer b_ptr = b.get_single_ptr();
if (!a_ptr || !b_ptr) {
return;
}
if (a_ptr.is_type<bke::GeometrySet>()) {
mix_geometries(*a_ptr.get<bke::GeometrySet>(), *b_ptr.get<bke::GeometrySet>(), factor);
}
else if (a_ptr.is_type<nodes::BundlePtr>()) {
nodes::BundlePtr &a_bundle_ptr = *a_ptr.get<nodes::BundlePtr>();
const nodes::BundlePtr &b_bundle_ptr = *b_ptr.get<nodes::BundlePtr>();
if (!a_bundle_ptr || !b_bundle_ptr) {
return;
}
mix_bundles(a_bundle_ptr.ensure_mutable_inplace(), *b_bundle_ptr, factor);
}
else {
mix(GMutableSpan(a_ptr.type(), a_ptr.get(), 1),
GVArray::from_single_ref(*b_ptr.type(), 1, b_ptr.get()),
factor);
}
}
else if (a.is_list() && b.is_list()) {
nodes::GListPtr a_list_ptr = a.extract<nodes::GListPtr>();
const nodes::GListPtr b_list = b.get<nodes::GListPtr>();
if (a_list_ptr->cpp_type() != b_list->cpp_type()) {
/* Lists with the same socket type can still have different CPPTypes, e.g. for fields and
* grids and single values. For now just don't try to support those combinations. */
return;
}
nodes::GList &a_list = a_list_ptr.get_for_write();
std::variant<GMutableSpan, GMutablePointer> a_values = a_list.values_for_write();
if (auto *a_span = std::get_if<GMutableSpan>(&a_values)) {
const GVArray b_varray = b_list->varray();
mix(*a_span, b_varray.slice(IndexRange(a_span->size())), factor);
}
else if (auto *a_pointer = std::get_if<GMutablePointer>(&a_values)) {
const GVArray b_varray = b_list->varray();
mix(GMutableSpan(*a_pointer->type(), a_pointer->get(), 1),
b_varray.slice(IndexRange(1)),
factor);
}
/* Ideally the API would not require extracting the list and storing it again. */
a = bke::SocketValueVariant::From(std::move(a_list_ptr));
}
}
void mix_socket_values(bke::SocketValueVariant &a,
const bke::SocketValueVariant &b,
const float factor)
{
if (a.socket_type() == SOCK_STRING) {
return;
}
std::optional<bke::SocketValueVariant> b_converted = nodes::implicitly_convert_socket_value(
*bke::node_socket_type_find_static(b.socket_type(), 0),
b,
*bke::node_socket_type_find_static(a.socket_type(), 0));
if (!b_converted) {
return;
}
mix_socket_values_same_type(a, *b_converted, factor);
}
static void mix_bundle_items(nodes::BundleItemValue &a,
const nodes::BundleItemValue &b,
const float factor)
{
auto *a_socket_value = std::get_if<nodes::BundleItemSocketValue>(&a.value);
if (!a_socket_value) {
return;
}
const auto *b_socket_value = std::get_if<nodes::BundleItemSocketValue>(&b.value);
if (!b_socket_value) {
return;
}
mix_socket_values(a_socket_value->value, b_socket_value->value, factor);
}
void mix_bundles(nodes::Bundle &a, const nodes::Bundle &b, const float factor)
{
for (const auto &[name, a_value] : a.items()) {
const nodes::BundleItemValue *b_value = b.lookup(name);
if (!b_value) {
continue;
}
mix_bundle_items(a_value, *b_value, factor);
}
}
void mix_geometries(bke::GeometrySet &a, const bke::GeometrySet &b, const float factor)
{
if (Mesh *mesh_a = a.get_mesh_for_write()) {
if (const Mesh *mesh_b = b.get_mesh()) {
Array<int> vert_map = create_id_index_map(
mesh_a->attributes(), mesh_b->attributes(), bke::AttrDomain::Point);
mix_attributes(mesh_a->attributes_for_write(),
mesh_b->attributes(),
vert_map,
bke::AttrDomain::Point,
factor,
{});
}
}
if (PointCloud *points_a = a.get_pointcloud_for_write()) {
if (const PointCloud *points_b = b.get_pointcloud()) {
const Array<int> index_map = create_id_index_map(
points_a->attributes(), points_b->attributes(), bke::AttrDomain::Point);
mix_attributes(points_a->attributes_for_write(),
points_b->attributes(),
index_map,
bke::AttrDomain::Point,
factor);
}
}
if (Curves *curves_a = a.get_curves_for_write()) {
if (const Curves *curves_b = b.get_curves()) {
bke::MutableAttributeAccessor a = curves_a->geometry.wrap().attributes_for_write();
const bke::AttributeAccessor b = curves_b->geometry.wrap().attributes();
const Array<int> index_map = create_id_index_map(a, b, bke::AttrDomain::Point);
mix_attributes(
a,
b,
index_map,
bke::AttrDomain::Point,
factor,
{"curve_type", "nurbs_order", "knots_mode", "handle_type_left", "handle_type_right"});
}
}
if (bke::Instances *instances_a = a.get_instances_for_write()) {
if (const bke::Instances *instances_b = b.get_instances()) {
const Array<int> index_map = create_id_index_map(
instances_a->attributes(), instances_b->attributes(), bke::AttrDomain::Instance);
mix_attributes(instances_a->attributes_for_write(),
instances_b->attributes(),
index_map,
bke::AttrDomain::Instance,
factor,
{".reference_index"});
}
}
if (a.has_bundle() && b.has_bundle()) {
mix_bundles(a.bundle_for_write(), *b.bundle(), factor);
}
}
} // namespace blender::geometry

View File

@@ -0,0 +1,123 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "BLI_kdtree.hh"
#include "BLI_offset_indices.hh"
#include "BLI_task.hh"
#include "DNA_pointcloud_types.h"
#include "BKE_attribute_math.hh"
#include "BKE_pointcloud.hh"
#include "GEO_point_merge.hh"
#include "GEO_randomize.hh"
#include "atomic_ops.h"
namespace blender::geometry {
PointCloud *merge_points(const PointCloud &src_points,
const IndexMask &selection,
const Span<int> merge_ids,
const bke::AttributeFilter &attribute_filter)
{
VectorSet<int> group_indices;
selection.foreach_index_optimized<int32_t>(
[&](const int i) { group_indices.add(merge_ids[i]); });
const int groups_num = group_indices.size();
Array<int> group_sizes(group_indices.size() + 1, 0);
selection.foreach_index_optimized<int>(
[&](const int i) {
const int group_i = group_indices.index_of(merge_ids[i]);
atomic_add_and_fetch_int32(&group_sizes[group_i], 1);
},
exec_mode::grain_size(8192));
BLI_assert(!group_sizes.as_span().drop_back(1).contains(0));
const OffsetIndices<int> group_offsets = offset_indices::accumulate_counts_to_offsets(
group_sizes);
Array<int> all_group_indices(group_offsets.total_size());
Array<int> group_counts(groups_num, 0);
selection.foreach_index_optimized<int>(
[&](const int i) {
const int group_i = group_indices.index_of(merge_ids[i]);
const int index_in_group = atomic_fetch_and_add_int32(&group_counts[group_i], 1);
all_group_indices[group_offsets[group_i][index_in_group]] = int(i);
},
exec_mode::grain_size(8192));
group_counts = {};
offset_indices::sort_groups(group_offsets, all_group_indices);
const GroupedSpan<int> indices_by_group(group_offsets, all_group_indices);
IndexMaskMemory memory;
const IndexMask unselected = selection.complement(IndexMask(src_points.totpoint), memory);
PointCloud *dst_pointcloud = BKE_pointcloud_new_nomain(unselected.size() + groups_num);
bke::MutableAttributeAccessor dst_attributes = dst_pointcloud->attributes_for_write();
src_points.attributes().foreach_attribute([&](const bke::AttributeIter &iter) {
if (attribute_filter.allow_skip(iter.name)) {
return;
}
bke::GAttributeReader src = iter.get();
const CommonVArrayInfo info = src.varray.common_info();
if (info.type == CommonVArrayInfo::Type::Single) {
const bke::AttributeInitValue init(GPointer(src.varray.type(), info.data));
if (dst_attributes.add(iter.name, bke::AttrDomain::Point, iter.data_type, init)) {
return;
}
}
const GVArraySpan src_span(*src);
bke::GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_only_span(
iter.name, bke::AttrDomain::Point, iter.data_type);
array_utils::gather(src_span, unselected, dst_attribute.span.take_front(unselected.size()));
if (iter.name == "id" && iter.data_type == bke::AttrType::Int32) {
const Span<int> src_typed = src_span.typed<int>();
MutableSpan<int> dst_typed = dst_attribute.span.typed<int>().take_back(groups_num);
threading::parallel_for(dst_typed.index_range(), 4096, [&](const IndexRange range) {
for (const int i : range) {
dst_typed[i] = src_typed[indices_by_group[i].first()];
}
});
}
else {
bke::attribute_math::mix_groups(
src_span, indices_by_group, dst_attribute.span.take_back(groups_num));
}
dst_attribute.finish();
});
debug_randomize_point_order(dst_pointcloud);
return dst_pointcloud;
}
PointCloud *point_merge_by_distance(const PointCloud &src_points,
const float merge_distance,
const IndexMask &selection,
const bke::AttributeFilter &attribute_filter)
{
const Span<float3> positions = src_points.positions();
KDTree<float3> *tree = kdtree_new<float3>(selection.size());
selection.foreach_index_optimized<int64_t>(
[&](const int64_t i) { kdtree_insert<float3>(tree, i, positions[i]); });
kdtree_balance<float3>(tree);
Array<int> root_indices(src_points.totpoint, -1);
kdtree_calc_duplicates_fast<float3>(tree, merge_distance, false, root_indices.data());
kdtree_free<float3>(tree);
threading::parallel_for(root_indices.index_range(), 1024, [&](const IndexRange range) {
for (const int i : range) {
if (root_indices[i] == -1) {
root_indices[i] = i;
}
}
});
return merge_points(src_points, selection, root_indices.as_span(), attribute_filter);
}
} // namespace blender::geometry

View File

@@ -0,0 +1,115 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_base.hh"
#include "BKE_volume.hh"
#include "BKE_volume_grid.hh"
#include "BKE_volume_openvdb.hh"
#include "GEO_points_to_volume.hh"
#ifdef WITH_OPENVDB
# include <openvdb/openvdb.h>
# include <openvdb/tools/LevelSetUtil.h>
# include <openvdb/tools/ParticlesToLevelSet.h>
namespace blender::geometry {
/* Implements the interface required by #openvdb::tools::ParticlesToLevelSet. */
class OpenVDBParticleList {
public:
using PosType = openvdb::Vec3R;
private:
Span<float3> positions_;
Span<float> radii_;
float voxel_size_inv_;
public:
OpenVDBParticleList(const Span<float3> positions,
const Span<float> radii,
const float voxel_size)
: positions_(positions), radii_(radii), voxel_size_inv_(math::rcp(voxel_size))
{
BLI_assert(voxel_size > 0.0f);
}
size_t size() const
{
return size_t(positions_.size());
}
void getPos(size_t n, openvdb::Vec3R &xyz) const
{
const float3 pos = positions_[n] * voxel_size_inv_;
xyz = &pos.x;
}
void getPosRad(size_t n, openvdb::Vec3R &xyz, openvdb::Real &radius) const
{
this->getPos(n, xyz);
radius = radii_[n] * voxel_size_inv_;
}
};
static openvdb::FloatGrid::Ptr points_to_sdf_grid_impl(const Span<float3> positions,
const Span<float> radii,
const float voxel_size)
{
if (!BKE_volume_voxel_size_valid(float3(voxel_size))) {
return nullptr;
}
/* Create a new grid that will be filled. #ParticlesToLevelSet requires
* the background value to be positive */
openvdb::FloatGrid::Ptr new_grid = openvdb::FloatGrid::create(1.0f);
/* Create a narrow-band level set grid based on the positions and radii. */
openvdb::tools::ParticlesToLevelSet op{*new_grid};
/* Don't ignore particles based on their radius. */
op.setRmin(0.0f);
op.setRmax(std::numeric_limits<float>::max());
OpenVDBParticleList particles{positions, radii, voxel_size};
op.rasterizeSpheres(particles);
op.finalize();
new_grid->transform().postScale(voxel_size);
new_grid->setGridClass(openvdb::GRID_LEVEL_SET);
return new_grid;
}
bke::VolumeGrid<float> points_to_sdf_grid(const Span<float3> positions,
const Span<float> radii,
const float voxel_size)
{
return bke::VolumeGrid<float>(points_to_sdf_grid_impl(positions, radii, voxel_size));
}
bke::VolumeGridData *fog_volume_grid_add_from_points(Volume *volume,
const StringRefNull name,
const Span<float3> positions,
const Span<float> radii,
const float voxel_size,
const float density)
{
openvdb::FloatGrid::Ptr new_grid = points_to_sdf_grid_impl(positions, radii, voxel_size);
new_grid->setGridClass(openvdb::GRID_FOG_VOLUME);
/* Convert the level set to a fog volume. This also sets the background value to zero. Inside the
* fog there will be a density of 1. */
openvdb::tools::sdfToFogVolume(*new_grid);
/* Take the desired density into account. */
openvdb::tools::foreach(new_grid->beginValueOn(),
[&](const openvdb::FloatGrid::ValueOnIter &iter) {
iter.modifyValue([&](float &value) { value *= density; });
});
return BKE_volume_grid_add_vdb(*volume, name, std::move(new_grid));
}
} // namespace blender::geometry
#endif

View File

@@ -0,0 +1,329 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <algorithm>
#include <random>
#include "GEO_randomize.hh"
#include "DNA_curves_types.h"
#include "DNA_mesh_types.h"
#include "DNA_pointcloud_types.h"
#include "BKE_attribute_storage.hh"
#include "BKE_curves.hh"
#include "BKE_customdata.hh"
#include "BKE_geometry_set.hh"
#include "BKE_global.hh"
#include "BKE_instances.hh"
#include "BLI_array.hh"
namespace blender::geometry {
static Array<int> get_permutation(const int length, const int seed)
{
Array<int> data(length);
for (const int i : IndexRange(length)) {
data[i] = i;
}
std::shuffle(data.begin(), data.end(), std::default_random_engine(seed));
return data;
}
static Array<int> invert_permutation(const Span<int> permutation)
{
Array<int> data(permutation.size());
for (const int i : permutation.index_range()) {
data[permutation[i]] = i;
}
return data;
}
/**
* We can't use a fully random seed, because then the randomization wouldn't be deterministic,
* which is important to avoid causing issues when determinism is expected. Using a single constant
* seed is not ideal either, because then two geometries might be randomized equally or very
* similar. Ideally, the seed would be a hash of everything that feeds into the geometry processing
* algorithm before the randomization, but that's too expensive. Just use something simple but
* correct for now.
*/
static int seed_from_mesh(const Mesh &mesh)
{
return mesh.verts_num;
}
static int seed_from_pointcloud(const PointCloud &pointcloud)
{
return pointcloud.totpoint;
}
static int seed_from_curves(const bke::CurvesGeometry &curves)
{
return curves.point_num;
}
static int seed_from_instances(const bke::Instances &instances)
{
return instances.instances_num();
}
static void reorder_customdata(CustomData &data, const Span<int> new_by_old_map)
{
CustomData new_data;
CustomData_init_layout_from(&data, &new_data, CD_MASK_ALL, CD_CONSTRUCT, new_by_old_map.size());
for (const int old_i : new_by_old_map.index_range()) {
const int new_i = new_by_old_map[old_i];
CustomData_copy_data(&data, &new_data, old_i, new_i, 1);
}
CustomData_free(&data);
data = new_data;
}
static void reorder_attribute_domain(bke::AttributeStorage &data,
const bke::AttrDomain domain,
const Span<int> new_by_old_map)
{
for (bke::Attribute &attr : data) {
if (attr.domain() != domain) {
continue;
}
const CPPType &type = bke::attribute_type_to_cpp_type(attr.data_type());
switch (attr.storage_type()) {
case bke::AttrStorageType::Array: {
const auto &data = std::get<bke::Attribute::ArrayData>(attr.data());
auto new_data = bke::Attribute::ArrayData::from_constructed(type, new_by_old_map.size());
bke::attribute_math::gather(GSpan(type, data.data, data.size),
new_by_old_map,
GMutableSpan(type, new_data.data, new_data.size));
attr.assign_data(std::move(new_data));
break;
}
case bke::AttrStorageType::Single: {
break;
}
}
}
}
void debug_randomize_vert_order(Mesh *mesh)
{
if (mesh == nullptr || !use_debug_randomization()) {
return;
}
const int seed = seed_from_mesh(*mesh);
const Array<int> new_by_old_map = get_permutation(mesh->verts_num, seed);
const Array<int> old_by_new_map = invert_permutation(new_by_old_map);
reorder_customdata(mesh->vert_data, new_by_old_map);
reorder_attribute_domain(mesh->attribute_storage.wrap(), bke::AttrDomain::Point, old_by_new_map);
for (int &v : mesh->edges_for_write().cast<int>()) {
v = new_by_old_map[v];
}
for (int &v : mesh->corner_verts_for_write()) {
v = new_by_old_map[v];
}
mesh->tag_topology_changed();
}
void debug_randomize_edge_order(Mesh *mesh)
{
if (mesh == nullptr || !use_debug_randomization()) {
return;
}
const int seed = seed_from_mesh(*mesh);
const Array<int> new_by_old_map = get_permutation(mesh->edges_num, seed);
const Array<int> old_by_new_map = invert_permutation(new_by_old_map);
reorder_customdata(mesh->edge_data, new_by_old_map);
reorder_attribute_domain(mesh->attribute_storage.wrap(), bke::AttrDomain::Edge, old_by_new_map);
for (int &e : mesh->corner_edges_for_write()) {
e = new_by_old_map[e];
}
mesh->tag_topology_changed();
}
static Array<int> make_new_offset_indices(const OffsetIndices<int> old_offsets,
const Span<int> old_by_new_map)
{
Array<int> new_offsets(old_offsets.size() + 1);
offset_indices::gather_group_sizes(old_offsets, old_by_new_map, new_offsets);
offset_indices::accumulate_counts_to_offsets(new_offsets.as_mutable_span());
return new_offsets;
}
static void reorder_customdata_groups(CustomData &data,
const OffsetIndices<int> old_offsets,
const OffsetIndices<int> new_offsets,
const Span<int> new_by_old_map)
{
const int elements_num = new_offsets.total_size();
const int groups_num = new_by_old_map.size();
CustomData new_data;
CustomData_init_layout_from(&data, &new_data, CD_MASK_ALL, CD_CONSTRUCT, elements_num);
for (const int old_i : IndexRange(groups_num)) {
const int new_i = new_by_old_map[old_i];
const IndexRange old_range = old_offsets[old_i];
const IndexRange new_range = new_offsets[new_i];
BLI_assert(old_range.size() == new_range.size());
CustomData_copy_data(&data, &new_data, old_range.start(), new_range.start(), old_range.size());
}
CustomData_free(&data);
data = new_data;
}
static void reorder_attribute_groups(bke::AttributeStorage &storage,
const bke::AttrDomain domain,
const OffsetIndices<int> old_offsets,
const OffsetIndices<int> new_offsets,
const Span<int> new_by_old_map)
{
const int groups_num = new_by_old_map.size();
BLI_assert(old_offsets.size() == groups_num);
BLI_assert(new_offsets.size() == groups_num);
for (bke::Attribute &attr : storage) {
if (attr.domain() != domain) {
continue;
}
const CPPType &type = bke::attribute_type_to_cpp_type(attr.data_type());
switch (attr.storage_type()) {
case bke::AttrStorageType::Array: {
const auto &data = std::get<bke::Attribute::ArrayData>(attr.data());
auto new_data = bke::Attribute::ArrayData::from_uninitialized(type,
new_offsets.total_size());
threading::parallel_for(IndexRange(groups_num), 1024, [&](const IndexRange range) {
for (const int old_i : range) {
const int new_i = new_by_old_map[old_i];
const IndexRange old_range = old_offsets[old_i];
const IndexRange new_range = new_offsets[new_i];
BLI_assert(old_range.size() == new_range.size());
type.copy_construct_n(POINTER_OFFSET(data.data, old_range.start() * type.size),
POINTER_OFFSET(new_data.data, new_range.start() * type.size),
old_range.size());
}
});
attr.assign_data(std::move(new_data));
break;
}
case bke::AttrStorageType::Single: {
break;
}
}
}
}
void debug_randomize_face_order(Mesh *mesh)
{
if (mesh == nullptr || mesh->faces_num == 0 || !use_debug_randomization()) {
return;
}
const int seed = seed_from_mesh(*mesh);
const Array<int> new_by_old_map = get_permutation(mesh->faces_num, seed);
const Array<int> old_by_new_map = invert_permutation(new_by_old_map);
reorder_customdata(mesh->face_data, new_by_old_map);
reorder_attribute_domain(mesh->attribute_storage.wrap(), bke::AttrDomain::Face, old_by_new_map);
const OffsetIndices old_faces = mesh->faces();
Array<int> new_face_offsets = make_new_offset_indices(old_faces, old_by_new_map);
const OffsetIndices<int> new_faces = new_face_offsets.as_span();
reorder_customdata_groups(mesh->corner_data, old_faces, new_faces, new_by_old_map);
reorder_attribute_groups(mesh->attribute_storage.wrap(),
bke::AttrDomain::Corner,
old_faces,
new_faces,
new_by_old_map);
mesh->face_offsets_for_write().copy_from(new_face_offsets);
mesh->tag_topology_changed();
}
void debug_randomize_point_order(PointCloud *pointcloud)
{
if (pointcloud == nullptr || !use_debug_randomization()) {
return;
}
const int seed = seed_from_pointcloud(*pointcloud);
const Array<int> new_by_old_map = get_permutation(pointcloud->totpoint, seed);
const Array<int> old_by_new_map = invert_permutation(new_by_old_map);
reorder_attribute_domain(
pointcloud->attribute_storage.wrap(), bke::AttrDomain::Point, old_by_new_map);
pointcloud->tag_positions_changed();
pointcloud->tag_radii_changed();
}
void debug_randomize_curve_order(bke::CurvesGeometry *curves)
{
if (curves == nullptr || !use_debug_randomization()) {
return;
}
const int seed = seed_from_curves(*curves);
const Array<int> new_by_old_map = get_permutation(curves->curve_num, seed);
const Array<int> old_by_new_map = invert_permutation(new_by_old_map);
bke::AttributeStorage &attributes = curves->attribute_storage.wrap();
reorder_attribute_domain(attributes, bke::AttrDomain::Curve, old_by_new_map);
const OffsetIndices old_points_by_curve = curves->points_by_curve();
Array<int> new_curve_offsets = make_new_offset_indices(old_points_by_curve, old_by_new_map);
const OffsetIndices<int> new_points_by_curve = new_curve_offsets.as_span();
reorder_customdata_groups(
curves->point_data, old_points_by_curve, new_points_by_curve, new_by_old_map);
reorder_attribute_groups(attributes,
bke::AttrDomain::Point,
old_points_by_curve,
new_points_by_curve,
new_by_old_map);
curves->offsets_for_write().copy_from(new_curve_offsets);
curves->tag_topology_changed();
}
void debug_randomize_mesh_order(Mesh *mesh)
{
if (mesh == nullptr || !use_debug_randomization()) {
return;
}
debug_randomize_vert_order(mesh);
debug_randomize_edge_order(mesh);
debug_randomize_face_order(mesh);
}
void debug_randomize_instance_order(bke::Instances *instances)
{
if (instances == nullptr || !use_debug_randomization()) {
return;
}
const int instances_num = instances->instances_num();
const int seed = seed_from_instances(*instances);
const Array<int> new_by_old_map = get_permutation(instances_num, seed);
reorder_attribute_domain(
instances->attribute_storage(), bke::AttrDomain::Instance, new_by_old_map);
}
bool use_debug_randomization()
{
return G.randomize_geometry_element_order;
}
} // namespace blender::geometry

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,545 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_attribute.hh"
#include "BKE_attribute_filters.hh"
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_curves_utils.hh"
#include "BKE_deform.hh"
#include "BKE_geometry_set.hh"
#include "BKE_instances.hh"
#include "BKE_mesh.hh"
#include "BKE_pointcloud.hh"
#include "BLI_array.hh"
#include "BLI_array_utils.hh"
#include "BLI_index_mask.hh"
#include "BLI_multi_value_map.hh"
#include "BLI_offset_indices.hh"
#include "BLI_sort.hh"
#include "BLI_task.hh"
#include "BLI_vector_set.hh"
#include "BLI_virtual_array.hh"
#include "DNA_curves_types.h"
#include "DNA_mesh_types.h"
#include "DNA_pointcloud_types.h"
#include "GEO_reorder.hh"
namespace blender::geometry {
const MultiValueMap<bke::GeometryComponent::Type, bke::AttrDomain> &
components_supported_reordering()
{
using namespace bke;
const static MultiValueMap<GeometryComponent::Type, AttrDomain> supported_types_and_domains =
[]() {
MultiValueMap<GeometryComponent::Type, AttrDomain> supported_types_and_domains;
supported_types_and_domains.add_multiple(
GeometryComponent::Type::Mesh,
{AttrDomain::Point, AttrDomain::Edge, AttrDomain::Face});
supported_types_and_domains.add(GeometryComponent::Type::Curve, AttrDomain::Curve);
supported_types_and_domains.add(GeometryComponent::Type::PointCloud, AttrDomain::Point);
supported_types_and_domains.add(GeometryComponent::Type::Instance, AttrDomain::Instance);
return supported_types_and_domains;
}();
return supported_types_and_domains;
}
static void reorder_attributes_group_to_group(const bke::AttributeAccessor src_attributes,
const bke::AttrDomain domain,
const OffsetIndices<int> src_offsets,
const OffsetIndices<int> dst_offsets,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
bke::MutableAttributeAccessor dst_attributes)
{
src_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
if (iter.domain != domain) {
return;
}
if (iter.data_type == bke::AttrType::String) {
return;
}
if (attribute_filter.allow_skip(iter.name)) {
return;
}
const GVArray src = *iter.get(domain);
const CommonVArrayInfo info = src.common_info();
if (info.type == CommonVArrayInfo::Type::Single) {
const GPointer value(src.type(), info.data);
if (dst_attributes.add(iter.name, domain, iter.data_type, bke::AttributeInitValue(value))) {
return;
}
}
bke::GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
iter.name, domain, iter.data_type);
if (!dst) {
return;
}
threading::parallel_for(old_by_new_map.index_range(), 1024, [&](const IndexRange range) {
for (const int new_i : range) {
const int old_i = old_by_new_map[new_i];
array_utils::copy(src.slice(src_offsets[old_i]), dst.span.slice(dst_offsets[new_i]));
}
});
dst.finish();
});
}
static Array<int> invert_permutation(const Span<int> permutation)
{
Array<int> data(permutation.size());
threading::parallel_for(permutation.index_range(), 2048, [&](const IndexRange range) {
for (const int64_t i : range) {
data[permutation[i]] = i;
}
});
return data;
}
static void copy_and_reorder_mesh_verts(const Mesh &src_mesh,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
Mesh &dst_mesh)
{
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write();
bke::gather_attributes(src_attributes,
bke::AttrDomain::Point,
bke::AttrDomain::Point,
attribute_filter,
old_by_new_map,
dst_attributes);
bke::copy_attributes(src_attributes,
bke::AttrDomain::Edge,
bke::AttrDomain::Edge,
bke::attribute_filter_with_skip_ref(attribute_filter, {".edge_verts"}),
dst_attributes);
bke::copy_attributes(src_attributes,
bke::AttrDomain::Face,
bke::AttrDomain::Face,
attribute_filter,
dst_attributes);
implicit_sharing::free_shared_data(&dst_mesh.face_offset_indices,
&dst_mesh.runtime->face_offsets_sharing_info);
implicit_sharing::copy_shared_pointer(src_mesh.face_offset_indices,
src_mesh.runtime->face_offsets_sharing_info,
&dst_mesh.face_offset_indices,
&dst_mesh.runtime->face_offsets_sharing_info);
bke::copy_attributes(src_attributes,
bke::AttrDomain::Corner,
bke::AttrDomain::Corner,
bke::attribute_filter_with_skip_ref(attribute_filter, {".corner_vert"}),
dst_attributes);
const Array<int> new_by_old_map = invert_permutation(old_by_new_map);
dst_attributes.add<int2>(".edge_verts", bke::AttrDomain::Edge, bke::AttributeInitConstruct());
array_utils::gather(new_by_old_map.as_span(),
src_mesh.edges().cast<int>(),
dst_mesh.edges_for_write().cast<int>());
dst_attributes.add<int>(".corner_vert", bke::AttrDomain::Corner, bke::AttributeInitConstruct());
array_utils::gather(
new_by_old_map.as_span(), src_mesh.corner_verts(), dst_mesh.corner_verts_for_write());
}
static void copy_and_reorder_mesh_edges(const Mesh &src_mesh,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
Mesh &dst_mesh)
{
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write();
bke::copy_attributes(src_attributes,
bke::AttrDomain::Point,
bke::AttrDomain::Point,
attribute_filter,
dst_attributes);
bke::gather_attributes(src_attributes,
bke::AttrDomain::Edge,
bke::AttrDomain::Edge,
attribute_filter,
old_by_new_map,
dst_attributes);
bke::copy_attributes(src_attributes,
bke::AttrDomain::Face,
bke::AttrDomain::Face,
attribute_filter,
dst_attributes);
implicit_sharing::free_shared_data(&dst_mesh.face_offset_indices,
&dst_mesh.runtime->face_offsets_sharing_info);
implicit_sharing::copy_shared_pointer(src_mesh.face_offset_indices,
src_mesh.runtime->face_offsets_sharing_info,
&dst_mesh.face_offset_indices,
&dst_mesh.runtime->face_offsets_sharing_info);
bke::copy_attributes(src_attributes,
bke::AttrDomain::Corner,
bke::AttrDomain::Corner,
bke::attribute_filter_with_skip_ref(attribute_filter, {".corner_edge"}),
dst_attributes);
const Array<int> new_by_old_map = invert_permutation(old_by_new_map);
dst_attributes.add<int>(".corner_edge", bke::AttrDomain::Corner, bke::AttributeInitConstruct());
array_utils::gather(
new_by_old_map.as_span(), src_mesh.corner_edges(), dst_mesh.corner_edges_for_write());
}
static void copy_and_reorder_mesh_faces(const Mesh &src_mesh,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
Mesh &dst_mesh)
{
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write();
bke::copy_attributes(src_attributes,
bke::AttrDomain::Point,
bke::AttrDomain::Point,
attribute_filter,
dst_attributes);
bke::copy_attributes(src_attributes,
bke::AttrDomain::Edge,
bke::AttrDomain::Edge,
attribute_filter,
dst_attributes);
bke::gather_attributes(src_attributes,
bke::AttrDomain::Face,
bke::AttrDomain::Face,
attribute_filter,
old_by_new_map,
dst_attributes);
const Span<int> old_offsets = src_mesh.face_offsets();
MutableSpan<int> new_offsets = dst_mesh.face_offsets_for_write();
offset_indices::gather_group_sizes(old_offsets, old_by_new_map, new_offsets);
offset_indices::accumulate_counts_to_offsets(new_offsets);
reorder_attributes_group_to_group(src_attributes,
bke::AttrDomain::Corner,
old_offsets,
new_offsets.as_span(),
old_by_new_map,
attribute_filter,
dst_attributes);
}
static void copy_and_reorder_mesh(const Mesh &src_mesh,
const Span<int> old_by_new_map,
const bke::AttrDomain domain,
const bke::AttributeFilter &attribute_filter,
Mesh &dst_mesh)
{
switch (domain) {
case bke::AttrDomain::Point:
copy_and_reorder_mesh_verts(src_mesh, old_by_new_map, attribute_filter, dst_mesh);
break;
case bke::AttrDomain::Edge:
copy_and_reorder_mesh_edges(src_mesh, old_by_new_map, attribute_filter, dst_mesh);
break;
case bke::AttrDomain::Face:
copy_and_reorder_mesh_faces(src_mesh, old_by_new_map, attribute_filter, dst_mesh);
break;
default:
break;
}
dst_mesh.tag_positions_changed();
dst_mesh.tag_topology_changed();
}
static void copy_and_reorder_points(const PointCloud &src_pointcloud,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
PointCloud &dst_pointcloud)
{
bke::gather_attributes(src_pointcloud.attributes(),
bke::AttrDomain::Point,
bke::AttrDomain::Point,
attribute_filter,
old_by_new_map,
dst_pointcloud.attributes_for_write());
dst_pointcloud.tag_positions_changed();
dst_pointcloud.tag_radii_changed();
}
static void copy_and_reorder_curves(const bke::CurvesGeometry &src_curves,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
bke::CurvesGeometry &dst_curves)
{
bke::gather_attributes(src_curves.attributes(),
bke::AttrDomain::Curve,
bke::AttrDomain::Curve,
attribute_filter,
old_by_new_map,
dst_curves.attributes_for_write());
const Span<int> old_offsets = src_curves.offsets();
MutableSpan<int> new_offsets = dst_curves.offsets_for_write();
offset_indices::gather_group_sizes(old_offsets, old_by_new_map, new_offsets);
offset_indices::accumulate_counts_to_offsets(new_offsets);
reorder_attributes_group_to_group(src_curves.attributes(),
bke::AttrDomain::Point,
old_offsets,
new_offsets.as_span(),
old_by_new_map,
attribute_filter,
dst_curves.attributes_for_write());
dst_curves.tag_topology_changed();
if (src_curves.nurbs_has_custom_knots()) {
dst_curves.nurbs_custom_knots_update_size();
IndexMaskMemory memory;
bke::curves::nurbs::gather_custom_knots(
src_curves, IndexMask::from_indices(old_by_new_map, memory), 0, dst_curves);
}
}
static void copy_and_reorder_instaces(const bke::Instances &src_instances,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
bke::Instances &dst_instances)
{
dst_instances.resize(src_instances.instances_num());
bke::gather_attributes(src_instances.attributes(),
bke::AttrDomain::Instance,
bke::AttrDomain::Instance,
attribute_filter,
old_by_new_map,
dst_instances.attributes_for_write());
for (const bke::InstanceReference &reference : src_instances.references()) {
dst_instances.add_reference(reference);
}
BLI_assert(src_instances.references() == dst_instances.references());
const Span<float4x4> old_transforms = src_instances.transforms();
MutableSpan<float4x4> new_transforms = dst_instances.transforms_for_write();
array_utils::gather(old_transforms, old_by_new_map, new_transforms);
}
Mesh *reorder_mesh(const Mesh &src_mesh,
Span<int> old_by_new_map,
bke::AttrDomain domain,
const bke::AttributeFilter &attribute_filter)
{
Mesh *dst_mesh = bke::mesh_new_no_attributes(
src_mesh.verts_num, src_mesh.edges_num, src_mesh.faces_num, src_mesh.corners_num);
BKE_mesh_copy_parameters_for_eval(dst_mesh, &src_mesh);
copy_and_reorder_mesh(src_mesh, old_by_new_map, domain, attribute_filter, *dst_mesh);
return dst_mesh;
}
PointCloud *reorder_points(const PointCloud &src_pointcloud,
Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter)
{
PointCloud *dst_pointcloud = bke::pointcloud_new_no_attributes(src_pointcloud.totpoint);
copy_and_reorder_points(src_pointcloud, old_by_new_map, attribute_filter, *dst_pointcloud);
return dst_pointcloud;
}
bke::CurvesGeometry reorder_curves_geometry(const bke::CurvesGeometry &src_curves,
Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter)
{
bke::CurvesGeometry dst_curves = bke::curves_new_no_attributes(src_curves.points_num(),
src_curves.curves_num());
BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &src_curves.vertex_group_names);
copy_and_reorder_curves(src_curves, old_by_new_map, attribute_filter, dst_curves);
return dst_curves;
}
Curves *reorder_curves(const Curves &src_curves,
Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter)
{
const bke::CurvesGeometry src_curve_geometry = src_curves.geometry.wrap();
Curves *dst_curves = bke::curves_new_nomain(0, 0);
dst_curves->geometry.wrap() = reorder_curves_geometry(
src_curve_geometry, old_by_new_map, attribute_filter);
return dst_curves;
}
bke::Instances *reorder_instaces(const bke::Instances &src_instances,
Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter)
{
bke::Instances *dst_instances = new bke::Instances(src_instances);
copy_and_reorder_instaces(src_instances, old_by_new_map, attribute_filter, *dst_instances);
return dst_instances;
}
bke::GeometryComponentPtr reordered_component(const bke::GeometryComponent &src_component,
const Span<int> old_by_new_map,
const bke::AttrDomain domain,
const bke::AttributeFilter &attribute_filter)
{
BLI_assert(!src_component.is_empty());
if (const bke::MeshComponent *src_mesh_component = dynamic_cast<const bke::MeshComponent *>(
&src_component))
{
Mesh *result_mesh = reorder_mesh(
*src_mesh_component->get(), old_by_new_map, domain, attribute_filter);
return bke::GeometryComponentPtr(new bke::MeshComponent(result_mesh));
}
if (const bke::PointCloudComponent *src_points_component =
dynamic_cast<const bke::PointCloudComponent *>(&src_component))
{
PointCloud *result_pointcloud = reorder_points(
*src_points_component->get(), old_by_new_map, attribute_filter);
return bke::GeometryComponentPtr(new bke::PointCloudComponent(result_pointcloud));
}
if (const bke::CurveComponent *src_curves_component = dynamic_cast<const bke::CurveComponent *>(
&src_component))
{
Curves *result_curves = reorder_curves(
*src_curves_component->get(), old_by_new_map, attribute_filter);
return bke::GeometryComponentPtr(new bke::CurveComponent(result_curves));
}
if (const bke::InstancesComponent *src_instances_component =
dynamic_cast<const bke::InstancesComponent *>(&src_component))
{
bke::Instances *result_instances = reorder_instaces(
*src_instances_component->get(), old_by_new_map, attribute_filter);
return bke::GeometryComponentPtr(new bke::InstancesComponent(result_instances));
}
BLI_assert_unreachable();
return {};
}
template<typename T, typename Func>
static void parallel_transform(MutableSpan<T> values, const int64_t grain_size, const Func &func)
{
threading::parallel_for(values.index_range(), grain_size, [&](const IndexRange range) {
MutableSpan<T> values_range = values.slice(range);
std::transform(values_range.begin(), values_range.end(), values_range.begin(), func);
});
}
static void grouped_sort(const OffsetIndices<int> offsets,
const Span<float> weights,
MutableSpan<int> indices)
{
const auto comparator = [&](const int index_a, const int index_b) {
const float weight_a = weights[index_a];
const float weight_b = weights[index_b];
if (UNLIKELY(weight_a == weight_b)) {
return index_a < index_b;
}
return weight_a < weight_b;
};
threading::parallel_for(offsets.index_range(), 250, [&](const IndexRange range) {
for (const int group_index : range) {
MutableSpan<int> group = indices.slice(offsets[group_index]);
parallel_sort(group.begin(), group.end(), comparator);
}
});
}
static void find_points_by_group_index(const Span<int> indices,
MutableSpan<int> r_offsets,
MutableSpan<int> r_indices)
{
const OffsetIndices offsets = offset_indices::build_reverse_offsets(indices, r_offsets);
offset_indices::reverse_indices_in_groups(indices, offsets, r_indices, false);
}
static int identifiers_to_indices(MutableSpan<int> r_identifiers_to_indices)
{
const VectorSet<int> deduplicated_identifiers(r_identifiers_to_indices);
parallel_transform(r_identifiers_to_indices, 2048, [&](const int identifier) {
return deduplicated_identifiers.index_of(identifier);
});
Array<int> indices(deduplicated_identifiers.size());
array_utils::fill_index_range<int>(indices);
parallel_sort(indices.begin(), indices.end(), [&](const int index_a, const int index_b) {
return deduplicated_identifiers[index_a] < deduplicated_identifiers[index_b];
});
Array<int> permutation = invert_permutation(indices);
parallel_transform(
r_identifiers_to_indices, 4096, [&](const int index) { return permutation[index]; });
return deduplicated_identifiers.size();
}
std::optional<Array<int>> sort_indices_by_weights(const int domain_size,
const IndexMask &mask,
const VArray<int> &group_id,
const VArray<float> &weight)
{
if (group_id.is_single() && weight.is_single()) {
return std::nullopt;
}
if (mask.is_empty()) {
return std::nullopt;
}
Array<int> gathered_indices(mask.size());
if (group_id.is_single()) {
mask.to_indices<int>(gathered_indices);
Array<float> weight_values(domain_size);
array_utils::copy(weight, mask, weight_values.as_mutable_span());
grouped_sort(Span({0, int(mask.size())}), weight_values, gathered_indices);
}
else {
Array<int> gathered_group_id(mask.size());
array_utils::gather(group_id, mask, gathered_group_id.as_mutable_span());
const int total_groups = identifiers_to_indices(gathered_group_id);
Array<int> offsets_to_sort(total_groups + 1, 0);
find_points_by_group_index(gathered_group_id, offsets_to_sort, gathered_indices);
if (!weight.is_single()) {
Array<float> weight_values(mask.size());
array_utils::gather(weight, mask, weight_values.as_mutable_span());
grouped_sort(offsets_to_sort.as_span(), weight_values, gathered_indices);
}
parallel_transform<int>(gathered_indices, 2048, [&](const int pos) { return mask[pos]; });
}
if (array_utils::indices_are_range(gathered_indices, IndexRange(domain_size))) {
return std::nullopt;
}
if (mask.size() == domain_size) {
return gathered_indices;
}
IndexMaskMemory memory;
const IndexMask unselected = mask.complement(IndexRange(domain_size), memory);
Array<int> indices(domain_size);
array_utils::scatter<int>(gathered_indices, mask, indices);
array_utils::fill_index_range<int>(unselected, indices);
if (array_utils::indices_are_range(indices, indices.index_range())) {
return std::nullopt;
}
return indices;
}
} // namespace blender::geometry

View File

@@ -0,0 +1,664 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "BLI_math_color.hh"
#include "BLI_math_quaternion.hh"
#include "BLI_math_vector.hh"
#include "BLI_length_parameterize.hh"
#include "BLI_task.hh"
#include "FN_field.hh"
#include "FN_multi_function_builder.hh"
#include "FN_multi_function_registry.hh"
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_curves_utils.hh"
#include "BKE_deform.hh"
#include "BKE_geometry_fields.hh"
#include "GEO_resample_curves.hh"
namespace blender::geometry {
static fn::Field<int> get_count_input_max_one(const fn::Field<int> &count_field)
{
return fn::Field<int>(
fn::FieldOperation::from(fn::multi_function::registry::lookup("max(int, int)"_ustr),
{fn::Field<int>(1), count_field}));
}
static int get_count_from_length(const float curve_length,
const float sample_length,
const bool keep_last_segment)
{
/* Find the number of sampled segments by dividing the total length by
* the sample length. Then there is one more sampled point than segment. */
if (UNLIKELY(sample_length == 0.0f)) {
return 1;
}
const int count = int(curve_length / sample_length) + 1;
return std::max(keep_last_segment ? 2 : 1, count);
}
static fn::Field<int> get_count_input_from_length(const fn::Field<float> &length_field,
const bool keep_last_segment)
{
static auto get_count_fn = mf::build::SI3_SO<float, float, bool, int>(
"Length Input to Count",
get_count_from_length,
mf::build::exec_presets::SomeSpanOrSingle<0, 1>());
auto get_count_op = fn::FieldOperation::from(
get_count_fn,
{fn::Field<float>::from_input<bke::CurveLengthFieldInput>(),
length_field,
fn::Field<bool>(keep_last_segment)});
return fn::Field<int>(std::move(get_count_op));
}
/**
* Return true if the attribute should be copied/interpolated to the result curves.
* Don't output attributes that correspond to curve types that have no curves in the result.
*/
static bool interpolate_attribute_to_curves(const StringRef name,
const std::array<int, CURVE_TYPES_NUM> &type_counts)
{
if (bke::attribute_name_is_anonymous(name)) {
return true;
}
if (ELEM(name, "handle_type_left", "handle_type_right", "handle_left", "handle_right")) {
return type_counts[CURVE_TYPE_BEZIER] != 0;
}
if (ELEM(name, "nurbs_weight")) {
return type_counts[CURVE_TYPE_NURBS] != 0;
}
return true;
}
/**
* Return true if the attribute should be copied to poly curves.
*/
static bool interpolate_attribute_to_poly_curve(const StringRef name)
{
static const Set<StringRef> no_interpolation{{
"handle_type_left",
"handle_type_right",
"handle_right",
"handle_left",
"nurbs_weight",
}};
return !no_interpolation.contains(name);
}
/**
* Retrieve spans from source and result attributes.
*/
static void retrieve_attribute_spans(const Span<StringRef> names,
const CurvesGeometry &src_curves,
CurvesGeometry &dst_curves,
Vector<GVArraySpan> &src_arrays,
Vector<GMutableSpan> &dst_arrays,
Vector<bke::GSpanAttributeWriter> &dst_writers)
{
const bke::AttributeAccessor src_attributes = src_curves.attributes();
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
for (const int i : names.index_range()) {
GVArray src_attribute = *src_attributes.lookup(names[i], bke::AttrDomain::Point);
const bke::AttrType data_type = bke::cpp_type_to_attribute_type(src_attribute.type());
const CommonVArrayInfo info = src_attribute.common_info();
if (info.type == CommonVArrayInfo::Type::Single) {
const bke::AttributeInitValue init(GPointer(src_attribute.type(), info.data));
dst_attributes.add(names[i], bke::AttrDomain::Point, data_type, init);
continue;
}
src_arrays.append(std::move(src_attribute));
bke::GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_only_span(
names[i], bke::AttrDomain::Point, data_type);
dst_arrays.append(dst_attribute.span);
dst_writers.append(std::move(dst_attribute));
}
}
struct AttributesForResample : NonCopyable, NonMovable {
Vector<GVArraySpan> src;
Vector<GMutableSpan> dst;
Vector<bke::GSpanAttributeWriter> dst_attributes;
Vector<GVArraySpan> src_no_interpolation;
Vector<GMutableSpan> dst_no_interpolation;
Span<float3> src_evaluated_tangents;
Span<float3> src_evaluated_normals;
MutableSpan<float3> dst_tangents;
MutableSpan<float3> dst_normals;
};
/**
* Gather a set of all generic attribute IDs to copy to the result curves.
*/
static void gather_point_attributes_to_interpolate(
const CurvesGeometry &src_curves,
CurvesGeometry &dst_curves,
AttributesForResample &result,
const ResampleCurvesOutputAttributeIDs &output_ids)
{
VectorSet<StringRef> names;
VectorSet<StringRef> names_no_interpolation;
src_curves.attributes().foreach_attribute([&](const bke::AttributeIter &iter) {
if (iter.domain != bke::AttrDomain::Point) {
return;
}
if (iter.data_type == bke::AttrType::String) {
return;
}
if (!interpolate_attribute_to_curves(iter.name, dst_curves.curve_type_counts())) {
return;
}
if (interpolate_attribute_to_poly_curve(iter.name)) {
names.add_new(iter.name);
}
else {
names_no_interpolation.add_new(iter.name);
}
});
/* Position is handled differently since it has non-generic interpolation for Bezier
* curves and because the evaluated positions are cached for each evaluated point. */
names.remove_contained("position");
retrieve_attribute_spans(
names, src_curves, dst_curves, result.src, result.dst, result.dst_attributes);
/* Attributes that aren't interpolated like Bezier handles still have to be copied
* to the result when there are any unselected curves of the corresponding type. */
retrieve_attribute_spans(names_no_interpolation,
src_curves,
dst_curves,
result.src_no_interpolation,
result.dst_no_interpolation,
result.dst_attributes);
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
if (output_ids.tangent_id) {
result.src_evaluated_tangents = src_curves.evaluated_tangents();
bke::GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_only_span(
*output_ids.tangent_id, bke::AttrDomain::Point, bke::AttrType::Float3);
result.dst_tangents = dst_attribute.span.typed<float3>();
result.dst_attributes.append(std::move(dst_attribute));
}
if (output_ids.normal_id) {
result.src_evaluated_normals = src_curves.evaluated_normals();
bke::GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_only_span(
*output_ids.normal_id, bke::AttrDomain::Point, bke::AttrType::Float3);
result.dst_normals = dst_attribute.span.typed<float3>();
result.dst_attributes.append(std::move(dst_attribute));
}
}
static void copy_or_defaults_for_unselected_curves(const CurvesGeometry &src_curves,
const IndexMask &unselected_curves,
const AttributesForResample &attributes,
CurvesGeometry &dst_curves)
{
const OffsetIndices src_points_by_curve = src_curves.points_by_curve();
const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve();
array_utils::copy_group_to_group(src_points_by_curve,
dst_points_by_curve,
unselected_curves,
src_curves.positions(),
dst_curves.positions_for_write());
for (const int i : attributes.src.index_range()) {
array_utils::copy_group_to_group(src_points_by_curve,
dst_points_by_curve,
unselected_curves,
attributes.src[i],
attributes.dst[i]);
}
for (const int i : attributes.src_no_interpolation.index_range()) {
array_utils::copy_group_to_group(src_points_by_curve,
dst_points_by_curve,
unselected_curves,
attributes.src_no_interpolation[i],
attributes.dst_no_interpolation[i]);
}
if (!attributes.dst_tangents.is_empty()) {
bke::curves::fill_points(
dst_points_by_curve, unselected_curves, float3(0), attributes.dst_tangents);
}
if (!attributes.dst_normals.is_empty()) {
bke::curves::fill_points(
dst_points_by_curve, unselected_curves, float3(0), attributes.dst_normals);
}
}
static void normalize_span(MutableSpan<float3> data)
{
for (const int i : data.index_range()) {
data[i] = math::normalize(data[i]);
}
}
static void normalize_curve_point_data(const IndexMaskSegment curve_selection,
const OffsetIndices<int> points_by_curve,
MutableSpan<float3> data)
{
for (const int i_curve : curve_selection) {
normalize_span(data.slice(points_by_curve[i_curve]));
}
}
/**
* Buffer for temporary evaluated curve data, used for memory reuse between multiple attributes of
* different types.
*/
struct EvalDataBuffer {
using AllocatorType = GuardedAlignedAllocator<>;
/* Use a default alignment that works for all attribute types, and don't use the inline buffer
* because it doesn't necessarily have the correct alignment. */
Vector<std::byte, 0, AllocatorType> heap_allocated;
alignas(AllocatorType::min_alignment) std::array<std::byte, 1024> inline_buffer;
template<typename T> MutableSpan<T> resize(const int64_t size)
{
const int64_t size_in_bytes = sizeof(T) * size;
if (size_in_bytes <= this->inline_buffer.size()) {
return MutableSpan<std::byte>(this->inline_buffer).slice(0, size_in_bytes).cast<T>();
}
this->heap_allocated.resize(size_in_bytes);
return this->heap_allocated.as_mutable_span().cast<T>();
}
};
static void resample_to_uniform(const CurvesGeometry &src_curves,
const IndexMask &selection,
const ResampleCurvesOutputAttributeIDs &output_ids,
CurvesGeometry &dst_curves)
{
if (src_curves.curves_range().is_empty()) {
return;
}
const OffsetIndices src_points_by_curve = src_curves.points_by_curve();
const OffsetIndices evaluated_points_by_curve = src_curves.evaluated_points_by_curve();
const VArray<bool> curves_cyclic = src_curves.cyclic();
const VArray<int8_t> curve_types = src_curves.curve_types();
const Span<float3> evaluated_positions = src_curves.evaluated_positions();
/* All resampled curves are poly curves. */
dst_curves.fill_curve_types(selection, CURVE_TYPE_POLY);
MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
AttributesForResample attributes;
gather_point_attributes_to_interpolate(src_curves, dst_curves, attributes, output_ids);
src_curves.ensure_evaluated_lengths();
/* Sampling arbitrary attributes works by first interpolating them to the curve's standard
* "evaluated points" and then interpolating that result with the uniform samples. This is
* potentially wasteful when down-sampling a curve to many fewer points. There are two possible
* solutions: only sample the necessary points for interpolation, or first sample curve
* parameter/segment indices and evaluate the curve directly. */
Array<int> sample_indices(dst_curves.points_num());
Array<float> sample_factors(dst_curves.points_num());
const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve();
/* Use a "for each group of curves: for each attribute: for each curve" pattern to work on
* smaller sections of data that ideally fit into CPU cache better than simply one attribute at a
* time or one curve at a time. */
selection.foreach_segment(
[&](const IndexMaskSegment selection_segment) {
EvalDataBuffer evaluated_buffer;
/* Gather uniform samples based on the accumulated lengths of the original curve. */
for (const int i_curve : selection_segment) {
const bool cyclic = curves_cyclic[i_curve];
const IndexRange dst_points = dst_points_by_curve[i_curve];
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
* curve's "evaluated points", then use linear interpolation to sample to the result. */
for (const int i_attribute : attributes.dst.index_range()) {
const CPPType &type = attributes.src[i_attribute].type();
bke::attribute_math::to_static_type(type, [&]<typename T>() {
if constexpr (!std::is_same_v<T, std::string>) {
Span<T> src = attributes.src[i_attribute].typed<T>();
MutableSpan<T> dst = attributes.dst[i_attribute].typed<T>();
for (const int i_curve : selection_segment) {
const IndexRange src_points = src_points_by_curve[i_curve];
const IndexRange dst_points = dst_points_by_curve[i_curve];
if (curve_types[i_curve] == CURVE_TYPE_POLY) {
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 {
MutableSpan evaluated = evaluated_buffer.resize<T>(
evaluated_points_by_curve[i_curve].size());
src_curves.interpolate_to_evaluated(i_curve, src.slice(src_points), evaluated);
length_parameterize::interpolate(evaluated.as_span(),
sample_indices.as_span().slice(dst_points),
sample_factors.as_span().slice(dst_points),
dst.slice(dst_points));
}
}
}
});
}
auto interpolate_evaluated_data = [&](const Span<float3> src, MutableSpan<float3> dst) {
for (const int i_curve : selection_segment) {
const IndexRange src_points = evaluated_points_by_curve[i_curve];
const IndexRange dst_points = dst_points_by_curve[i_curve];
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));
}
};
/* Interpolate the evaluated positions to the resampled curves. */
interpolate_evaluated_data(evaluated_positions, dst_positions);
if (!attributes.dst_tangents.is_empty()) {
interpolate_evaluated_data(attributes.src_evaluated_tangents, attributes.dst_tangents);
normalize_curve_point_data(
selection_segment, dst_points_by_curve, attributes.dst_tangents);
}
if (!attributes.dst_normals.is_empty()) {
interpolate_evaluated_data(attributes.src_evaluated_normals, attributes.dst_normals);
normalize_curve_point_data(
selection_segment, dst_points_by_curve, attributes.dst_normals);
}
/* Fill the default value for non-interpolating attributes that still must be copied. */
for (GMutableSpan dst : attributes.dst_no_interpolation) {
for (const int i_curve : selection_segment) {
const IndexRange dst_points = dst_points_by_curve[i_curve];
dst.type().value_initialize_n(dst.slice(dst_points).data(), dst_points.size());
}
}
},
exec_mode::grain_size(512));
IndexMaskMemory memory;
const IndexMask unselected = selection.complement(src_curves.curves_range(), memory);
copy_or_defaults_for_unselected_curves(src_curves, unselected, attributes, dst_curves);
for (bke::GSpanAttributeWriter &attribute : attributes.dst_attributes) {
attribute.finish();
}
}
static CurvesGeometry resample_to_uniform(const CurvesGeometry &src_curves,
const fn::FieldContext &field_context,
const fn::Field<bool> &selection_field,
const fn::Field<int> &count_field,
const ResampleCurvesOutputAttributeIDs &output_ids)
{
if (src_curves.curves_range().is_empty()) {
return {};
}
const OffsetIndices src_points_by_curve = src_curves.points_by_curve();
CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves);
/* Copy vertex groups from source curves to allow copying vertex group attributes. */
BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &src_curves.vertex_group_names);
MutableSpan<int> dst_offsets = dst_curves.offsets_for_write();
fn::FieldEvaluator evaluator{field_context, src_curves.curves_num()};
evaluator.set_selection(selection_field);
evaluator.add_with_destination(count_field, dst_offsets.drop_back(1));
evaluator.evaluate();
const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
IndexMaskMemory memory;
const IndexMask unselected = selection.complement(src_curves.curves_range(), memory);
/* Fill the counts for the curves that aren't selected and accumulate the counts into offsets. */
offset_indices::copy_group_sizes(src_points_by_curve, unselected, dst_offsets);
if (!offset_indices::accumulate_counts_to_offsets_with_overflow_check(dst_offsets)) {
return {};
}
dst_curves.resize(dst_offsets.last(), dst_curves.curves_num());
resample_to_uniform(src_curves, selection, output_ids, dst_curves);
bke::curves::nurbs::copy_custom_knots(src_curves, selection, dst_curves);
return dst_curves;
}
CurvesGeometry resample_to_count(const CurvesGeometry &src_curves,
const IndexMask &selection,
const VArray<int> &counts,
const ResampleCurvesOutputAttributeIDs &output_ids)
{
if (src_curves.curves_range().is_empty()) {
return {};
}
const OffsetIndices src_points_by_curve = src_curves.points_by_curve();
CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves);
/* Copy vertex groups from source curves to allow copying vertex group attributes. */
BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &src_curves.vertex_group_names);
MutableSpan<int> dst_offsets = dst_curves.offsets_for_write();
array_utils::copy(counts, selection, dst_offsets);
IndexMaskMemory memory;
const IndexMask unselected = selection.complement(src_curves.curves_range(), memory);
/* Fill the counts for the curves that aren't selected and accumulate the counts into offsets. */
offset_indices::copy_group_sizes(src_points_by_curve, unselected, dst_offsets);
/* We assume the counts are at least 1. */
BLI_assert(std::all_of(dst_offsets.begin(),
dst_offsets.drop_back(1).end(),
[&](const int count) { return count > 0; }));
offset_indices::accumulate_counts_to_offsets(dst_offsets);
dst_curves.resize(dst_offsets.last(), dst_curves.curves_num());
resample_to_uniform(src_curves, selection, output_ids, dst_curves);
bke::curves::nurbs::copy_custom_knots(src_curves, selection, dst_curves);
return dst_curves;
}
CurvesGeometry resample_to_count(const CurvesGeometry &src_curves,
const fn::FieldContext &field_context,
const fn::Field<bool> &selection_field,
const fn::Field<int> &count_field,
const ResampleCurvesOutputAttributeIDs &output_ids)
{
return resample_to_uniform(src_curves,
field_context,
selection_field,
get_count_input_max_one(count_field),
output_ids);
}
CurvesGeometry resample_to_length(const CurvesGeometry &src_curves,
const IndexMask &selection,
const VArray<float> &sample_lengths,
const ResampleCurvesOutputAttributeIDs &output_ids,
const bool keep_last_segment)
{
if (src_curves.curves_range().is_empty()) {
return {};
}
const OffsetIndices src_points_by_curve = src_curves.points_by_curve();
const VArray<bool> curves_cyclic = src_curves.cyclic();
CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves);
/* Copy vertex groups from source curves to allow copying vertex group attributes. */
BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &src_curves.vertex_group_names);
MutableSpan<int> dst_offsets = dst_curves.offsets_for_write();
src_curves.ensure_evaluated_lengths();
selection.foreach_index(
[&](const int curve_i) {
const float curve_length = src_curves.evaluated_length_total_for_curve(
curve_i, curves_cyclic[curve_i]);
dst_offsets[curve_i] = get_count_from_length(
curve_length, sample_lengths[curve_i], keep_last_segment);
},
exec_mode::grain_size(1024));
IndexMaskMemory memory;
const IndexMask unselected = selection.complement(src_curves.curves_range(), memory);
/* Fill the counts for the curves that aren't selected and accumulate the counts into offsets. */
offset_indices::copy_group_sizes(src_points_by_curve, unselected, dst_offsets);
offset_indices::accumulate_counts_to_offsets(dst_offsets);
dst_curves.resize(dst_offsets.last(), dst_curves.curves_num());
resample_to_uniform(src_curves, selection, output_ids, dst_curves);
bke::curves::nurbs::copy_custom_knots(src_curves, selection, dst_curves);
return dst_curves;
}
CurvesGeometry resample_to_length(const CurvesGeometry &src_curves,
const fn::FieldContext &field_context,
const fn::Field<bool> &selection_field,
const fn::Field<float> &segment_length_field,
const ResampleCurvesOutputAttributeIDs &output_ids,
const bool keep_last_segment)
{
return resample_to_uniform(src_curves,
field_context,
selection_field,
get_count_input_from_length(segment_length_field, keep_last_segment),
output_ids);
}
CurvesGeometry resample_to_evaluated(const CurvesGeometry &src_curves,
const IndexMask &selection,
const ResampleCurvesOutputAttributeIDs &output_ids)
{
if (src_curves.curves_range().is_empty()) {
return {};
}
const OffsetIndices src_points_by_curve = src_curves.points_by_curve();
const OffsetIndices src_evaluated_points_by_curve = src_curves.evaluated_points_by_curve();
const Span<float3> evaluated_positions = src_curves.evaluated_positions();
IndexMaskMemory memory;
const IndexMask unselected = selection.complement(src_curves.curves_range(), memory);
CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves);
/* Copy vertex groups from source curves to allow copying vertex group attributes. */
BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &src_curves.vertex_group_names);
dst_curves.fill_curve_types(selection, CURVE_TYPE_POLY);
MutableSpan<int> dst_offsets = dst_curves.offsets_for_write();
offset_indices::copy_group_sizes(src_evaluated_points_by_curve, selection, dst_offsets);
offset_indices::copy_group_sizes(src_points_by_curve, unselected, dst_offsets);
offset_indices::accumulate_counts_to_offsets(dst_offsets);
const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve();
dst_curves.resize(dst_offsets.last(), dst_curves.curves_num());
MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
AttributesForResample attributes;
gather_point_attributes_to_interpolate(src_curves, dst_curves, attributes, output_ids);
src_curves.ensure_can_interpolate_to_evaluated();
selection.foreach_segment(
[&](const IndexMaskSegment selection_segment) {
/* Evaluate generic point attributes directly to the result attributes. */
for (const int i_attribute : attributes.dst.index_range()) {
for (const int i_curve : selection_segment) {
const IndexRange src_points = src_points_by_curve[i_curve];
const IndexRange dst_points = dst_points_by_curve[i_curve];
src_curves.interpolate_to_evaluated(i_curve,
attributes.src[i_attribute].slice(src_points),
attributes.dst[i_attribute].slice(dst_points));
}
}
auto copy_evaluated_data = [&](const Span<float3> src, MutableSpan<float3> dst) {
for (const int i_curve : selection_segment) {
const IndexRange src_points = src_evaluated_points_by_curve[i_curve];
const IndexRange dst_points = dst_points_by_curve[i_curve];
dst.slice(dst_points).copy_from(src.slice(src_points));
}
};
/* Copy the evaluated positions to the selected curves. */
copy_evaluated_data(evaluated_positions, dst_positions);
if (!attributes.dst_tangents.is_empty()) {
copy_evaluated_data(attributes.src_evaluated_tangents, attributes.dst_tangents);
normalize_curve_point_data(
selection_segment, dst_points_by_curve, attributes.dst_tangents);
}
if (!attributes.dst_normals.is_empty()) {
copy_evaluated_data(attributes.src_evaluated_normals, attributes.dst_normals);
normalize_curve_point_data(
selection_segment, dst_points_by_curve, attributes.dst_normals);
}
/* Fill the default value for non-interpolating attributes that still must be copied. */
for (GMutableSpan dst : attributes.dst_no_interpolation) {
for (const int i_curve : selection_segment) {
const IndexRange dst_points = dst_points_by_curve[i_curve];
dst.type().value_initialize_n(dst.slice(dst_points).data(), dst_points.size());
}
}
},
exec_mode::grain_size(512));
copy_or_defaults_for_unselected_curves(src_curves, unselected, attributes, dst_curves);
for (bke::GSpanAttributeWriter &attribute : attributes.dst_attributes) {
attribute.finish();
}
bke::curves::nurbs::copy_custom_knots(src_curves, selection, dst_curves);
return dst_curves;
}
CurvesGeometry resample_to_evaluated(const CurvesGeometry &src_curves,
const fn::FieldContext &field_context,
const fn::Field<bool> &selection_field,
const ResampleCurvesOutputAttributeIDs &output_ids)
{
if (src_curves.curves_range().is_empty()) {
return {};
}
fn::FieldEvaluator evaluator{field_context, src_curves.curves_num()};
evaluator.set_selection(selection_field);
evaluator.evaluate();
return resample_to_evaluated(
src_curves, evaluator.get_evaluated_selection_as_mask(), output_ids);
}
} // namespace blender::geometry

View File

@@ -0,0 +1,315 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <algorithm>
#include <fmt/format.h>
#include "GEO_reverse_uv_sampler.hh"
#include "BLI_bounds.hh"
#include "BLI_enumerable_thread_specific.hh"
#include "BLI_linear_allocator_chunked_list.hh"
#include "BLI_map.hh"
#include "BLI_math_geom.h"
#include "BLI_math_vector.hh"
#include "BLI_offset_indices.hh"
#include "BLI_task.hh"
#include "PRF_profile.hh"
namespace blender::geometry {
struct Row {
/** The min and max horizontal cell index that is used in this row. */
int x_min = 0;
int x_max = 0;
/**
* Offsets into the array of indices below. Also see #OffsetIndices. May be empty if there are
* no triangles in this row.
*/
Array<int> offsets;
/** A flat array containing the triangle indices contained in each cell. */
Array<int> tri_indices;
};
struct ReverseUVSampler::LookupGrid {
/** Minimum vertical cell index that contains triangles. */
int y_min = 0;
/** Information about all rows starting at `y_min`. */
Array<Row> rows;
};
struct TriWithRange {
int tri_index;
int x_min;
int x_max;
};
struct LocalRowData {
linear_allocator::ChunkedList<TriWithRange, 8> tris;
int x_min = INT32_MAX;
int x_max = INT32_MIN;
};
struct LocalData {
LinearAllocator<> allocator;
Map<int, destruct_ptr<LocalRowData>> rows;
};
static int2 uv_to_cell(const float2 &uv, const int resolution)
{
return int2{uv * resolution};
}
static Bounds<int2> tri_to_cell_bounds(const int3 &tri,
const int resolution,
const Span<float2> uv_map)
{
const float2 &uv_0 = uv_map[tri[0]];
const float2 &uv_1 = uv_map[tri[1]];
const float2 &uv_2 = uv_map[tri[2]];
const int2 cell_0 = uv_to_cell(uv_0, resolution);
const int2 cell_1 = uv_to_cell(uv_1, resolution);
const int2 cell_2 = uv_to_cell(uv_2, resolution);
const int2 min_cell = math::min(math::min(cell_0, cell_1), cell_2);
const int2 max_cell = math::max(math::max(cell_0, cell_1), cell_2);
return {min_cell, max_cell};
}
/**
* Add each triangle to the rows that it is in. After this, the information about each row is still
* scattered across multiple thread-specific lists. Those separate lists are then joined in a
* separate step.
*/
static void sort_tris_into_rows(const Span<float2> uv_map,
const Span<int3> corner_tris,
const int resolution,
threading::EnumerableThreadSpecific<LocalData> &data_per_thread)
{
threading::parallel_for(corner_tris.index_range(), 256, [&](const IndexRange tris_range) {
LocalData &local_data = data_per_thread.local();
for (const int tri_i : tris_range) {
const int3 &tri = corner_tris[tri_i];
/* Compute the cells that the triangle touches approximately. */
const Bounds<int2> cell_bounds = tri_to_cell_bounds(tri, resolution, uv_map);
const TriWithRange tri_with_range{tri_i, cell_bounds.min.x, cell_bounds.max.x};
/* Go over each row that the triangle is in. */
for (int cell_y = cell_bounds.min.y; cell_y <= cell_bounds.max.y; cell_y++) {
LocalRowData &row = *local_data.rows.lookup_or_add_cb(
cell_y, [&]() { return local_data.allocator.construct<LocalRowData>(); });
row.tris.append(local_data.allocator, tri_with_range);
row.x_min = std::min<int>(row.x_min, cell_bounds.min.x);
row.x_max = std::max<int>(row.x_max, cell_bounds.max.x);
}
}
});
}
/**
* Consolidates the data that has been gather for each row so that it is each to look up which
* triangles are in each cell.
*/
static void finish_rows(const Span<int> all_ys,
const Span<const LocalData *> local_data_vec,
const Bounds<int> y_bounds,
ReverseUVSampler::LookupGrid &lookup_grid)
{
threading::parallel_for(all_ys.index_range(), 8, [&](const IndexRange all_ys_range) {
Vector<const LocalRowData *, 32> local_rows;
for (const int y : all_ys.slice(all_ys_range)) {
Row &row = lookup_grid.rows[y - y_bounds.min];
local_rows.clear();
for (const LocalData *local_data : local_data_vec) {
if (const destruct_ptr<LocalRowData> *local_row = local_data->rows.lookup_ptr(y)) {
local_rows.append(local_row->get());
}
}
int x_min = INT32_MAX;
int x_max = INT32_MIN;
for (const LocalRowData *local_row : local_rows) {
x_min = std::min(x_min, local_row->x_min);
x_max = std::max(x_max, local_row->x_max);
}
const int x_num = x_max - x_min + 1;
row.offsets.reinitialize(x_num + 1);
{
/* Count how many triangles are in each cell in the current row. */
MutableSpan<int> counts = row.offsets;
counts.fill(0);
for (const LocalRowData *local_row : local_rows) {
for (const TriWithRange &tri_with_range : local_row->tris) {
for (int x = tri_with_range.x_min; x <= tri_with_range.x_max; x++) {
counts[x - x_min]++;
}
}
}
offset_indices::accumulate_counts_to_offsets(counts);
}
const int tri_indices_num = row.offsets.last();
row.tri_indices.reinitialize(tri_indices_num);
/* Populate the array containing all triangle indices in all cells in this row. */
Array<int, 1000> current_offsets(x_num, 0);
for (const LocalRowData *local_row : local_rows) {
for (const TriWithRange &tri_with_range : local_row->tris) {
for (int x = tri_with_range.x_min; x <= tri_with_range.x_max; x++) {
const int offset_x = x - x_min;
row.tri_indices[row.offsets[offset_x] + current_offsets[offset_x]] =
tri_with_range.tri_index;
current_offsets[offset_x]++;
}
}
}
row.x_min = x_min;
row.x_max = x_max;
}
});
}
ReverseUVSampler::ReverseUVSampler(const Span<float2> uv_map, const Span<int3> corner_tris)
: uv_map_(uv_map), corner_tris_(corner_tris), lookup_grid_(std::make_unique<LookupGrid>())
{
PRF_scope(ProfileCategory::Default);
/* A lower resolution means that there will be fewer cells and more triangles in each cell. Fewer
* cells make construction faster, but more triangles per cell make lookup slower. This value
* needs to be determined experimentally. */
resolution_ = std::max<int>(3, std::sqrt(corner_tris.size()) * 3);
if (corner_tris.is_empty()) {
return;
}
threading::EnumerableThreadSpecific<LocalData> data_per_thread;
sort_tris_into_rows(uv_map_, corner_tris_, resolution_, data_per_thread);
VectorSet<int> all_ys;
Vector<const LocalData *> local_data_vec;
for (const LocalData &local_data : data_per_thread) {
local_data_vec.append(&local_data);
for (const int y : local_data.rows.keys()) {
all_ys.add(y);
}
}
const Bounds<int> y_bounds = *bounds::min_max(all_ys.as_span());
lookup_grid_->y_min = y_bounds.min;
const int rows_num = y_bounds.max - y_bounds.min + 1;
lookup_grid_->rows.reinitialize(rows_num);
finish_rows(all_ys, local_data_vec, y_bounds, *lookup_grid_);
}
static Span<int> lookup_tris_in_cell(const int2 cell,
const ReverseUVSampler::LookupGrid &lookup_grid)
{
if (cell.y < lookup_grid.y_min) {
return {};
}
if (cell.y >= lookup_grid.y_min + lookup_grid.rows.size()) {
return {};
}
const Row &row = lookup_grid.rows[cell.y - lookup_grid.y_min];
if (cell.x < row.x_min) {
return {};
}
if (cell.x > row.x_max) {
return {};
}
if (row.tri_indices.is_empty()) {
return {};
}
const int offset = row.offsets[cell.x - row.x_min];
const int tris_num = row.offsets[cell.x - row.x_min + 1] - offset;
return row.tri_indices.as_span().slice(offset, tris_num);
}
ReverseUVSampler::Result ReverseUVSampler::sample(const float2 &query_uv) const
{
const int2 cell = uv_to_cell(query_uv, resolution_);
const Span<int> tri_indices = lookup_tris_in_cell(cell, *lookup_grid_);
float best_dist = FLT_MAX;
float3 best_bary_weights;
int best_tri_index;
/* The distance to an edge that is allowed to be inside or outside the triangle. Without this,
* the lookup can fail for floating point accuracy reasons when the uv is almost exact on an
* edge. */
const float edge_epsilon = 0.00001f;
/* If uv triangles are very small, it may look like the query hits multiple triangles due to
* floating point precision issues. Better just pick one of the triangles instead of failing the
* entire operation in this case. */
const float area_epsilon = 0.00001f;
for (const int tri_i : tri_indices) {
const int3 &tri = corner_tris_[tri_i];
const float2 &uv_0 = uv_map_[tri[0]];
const float2 &uv_1 = uv_map_[tri[1]];
const float2 &uv_2 = uv_map_[tri[2]];
float3 bary_weights;
if (!barycentric_coords_v2(uv_0, uv_1, uv_2, query_uv, bary_weights)) {
continue;
}
/* 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 = std::max({x_dist, y_dist, z_dist});
if (dist <= 0.0f && best_dist <= 0.0f) {
const float worse_dist = std::max(dist, best_dist);
/* Allow ignoring multiple triangle intersections if the uv is almost exactly on an edge. */
if (worse_dist < -edge_epsilon) {
const int3 &best_tri = corner_tris_[tri_i];
const float best_tri_area = area_tri_v2(
uv_map_[best_tri[0]], uv_map_[best_tri[1]], uv_map_[best_tri[2]]);
const float current_tri_area = area_tri_v2(uv_0, uv_1, uv_2);
if (best_tri_area > area_epsilon && current_tri_area > area_epsilon) {
/* The uv sample is in multiple triangles. */
return Result{ResultType::Multiple};
}
}
}
if (dist < best_dist) {
best_dist = dist;
best_bary_weights = bary_weights;
best_tri_index = tri_i;
}
}
/* Allow using the closest (but not intersecting) triangle if the uv is almost exactly on an
* edge. */
if (best_dist < edge_epsilon) {
return Result{ResultType::Ok, best_tri_index, math::clamp(best_bary_weights, 0.0f, 1.0f)};
}
return Result{};
}
ReverseUVSampler::~ReverseUVSampler() = default;
void ReverseUVSampler::sample_many(const Span<float2> query_uvs,
MutableSpan<Result> r_results) const
{
PRF_scope_with_name("ReverseUVSampler::sample_many", ProfileCategory::Default);
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

View File

@@ -0,0 +1,262 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "GEO_separate_geometry.hh"
#include "BKE_curves.hh"
#include "BKE_customdata.hh"
#include "BKE_geometry_fields.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_instances.hh"
#include "BKE_mesh.hh"
#include "BKE_pointcloud.hh"
#include "DNA_pointcloud_types.h"
#include "GEO_mesh_copy_selection.hh"
namespace blender::geometry {
using bke::AttrDomain;
/** \return std::nullopt if the geometry should remain unchanged. */
static std::optional<bke::CurvesGeometry> separate_curves_selection(
const bke::CurvesGeometry &src_curves,
const fn::FieldContext &field_context,
const fn::Field<bool> &selection_field,
const AttrDomain domain,
const bke::AttributeFilter &attribute_filter)
{
const int domain_size = src_curves.attributes().domain_size(domain);
fn::FieldEvaluator evaluator{field_context, domain_size};
evaluator.set_selection(selection_field);
evaluator.evaluate();
const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
if (selection.size() == domain_size) {
return std::nullopt;
}
if (selection.is_empty()) {
return bke::CurvesGeometry();
}
if (domain == AttrDomain::Point) {
return bke::curves_copy_point_selection(src_curves, selection, attribute_filter);
}
if (domain == AttrDomain::Curve) {
return bke::curves_copy_curve_selection(src_curves, selection, attribute_filter);
}
BLI_assert_unreachable();
return std::nullopt;
}
/** \return std::nullopt if the geometry should remain unchanged. */
static std::optional<PointCloud *> separate_pointcloud_selection(
const PointCloud &src_pointcloud,
const fn::Field<bool> &selection_field,
const bke::AttributeFilter &attribute_filter)
{
const bke::PointCloudFieldContext context{src_pointcloud};
fn::FieldEvaluator evaluator{context, src_pointcloud.totpoint};
evaluator.set_selection(selection_field);
evaluator.evaluate();
const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
if (selection.size() == src_pointcloud.totpoint) {
return std::nullopt;
}
if (selection.is_empty()) {
return nullptr;
}
PointCloud *pointcloud = BKE_pointcloud_new_nomain(selection.size());
bke::gather_attributes(src_pointcloud.attributes(),
AttrDomain::Point,
AttrDomain::Point,
attribute_filter,
selection,
pointcloud->attributes_for_write());
return pointcloud;
}
static void delete_selected_instances(bke::GeometrySet &geometry_set,
const fn::Field<bool> &selection_field,
const bke::AttributeFilter &attribute_filter)
{
bke::Instances &instances = *geometry_set.get_instances_for_write();
bke::InstancesFieldContext field_context{instances};
fn::FieldEvaluator evaluator{field_context, instances.instances_num()};
evaluator.set_selection(selection_field);
evaluator.evaluate();
const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
if (selection.is_empty()) {
geometry_set.remove<bke::InstancesComponent>();
return;
}
instances.remove(selection, attribute_filter);
}
static std::optional<Mesh *> separate_mesh_selection(const Mesh &mesh,
const fn::Field<bool> &selection_field,
const AttrDomain selection_domain,
const GeometryNodeDeleteGeometryMode mode,
const bke::AttributeFilter &attribute_filter)
{
const bke::AttributeAccessor attributes = mesh.attributes();
const bke::MeshFieldContext context(mesh, selection_domain);
fn::FieldEvaluator evaluator(context, attributes.domain_size(selection_domain));
evaluator.add(selection_field);
evaluator.evaluate();
const VArray<bool> selection = evaluator.get_evaluated<bool>(0);
switch (mode) {
case GEO_NODE_DELETE_GEOMETRY_MODE_ALL:
return mesh_copy_selection(mesh, selection, selection_domain, attribute_filter);
case GEO_NODE_DELETE_GEOMETRY_MODE_EDGE_FACE:
return mesh_copy_selection_keep_verts(mesh, selection, selection_domain, attribute_filter);
case GEO_NODE_DELETE_GEOMETRY_MODE_ONLY_FACE:
return mesh_copy_selection_keep_edges(mesh, selection, selection_domain, attribute_filter);
}
return nullptr;
}
static std::optional<GreasePencil *> separate_grease_pencil_layer_selection(
const GreasePencil &src_grease_pencil,
const fn::Field<bool> &selection_field,
const bke::AttributeFilter &attribute_filter)
{
const bke::AttributeAccessor attributes = src_grease_pencil.attributes();
const bke::GeometryFieldContext context(src_grease_pencil);
fn::FieldEvaluator evaluator(context, attributes.domain_size(AttrDomain::Layer));
evaluator.set_selection(selection_field);
evaluator.evaluate();
const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
if (selection.size() == attributes.domain_size(AttrDomain::Layer)) {
return std::nullopt;
}
if (selection.is_empty()) {
return nullptr;
}
const int dst_layers_num = selection.size();
GreasePencil *dst_grease_pencil = BKE_grease_pencil_new_nomain();
BKE_grease_pencil_copy_parameters(src_grease_pencil, *dst_grease_pencil);
dst_grease_pencil->add_layers_with_empty_drawings_for_eval(dst_layers_num);
selection.foreach_index([&](const int src_layer_i, const int dst_layer_i) {
const bke::greasepencil::Layer &src_layer = src_grease_pencil.layer(src_layer_i);
const bke::greasepencil::Drawing *src_drawing = src_grease_pencil.get_eval_drawing(src_layer);
bke::greasepencil::Layer &dst_layer = dst_grease_pencil->layer(dst_layer_i);
bke::greasepencil::Drawing &dst_drawing = *dst_grease_pencil->get_eval_drawing(dst_layer);
BKE_grease_pencil_copy_layer_parameters(src_layer, dst_layer);
dst_layer.set_name(src_layer.name());
if (src_drawing) {
dst_drawing = *src_drawing;
}
});
bke::gather_attributes(src_grease_pencil.attributes(),
AttrDomain::Layer,
AttrDomain::Layer,
attribute_filter,
selection,
dst_grease_pencil->attributes_for_write());
return dst_grease_pencil;
}
void separate_geometry(bke::GeometrySet &geometry_set,
const AttrDomain domain,
const GeometryNodeDeleteGeometryMode mode,
const fn::Field<bool> &selection,
const bke::AttributeFilter &attribute_filter,
bool &r_is_error)
{
bool some_valid_domain = false;
if (const PointCloud *points = geometry_set.get_pointcloud()) {
if (domain == AttrDomain::Point) {
std::optional<PointCloud *> dst_points = separate_pointcloud_selection(
*points, selection, attribute_filter);
if (dst_points) {
geometry_set.replace_pointcloud(*dst_points);
}
some_valid_domain = true;
}
}
if (const Mesh *mesh = geometry_set.get_mesh()) {
if (ELEM(domain, AttrDomain::Point, AttrDomain::Edge, AttrDomain::Face)) {
std::optional<Mesh *> dst_mesh = separate_mesh_selection(
*mesh, selection, domain, mode, attribute_filter);
if (dst_mesh) {
geometry_set.replace_mesh(*dst_mesh);
}
some_valid_domain = true;
}
}
if (const Curves *src_curves_id = geometry_set.get_curves()) {
if (ELEM(domain, AttrDomain::Point, AttrDomain::Curve)) {
const bke::CurvesGeometry &src_curves = src_curves_id->geometry.wrap();
const bke::CurvesFieldContext field_context{*src_curves_id, domain};
std::optional<bke::CurvesGeometry> dst_curves = separate_curves_selection(
src_curves, field_context, selection, domain, attribute_filter);
if (dst_curves) {
if (dst_curves->is_empty()) {
geometry_set.remove<bke::CurveComponent>();
}
else {
Curves *dst_curves_id = bke::curves_new_nomain(*dst_curves);
bke::curves_copy_parameters(*src_curves_id, *dst_curves_id);
geometry_set.replace_curves(dst_curves_id);
}
}
some_valid_domain = true;
}
}
if (geometry_set.get_grease_pencil()) {
using namespace blender::bke::greasepencil;
if (domain == AttrDomain::Layer) {
const GreasePencil &grease_pencil = *geometry_set.get_grease_pencil();
std::optional<GreasePencil *> dst_grease_pencil = separate_grease_pencil_layer_selection(
grease_pencil, selection, attribute_filter);
if (dst_grease_pencil) {
geometry_set.replace_grease_pencil(*dst_grease_pencil);
}
some_valid_domain = true;
}
else if (ELEM(domain, AttrDomain::Point, AttrDomain::Curve)) {
GreasePencil &grease_pencil = *geometry_set.get_grease_pencil_for_write();
for (const int layer_index : grease_pencil.layers().index_range()) {
Drawing *drawing = grease_pencil.get_eval_drawing(grease_pencil.layer(layer_index));
if (drawing == nullptr) {
continue;
}
const bke::CurvesGeometry &src_curves = drawing->strokes();
const bke::GreasePencilLayerFieldContext field_context(grease_pencil, domain, layer_index);
std::optional<bke::CurvesGeometry> dst_curves = separate_curves_selection(
src_curves, field_context, selection, domain, attribute_filter);
if (!dst_curves) {
continue;
}
drawing->strokes_for_write() = std::move(*dst_curves);
drawing->tag_topology_changed();
some_valid_domain = true;
}
}
}
if (geometry_set.has_instances()) {
if (domain == AttrDomain::Instance) {
delete_selected_instances(geometry_set, selection, attribute_filter);
some_valid_domain = true;
}
}
r_is_error = !some_valid_domain && geometry_set.has_realized_data();
}
} // namespace blender::geometry

View File

@@ -0,0 +1,808 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_attribute.hh"
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_curves_utils.hh"
#include "BKE_deform.hh"
#include "BLI_array_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;
}
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];
}
}
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);
}
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 : std::max(1, 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 bke::CurvesGeometry convert_curves_to_bezier(const bke::CurvesGeometry &src_curves,
const IndexMask &selection,
const bke::AttributeFilter &attribute_filter)
{
const OffsetIndices src_points_by_curve = src_curves.points_by_curve();
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();
const bke::AttributeAccessor src_attributes = src_curves.attributes();
IndexMaskMemory memory;
const IndexMask unselected = selection.complement(src_curves.curves_range(), memory);
bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves);
/* Copy vertex groups from source curves to allow copying vertex group attributes. */
BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &src_curves.vertex_group_names);
dst_curves.fill_curve_types(selection, CURVE_TYPE_BEZIER);
MutableSpan<int> dst_offsets = dst_curves.offsets_for_write();
offset_indices::copy_group_sizes(src_points_by_curve, unselected, dst_offsets);
selection.foreach_index(
[&](const int i) {
dst_offsets[i] = to_bezier_size(CurveType(src_types[i]),
src_cyclic[i],
KnotsMode(src_knot_modes[i]),
src_points_by_curve[i].size());
},
exec_mode::grain_size(1024));
offset_indices::accumulate_counts_to_offsets(dst_offsets);
dst_curves.resize(dst_offsets.last(), dst_curves.curves_num());
const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve();
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
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();
Vector<bke::AttributeTransferData> generic_attributes = bke::retrieve_attributes_for_transfer(
src_attributes, dst_attributes, {bke::AttrDomain::Point}, attribute_filter);
Set<StringRef> attributes_to_skip = {
"position", "handle_type_left", "handle_type_right", "handle_right", "handle_left"};
if (!dst_curves.has_curve_with_type(CURVE_TYPE_NURBS)) {
attributes_to_skip.add_new("nurbs_weight");
}
auto catmull_rom_to_bezier = [&](const IndexMask &selection) {
bke::curves::fill_points<int8_t>(
dst_points_by_curve, selection, BEZIER_HANDLE_ALIGN, dst_types_l);
bke::curves::fill_points<int8_t>(
dst_points_by_curve, selection, BEZIER_HANDLE_ALIGN, dst_types_r);
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, selection, src_positions, dst_positions);
selection.foreach_index(
[&](const int i) {
const IndexRange src_points = src_points_by_curve[i];
const IndexRange dst_points = dst_points_by_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));
},
exec_mode::grain_size(512));
for (bke::AttributeTransferData &attribute : generic_attributes) {
if (attributes_to_skip.contains(attribute.name)) {
continue;
}
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, selection, attribute.src, attribute.dst.span);
}
};
auto poly_to_bezier = [&](const IndexMask &selection) {
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, selection, src_positions, dst_positions);
bke::curves::fill_points<int8_t>(
dst_points_by_curve, selection, BEZIER_HANDLE_VECTOR, dst_types_l);
bke::curves::fill_points<int8_t>(
dst_points_by_curve, selection, BEZIER_HANDLE_VECTOR, dst_types_r);
dst_curves.calculate_bezier_auto_handles();
for (bke::AttributeTransferData &attribute : generic_attributes) {
if (attributes_to_skip.contains(attribute.name)) {
continue;
}
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, selection, attribute.src, attribute.dst.span);
}
};
auto bezier_to_bezier = [&](const 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();
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, selection, src_positions, dst_positions);
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, selection, src_handles_l, dst_handles_l);
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, selection, src_handles_r, dst_handles_r);
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, selection, src_types_l, dst_types_l);
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, selection, src_types_r, dst_types_r);
dst_curves.calculate_bezier_auto_handles();
for (bke::AttributeTransferData &attribute : generic_attributes) {
if (attributes_to_skip.contains(attribute.name)) {
continue;
}
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, selection, attribute.src, attribute.dst.span);
}
};
auto nurbs_to_bezier = [&](const IndexMask &selection) {
bke::curves::fill_points<int8_t>(
dst_points_by_curve, selection, BEZIER_HANDLE_ALIGN, dst_types_l);
bke::curves::fill_points<int8_t>(
dst_points_by_curve, selection, BEZIER_HANDLE_ALIGN, dst_types_r);
selection.foreach_index(
[&](const int i) {
const IndexRange src_points = src_points_by_curve[i];
const IndexRange dst_points = dst_points_by_curve[i];
const Span<float3> src_curve_positions = src_positions.slice(src_points);
if (dst_points.size() == 1) {
const float3 &position = src_positions[src_points.first()];
dst_positions[dst_points.first()] = position;
dst_handles_l[dst_points.first()] = position;
dst_handles_r[dst_points.first()] = position;
return;
}
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));
},
exec_mode::grain_size(64));
const IndexMask selection_points = bke::curves::curve_to_point_selection(
dst_points_by_curve, selection, memory);
Array<int> src_point_by_dst_point(selection_points.min_array_size());
selection.foreach_index(
[&](const int curve) {
const IndexRange src_points = src_points_by_curve[curve];
const IndexRange dst_points = dst_points_by_curve[curve];
MutableSpan<int> dst = src_point_by_dst_point.as_mutable_span().slice(dst_points);
switch (KnotsMode(src_knot_modes[curve])) {
case NURBS_KNOT_MODE_NORMAL:
for (const int i : dst.index_range()) {
dst[i] = src_points[(i + 1) % src_points.size()];
}
break;
case NURBS_KNOT_MODE_ENDPOINT:
for (const int i : dst.index_range().drop_back(1).drop_front(1)) {
dst[i] = src_points[i + 1];
}
dst.first() = src_points.first();
dst.last() = src_points.last();
break;
default:
/* Every 3rd NURBS position (starting from index 1) should have data transferred. */
for (const int i : dst.index_range()) {
dst[i] = src_points[i * 3 + 1];
}
break;
}
},
exec_mode::grain_size(512));
for (bke::AttributeTransferData &attribute : generic_attributes) {
if (attributes_to_skip.contains(attribute.name)) {
continue;
}
bke::attribute_math::gather(
attribute.src, src_point_by_dst_point, selection_points, attribute.dst.span);
}
};
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);
for (bke::AttributeTransferData &attribute : generic_attributes) {
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, unselected, attribute.src, attribute.dst.span);
attribute.dst.finish();
}
bke::curves::nurbs::copy_custom_knots(src_curves, selection, dst_curves);
return dst_curves;
}
static bke::CurvesGeometry convert_curves_to_nurbs(const bke::CurvesGeometry &src_curves,
const IndexMask &selection,
const bke::AttributeFilter &attribute_filter)
{
const OffsetIndices src_points_by_curve = src_curves.points_by_curve();
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();
const bke::AttributeAccessor src_attributes = src_curves.attributes();
IndexMaskMemory memory;
const IndexMask unselected = selection.complement(src_curves.curves_range(), memory);
bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves);
/* Copy vertex groups from source curves to allow copying vertex group attributes. */
BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &src_curves.vertex_group_names);
dst_curves.fill_curve_types(selection, CURVE_TYPE_NURBS);
MutableSpan<int> dst_offsets = dst_curves.offsets_for_write();
offset_indices::copy_group_sizes(src_points_by_curve, unselected, dst_offsets);
selection.foreach_index(
[&](const int i) {
dst_offsets[i] = to_nurbs_size(CurveType(src_types[i]), src_points_by_curve[i].size());
},
exec_mode::grain_size(1024));
offset_indices::accumulate_counts_to_offsets(dst_offsets);
dst_curves.resize(dst_offsets.last(), dst_curves.curves_num());
const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve();
MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
Vector<bke::AttributeTransferData> generic_attributes = bke::retrieve_attributes_for_transfer(
src_attributes, dst_attributes, {bke::AttrDomain::Point}, attribute_filter);
const Set<StringRef> attributes_to_skip = {"position",
"handle_type_left",
"handle_type_right",
"handle_right",
"handle_left",
"nurbs_weight"};
auto fill_weights_if_necessary = [&](const IndexMask &selection) {
if (src_attributes.contains("nurbs_weight")) {
bke::curves::fill_points(
dst_points_by_curve, selection, 1.0f, dst_curves.nurbs_weights_for_write());
}
};
auto bezier_to_nurbs_copy_attributes = [&](const IndexMask &selection) {
const IndexMask selection_points = bke::curves::curve_to_point_selection(
dst_points_by_curve, selection, memory);
Array<int> src_point_by_dst_point(selection_points.min_array_size());
selection.foreach_index(
[&](const int curve) {
const IndexRange src_points = src_points_by_curve[curve];
const IndexRange dst_points = dst_points_by_curve[curve];
MutableSpan<int> dst = src_point_by_dst_point.as_mutable_span().slice(dst_points);
/* The Bezier control point and its handles become three control points on the NURBS
* curve, so each attribute value is duplicated three times. */
for (const int i : src_points.index_range()) {
dst[i * 3] = src_points[i];
dst[i * 3 + 1] = src_points[i];
dst[i * 3 + 2] = src_points[i];
}
},
exec_mode::grain_size(512));
for (bke::AttributeTransferData &attribute : generic_attributes) {
if (attributes_to_skip.contains(attribute.name)) {
continue;
}
bke::attribute_math::gather(
attribute.src, src_point_by_dst_point, selection_points, attribute.dst.span);
}
};
auto catmull_rom_to_nurbs = [&](const IndexMask &selection) {
index_mask::masked_fill<int8_t>(dst_curves.nurbs_orders_for_write(), 4, selection);
index_mask::masked_fill<int8_t>(
dst_curves.nurbs_knots_modes_for_write(), NURBS_KNOT_MODE_BEZIER, selection);
fill_weights_if_necessary(selection);
selection.foreach_segment(
[&](const IndexMaskSegment segment) {
for (const int i : segment) {
const IndexRange src_points = src_points_by_curve[i];
const IndexRange dst_points = dst_points_by_curve[i];
catmull_rom_to_nurbs_positions(
src_positions.slice(src_points), src_cyclic[i], dst_positions.slice(dst_points));
}
},
exec_mode::grain_size(512));
bezier_to_nurbs_copy_attributes(selection);
};
auto poly_to_nurbs = [&](const IndexMask &selection) {
index_mask::masked_fill<int8_t>(dst_curves.nurbs_orders_for_write(), 4, selection);
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, 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()) {
index_mask::masked_fill<int8_t>(dst_curves.nurbs_knots_modes_for_write(),
src_cyclic.get_internal_single() ? NURBS_KNOT_MODE_NORMAL :
NURBS_KNOT_MODE_ENDPOINT,
selection);
}
else {
VArraySpan<bool> cyclic{src_cyclic};
MutableSpan<int8_t> knots_modes = dst_curves.nurbs_knots_modes_for_write();
selection.foreach_index_optimized<int>(
[&](const int i) {
knots_modes[i] = cyclic[i] ? NURBS_KNOT_MODE_NORMAL : NURBS_KNOT_MODE_ENDPOINT;
},
exec_mode::grain_size(4096));
}
for (bke::AttributeTransferData &attribute : generic_attributes) {
if (attributes_to_skip.contains(attribute.name)) {
continue;
}
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, selection, attribute.src, attribute.dst.span);
}
};
auto bezier_to_nurbs = [&](const IndexMask &selection) {
const Span<float3> src_handles_l = *src_curves.handle_positions_left();
const Span<float3> src_handles_r = *src_curves.handle_positions_right();
index_mask::masked_fill<int8_t>(dst_curves.nurbs_orders_for_write(), 4, selection);
index_mask::masked_fill<int8_t>(
dst_curves.nurbs_knots_modes_for_write(), NURBS_KNOT_MODE_BEZIER, selection);
fill_weights_if_necessary(selection);
selection.foreach_index(
[&](const int i) {
const IndexRange src_points = src_points_by_curve[i];
const IndexRange dst_points = dst_points_by_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));
},
exec_mode::grain_size(512));
bezier_to_nurbs_copy_attributes(selection);
};
auto nurbs_to_nurbs = [&](const IndexMask &selection) {
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, selection, src_positions, dst_positions);
if (const std::optional<Span<float>> nurbs_weights = src_curves.nurbs_weights()) {
array_utils::copy_group_to_group(src_points_by_curve,
dst_points_by_curve,
selection,
*nurbs_weights,
dst_curves.nurbs_weights_for_write());
}
for (bke::AttributeTransferData &attribute : generic_attributes) {
if (attributes_to_skip.contains(attribute.name)) {
continue;
}
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, selection, attribute.src, attribute.dst.span);
}
};
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);
for (bke::AttributeTransferData &attribute : generic_attributes) {
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, unselected, attribute.src, attribute.dst.span);
attribute.dst.finish();
}
bke::curves::nurbs::copy_custom_knots(src_curves, IndexMask(), dst_curves);
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;
}
static bke::CurvesGeometry convert_curves_to_catmull_rom_or_poly(
const bke::CurvesGeometry &src_curves,
const IndexMask &selection,
const CurveType dst_type,
const bke::AttributeFilter &attribute_filter,
const ConvertCurvesOptions &options)
{
const bool use_bezier_handles = (dst_type == CURVE_TYPE_CATMULL_ROM) ?
options.convert_bezier_handles_to_catmull_rom_points :
options.convert_bezier_handles_to_poly_points;
if (!use_bezier_handles || !src_curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
return convert_curves_trivial(src_curves, selection, dst_type);
}
const OffsetIndices src_points_by_curve = src_curves.points_by_curve();
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();
const bke::AttributeAccessor src_attributes = src_curves.attributes();
IndexMaskMemory memory;
const IndexMask unselected = selection.complement(src_curves.curves_range(), memory);
bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves);
/* Copy vertex groups from source curves to allow copying vertex group attributes. */
BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &src_curves.vertex_group_names);
dst_curves.fill_curve_types(selection, dst_type);
MutableSpan<int> dst_offsets = dst_curves.offsets_for_write();
offset_indices::copy_group_sizes(src_points_by_curve, unselected, dst_offsets);
selection.foreach_index(
[&](const int i) {
const IndexRange src_points = src_points_by_curve[i];
const CurveType src_curve_type = CurveType(src_types[i]);
int &size = dst_offsets[i];
if (src_curve_type == CURVE_TYPE_BEZIER) {
size = src_points.size() * 3;
}
else {
size = src_points.size();
}
},
exec_mode::grain_size(1024));
offset_indices::accumulate_counts_to_offsets(dst_offsets);
dst_curves.resize(dst_offsets.last(), dst_curves.curves_num());
const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve();
MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
Vector<bke::AttributeTransferData> generic_attributes = bke::retrieve_attributes_for_transfer(
src_attributes, dst_attributes, {bke::AttrDomain::Point}, attribute_filter);
const Set<StringRef> attributes_to_skip = {"position",
"handle_type_left",
"handle_type_right",
"handle_right",
"handle_left",
"nurbs_weight"};
auto convert_from_catmull_rom_or_poly_or_nurbs = [&](const IndexMask &selection) {
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, selection, src_positions, dst_positions);
for (bke::AttributeTransferData &attribute : generic_attributes) {
if (attributes_to_skip.contains(attribute.name)) {
continue;
}
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, selection, attribute.src, attribute.dst.span);
}
};
auto convert_from_bezier = [&](const IndexMask &selection) {
const Span<float3> src_left_handles = *src_curves.handle_positions_left();
const Span<float3> src_right_handles = *src_curves.handle_positions_right();
/* Transfer positions. */
selection.foreach_index([&](const int curve_i) {
const IndexRange src_points = src_points_by_curve[curve_i];
const IndexRange dst_points = dst_points_by_curve[curve_i];
for (const int i : src_points.index_range()) {
const int src_point_i = src_points[i];
const int dst_points_start = dst_points.start() + 3 * i;
dst_positions[dst_points_start + 0] = src_left_handles[src_point_i];
dst_positions[dst_points_start + 1] = src_positions[src_point_i];
dst_positions[dst_points_start + 2] = src_right_handles[src_point_i];
}
});
/* Transfer attributes. The handles the same attribute values as their corresponding control
* point. */
for (bke::AttributeTransferData &attribute : generic_attributes) {
if (attributes_to_skip.contains(attribute.name)) {
continue;
}
const CPPType &cpp_type = attribute.src.type();
selection.foreach_index([&](const int curve_i) {
const IndexRange src_points = src_points_by_curve[curve_i];
const IndexRange dst_points = dst_points_by_curve[curve_i];
for (const int i : src_points.index_range()) {
const int src_point_i = src_points[i];
const int dst_points_start = dst_points.start() + 3 * i;
const void *src_value = attribute.src[src_point_i];
cpp_type.fill_assign_n(src_value, attribute.dst.span[dst_points_start], 3);
}
});
}
};
bke::curves::foreach_curve_by_type(src_curves.curve_types(),
src_curves.curve_type_counts(),
selection,
convert_from_catmull_rom_or_poly_or_nurbs,
convert_from_catmull_rom_or_poly_or_nurbs,
convert_from_bezier,
convert_from_catmull_rom_or_poly_or_nurbs);
for (bke::AttributeTransferData &attribute : generic_attributes) {
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, unselected, attribute.src, attribute.dst.span);
attribute.dst.finish();
}
bke::curves::nurbs::copy_custom_knots(src_curves, selection, dst_curves);
return dst_curves;
}
/**
* Converts some curves to poly curves before they are converted to nurbs. This is useful because
* it discards the bezier/catmull-rom shape which is sometimes the desired behavior.
*/
static bke::CurvesGeometry convert_bezier_or_catmull_rom_to_poly_before_conversion_to_nurbs(
const bke::CurvesGeometry &src_curves,
const IndexMask &selection,
const ConvertCurvesOptions &options)
{
const VArray<int8_t> src_curve_types = src_curves.curve_types();
IndexMaskMemory memory;
const IndexMask mask = IndexMask::from_predicate(selection, memory, [&](const int curve_i) {
const CurveType type = CurveType(src_curve_types[curve_i]);
if (!options.keep_bezier_shape_as_nurbs && type == CURVE_TYPE_BEZIER) {
return true;
}
if (!options.keep_catmull_rom_shape_as_nurbs && type == CURVE_TYPE_CATMULL_ROM) {
return true;
}
return false;
});
return convert_curves_trivial(src_curves, mask, CURVE_TYPE_POLY);
}
bke::CurvesGeometry convert_curves(const bke::CurvesGeometry &src_curves,
const IndexMask &selection,
const CurveType dst_type,
const bke::AttributeFilter &attribute_filter,
const ConvertCurvesOptions &options)
{
switch (dst_type) {
case CURVE_TYPE_CATMULL_ROM:
case CURVE_TYPE_POLY:
return convert_curves_to_catmull_rom_or_poly(
src_curves, selection, dst_type, attribute_filter, options);
case CURVE_TYPE_BEZIER:
return convert_curves_to_bezier(src_curves, selection, attribute_filter);
case CURVE_TYPE_NURBS: {
if (!options.keep_bezier_shape_as_nurbs || !options.keep_catmull_rom_shape_as_nurbs) {
const bke::CurvesGeometry tmp_src_curves =
convert_bezier_or_catmull_rom_to_poly_before_conversion_to_nurbs(
src_curves, selection, options);
return convert_curves_to_nurbs(tmp_src_curves, selection, attribute_filter);
}
return convert_curves_to_nurbs(src_curves, selection, attribute_filter);
}
}
BLI_assert_unreachable();
return {};
}
} // namespace blender::geometry

View File

@@ -0,0 +1,161 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "BLI_stack.hh"
#include "BKE_curves_utils.hh"
#include "GEO_simplify_curves.hh"
namespace blender::geometry {
/**
* Computes a "perpendicular distance" value for the generic attribute data based on the
* positions of the curve.
*
* First, we compute a lambda value that represents a factor from the first point to the last
* point of the current range. This is the projection of the point of interest onto the vector
* from the first to the last point.
*
* Then this lambda value is used to compute an interpolated value of the first and last point
* and finally we compute the distance from the interpolated value to the actual value.
* This is the "perpendicular distance".
*/
template<typename T>
float perpendicular_distance(const Span<float3> positions,
const Span<T> attribute_data,
const int64_t first_index,
const int64_t last_index,
const int64_t index)
{
const float3 ray_dir = positions[last_index] - positions[first_index];
float lambda = 0.0f;
if (!math::is_zero(ray_dir)) {
lambda = math::dot(ray_dir, positions[index] - positions[first_index]) /
math::dot(ray_dir, ray_dir);
}
const T &from = attribute_data[first_index];
const T &to = attribute_data[last_index];
const T &value = attribute_data[index];
const T &interpolated = math::interpolate(from, to, lambda);
return math::distance(value, interpolated);
}
/**
* An implementation of the Ramer-Douglas-Peucker algorithm.
*/
template<typename T>
static void ramer_douglas_peucker(const IndexRange range,
const Span<float3> positions,
const float epsilon,
const Span<T> attribute_data,
MutableSpan<bool> points_to_delete)
{
/* Mark all points to be kept. */
points_to_delete.slice(range).fill(false);
Stack<IndexRange, 32> stack;
stack.push(range);
while (!stack.is_empty()) {
const IndexRange sub_range = stack.pop();
/* Skip ranges with less than 3 points. All points are kept. */
if (sub_range.size() < 3) {
continue;
}
const IndexRange inside_range = sub_range.drop_front(1).drop_back(1);
/* Compute the maximum distance and the corresponding index. */
float max_dist = -1.0f;
int max_index = -1;
for (const int64_t index : inside_range) {
const float dist = perpendicular_distance(
positions, attribute_data, sub_range.first(), sub_range.last(), index);
if (dist > max_dist) {
max_dist = dist;
max_index = index - sub_range.first();
}
}
if (max_dist > epsilon) {
/* Found point outside the epsilon-sized strip. The point at `max_index` will be kept, repeat
* the search on the left & right side. */
stack.push(sub_range.slice(0, max_index + 1));
stack.push(sub_range.slice(max_index, sub_range.size() - max_index));
}
else {
/* Points in `sub_range` are inside the epsilon-sized strip. Mark them to be deleted. */
points_to_delete.slice(inside_range).fill(true);
}
}
}
template<typename T>
static void curve_simplify(const Span<float3> positions,
const bool cyclic,
const float epsilon,
const Span<T> attribute_data,
MutableSpan<bool> points_to_delete)
{
const Vector<IndexRange> selection_ranges = array_utils::find_all_ranges(
points_to_delete.as_span(), true);
threading::parallel_for(
selection_ranges.index_range(), 512, [&](const IndexRange range_of_ranges) {
for (const IndexRange range : selection_ranges.as_span().slice(range_of_ranges)) {
ramer_douglas_peucker(range, positions, epsilon, attribute_data, points_to_delete);
}
});
/* For cyclic curves, handle the last segment separately. */
const int points_num = positions.size();
if (cyclic && points_num > 2) {
const float dist = perpendicular_distance(
positions, attribute_data, points_num - 2, 0, points_num - 1);
if (dist <= epsilon) {
points_to_delete[points_num - 1] = true;
}
}
}
void curve_simplify(const Span<float3> positions,
const bool cyclic,
const float epsilon,
const GSpan attribute_data,
MutableSpan<bool> points_to_delete)
{
attribute_data.type().to_static_type<float, float2, float3>([&]<typename T>() {
curve_simplify(positions, cyclic, epsilon, attribute_data.typed<T>(), points_to_delete);
});
}
IndexMask simplify_curve_attribute(const Span<float3> positions,
const IndexMask &curves_selection,
const OffsetIndices<int> points_by_curve,
const VArray<bool> &cyclic,
const float epsilon,
const GSpan attribute_data,
IndexMaskMemory &memory)
{
Array<bool> points_to_delete(positions.size(), false);
if (epsilon <= 0.0f) {
return IndexMask::from_bools(points_to_delete, memory);
}
bke::curves::fill_points(
points_by_curve, curves_selection, true, points_to_delete.as_mutable_span());
curves_selection.foreach_index(
[&](const int64_t curve_i) {
const IndexRange points = points_by_curve[curve_i];
attribute_data.type().to_static_type<float, float2, float3>([&]<typename T>() {
curve_simplify(positions.slice(points),
cyclic[curve_i],
epsilon,
attribute_data.typed<T>().slice(points),
points_to_delete.as_mutable_span().slice(points));
});
},
exec_mode::grain_size(512));
return IndexMask::from_bools(points_to_delete, memory);
}
} // namespace blender::geometry

View File

@@ -0,0 +1,383 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_curves_utils.hh"
#include "BLI_array.hh"
#include "BLI_generic_span.hh"
#include "BLI_index_mask.hh"
#include "BLI_index_range.hh"
#include "BLI_vector.hh"
#include "BLI_virtual_array.hh"
#include "GEO_smooth_curves.hh"
namespace blender::geometry {
template<typename T>
static void gaussian_blur_1D(const Span<T> src,
const int iterations,
const VArray<float> &influence_by_point,
const bool smooth_ends,
const bool keep_shape,
const bool is_cyclic,
MutableSpan<T> dst)
{
/**
* 1D Gaussian-like smoothing function.
*
* NOTE: This is the algorithm used by #BKE_gpencil_stroke_smooth_point (legacy),
* but generalized and written in C++.
*
* This function uses a binomial kernel, which is the discrete version of gaussian blur.
* The weight for a value at the relative index is:
* `w = nCr(n, j + n/2) / 2^n = (n/1 * (n-1)/2 * ... * (n-j-n/2)/(j+n/2)) / 2^n`.
* All weights together sum up to 1.
* This is equivalent to doing multiple iterations of averaging neighbors,
* where: `n = iterations * 2 and -n/2 <= j <= n/2`.
*
* Now the problem is that `nCr(n, j + n/2)` is very hard to compute for `n > 500`, since even
* double precision isn't sufficient. A very good robust approximation for `n > 20` is:
* `nCr(n, j + n/2) / 2^n = sqrt(2/(pi*n)) * exp(-2*j*j/n)`.
*
* `keep_shape` is a new option to stop the points from severely deforming.
* It uses different partially negative weights.
* `w = 2 * (nCr(n, j + n/2) / 2^n) - (nCr(3*n, j + n) / 2^(3*n))`
* ` ~ 2 * sqrt(2/(pi*n)) * exp(-2*j*j/n) - sqrt(2/(pi*3*n)) * exp(-2*j*j/(3*n))`
* All weights still sum up to 1.
* Note that these weights only work because the averaging is done in relative coordinates.
*/
BLI_assert(!src.is_empty());
BLI_assert(src.size() == dst.size());
/* Avoid computation if there is just one point. */
if (src.size() == 1) {
return;
}
/* Weight Initialization. */
const int n_half = keep_shape ? (iterations * iterations) / 8 + iterations :
(iterations * iterations) / 4 + 2 * iterations + 12;
double w = keep_shape ? 2.0 : 1.0;
double w2 = keep_shape ?
(1.0 / M_SQRT3) * exp((2 * iterations * iterations) / double(n_half * 3)) :
0.0;
Array<double> total_weight(src.size(), 0.0);
const int64_t total_points = src.size();
const int64_t last_pt = total_points - 1;
auto is_end_and_fixed = [smooth_ends, is_cyclic, last_pt](int index) {
return !smooth_ends && !is_cyclic && ELEM(index, 0, last_pt);
};
/* Initialize at zero. */
threading::parallel_for(dst.index_range(), 1024, [&](const IndexRange range) {
for (const int64_t index : range) {
if (!is_end_and_fixed(index)) {
dst[index] = T(0);
}
}
});
/* Compute weights. */
for (const int64_t step : IndexRange(iterations)) {
const int64_t offset = iterations - step;
threading::parallel_for(dst.index_range(), 1024, [&](const IndexRange range) {
for (const int64_t index : range) {
/* Filter out endpoints. */
if (is_end_and_fixed(index)) {
continue;
}
double w_before = w - w2;
double w_after = w - w2;
/* Compute the neighboring points. */
int64_t before = index - offset;
int64_t after = index + offset;
if (is_cyclic) {
before = (before % total_points + total_points) % total_points;
after = after % total_points;
}
else {
if (!smooth_ends && (before < 0)) {
w_before *= -before / float(index);
}
before = math::max(before, int64_t(0));
if (!smooth_ends && (after > last_pt)) {
w_after *= (after - (total_points - 1)) / float(total_points - 1 - index);
}
after = math::min(after, last_pt);
}
/* Add the neighboring values. */
const T bval = src[before];
const T aval = src[after];
const T cval = src[index];
dst[index] += (bval - cval) * w_before;
dst[index] += (aval - cval) * w_after;
/* Update the weight values. */
total_weight[index] += w_before;
total_weight[index] += w_after;
}
});
w *= (n_half + offset) / double(n_half + 1 - offset);
w2 *= (n_half * 3 + offset) / double(n_half * 3 + 1 - offset);
}
/* Normalize the weights. */
devirtualize_varray(influence_by_point, [&](const auto influence_by_point) {
threading::parallel_for(dst.index_range(), 1024, [&](const IndexRange range) {
for (const int64_t index : range) {
if (!is_end_and_fixed(index)) {
total_weight[index] += w - w2;
dst[index] = src[index] + influence_by_point[index] * dst[index] / total_weight[index];
}
}
});
});
}
void gaussian_blur_1D(const GSpan src,
const int iterations,
const VArray<float> &influence_by_point,
const bool smooth_ends,
const bool keep_shape,
const bool is_cyclic,
GMutableSpan dst)
{
bke::attribute_math::to_static_type(src.type(), [&]<typename T>() {
/* Only allow smoothing of float, float2, or float3. */
/* Reduces unnecessary code generation. */
if constexpr (is_same_any_v<T, float, float2, float3>) {
gaussian_blur_1D(src.typed<T>(),
iterations,
influence_by_point,
smooth_ends,
keep_shape,
is_cyclic,
dst.typed<T>());
}
});
}
void smooth_curve_attribute(const IndexMask &curves_to_smooth,
const OffsetIndices<int> points_by_curve,
const VArray<bool> &point_selection,
const VArray<bool> &cyclic,
const int iterations,
const VArray<float> &influence_by_point,
const bool smooth_ends,
const bool keep_shape,
GMutableSpan attribute_data)
{
VArraySpan<float> influences(influence_by_point);
auto smooth_points_range =
[&](const IndexRange range, const bool cyclic, Vector<std::byte> &orig_data) {
GMutableSpan dst_data = attribute_data.slice(range);
orig_data.resize(dst_data.size_in_bytes());
dst_data.type().copy_assign_n(dst_data.data(), orig_data.data(), range.size());
const GSpan src_data(dst_data.type(), orig_data.data(), range.size());
gaussian_blur_1D(src_data,
iterations,
VArray<float>::from_span(influences.slice(range)),
smooth_ends,
keep_shape,
cyclic,
dst_data);
};
curves_to_smooth.foreach_index(
[&](const int curve_i) {
Vector<std::byte> orig_data;
const IndexRange points = points_by_curve[curve_i];
IndexMaskMemory memory;
const IndexMask selection_mask = IndexMask::from_bools(points, point_selection, memory);
if (selection_mask.is_empty()) {
return;
}
const std::optional<IndexRange> selection_range = selection_mask.to_range();
if (selection_range && *selection_range == points) {
smooth_points_range(points, cyclic[curve_i], orig_data);
}
else {
selection_mask.foreach_range([&](const IndexRange range) {
/* Individual ranges should be treated as non-cyclic. */
smooth_points_range(range, false, orig_data);
});
}
},
exec_mode::grain_size(512));
}
void smooth_curve_attribute(const IndexMask &curves_to_smooth,
const OffsetIndices<int> points_by_curve,
const VArray<bool> &point_selection,
const VArray<bool> &cyclic,
const int iterations,
const float influence,
const bool smooth_ends,
const bool keep_shape,
GMutableSpan attribute_data)
{
smooth_curve_attribute(curves_to_smooth,
points_by_curve,
point_selection,
cyclic,
iterations,
VArray<float>::from_single(influence, points_by_curve.total_size()),
smooth_ends,
keep_shape,
attribute_data);
}
void smooth_curve_positions(bke::CurvesGeometry &curves,
const IndexMask &curves_to_smooth,
const VArray<bool> &point_selection,
const int iterations,
const VArray<float> &influence_by_point,
const bool smooth_ends,
const bool keep_shape)
{
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
const OffsetIndices points_by_curve = curves.points_by_curve();
const VArray<bool> cyclic = curves.cyclic();
if (!curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
bke::GSpanAttributeWriter positions = attributes.lookup_for_write_span("position");
smooth_curve_attribute(curves_to_smooth,
points_by_curve,
point_selection,
cyclic,
iterations,
influence_by_point,
smooth_ends,
keep_shape,
positions.span);
positions.finish();
}
else {
IndexMaskMemory memory;
const IndexMask bezier_curves_to_smooth = curves.indices_for_curve_type(
CURVE_TYPE_BEZIER, curves_to_smooth, memory);
/* Write the positions of the handles and the control points into a flat array.
* This will smooth the handle positions together with the control point positions, because the
* smoothing algorithm takes neighboring values to apply the gaussian smoothing to. */
Array<float3> all_positions = bke::curves::bezier::retrieve_all_positions(
curves, bezier_curves_to_smooth);
VArraySpan<float> influences(influence_by_point);
bezier_curves_to_smooth.foreach_index(
[&](const int curve) {
Vector<float3> orig_data;
const IndexRange points = points_by_curve[curve];
IndexMaskMemory memory;
const IndexMask selection_mask = IndexMask::from_bools(points, point_selection, memory);
if (selection_mask.is_empty()) {
return;
}
selection_mask.foreach_range([&](const IndexRange range) {
IndexRange positions_range(range.start() * 3, range.size() * 3);
/* Ignore the left handle of the first point and the right handle of the last point. */
if (!smooth_ends && !cyclic[curve]) {
positions_range = positions_range.drop_front(1).drop_back(1);
}
MutableSpan<float3> dst_data = all_positions.as_mutable_span().slice(positions_range);
orig_data.resize(dst_data.size());
orig_data.as_mutable_span().copy_from(dst_data);
/* The influence is mapped from handle+control point index to only control point index.
*/
Array<float> point_influences(positions_range.size());
if (!smooth_ends && !cyclic[curve]) {
threading::parallel_for(
positions_range.index_range(), 4096, [&](const IndexRange influences_range) {
for (const int index : influences_range) {
/* Account for the left handle of the first
* point being ignored. */
point_influences[index] = influences.slice(range)[(index + 1) / 3];
}
});
}
else {
threading::parallel_for(
positions_range.index_range(), 4096, [&](const IndexRange influences_range) {
for (const int index : influences_range) {
point_influences[index] = influences.slice(range)[index / 3];
}
});
}
gaussian_blur_1D(orig_data.as_span(),
iterations,
VArray<float>::from_span(point_influences.as_span()),
smooth_ends,
keep_shape,
cyclic[curve],
dst_data);
});
},
exec_mode::grain_size(512));
/* Copy the resulting values from the flat array back into the three position attributes for
* the left and right handles as well as the control points. */
bke::curves::bezier::write_all_positions(curves, bezier_curves_to_smooth, all_positions);
/* Smooth the other curve positions. */
const IndexMask other_curves_to_smooth = bezier_curves_to_smooth.complement(
curves.curves_range(), memory);
if (!other_curves_to_smooth.is_empty()) {
bke::GSpanAttributeWriter positions = attributes.lookup_for_write_span("position");
smooth_curve_attribute(other_curves_to_smooth,
points_by_curve,
point_selection,
cyclic,
iterations,
influence_by_point,
smooth_ends,
keep_shape,
positions.span);
positions.finish();
}
curves.calculate_bezier_auto_handles();
curves.calculate_bezier_aligned_handles();
}
curves.tag_positions_changed();
}
void smooth_curve_positions(bke::CurvesGeometry &curves,
const IndexMask &curves_to_smooth,
const VArray<bool> &point_selection,
const int iterations,
const float influence,
const bool smooth_ends,
const bool keep_shape)
{
smooth_curve_positions(curves,
curves_to_smooth,
point_selection,
iterations,
VArray<float>::from_single(influence, curves.points_num()),
smooth_ends,
keep_shape);
}
} // namespace blender::geometry

View File

@@ -0,0 +1,435 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_curves_utils.hh"
#include "BKE_deform.hh"
#include "BLI_array_utils.hh"
#include "BLI_task.hh"
#include "GEO_subdivide_curves.hh"
namespace blender::geometry {
static void calculate_result_offsets(const bke::CurvesGeometry &src_curves,
const IndexMask &selection,
const IndexMask &unselected,
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. */
const OffsetIndices src_points_by_curve = src_curves.points_by_curve();
offset_indices::copy_group_sizes(src_points_by_curve, unselected, dst_curve_offsets);
selection.foreach_index(
[&](const int curve_i) {
const IndexRange src_points = src_points_by_curve[curve_i];
const IndexRange src_segments = bke::curves::per_curve_point_offsets_range(src_points,
curve_i);
MutableSpan<int> point_offsets = dst_point_offsets.slice(src_segments);
MutableSpan<int> point_counts = point_offsets.drop_back(1);
if (src_points.size() == 1) {
point_counts.first() = 1;
}
else {
cuts.materialize_compressed(src_points, point_counts);
for (int &count : point_counts) {
/* Make sure there at least one cut, 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;
}
}
offset_indices::accumulate_counts_to_offsets(point_offsets);
dst_curve_offsets[curve_i] = point_offsets.last();
},
exec_mode::grain_size(1024));
offset_indices::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] = bke::attribute_math::mix2(i * step, a, b);
}
}
template<typename T>
static void subdivide_attribute_linear(const OffsetIndices<int> src_points_by_curve,
const OffsetIndices<int> dst_points_by_curve,
const IndexMask &selection,
const Span<int> all_point_offsets,
const Span<T> src,
MutableSpan<T> dst)
{
selection.foreach_index(
[&](const int curve_i) {
const IndexRange src_points = src_points_by_curve[curve_i];
const IndexRange src_segments = bke::curves::per_curve_point_offsets_range(src_points,
curve_i);
const OffsetIndices<int> curve_offsets = all_point_offsets.slice(src_segments);
const IndexRange dst_points = dst_points_by_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 = curve_offsets[i];
linear_interpolation(curve_src[i], curve_src[i + 1], curve_dst.slice(segment_points));
}
});
const IndexRange dst_last_segment = dst_points.slice(curve_offsets[src_points.size() - 1]);
linear_interpolation(curve_src.last(), curve_src.first(), dst.slice(dst_last_segment));
},
exec_mode::grain_size(512));
}
static void subdivide_attribute_linear(const OffsetIndices<int> src_points_by_curve,
const OffsetIndices<int> dst_points_by_curve,
const IndexMask &selection,
const Span<int> all_point_offsets,
const GSpan src,
GMutableSpan dst)
{
bke::attribute_math::to_static_type(dst.type(), [&]<typename T>() {
if constexpr (!std::is_same_v<T, std::string>) {
subdivide_attribute_linear(src_points_by_curve,
dst_points_by_curve,
selection,
all_point_offsets,
src.typed<T>(),
dst.typed<T>());
}
});
}
static void subdivide_attribute_catmull_rom(const OffsetIndices<int> src_points_by_curve,
const OffsetIndices<int> dst_points_by_curve,
const IndexMask &selection,
const Span<int> all_point_offsets,
const Span<bool> cyclic,
const GSpan src,
GMutableSpan dst)
{
selection.foreach_index(
[&](const int curve_i) {
const IndexRange src_points = src_points_by_curve[curve_i];
const IndexRange src_segments = bke::curves::per_curve_point_offsets_range(src_points,
curve_i);
const IndexRange dst_points = dst_points_by_curve[curve_i];
bke::curves::catmull_rom::interpolate_to_evaluated(src.slice(src_points),
cyclic[curve_i],
all_point_offsets.slice(src_segments),
dst.slice(dst_points));
},
exec_mode::grain_size(512));
}
static HandleType aligned_or_free_handle_type(const HandleType type)
{
switch (type) {
case BEZIER_HANDLE_FREE:
return BEZIER_HANDLE_FREE;
case BEZIER_HANDLE_AUTO:
return BEZIER_HANDLE_ALIGN;
case BEZIER_HANDLE_VECTOR:
return BEZIER_HANDLE_FREE;
case BEZIER_HANDLE_ALIGN:
return BEZIER_HANDLE_ALIGN;
}
BLI_assert_unreachable();
return BEZIER_HANDLE_FREE;
}
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,
const int dst_next_segment_start,
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)
{
if (bke::curves::bezier::segment_is_vector(type_prev, type_next)) {
linear_interpolation(position_prev, position_next, dst_positions.slice(segment_points));
/* All of the segment handles should be vector handles. */
dst_types_r[segment_points.first()] = BEZIER_HANDLE_VECTOR;
dst_types_l[dst_next_segment_start] = BEZIER_HANDLE_VECTOR;
dst_types_l.slice(segment_points.drop_front(1)).fill(BEZIER_HANDLE_VECTOR);
dst_types_r.slice(segment_points.drop_front(1)).fill(BEZIER_HANDLE_VECTOR);
}
else {
/* The first point in the segment is always copied. */
dst_positions[segment_points.first()] = position_prev;
/* 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. */
dst_handles_r[segment_points.last()] = segment_handle_prev;
dst_handles_l[dst_next_segment_start] = segment_handle_next;
/* First and last handles at the ends of the segment are aligned if possible. */
dst_types_r[segment_points.first()] = aligned_or_free_handle_type(type_prev);
dst_types_l[dst_next_segment_start] = aligned_or_free_handle_type(type_next);
/* Handles inside the segment are aligned. */
dst_types_l.slice(segment_points.drop_front(1)).fill(BEZIER_HANDLE_ALIGN);
dst_types_r.slice(segment_points.drop_front(1)).fill(BEZIER_HANDLE_ALIGN);
}
}
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 OffsetIndices<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 = 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,
segment.one_after_last(),
dst_positions,
dst_handles_l,
dst_handles_r,
dst_types_l,
dst_types_r);
}
});
if (cyclic) {
const int last_index = src_positions.index_range().last();
const IndexRange segment = evaluated_offsets[last_index];
subdivide_bezier_segment(src_positions.last(),
src_handles_r.last(),
src_handles_l.first(),
src_positions.first(),
HandleType(src_types_r.last()),
HandleType(src_types_l.first()),
segment,
0,
dst_positions,
dst_handles_l,
dst_handles_r,
dst_types_l,
dst_types_r);
}
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 bke::AttributeFilter &attribute_filter)
{
if (src_curves.is_empty()) {
return src_curves;
}
const OffsetIndices src_points_by_curve = src_curves.points_by_curve();
/* Cyclic is accessed a lot, it's probably worth it to make sure it's a span. */
const VArraySpan<bool> cyclic{src_curves.cyclic()};
IndexMaskMemory memory;
const IndexMask unselected = selection.complement(src_curves.curves_range(), memory);
bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves);
/* Copy vertex groups from source curves to allow copying vertex group attributes. */
BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &src_curves.vertex_group_names);
/* 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> all_point_offset_data(src_curves.points_num() + src_curves.curves_num());
#ifndef NDEBUG
all_point_offset_data.fill(-1);
#endif
calculate_result_offsets(src_curves,
selection,
unselected,
cuts,
cyclic,
dst_curves.offsets_for_write(),
all_point_offset_data);
const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve();
const Span<int> all_point_offsets(all_point_offset_data);
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();
Vector<bke::AttributeTransferData> attributes_to_transfer =
bke::retrieve_attributes_for_transfer(
src_attributes, dst_attributes, {bke::AttrDomain::Point}, attribute_filter);
auto subdivide_catmull_rom = [&](const IndexMask &selection) {
for (auto &attribute : attributes_to_transfer) {
subdivide_attribute_catmull_rom(src_points_by_curve,
dst_points_by_curve,
selection,
all_point_offsets,
cyclic,
attribute.src,
attribute.dst.span);
}
};
auto subdivide_poly = [&](const IndexMask &selection) {
for (auto &attribute : attributes_to_transfer) {
subdivide_attribute_linear(src_points_by_curve,
dst_points_by_curve,
selection,
all_point_offsets,
attribute.src,
attribute.dst.span);
}
};
auto subdivide_bezier = [&](const 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();
const OffsetIndices<int> dst_points_by_curve = dst_curves.points_by_curve();
selection.foreach_index(
[&](const int curve_i) {
const IndexRange src_points = src_points_by_curve[curve_i];
const IndexRange src_segments = bke::curves::per_curve_point_offsets_range(src_points,
curve_i);
const IndexRange dst_points = dst_points_by_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),
all_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));
},
exec_mode::grain_size(512));
/* Filter out positions and handles that are already interpolated. */
const Set<StringRef> attributes_to_skip = {
"position", "handle_type_left", "handle_type_right", "handle_right", "handle_left"};
for (auto &attribute : attributes_to_transfer) {
if (attributes_to_skip.contains(attribute.name)) {
continue;
}
subdivide_attribute_linear(src_points_by_curve,
dst_points_by_curve,
selection,
all_point_offsets,
attribute.src,
attribute.dst.span);
}
};
/* 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);
for (auto &attribute : attributes_to_transfer) {
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, unselected, attribute.src, attribute.dst.span);
attribute.dst.finish();
}
bke::curves::nurbs::copy_custom_knots(src_curves, selection, dst_curves);
return dst_curves;
}
} // namespace blender::geometry

View File

@@ -0,0 +1,319 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#ifdef WITH_OPENVDB
# include <openvdb/openvdb.h>
#endif
#include "GEO_transform.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_matrix.hh"
#include "BLI_math_vector.hh"
#include "BLI_task.hh"
#include "DNA_grease_pencil_types.h"
#include "DNA_mesh_types.h"
#include "DNA_pointcloud_types.h"
#include "BKE_attribute.hh"
#include "BKE_curves.hh"
#include "BKE_geometry_nodes_gizmos_transforms.hh"
#include "BKE_geometry_set.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_instances.hh"
#include "BKE_mesh.hh"
#include "BKE_pointcloud.hh"
#include "BKE_volume.hh"
namespace blender::geometry {
static void translate_positions(MutableSpan<float3> positions, const float3 &translation)
{
threading::parallel_for(positions.index_range(), 2048, [&](const IndexRange range) {
for (float3 &position : positions.slice(range)) {
position += translation;
}
});
}
static void translate_pointcloud(PointCloud &pointcloud, const float3 translation)
{
if (math::is_zero(translation)) {
return;
}
std::optional<Bounds<float3>> bounds;
if (pointcloud.runtime->bounds_cache.is_cached()) {
bounds = pointcloud.runtime->bounds_cache.data();
}
bke::MutableAttributeAccessor attributes = pointcloud.attributes_for_write();
bke::SpanAttributeWriter position = attributes.lookup_or_add_for_write_span<float3>(
"position", bke::AttrDomain::Point);
translate_positions(position.span, translation);
position.finish();
if (bounds) {
bounds->min += translation;
bounds->max += translation;
pointcloud.runtime->bounds_cache.ensure([&](Bounds<float3> &r_data) { r_data = *bounds; });
}
}
static void transform_pointcloud(PointCloud &pointcloud, const float4x4 &transform)
{
bke::MutableAttributeAccessor attributes = pointcloud.attributes_for_write();
bke::SpanAttributeWriter position = attributes.lookup_or_add_for_write_span<float3>(
"position", bke::AttrDomain::Point);
math::transform_points(transform, position.span);
position.finish();
}
static void translate_greasepencil(GreasePencil &grease_pencil, const float3 translation)
{
using namespace blender::bke::greasepencil;
for (const int layer_index : grease_pencil.layers().index_range()) {
Layer &layer = grease_pencil.layer(layer_index);
float4x4 local_transform = layer.local_transform();
local_transform.location() += translation;
layer.set_local_transform(local_transform);
}
}
static void transform_greasepencil(GreasePencil &grease_pencil, const float4x4 &transform)
{
using namespace blender::bke::greasepencil;
for (const int layer_index : grease_pencil.layers().index_range()) {
Layer &layer = grease_pencil.layer(layer_index);
float4x4 local_transform = layer.local_transform();
local_transform = transform * local_transform;
layer.set_local_transform(local_transform);
}
}
static void translate_instances(bke::Instances &instances, const float3 translation)
{
MutableSpan<float4x4> transforms = instances.transforms_for_write();
threading::parallel_for(transforms.index_range(), 1024, [&](const IndexRange range) {
for (float4x4 &instance_transform : transforms.slice(range)) {
add_v3_v3(instance_transform.ptr()[3], translation);
}
});
}
static void transform_instances(bke::Instances &instances, const float4x4 &transform)
{
MutableSpan<float4x4> transforms = instances.transforms_for_write();
threading::parallel_for(transforms.index_range(), 1024, [&](const IndexRange range) {
for (float4x4 &instance_transform : transforms.slice(range)) {
instance_transform = transform * instance_transform;
}
});
}
static void transform_volume(Volume &volume,
const float4x4 &transform,
TransformGeometryErrors &r_errors)
{
#ifdef WITH_OPENVDB
openvdb::Mat4s vdb_matrix;
memcpy(vdb_matrix.asPointer(), &transform, sizeof(float[4][4]));
openvdb::Mat4d vdb_matrix_d{vdb_matrix};
const int grids_num = BKE_volume_num_grids(&volume);
for (const int i : IndexRange(grids_num)) {
bke::VolumeGridData *volume_grid = BKE_volume_grid_get_for_write(&volume, i);
float4x4 grid_matrix = bke::volume_grid::get_transform_matrix(*volume_grid);
grid_matrix = transform * grid_matrix;
const float determinant = math::determinant(grid_matrix);
if (!BKE_volume_grid_determinant_valid(determinant)) {
r_errors.volume_too_small = true;
/* Clear the tree because it is too small. */
bke::volume_grid::clear_tree(*volume_grid);
if (determinant == 0) {
/* Reset rotation and scale. */
grid_matrix.x_axis() = float3(1, 0, 0);
grid_matrix.y_axis() = float3(0, 1, 0);
grid_matrix.z_axis() = float3(0, 0, 1);
}
else {
/* Keep rotation but reset scale. */
grid_matrix.x_axis() = math::normalize(grid_matrix.x_axis());
grid_matrix.y_axis() = math::normalize(grid_matrix.y_axis());
grid_matrix.z_axis() = math::normalize(grid_matrix.z_axis());
}
}
try {
bke::volume_grid::set_transform_matrix(*volume_grid, grid_matrix);
}
catch (...) {
r_errors.bad_volume_transform = true;
}
}
#else
UNUSED_VARS(volume, transform, r_errors);
#endif
}
static void translate_volume(Volume &volume, const float3 translation)
{
TransformGeometryErrors errors;
transform_volume(volume, math::from_location<float4x4>(translation), errors);
}
static void transform_curve_edit_hints(bke::CurvesEditHints &edit_hints, const float4x4 &transform)
{
if (const std::optional<MutableSpan<float3>> positions = edit_hints.positions_for_write()) {
math::transform_points(transform, *positions);
}
float3x3 deform_mat;
copy_m3_m4(deform_mat.ptr(), transform.ptr());
if (edit_hints.deform_mats.has_value()) {
MutableSpan<float3x3> deform_mats = *edit_hints.deform_mats;
threading::parallel_for(deform_mats.index_range(), 1024, [&](const IndexRange range) {
for (const int64_t i : range) {
deform_mats[i] = deform_mat * deform_mats[i];
}
});
}
else {
edit_hints.deform_mats.emplace(edit_hints.curves_id_orig.geometry.point_num, deform_mat);
}
}
static void transform_grease_pencil_edit_hints(bke::GreasePencilEditHints &edit_hints,
const float4x4 &transform)
{
if (!edit_hints.drawing_hints) {
return;
}
for (bke::GreasePencilDrawingEditHints &drawing_hints : *edit_hints.drawing_hints) {
if (const std::optional<MutableSpan<float3>> positions = drawing_hints.positions_for_write()) {
math::transform_points(transform, *positions);
}
float3x3 deform_mat = transform.view<3, 3>();
if (drawing_hints.deform_mats.has_value()) {
MutableSpan<float3x3> deform_mats = *drawing_hints.deform_mats;
threading::parallel_for(deform_mats.index_range(), 1024, [&](const IndexRange range) {
for (const int64_t i : range) {
deform_mats[i] = deform_mat * deform_mats[i];
}
});
}
else if (drawing_hints.drawing_orig) {
drawing_hints.deform_mats.emplace(drawing_hints.drawing_orig->strokes().points_num(),
deform_mat);
}
}
}
static void transform_gizmo_edit_hints(bke::GizmoEditHints &edit_hints, const float4x4 &transform)
{
for (float4x4 &m : edit_hints.gizmo_transforms.values()) {
m = transform * m;
}
}
static void translate_curve_edit_hints(bke::CurvesEditHints &edit_hints, const float3 &translation)
{
if (const std::optional<MutableSpan<float3>> positions = edit_hints.positions_for_write()) {
translate_positions(*positions, translation);
}
}
static void translate_gizmos_edit_hints(bke::GizmoEditHints &edit_hints, const float3 &translation)
{
for (float4x4 &m : edit_hints.gizmo_transforms.values()) {
m.location() += translation;
}
}
void translate_geometry(bke::GeometrySet &geometry, const float3 translation)
{
if (math::is_zero(translation)) {
return;
}
if (Curves *curves = geometry.get_curves_for_write()) {
curves->geometry.wrap().translate(translation);
}
if (Mesh *mesh = geometry.get_mesh_for_write()) {
bke::mesh_translate(*mesh, translation, false);
}
if (PointCloud *pointcloud = geometry.get_pointcloud_for_write()) {
translate_pointcloud(*pointcloud, translation);
}
if (GreasePencil *grease_pencil = geometry.get_grease_pencil_for_write()) {
translate_greasepencil(*grease_pencil, translation);
}
if (Volume *volume = geometry.get_volume_for_write()) {
translate_volume(*volume, translation);
}
if (bke::Instances *instances = geometry.get_instances_for_write()) {
translate_instances(*instances, translation);
}
if (bke::CurvesEditHints *curve_edit_hints = geometry.get_curve_edit_hints_for_write()) {
translate_curve_edit_hints(*curve_edit_hints, translation);
}
if (bke::GizmoEditHints *gizmo_edit_hints = geometry.get_gizmo_edit_hints_for_write()) {
translate_gizmos_edit_hints(*gizmo_edit_hints, translation);
}
}
std::optional<TransformGeometryErrors> transform_geometry(bke::GeometrySet &geometry,
const float4x4 &transform)
{
if (transform == float4x4::identity()) {
return std::nullopt;
}
TransformGeometryErrors errors;
if (Curves *curves = geometry.get_curves_for_write()) {
curves->geometry.wrap().transform(transform);
}
if (Mesh *mesh = geometry.get_mesh_for_write()) {
bke::mesh_transform(*mesh, transform, false);
}
if (PointCloud *pointcloud = geometry.get_pointcloud_for_write()) {
transform_pointcloud(*pointcloud, transform);
}
if (GreasePencil *grease_pencil = geometry.get_grease_pencil_for_write()) {
transform_greasepencil(*grease_pencil, transform);
}
if (Volume *volume = geometry.get_volume_for_write()) {
transform_volume(*volume, transform, errors);
}
if (bke::Instances *instances = geometry.get_instances_for_write()) {
transform_instances(*instances, transform);
}
if (bke::CurvesEditHints *curve_edit_hints = geometry.get_curve_edit_hints_for_write()) {
transform_curve_edit_hints(*curve_edit_hints, transform);
}
if (bke::GreasePencilEditHints *grease_pencil_edit_hints =
geometry.get_grease_pencil_edit_hints_for_write())
{
transform_grease_pencil_edit_hints(*grease_pencil_edit_hints, transform);
}
if (bke::GizmoEditHints *gizmo_edit_hints = geometry.get_gizmo_edit_hints_for_write()) {
transform_gizmo_edit_hints(*gizmo_edit_hints, transform);
}
if (errors.volume_too_small) {
return errors;
}
return std::nullopt;
}
void transform_mesh(Mesh &mesh,
const float3 translation,
const math::Quaternion rotation,
const float3 scale)
{
const float4x4 matrix = math::from_loc_rot_scale<float4x4>(translation, rotation, scale);
bke::mesh_transform(mesh, matrix, false);
}
} // namespace blender::geometry

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#ifdef WITH_OPENVDB
# include "BKE_volume_grid.hh"
# include "GEO_volume_grid_resample.hh"
# include <openvdb/openvdb.h>
# include <openvdb/tools/GridTransformer.h>
namespace blender::geometry {
openvdb::FloatGrid &resample_sdf_grid_if_necessary(bke::VolumeGrid<float> &volume_grid,
bke::VolumeTreeAccessToken &tree_token,
const openvdb::math::Transform &transform,
std::shared_ptr<openvdb::FloatGrid> &storage)
{
const openvdb::FloatGrid &grid = volume_grid.grid(tree_token);
if (grid.transform() == transform) {
return volume_grid.grid_for_write(tree_token);
}
storage = openvdb::FloatGrid::create();
storage->setTransform(transform.copy());
/* TODO: Using #doResampleToMatch when the transform is affine and non-scaled may be faster. */
openvdb::tools::resampleToMatch<openvdb::tools::BoxSampler>(grid, *storage);
/* Ensure valid background value for level set grids, otherwise pruning will throw an exception.
*/
if (storage->background() < 0.0f) {
storage->tree().root().setBackground(0.0f, true);
}
openvdb::tools::pruneLevelSet(storage->tree());
return *storage;
}
} // namespace blender::geometry
#endif