Add Chromium-only Blender WebEngine parity work
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
370
blender-5.2.0/source/blender/geometry/intern/extend_curves.cc
Normal file
370
blender-5.2.0/source/blender/geometry/intern/extend_curves.cc
Normal 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
|
||||
549
blender-5.2.0/source/blender/geometry/intern/extract_elements.cc
Normal file
549
blender-5.2.0/source/blender/geometry/intern/extract_elements.cc
Normal 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
|
||||
560
blender-5.2.0/source/blender/geometry/intern/fillet_curves.cc
Normal file
560
blender-5.2.0/source/blender/geometry/intern/fillet_curves.cc
Normal 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
|
||||
292
blender-5.2.0/source/blender/geometry/intern/fit_curves.cc
Normal file
292
blender-5.2.0/source/blender/geometry/intern/fit_curves.cc
Normal 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
|
||||
105
blender-5.2.0/source/blender/geometry/intern/foreach_geometry.cc
Normal file
105
blender-5.2.0/source/blender/geometry/intern/foreach_geometry.cc
Normal 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
|
||||
1357
blender-5.2.0/source/blender/geometry/intern/interpolate_curves.cc
Normal file
1357
blender-5.2.0/source/blender/geometry/intern/interpolate_curves.cc
Normal file
File diff suppressed because it is too large
Load Diff
278
blender-5.2.0/source/blender/geometry/intern/join_geometries.cc
Normal file
278
blender-5.2.0/source/blender/geometry/intern/join_geometries.cc
Normal 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
|
||||
329
blender-5.2.0/source/blender/geometry/intern/merge_curves.cc
Normal file
329
blender-5.2.0/source/blender/geometry/intern/merge_curves.cc
Normal 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
|
||||
157
blender-5.2.0/source/blender/geometry/intern/merge_layers.cc
Normal file
157
blender-5.2.0/source/blender/geometry/intern/merge_layers.cc
Normal 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
|
||||
7855
blender-5.2.0/source/blender/geometry/intern/mesh_bevel.cc
Normal file
7855
blender-5.2.0/source/blender/geometry/intern/mesh_bevel.cc
Normal file
File diff suppressed because it is too large
Load Diff
1268
blender-5.2.0/source/blender/geometry/intern/mesh_boolean.cc
Normal file
1268
blender-5.2.0/source/blender/geometry/intern/mesh_boolean.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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
|
||||
1950
blender-5.2.0/source/blender/geometry/intern/mesh_merge_verts.cc
Normal file
1950
blender-5.2.0/source/blender/geometry/intern/mesh_merge_verts.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
103
blender-5.2.0/source/blender/geometry/intern/mesh_selection.cc
Normal file
103
blender-5.2.0/source/blender/geometry/intern/mesh_selection.cc
Normal 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
|
||||
636
blender-5.2.0/source/blender/geometry/intern/mesh_split_edges.cc
Normal file
636
blender-5.2.0/source/blender/geometry/intern/mesh_split_edges.cc
Normal 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
|
||||
@@ -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
|
||||
226
blender-5.2.0/source/blender/geometry/intern/mesh_to_volume.cc
Normal file
226
blender-5.2.0/source/blender/geometry/intern/mesh_to_volume.cc
Normal 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
|
||||
726
blender-5.2.0/source/blender/geometry/intern/mesh_triangulate.cc
Normal file
726
blender-5.2.0/source/blender/geometry/intern/mesh_triangulate.cc
Normal 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
|
||||
314
blender-5.2.0/source/blender/geometry/intern/mix_geometries.cc
Normal file
314
blender-5.2.0/source/blender/geometry/intern/mix_geometries.cc
Normal 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
|
||||
123
blender-5.2.0/source/blender/geometry/intern/point_merge.cc
Normal file
123
blender-5.2.0/source/blender/geometry/intern/point_merge.cc
Normal 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
|
||||
115
blender-5.2.0/source/blender/geometry/intern/points_to_volume.cc
Normal file
115
blender-5.2.0/source/blender/geometry/intern/points_to_volume.cc
Normal 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
|
||||
329
blender-5.2.0/source/blender/geometry/intern/randomize.cc
Normal file
329
blender-5.2.0/source/blender/geometry/intern/randomize.cc
Normal 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
|
||||
3031
blender-5.2.0/source/blender/geometry/intern/realize_instances.cc
Normal file
3031
blender-5.2.0/source/blender/geometry/intern/realize_instances.cc
Normal file
File diff suppressed because it is too large
Load Diff
545
blender-5.2.0/source/blender/geometry/intern/reorder.cc
Normal file
545
blender-5.2.0/source/blender/geometry/intern/reorder.cc
Normal 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
|
||||
664
blender-5.2.0/source/blender/geometry/intern/resample_curves.cc
Normal file
664
blender-5.2.0/source/blender/geometry/intern/resample_curves.cc
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
808
blender-5.2.0/source/blender/geometry/intern/set_curve_type.cc
Normal file
808
blender-5.2.0/source/blender/geometry/intern/set_curve_type.cc
Normal 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
|
||||
161
blender-5.2.0/source/blender/geometry/intern/simplify_curves.cc
Normal file
161
blender-5.2.0/source/blender/geometry/intern/simplify_curves.cc
Normal 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
|
||||
383
blender-5.2.0/source/blender/geometry/intern/smooth_curves.cc
Normal file
383
blender-5.2.0/source/blender/geometry/intern/smooth_curves.cc
Normal 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
|
||||
435
blender-5.2.0/source/blender/geometry/intern/subdivide_curves.cc
Normal file
435
blender-5.2.0/source/blender/geometry/intern/subdivide_curves.cc
Normal 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
|
||||
319
blender-5.2.0/source/blender/geometry/intern/transform.cc
Normal file
319
blender-5.2.0/source/blender/geometry/intern/transform.cc
Normal 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
|
||||
1108
blender-5.2.0/source/blender/geometry/intern/trim_curves.cc
Normal file
1108
blender-5.2.0/source/blender/geometry/intern/trim_curves.cc
Normal file
File diff suppressed because it is too large
Load Diff
2453
blender-5.2.0/source/blender/geometry/intern/uv_pack.cc
Normal file
2453
blender-5.2.0/source/blender/geometry/intern/uv_pack.cc
Normal file
File diff suppressed because it is too large
Load Diff
5491
blender-5.2.0/source/blender/geometry/intern/uv_parametrizer.cc
Normal file
5491
blender-5.2.0/source/blender/geometry/intern/uv_parametrizer.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
Reference in New Issue
Block a user