This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
Jacques Lucke ae94e36cfb Geometry Nodes: refactor array devirtualization
Goals:
* Better high level control over where devirtualization occurs. There is always
  a trade-off between performance and compile-time/binary-size.
* Simplify using array devirtualization.
* Better performance for cases where devirtualization wasn't used before.

Many geometry nodes accept fields as inputs. Internally, that means that the
execution functions have to accept so called "virtual arrays" as inputs. Those
 can be e.g. actual arrays, just single values, or lazily computed arrays.
Due to these different possible virtual arrays implementations, access to
individual elements is slower than it would be if everything was just a normal
array (access does through a virtual function call). For more complex execution
functions, this overhead does not matter, but for small functions (like a simple
addition) it very much does. The virtual function call also prevents the compiler
from doing some optimizations (e.g. loop unrolling and inserting simd instructions).

The solution is to "devirtualize" the virtual arrays for small functions where the
overhead is measurable. Essentially, the function is generated many times with
different array types as input. Then there is a run-time dispatch that calls the
best implementation. We have been doing devirtualization in e.g. math nodes
for a long time already. This patch just generalizes the concept and makes it
easier to control. It also makes it easier to investigate the different trade-offs
when it comes to devirtualization.

Nodes that we've optimized using devirtualization before didn't get a speedup.
However, a couple of nodes are using devirtualization now, that didn't before.
Those got a 2-4x speedup in common cases.
* Map Range
* Random Value
* Switch
* Combine XYZ

Differential Revision: https://developer.blender.org/D14628
2022-04-26 17:12:34 +02:00

