Files
test/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
Hans Goudey 9785f2631e Mesh: Add flag to store presence of overlapping topology
For improving performance in the triangulate node (#112264), it's
helpful to know whether the mesh has edges or faces that overlap
each other. If that is known to be false, the node can skip edge de-
duplication.This might apply to the future "Replace Faces" node too.

This information is stored as a flag on the mesh and set in various
places that create "clean" new meshes. It isn't calculated lazily unlike
other other areas, because the point is to improve performance, and
the calculation probably isn't faster than the duplication check it's
meant to replace.

Pull Request: https://projects.blender.org/blender/blender/pulls/113205
2023-11-15 14:02:48 +01:00

972 lines
39 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array.hh"
#include "BLI_array_utils.hh"
#include "BLI_math_matrix.hh"
#include "BLI_math_rotation.h"
#include "BLI_set.hh"
#include "BLI_task.hh"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
#include "BKE_material.h"
#include "BKE_mesh.hh"
#include "BKE_curve_to_mesh.hh"
namespace blender::bke {
static int segments_num_no_duplicate_edge(const int points_num, const bool cyclic)
{
if (points_num <= 2) {
return curves::segments_num(points_num, false);
}
return curves::segments_num(points_num, cyclic);
}
static void fill_mesh_topology(const int vert_offset,
const int edge_offset,
const int face_offset,
const int loop_offset,
const int main_point_num,
const int profile_point_num,
const bool main_cyclic,
const bool profile_cyclic,
const bool fill_caps,
MutableSpan<int2> edges,
MutableSpan<int> corner_verts,
MutableSpan<int> corner_edges,
MutableSpan<int> face_offsets)
{
const int main_segment_num = segments_num_no_duplicate_edge(main_point_num, main_cyclic);
const int profile_segment_num = curves::segments_num(profile_point_num, profile_cyclic);
if (profile_point_num == 1) {
for (const int i : IndexRange(main_point_num - 1)) {
int2 &edge = edges[edge_offset + i];
edge[0] = vert_offset + i;
edge[1] = vert_offset + i + 1;
}
if (main_cyclic && main_segment_num > 2) {
int2 &edge = edges[edge_offset + main_segment_num - 1];
edge[0] = vert_offset + main_point_num - 1;
edge[1] = vert_offset;
}
return;
}
/* Add the edges running along the length of the curve, starting at each profile vertex. */
const int main_edges_start = edge_offset;
for (const int i_profile : IndexRange(profile_point_num)) {
const int profile_edge_offset = main_edges_start + i_profile * main_segment_num;
for (const int i_ring : IndexRange(main_segment_num)) {
const int i_next_ring = (i_ring == main_point_num - 1) ? 0 : i_ring + 1;
const int ring_vert_offset = vert_offset + profile_point_num * i_ring;
const int next_ring_vert_offset = vert_offset + profile_point_num * i_next_ring;
int2 &edge = edges[profile_edge_offset + i_ring];
edge[0] = ring_vert_offset + i_profile;
edge[1] = next_ring_vert_offset + i_profile;
}
}
/* Add the edges running along each profile ring. */
const int profile_edges_start = main_edges_start + profile_point_num * main_segment_num;
for (const int i_ring : IndexRange(main_point_num)) {
const int ring_vert_offset = vert_offset + profile_point_num * i_ring;
const int ring_edge_offset = profile_edges_start + i_ring * profile_segment_num;
for (const int i_profile : IndexRange(profile_segment_num)) {
const int i_next_profile = (i_profile == profile_point_num - 1) ? 0 : i_profile + 1;
int2 &edge = edges[ring_edge_offset + i_profile];
edge[0] = ring_vert_offset + i_profile;
edge[1] = ring_vert_offset + i_next_profile;
}
}
/* Calculate face and corner indices. */
for (const int i_ring : IndexRange(main_segment_num)) {
const int i_next_ring = (i_ring == main_point_num - 1) ? 0 : i_ring + 1;
const int ring_vert_offset = vert_offset + profile_point_num * i_ring;
const int next_ring_vert_offset = vert_offset + profile_point_num * i_next_ring;
const int ring_edge_start = profile_edges_start + profile_segment_num * i_ring;
const int next_ring_edge_offset = profile_edges_start + profile_segment_num * i_next_ring;
const int ring_face_offset = face_offset + i_ring * profile_segment_num;
const int ring_loop_offset = loop_offset + i_ring * profile_segment_num * 4;
for (const int i_profile : IndexRange(profile_segment_num)) {
const int ring_segment_loop_offset = ring_loop_offset + i_profile * 4;
const int i_next_profile = (i_profile == profile_point_num - 1) ? 0 : i_profile + 1;
const int main_edge_start = main_edges_start + main_segment_num * i_profile;
const int next_main_edge_start = main_edges_start + main_segment_num * i_next_profile;
face_offsets[ring_face_offset + i_profile] = ring_segment_loop_offset;
corner_verts[ring_segment_loop_offset] = ring_vert_offset + i_profile;
corner_edges[ring_segment_loop_offset] = ring_edge_start + i_profile;
corner_verts[ring_segment_loop_offset + 1] = ring_vert_offset + i_next_profile;
corner_edges[ring_segment_loop_offset + 1] = next_main_edge_start + i_ring;
corner_verts[ring_segment_loop_offset + 2] = next_ring_vert_offset + i_next_profile;
corner_edges[ring_segment_loop_offset + 2] = next_ring_edge_offset + i_profile;
corner_verts[ring_segment_loop_offset + 3] = next_ring_vert_offset + i_profile;
corner_edges[ring_segment_loop_offset + 3] = main_edge_start + i_ring;
}
}
const bool has_caps = fill_caps && !main_cyclic && profile_cyclic && profile_point_num > 2;
if (has_caps) {
const int face_num = main_segment_num * profile_segment_num;
const int cap_loop_offset = loop_offset + face_num * 4;
const int cap_face_offset = face_offset + face_num;
face_offsets[cap_face_offset] = cap_loop_offset;
face_offsets[cap_face_offset + 1] = cap_loop_offset + profile_segment_num;
const int last_ring_index = main_point_num - 1;
const int last_ring_vert_offset = vert_offset + profile_point_num * last_ring_index;
const int last_ring_edge_offset = profile_edges_start + profile_segment_num * last_ring_index;
for (const int i : IndexRange(profile_segment_num)) {
const int i_inv = profile_segment_num - i - 1;
corner_verts[cap_loop_offset + i] = vert_offset + i_inv;
corner_edges[cap_loop_offset + i] = profile_edges_start + ((i == (profile_segment_num - 1)) ?
(profile_segment_num - 1) :
(i_inv - 1));
corner_verts[cap_loop_offset + profile_segment_num + i] = last_ring_vert_offset + i;
corner_edges[cap_loop_offset + profile_segment_num + i] = last_ring_edge_offset + i;
}
}
}
/** Set the sharp status for edges that correspond to control points with vector handles. */
static void mark_bezier_vector_edges_sharp(const int profile_point_num,
const int main_segment_num,
const Span<int> control_point_offsets,
const Span<int8_t> handle_types_left,
const Span<int8_t> handle_types_right,
MutableSpan<bool> sharp_edges)
{
const int main_edges_start = 0;
if (curves::bezier::point_is_sharp(handle_types_left, handle_types_right, 0)) {
sharp_edges.slice(main_edges_start, main_segment_num).fill(true);
}
for (const int i : IndexRange(profile_point_num).drop_front(1)) {
if (curves::bezier::point_is_sharp(handle_types_left, handle_types_right, i)) {
const int offset = main_edges_start + main_segment_num * control_point_offsets[i];
sharp_edges.slice(offset, main_segment_num).fill(true);
}
}
}
static void fill_mesh_positions(const int main_point_num,
const int profile_point_num,
const Span<float3> main_positions,
const Span<float3> profile_positions,
const Span<float3> tangents,
const Span<float3> normals,
const Span<float> radii,
MutableSpan<float3> mesh_positions)
{
if (profile_point_num == 1) {
for (const int i_ring : IndexRange(main_point_num)) {
float4x4 point_matrix = math::from_orthonormal_axes<float4x4>(
main_positions[i_ring], normals[i_ring], tangents[i_ring]);
if (!radii.is_empty()) {
point_matrix = math::scale(point_matrix, float3(radii[i_ring]));
}
mesh_positions[i_ring] = math::transform_point(point_matrix, profile_positions.first());
}
}
else {
for (const int i_ring : IndexRange(main_point_num)) {
float4x4 point_matrix = math::from_orthonormal_axes<float4x4>(
main_positions[i_ring], normals[i_ring], tangents[i_ring]);
if (!radii.is_empty()) {
point_matrix = math::scale(point_matrix, float3(radii[i_ring]));
}
const int ring_vert_start = i_ring * profile_point_num;
for (const int i_profile : IndexRange(profile_point_num)) {
mesh_positions[ring_vert_start + i_profile] = math::transform_point(
point_matrix, profile_positions[i_profile]);
}
}
}
}
struct CurvesInfo {
const CurvesGeometry &main;
const CurvesGeometry &profile;
/* Make sure these are spans because they are potentially accessed many times. */
VArraySpan<bool> main_cyclic;
VArraySpan<bool> profile_cyclic;
};
static CurvesInfo get_curves_info(const CurvesGeometry &main, const CurvesGeometry &profile)
{
return {main, profile, main.cyclic(), profile.cyclic()};
}
static bool offsets_contain_single_point(const OffsetIndices<int> offsets)
{
for (const int64_t i : offsets.index_range()) {
if (offsets[i].size() == 1) {
return true;
}
}
return false;
}
struct ResultOffsets {
/** The total number of curve combinations. */
int total;
/** Offsets into the result mesh for each combination. */
Array<int> vert;
Array<int> edge;
Array<int> loop;
Array<int> face;
/* The indices of the main and profile curves that form each combination. */
Array<int> main_indices;
Array<int> profile_indices;
/** Whether any curve in the profile or curve input has only a single evaluated point. */
bool any_single_point_main;
bool any_single_point_profile;
};
static ResultOffsets calculate_result_offsets(const CurvesInfo &info, const bool fill_caps)
{
ResultOffsets result;
result.total = info.main.curves_num() * info.profile.curves_num();
const OffsetIndices<int> main_offsets = info.main.evaluated_points_by_curve();
const OffsetIndices<int> profile_offsets = info.profile.evaluated_points_by_curve();
threading::parallel_invoke(
result.total > 1024,
[&]() {
result.vert.reinitialize(result.total + 1);
result.edge.reinitialize(result.total + 1);
result.loop.reinitialize(result.total + 1);
result.face.reinitialize(result.total + 1);
int mesh_index = 0;
int vert_offset = 0;
int edge_offset = 0;
int loop_offset = 0;
int face_offset = 0;
for (const int i_main : main_offsets.index_range()) {
const bool main_cyclic = info.main_cyclic[i_main];
const int main_point_num = main_offsets[i_main].size();
const int main_segment_num = segments_num_no_duplicate_edge(main_point_num, main_cyclic);
for (const int i_profile : profile_offsets.index_range()) {
result.vert[mesh_index] = vert_offset;
result.edge[mesh_index] = edge_offset;
result.loop[mesh_index] = loop_offset;
result.face[mesh_index] = face_offset;
const bool profile_cyclic = info.profile_cyclic[i_profile];
const int profile_point_num = profile_offsets[i_profile].size();
const int profile_segment_num = curves::segments_num(profile_point_num,
profile_cyclic);
const bool has_caps = fill_caps && !main_cyclic && profile_cyclic &&
profile_point_num > 2;
const int tube_face_num = main_segment_num * profile_segment_num;
vert_offset += main_point_num * profile_point_num;
/* Add the ring edges, with one ring for every curve vertex, and the edge loops
* that run along the length of the curve, starting on the first profile. */
edge_offset += main_point_num * profile_segment_num +
main_segment_num * profile_point_num;
/* Add two cap N-gons for every ending. */
face_offset += tube_face_num + (has_caps ? 2 : 0);
/* All faces on the tube are quads, and all cap faces are N-gons with an edge for each
* profile edge. */
loop_offset += tube_face_num * 4 + (has_caps ? profile_segment_num * 2 : 0);
mesh_index++;
}
}
result.vert.last() = vert_offset;
result.edge.last() = edge_offset;
result.loop.last() = loop_offset;
result.face.last() = face_offset;
},
[&]() {
result.main_indices.reinitialize(result.total);
result.profile_indices.reinitialize(result.total);
int mesh_index = 0;
for (const int i_main : main_offsets.index_range()) {
for (const int i_profile : profile_offsets.index_range()) {
result.main_indices[mesh_index] = i_main;
result.profile_indices[mesh_index] = i_profile;
mesh_index++;
}
}
},
[&]() { result.any_single_point_main = offsets_contain_single_point(main_offsets); },
[&]() { result.any_single_point_profile = offsets_contain_single_point(profile_offsets); });
return result;
}
static eAttrDomain get_attribute_domain_for_mesh(const AttributeAccessor &mesh_attributes,
const AttributeIDRef &attribute_id)
{
/* Only use a different domain if it is builtin and must only exist on one domain. */
if (!mesh_attributes.is_builtin(attribute_id)) {
return ATTR_DOMAIN_POINT;
}
std::optional<AttributeMetaData> meta_data = mesh_attributes.lookup_meta_data(attribute_id);
if (!meta_data) {
return ATTR_DOMAIN_POINT;
}
return meta_data->domain;
}
static bool should_add_attribute_to_mesh(const AttributeAccessor &curve_attributes,
const AttributeAccessor &mesh_attributes,
const AttributeIDRef &id,
const AttributeMetaData &meta_data,
const AnonymousAttributePropagationInfo &propagation_info)
{
/* The position attribute has special non-generic evaluation. */
if (id.name() == "position") {
return false;
}
/* Don't propagate built-in curves attributes that are not built-in on meshes. */
if (curve_attributes.is_builtin(id) && !mesh_attributes.is_builtin(id)) {
return false;
}
if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) {
return false;
}
if (meta_data.data_type == CD_PROP_STRING) {
return false;
}
return true;
}
static GSpan evaluate_attribute(const GVArray &src,
const CurvesGeometry &curves,
Vector<std::byte> &buffer)
{
if (curves.is_single_type(CURVE_TYPE_POLY) && src.is_span()) {
return src.get_internal_span();
}
buffer.reinitialize(curves.evaluated_points_num() * src.type().size());
GMutableSpan eval{src.type(), buffer.data(), curves.evaluated_points_num()};
curves.interpolate_to_evaluated(src.get_internal_span(), eval);
return eval;
}
/** Information at a specific combination of main and profile curves. */
struct CombinationInfo {
int i_main;
int i_profile;
IndexRange main_points;
IndexRange profile_points;
bool main_cyclic;
bool profile_cyclic;
int main_segment_num;
int profile_segment_num;
IndexRange vert_range;
IndexRange edge_range;
IndexRange face_range;
IndexRange loop_range;
};
template<typename Fn>
static void foreach_curve_combination(const CurvesInfo &info,
const ResultOffsets &offsets,
const Fn &fn)
{
const OffsetIndices<int> main_offsets = info.main.evaluated_points_by_curve();
const OffsetIndices<int> profile_offsets = info.profile.evaluated_points_by_curve();
const OffsetIndices<int> vert_offsets(offsets.vert);
const OffsetIndices<int> edge_offsets(offsets.edge);
const OffsetIndices<int> face_offsets(offsets.face);
const OffsetIndices<int> loop_offsets(offsets.loop);
threading::parallel_for(IndexRange(offsets.total), 512, [&](IndexRange range) {
for (const int i : range) {
const int i_main = offsets.main_indices[i];
const int i_profile = offsets.profile_indices[i];
const IndexRange main_points = main_offsets[i_main];
const IndexRange profile_points = profile_offsets[i_profile];
const bool main_cyclic = info.main_cyclic[i_main];
const bool profile_cyclic = info.profile_cyclic[i_profile];
/* Pass all information in a struct to avoid repeating arguments in many lambdas.
* The idea is that inlining `fn` will help avoid accessing unnecessary information,
* though that may or may not happen in practice. */
fn(CombinationInfo{i_main,
i_profile,
main_points,
profile_points,
main_cyclic,
profile_cyclic,
curves::segments_num(main_points.size(), main_cyclic),
curves::segments_num(profile_points.size(), profile_cyclic),
vert_offsets[i],
edge_offsets[i],
face_offsets[i],
loop_offsets[i]});
}
});
}
static void build_mesh_positions(const CurvesInfo &curves_info,
const ResultOffsets &offsets,
Vector<std::byte> &eval_buffer,
Mesh &mesh)
{
BLI_assert(!mesh.attributes().contains("position"));
const Span<float3> profile_positions = curves_info.profile.evaluated_positions();
const bool ignore_profile_position = profile_positions.size() == 1 &&
math::is_equal(profile_positions.first(), float3(0.0f));
if (ignore_profile_position) {
if (mesh.totvert == curves_info.main.points_num()) {
const GAttributeReader src = curves_info.main.attributes().lookup("position");
if (src.sharing_info && src.varray.is_span()) {
const AttributeInitShared init(src.varray.get_internal_span().data(), *src.sharing_info);
if (mesh.attributes_for_write().add<float3>("position", ATTR_DOMAIN_POINT, init)) {
return;
}
}
}
}
const Span<float3> main_positions = curves_info.main.evaluated_positions();
mesh.attributes_for_write().add<float3>("position", ATTR_DOMAIN_POINT, AttributeInitConstruct());
MutableSpan<float3> positions = mesh.vert_positions_for_write();
if (ignore_profile_position) {
array_utils::copy(main_positions, positions);
return;
}
const Span<float3> tangents = curves_info.main.evaluated_tangents();
const Span<float3> normals = curves_info.main.evaluated_normals();
Span<float> radii_eval = {};
if (const GVArray radii = *curves_info.main.attributes().lookup("radius", ATTR_DOMAIN_POINT)) {
radii_eval = evaluate_attribute(radii, curves_info.main, eval_buffer).typed<float>();
}
foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
fill_mesh_positions(info.main_points.size(),
info.profile_points.size(),
main_positions.slice(info.main_points),
profile_positions.slice(info.profile_points),
tangents.slice(info.main_points),
normals.slice(info.main_points),
radii_eval.is_empty() ? radii_eval : radii_eval.slice(info.main_points),
positions.slice(info.vert_range));
});
}
template<typename T>
static void copy_main_point_data_to_mesh_verts(const Span<T> src,
const int profile_point_num,
MutableSpan<T> dst)
{
for (const int i_ring : src.index_range()) {
const int ring_vert_start = i_ring * profile_point_num;
dst.slice(ring_vert_start, profile_point_num).fill(src[i_ring]);
}
}
template<typename T>
static void copy_main_point_data_to_mesh_edges(const Span<T> src,
const int profile_point_num,
const int main_segment_num,
const int profile_segment_num,
MutableSpan<T> dst)
{
const int edges_start = profile_point_num * main_segment_num;
for (const int i_ring : src.index_range()) {
const int ring_edge_start = edges_start + profile_segment_num * i_ring;
dst.slice(ring_edge_start, profile_segment_num).fill(src[i_ring]);
}
}
template<typename T>
static void copy_main_point_data_to_mesh_faces(const Span<T> src,
const int main_segment_num,
const int profile_segment_num,
MutableSpan<T> dst)
{
for (const int i_ring : IndexRange(main_segment_num)) {
const int ring_face_start = profile_segment_num * i_ring;
dst.slice(ring_face_start, profile_segment_num).fill(src[i_ring]);
}
}
static bool try_sharing_point_data(const CurvesGeometry &main,
const AttributeIDRef &id,
const GAttributeReader &src,
MutableAttributeAccessor mesh_attributes)
{
if (mesh_attributes.domain_size(ATTR_DOMAIN_POINT) != main.points_num()) {
return false;
}
if (!src.sharing_info || !src.varray.is_span()) {
return false;
}
return mesh_attributes.add(
id,
ATTR_DOMAIN_POINT,
bke::cpp_type_to_custom_data_type(src.varray.type()),
AttributeInitShared(src.varray.get_internal_span().data(), *src.sharing_info));
}
static bool try_direct_evaluate_point_data(const CurvesGeometry &main,
const GAttributeReader &src,
GMutableSpan dst)
{
if (dst.size() != main.evaluated_points_num()) {
return false;
}
if (!src.varray.is_span()) {
return false;
}
main.interpolate_to_evaluated(src.varray.get_internal_span(), dst);
return true;
}
static void copy_main_point_domain_attribute_to_mesh(const CurvesInfo &curves_info,
const AttributeIDRef &id,
const ResultOffsets &offsets,
const eAttrDomain dst_domain,
const GAttributeReader &src_attribute,
Vector<std::byte> &eval_buffer,
MutableAttributeAccessor mesh_attributes)
{
if (dst_domain == ATTR_DOMAIN_POINT) {
if (try_sharing_point_data(curves_info.main, id, src_attribute, mesh_attributes)) {
return;
}
}
GSpanAttributeWriter dst_attribute = mesh_attributes.lookup_or_add_for_write_only_span(
id, dst_domain, bke::cpp_type_to_custom_data_type(src_attribute.varray.type()));
if (!dst_attribute) {
return;
}
if (dst_domain == ATTR_DOMAIN_POINT) {
if (try_direct_evaluate_point_data(curves_info.main, src_attribute, dst_attribute.span)) {
dst_attribute.finish();
return;
}
}
const GSpan src_all = evaluate_attribute(*src_attribute, curves_info.main, eval_buffer);
attribute_math::convert_to_static_type(src_attribute.varray.type(), [&](auto dummy) {
using T = decltype(dummy);
const Span<T> src = src_all.typed<T>();
MutableSpan<T> dst = dst_attribute.span.typed<T>();
switch (dst_domain) {
case ATTR_DOMAIN_POINT:
foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
copy_main_point_data_to_mesh_verts(
src.slice(info.main_points), info.profile_points.size(), dst.slice(info.vert_range));
});
break;
case ATTR_DOMAIN_EDGE:
foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
copy_main_point_data_to_mesh_edges(src.slice(info.main_points),
info.profile_points.size(),
info.main_segment_num,
info.profile_segment_num,
dst.slice(info.edge_range));
});
break;
case ATTR_DOMAIN_FACE:
foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
copy_main_point_data_to_mesh_faces(src.slice(info.main_points),
info.main_segment_num,
info.profile_segment_num,
dst.slice(info.face_range));
});
break;
case ATTR_DOMAIN_CORNER:
/* Unsupported for now, since there are no builtin attributes to convert into. */
break;
default:
BLI_assert_unreachable();
break;
}
});
dst_attribute.finish();
}
template<typename T>
static void copy_profile_point_data_to_mesh_verts(const Span<T> src,
const int main_point_num,
MutableSpan<T> dst)
{
for (const int i_ring : IndexRange(main_point_num)) {
const int profile_vert_start = i_ring * src.size();
for (const int i_profile : src.index_range()) {
dst[profile_vert_start + i_profile] = src[i_profile];
}
}
}
template<typename T>
static void copy_profile_point_data_to_mesh_edges(const Span<T> src,
const int main_segment_num,
MutableSpan<T> dst)
{
for (const int i_profile : src.index_range()) {
const int profile_edge_offset = i_profile * main_segment_num;
dst.slice(profile_edge_offset, main_segment_num).fill(src[i_profile]);
}
}
template<typename T>
static void copy_profile_point_data_to_mesh_faces(const Span<T> src,
const int main_segment_num,
const int profile_segment_num,
MutableSpan<T> dst)
{
for (const int i_ring : IndexRange(main_segment_num)) {
const int profile_face_start = i_ring * profile_segment_num;
for (const int i_profile : IndexRange(profile_segment_num)) {
dst[profile_face_start + i_profile] = src[i_profile];
}
}
}
static void copy_profile_point_domain_attribute_to_mesh(const CurvesInfo &curves_info,
const ResultOffsets &offsets,
const eAttrDomain dst_domain,
const GSpan src_all,
GMutableSpan dst_all)
{
attribute_math::convert_to_static_type(src_all.type(), [&](auto dummy) {
using T = decltype(dummy);
const Span<T> src = src_all.typed<T>();
MutableSpan<T> dst = dst_all.typed<T>();
switch (dst_domain) {
case ATTR_DOMAIN_POINT:
foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
copy_profile_point_data_to_mesh_verts(
src.slice(info.profile_points), info.main_points.size(), dst.slice(info.vert_range));
});
break;
case ATTR_DOMAIN_EDGE:
foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
copy_profile_point_data_to_mesh_edges(
src.slice(info.profile_points), info.main_segment_num, dst.slice(info.edge_range));
});
break;
case ATTR_DOMAIN_FACE:
foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
copy_profile_point_data_to_mesh_faces(src.slice(info.profile_points),
info.main_segment_num,
info.profile_segment_num,
dst.slice(info.face_range));
});
break;
case ATTR_DOMAIN_CORNER:
/* Unsupported for now, since there are no builtin attributes to convert into. */
break;
default:
BLI_assert_unreachable();
break;
}
});
}
template<typename T>
static void copy_indices_to_offset_ranges(const VArray<T> &src,
const Span<int> curve_indices,
const OffsetIndices<int> mesh_offsets,
MutableSpan<T> dst)
{
/* This unnecessarily instantiates the "is single" case (which should be handled elsewhere if
* it's ever used for attributes), but the alternative is duplicating the function for spans and
* other virtual arrays. */
devirtualize_varray(src, [&](const auto &src) {
threading::parallel_for(curve_indices.index_range(), 512, [&](IndexRange range) {
for (const int i : range) {
dst.slice(mesh_offsets[i]).fill(src[curve_indices[i]]);
}
});
});
}
static void copy_curve_domain_attribute_to_mesh(const ResultOffsets &mesh_offsets,
const Span<int> curve_indices,
const eAttrDomain dst_domain,
const GVArray &src,
GMutableSpan dst)
{
Span<int> offsets;
switch (dst_domain) {
case ATTR_DOMAIN_POINT:
offsets = mesh_offsets.vert;
break;
case ATTR_DOMAIN_EDGE:
offsets = mesh_offsets.edge;
break;
case ATTR_DOMAIN_FACE:
offsets = mesh_offsets.face;
break;
case ATTR_DOMAIN_CORNER:
offsets = mesh_offsets.loop;
break;
default:
BLI_assert_unreachable();
return;
}
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
copy_indices_to_offset_ranges(src.typed<T>(), curve_indices, offsets, dst.typed<T>());
});
}
static void write_sharp_bezier_edges(const CurvesInfo &curves_info,
const ResultOffsets &offsets,
MutableAttributeAccessor mesh_attributes,
SpanAttributeWriter<bool> &sharp_edges)
{
const CurvesGeometry &profile = curves_info.profile;
if (!profile.has_curve_with_type(CURVE_TYPE_BEZIER)) {
return;
}
const VArraySpan<int8_t> handle_types_left{profile.handle_types_left()};
const VArraySpan<int8_t> handle_types_right{profile.handle_types_right()};
if (!handle_types_left.contains(BEZIER_HANDLE_VECTOR) &&
!handle_types_right.contains(BEZIER_HANDLE_VECTOR))
{
return;
}
sharp_edges = mesh_attributes.lookup_or_add_for_write_span<bool>("sharp_edge", ATTR_DOMAIN_EDGE);
const OffsetIndices profile_points_by_curve = profile.points_by_curve();
const VArray<int8_t> types = profile.curve_types();
foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
if (types[info.i_profile] == CURVE_TYPE_BEZIER) {
const IndexRange points = profile_points_by_curve[info.i_profile];
mark_bezier_vector_edges_sharp(points.size(),
info.main_segment_num,
profile.bezier_evaluated_offsets_for_curve(info.i_profile),
handle_types_left.slice(points),
handle_types_right.slice(points),
sharp_edges.span.slice(info.edge_range));
}
});
}
Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
const CurvesGeometry &profile,
const bool fill_caps,
const AnonymousAttributePropagationInfo &propagation_info)
{
const CurvesInfo curves_info = get_curves_info(main, profile);
const ResultOffsets offsets = calculate_result_offsets(curves_info, fill_caps);
if (offsets.vert.last() == 0) {
return nullptr;
}
/* Add the position attribute later so it can be shared in some cases. */
Mesh *mesh = BKE_mesh_new_nomain(
0, offsets.edge.last(), offsets.face.last(), offsets.loop.last());
CustomData_free_layer_named(&mesh->vert_data, "position", 0);
mesh->totvert = offsets.vert.last();
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();
MutableAttributeAccessor mesh_attributes = mesh->attributes_for_write();
foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
fill_mesh_topology(info.vert_range.start(),
info.edge_range.start(),
info.face_range.start(),
info.loop_range.start(),
info.main_points.size(),
info.profile_points.size(),
info.main_cyclic,
info.profile_cyclic,
fill_caps,
edges,
corner_verts,
corner_edges,
face_offsets);
});
if (fill_caps) {
/* TODO: This is used to keep the tests passing after refactoring mesh shade smooth flags. It
* can be removed if the tests are updated and the final shading results will be the same. */
SpanAttributeWriter<bool> sharp_faces = mesh_attributes.lookup_or_add_for_write_span<bool>(
"sharp_face", ATTR_DOMAIN_FACE);
foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
const bool has_caps = fill_caps && !info.main_cyclic && info.profile_cyclic;
if (has_caps) {
const int face_num = info.main_segment_num * info.profile_segment_num;
const int cap_face_offset = info.face_range.start() + face_num;
sharp_faces.span[cap_face_offset] = true;
sharp_faces.span[cap_face_offset + 1] = true;
}
});
sharp_faces.finish();
}
Vector<std::byte> eval_buffer;
build_mesh_positions(curves_info, offsets, eval_buffer, *mesh);
mesh->tag_overlapping_none();
if (!offsets.any_single_point_main) {
/* If there are no single point curves, every combination will have at least loose edges. */
mesh->tag_loose_verts_none();
if (!offsets.any_single_point_profile) {
/* If there are no single point profiles, every combination will have faces. */
mesh->tag_loose_edges_none();
}
}
SpanAttributeWriter<bool> sharp_edges;
write_sharp_bezier_edges(curves_info, offsets, mesh_attributes, sharp_edges);
if (fill_caps) {
if (!sharp_edges) {
sharp_edges = mesh_attributes.lookup_or_add_for_write_span<bool>("sharp_edge",
ATTR_DOMAIN_EDGE);
}
foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
if (info.main_cyclic || !info.profile_cyclic) {
return;
}
const int main_edges_start = info.edge_range.start();
const int last_ring_index = info.main_points.size() - 1;
const int profile_edges_start = main_edges_start +
info.profile_points.size() * info.main_segment_num;
const int last_ring_edge_offset = profile_edges_start +
info.profile_segment_num * last_ring_index;
sharp_edges.span.slice(profile_edges_start, info.profile_segment_num).fill(true);
sharp_edges.span.slice(last_ring_edge_offset, info.profile_segment_num).fill(true);
});
}
sharp_edges.finish();
const AttributeAccessor main_attributes = main.attributes();
main_attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) {
if (!should_add_attribute_to_mesh(
main_attributes, mesh_attributes, id, meta_data, propagation_info))
{
return true;
}
const eAttrDomain src_domain = meta_data.domain;
const eCustomDataType type = meta_data.data_type;
const GAttributeReader src = main_attributes.lookup(id, src_domain, type);
const eAttrDomain dst_domain = get_attribute_domain_for_mesh(mesh_attributes, id);
if (src_domain == ATTR_DOMAIN_POINT) {
copy_main_point_domain_attribute_to_mesh(
curves_info, id, offsets, dst_domain, src, eval_buffer, mesh_attributes);
}
else if (src_domain == ATTR_DOMAIN_CURVE) {
GSpanAttributeWriter dst = mesh_attributes.lookup_or_add_for_write_only_span(
id, dst_domain, type);
if (dst) {
copy_curve_domain_attribute_to_mesh(
offsets, offsets.main_indices, dst_domain, *src, dst.span);
}
dst.finish();
}
return true;
});
const AttributeAccessor profile_attributes = profile.attributes();
profile_attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) {
if (main_attributes.contains(id)) {
return true;
}
if (!should_add_attribute_to_mesh(
profile_attributes, mesh_attributes, id, meta_data, propagation_info))
{
return true;
}
const eAttrDomain src_domain = meta_data.domain;
const eCustomDataType type = meta_data.data_type;
const GVArray src = *profile_attributes.lookup(id, src_domain, type);
const eAttrDomain dst_domain = get_attribute_domain_for_mesh(mesh_attributes, id);
GSpanAttributeWriter dst = mesh_attributes.lookup_or_add_for_write_only_span(
id, dst_domain, type);
if (!dst) {
return true;
}
if (src_domain == ATTR_DOMAIN_POINT) {
copy_profile_point_domain_attribute_to_mesh(curves_info,
offsets,
dst_domain,
evaluate_attribute(src, profile, eval_buffer),
dst.span);
}
else if (src_domain == ATTR_DOMAIN_CURVE) {
copy_curve_domain_attribute_to_mesh(
offsets, offsets.profile_indices, dst_domain, src, dst.span);
}
dst.finish();
return true;
});
return mesh;
}
static CurvesGeometry get_curve_single_vert()
{
CurvesGeometry curves(1, 1);
curves.offsets_for_write().last() = 1;
curves.positions_for_write().fill(float3(0.0f));
curves.fill_curve_types(CURVE_TYPE_POLY);
return curves;
}
Mesh *curve_to_wire_mesh(const CurvesGeometry &curve,
const AnonymousAttributePropagationInfo &propagation_info)
{
static const CurvesGeometry vert_curve = get_curve_single_vert();
return curve_to_mesh_sweep(curve, vert_curve, false, propagation_info);
}
} // namespace blender::bke