814 lines
32 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array.hh"
#include "BLI_devirtualize_parameters.hh"
#include "BLI_set.hh"
#include "BLI_task.hh"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "BKE_attribute_access.hh"
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
#include "BKE_material.h"
#include "BKE_mesh.h"
#include "BKE_curve_to_mesh.hh"
namespace blender::bke {
static void mark_edges_sharp(MutableSpan<MEdge> edges)
{
for (MEdge &edge : edges) {
edge.flag |= ME_SHARP;
}
}
static void fill_mesh_topology(const int vert_offset,
const int edge_offset,
const int poly_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<MEdge> edges,
MutableSpan<MLoop> loops,
MutableSpan<MPoly> polys)
{
const int main_segment_num = curves::curve_segment_size(main_point_num, main_cyclic);
const int profile_segment_num = curves::curve_segment_size(profile_point_num, profile_cyclic);
if (profile_point_num == 1) {
for (const int i : IndexRange(main_point_num - 1)) {
MEdge &edge = edges[edge_offset + i];
edge.v1 = vert_offset + i;
edge.v2 = vert_offset + i + 1;
edge.flag = ME_LOOSEEDGE;
}
if (main_cyclic && main_segment_num > 1) {
MEdge &edge = edges[edge_offset + main_segment_num - 1];
edge.v1 = vert_offset + main_point_num - 1;
edge.v2 = vert_offset;
edge.flag = ME_LOOSEEDGE;
}
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;
MEdge &edge = edges[profile_edge_offset + i_ring];
edge.v1 = ring_vert_offset + i_profile;
edge.v2 = next_ring_vert_offset + i_profile;
edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
}
}
/* 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;
MEdge &edge = edges[ring_edge_offset + i_profile];
edge.v1 = ring_vert_offset + i_profile;
edge.v2 = ring_vert_offset + i_next_profile;
edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
}
}
/* Calculate poly 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_poly_offset = poly_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;
MPoly &poly = polys[ring_poly_offset + i_profile];
poly.loopstart = ring_segment_loop_offset;
poly.totloop = 4;
poly.flag = ME_SMOOTH;
MLoop &loop_a = loops[ring_segment_loop_offset];
loop_a.v = ring_vert_offset + i_profile;
loop_a.e = ring_edge_start + i_profile;
MLoop &loop_b = loops[ring_segment_loop_offset + 1];
loop_b.v = ring_vert_offset + i_next_profile;
loop_b.e = next_main_edge_start + i_ring;
MLoop &loop_c = loops[ring_segment_loop_offset + 2];
loop_c.v = next_ring_vert_offset + i_next_profile;
loop_c.e = next_ring_edge_offset + i_profile;
MLoop &loop_d = loops[ring_segment_loop_offset + 3];
loop_d.v = next_ring_vert_offset + i_profile;
loop_d.e = main_edge_start + i_ring;
}
}
const bool has_caps = fill_caps && !main_cyclic && profile_cyclic;
if (has_caps) {
const int poly_size = main_segment_num * profile_segment_num;
const int cap_loop_offset = loop_offset + poly_size * 4;
const int cap_poly_offset = poly_offset + poly_size;
MPoly &poly_start = polys[cap_poly_offset];
poly_start.loopstart = cap_loop_offset;
poly_start.totloop = profile_segment_num;
MPoly &poly_end = polys[cap_poly_offset + 1];
poly_end.loopstart = cap_loop_offset + profile_segment_num;
poly_end.totloop = 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;
MLoop &loop_start = loops[cap_loop_offset + i];
loop_start.v = vert_offset + i_inv;
loop_start.e = profile_edges_start +
((i == (profile_segment_num - 1)) ? (profile_segment_num - 1) : (i_inv - 1));
MLoop &loop_end = loops[cap_loop_offset + profile_segment_num + i];
loop_end.v = last_ring_vert_offset + i;
loop_end.e = last_ring_edge_offset + i;
}
mark_edges_sharp(edges.slice(profile_edges_start, profile_segment_num));
mark_edges_sharp(edges.slice(last_ring_edge_offset, profile_segment_num));
}
}
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<MEdge> edges)
{
const int main_edges_start = 0;
if (curves::bezier::point_is_sharp(handle_types_left, handle_types_right, 0)) {
mark_edges_sharp(edges.slice(main_edges_start, main_segment_num));
}
for (const int i : IndexRange(profile_point_num).drop_front(1)) {
if (curves::bezier::point_is_sharp(handle_types_left, handle_types_right, i)) {
mark_edges_sharp(edges.slice(
main_edges_start + main_segment_num * control_point_offsets[i - 1], main_segment_num));
}
}
}
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<MVert> mesh_positions)
{
if (profile_point_num == 1) {
for (const int i_ring : IndexRange(main_point_num)) {
float4x4 point_matrix = float4x4::from_normalized_axis_data(
main_positions[i_ring], normals[i_ring], tangents[i_ring]);
if (!radii.is_empty()) {
point_matrix.apply_scale(radii[i_ring]);
}
MVert &vert = mesh_positions[i_ring];
copy_v3_v3(vert.co, point_matrix * profile_positions.first());
}
}
else {
for (const int i_ring : IndexRange(main_point_num)) {
float4x4 point_matrix = float4x4::from_normalized_axis_data(
main_positions[i_ring], normals[i_ring], tangents[i_ring]);
if (!radii.is_empty()) {
point_matrix.apply_scale(radii[i_ring]);
}
const int ring_vert_start = i_ring * profile_point_num;
for (const int i_profile : IndexRange(profile_point_num)) {
MVert &vert = mesh_positions[ring_vert_start + i_profile];
copy_v3_v3(vert.co, 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. */
VArray_Span<bool> main_cyclic;
VArray_Span<bool> profile_cyclic;
};
static CurvesInfo get_curves_info(const CurvesGeometry &main, const CurvesGeometry &profile)
{
return {main, profile, main.cyclic(), profile.cyclic()};
}
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> poly;
/* The indices of the main and profile curves that form each combination. */
Array<int> main_indices;
Array<int> profile_indices;
};
static ResultOffsets calculate_result_offsets(const CurvesInfo &info, const bool fill_caps)
{
ResultOffsets result;
result.total = info.main.curves_num() * info.profile.curves_num();
result.vert.reinitialize(result.total + 1);
result.edge.reinitialize(result.total + 1);
result.loop.reinitialize(result.total + 1);
result.poly.reinitialize(result.total + 1);
result.main_indices.reinitialize(result.total);
result.profile_indices.reinitialize(result.total);
info.main.ensure_evaluated_offsets();
info.profile.ensure_evaluated_offsets();
int mesh_index = 0;
int vert_offset = 0;
int edge_offset = 0;
int loop_offset = 0;
int poly_offset = 0;
for (const int i_main : info.main.curves_range()) {
const bool main_cyclic = info.main_cyclic[i_main];
const int main_point_num = info.main.evaluated_points_for_curve(i_main).size();
const int main_segment_num = curves::curve_segment_size(main_point_num, main_cyclic);
for (const int i_profile : info.profile.curves_range()) {
result.vert[mesh_index] = vert_offset;
result.edge[mesh_index] = edge_offset;
result.loop[mesh_index] = loop_offset;
result.poly[mesh_index] = poly_offset;
result.main_indices[mesh_index] = i_main;
result.profile_indices[mesh_index] = i_profile;
const bool profile_cyclic = info.profile_cyclic[i_profile];
const int profile_point_num = info.profile.evaluated_points_for_curve(i_profile).size();
const int profile_segment_num = curves::curve_segment_size(profile_point_num,
profile_cyclic);
const bool has_caps = fill_caps && !main_cyclic && profile_cyclic;
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. */
poly_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.poly.last() = poly_offset;
return result;
}
static AttributeDomain get_attribute_domain_for_mesh(const MeshComponent &mesh,
const AttributeIDRef &attribute_id)
{
/* Only use a different domain if it is builtin and must only exist on one domain. */
if (!mesh.attribute_is_builtin(attribute_id)) {
return ATTR_DOMAIN_POINT;
}
std::optional<AttributeMetaData> meta_data = mesh.attribute_get_meta_data(attribute_id);
if (!meta_data) {
return ATTR_DOMAIN_POINT;
}
return meta_data->domain;
}
static bool should_add_attribute_to_mesh(const CurveComponent &curve_component,
const MeshComponent &mesh_component,
const AttributeIDRef &id)
{
/* The position attribute has special non-generic evaluation. */
if (id.is_named() && id.name() == "position") {
return false;
}
/* Don't propagate built-in curves attributes that are not built-in on meshes. */
if (curve_component.attribute_is_builtin(id) && !mesh_component.attribute_is_builtin(id)) {
return false;
}
if (!id.should_be_kept()) {
return false;
}
return true;
}
static GSpan evaluated_attribute_if_necessary(const GVArray &src,
const CurvesGeometry &curves,
const std::array<int, CURVE_TYPES_NUM> &type_counts,
Vector<std::byte> &buffer)
{
if (type_counts[CURVE_TYPE_POLY] == curves.curves_num() && 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 poly_range;
IndexRange loop_range;
};
template<typename Fn>
static void foreach_curve_combination(const CurvesInfo &info,
const ResultOffsets &offsets,
const Fn &fn)
{
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 = info.main.evaluated_points_for_curve(i_main);
const IndexRange profile_points = info.profile.evaluated_points_for_curve(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::curve_segment_size(main_points.size(), main_cyclic),
curves::curve_segment_size(profile_points.size(), profile_cyclic),
offsets_to_range(offsets.vert.as_span(), i),
offsets_to_range(offsets.edge.as_span(), i),
offsets_to_range(offsets.poly.as_span(), i),
offsets_to_range(offsets.loop.as_span(), i)});
}
});
}
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 void copy_main_point_domain_attribute_to_mesh(const CurvesInfo &curves_info,
const ResultOffsets &offsets,
const AttributeDomain 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_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.poly_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_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 AttributeDomain 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.poly_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 Span<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(offsets_to_range(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 AttributeDomain 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.poly;
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>());
});
}
Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
const CurvesGeometry &profile,
const bool fill_caps)
{
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;
}
Mesh *mesh = BKE_mesh_new_nomain(
offsets.vert.last(), offsets.edge.last(), 0, offsets.loop.last(), offsets.poly.last());
mesh->flag |= ME_AUTOSMOOTH;
mesh->smoothresh = DEG2RADF(180.0f);
MutableSpan<MVert> verts(mesh->mvert, mesh->totvert);
MutableSpan<MEdge> edges(mesh->medge, mesh->totedge);
MutableSpan<MLoop> loops(mesh->mloop, mesh->totloop);
MutableSpan<MPoly> polys(mesh->mpoly, mesh->totpoly);
foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
fill_mesh_topology(info.vert_range.start(),
info.edge_range.start(),
info.poly_range.start(),
info.loop_range.start(),
info.main_points.size(),
info.profile_points.size(),
info.main_cyclic,
info.profile_cyclic,
fill_caps,
edges,
loops,
polys);
});
const Span<float3> main_positions = main.evaluated_positions();
const Span<float3> tangents = main.evaluated_tangents();
const Span<float3> normals = main.evaluated_normals();
const Span<float3> profile_positions = profile.evaluated_positions();
Vector<std::byte> eval_buffer;
Curves main_id = {nullptr};
main_id.geometry = reinterpret_cast<const ::CurvesGeometry &>(main);
CurveComponent main_component;
main_component.replace(&main_id, GeometryOwnershipType::Editable);
Curves profile_id = {nullptr};
profile_id.geometry = reinterpret_cast<const ::CurvesGeometry &>(profile);
CurveComponent profile_component;
profile_component.replace(&profile_id, GeometryOwnershipType::Editable);
Span<float> radii = {};
if (main_component.attribute_exists("radius")) {
radii = evaluated_attribute_if_necessary(
main_component.attribute_get_for_read<float>("radius", ATTR_DOMAIN_POINT, 1.0f),
main,
main.curve_type_counts(),
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.is_empty() ? radii : radii.slice(info.main_points),
verts.slice(info.vert_range));
});
if (profile.curve_type_counts()[CURVE_TYPE_BEZIER] > 0) {
const VArray<int8_t> curve_types = profile.curve_types();
const VArray_Span<int8_t> handle_types_left{profile.handle_types_left()};
const VArray_Span<int8_t> handle_types_right{profile.handle_types_right()};
foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
if (curve_types[info.i_profile] == CURVE_TYPE_BEZIER) {
const IndexRange points = profile.points_for_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),
edges.slice(info.edge_range));
}
});
}
Set<AttributeIDRef> main_attributes;
MeshComponent mesh_component;
mesh_component.replace(mesh, GeometryOwnershipType::Editable);
main_component.attribute_foreach([&](const AttributeIDRef &id,
const AttributeMetaData meta_data) {
if (!should_add_attribute_to_mesh(main_component, mesh_component, id)) {
return true;
}
main_attributes.add_new(id);
const AttributeDomain src_domain = meta_data.domain;
const CustomDataType type = meta_data.data_type;
GVArray src = main_component.attribute_try_get_for_read(id, src_domain, type);
const AttributeDomain dst_domain = get_attribute_domain_for_mesh(mesh_component, id);
OutputAttribute dst = mesh_component.attribute_try_get_for_output_only(id, dst_domain, type);
if (!dst) {
return true;
}
if (src_domain == ATTR_DOMAIN_POINT) {
copy_main_point_domain_attribute_to_mesh(
curves_info,
offsets,
dst_domain,
evaluated_attribute_if_necessary(src, main, main.curve_type_counts(), eval_buffer),
dst.as_span());
}
else if (src_domain == ATTR_DOMAIN_CURVE) {
copy_curve_domain_attribute_to_mesh(
offsets, offsets.main_indices, dst_domain, src, dst.as_span());
}
dst.save();
return true;
});
profile_component.attribute_foreach([&](const AttributeIDRef &id,
const AttributeMetaData meta_data) {
if (main_attributes.contains(id)) {
return true;
}
if (!should_add_attribute_to_mesh(profile_component, mesh_component, id)) {
return true;
}
const AttributeDomain src_domain = meta_data.domain;
const CustomDataType type = meta_data.data_type;
GVArray src = profile_component.attribute_try_get_for_read(id, src_domain, type);
const AttributeDomain dst_domain = get_attribute_domain_for_mesh(mesh_component, id);
OutputAttribute dst = mesh_component.attribute_try_get_for_output_only(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,
evaluated_attribute_if_necessary(src, profile, profile.curve_type_counts(), eval_buffer),
dst.as_span());
}
else if (src_domain == ATTR_DOMAIN_CURVE) {
copy_curve_domain_attribute_to_mesh(
offsets, offsets.profile_indices, dst_domain, src, dst.as_span());
}
dst.save();
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));
curves.fill_curve_types(CURVE_TYPE_POLY);
return curves;
}
Mesh *curve_to_wire_mesh(const CurvesGeometry &curve)
{
static const CurvesGeometry vert_curve = get_curve_single_vert();
return curve_to_mesh_sweep(curve, vert_curve, false);
}
} // namespace blender::bke