Geometry Nodes: Add "Fill Caps" option to curve to mesh node

This adds an option to fill the ends of the generated mesh for
each spline combination with an N-gon. The resulting mesh is
manifold, so it can be used for operations like Boolean.

Differential Revision: https://developer.blender.org/D12982
This commit is contained in:
Hans Goudey 2021-10-25 09:10:44 -05:00
parent 4b1ad2dc17
commit bc2f4dd8b4
Notes: blender-bot 2023-02-14 01:35:49 +01:00
Referenced by commit ddc52f2e1d, Fix: Curve to Mesh node creates caps when curve is cyclic
Referenced by issue #91934, Add "Fill Caps" option to the curve to mesh node
3 changed files with 64 additions and 15 deletions

View File

@ -25,7 +25,7 @@ struct CurveEval;
namespace blender::bke {
Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile);
Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile, bool fill_caps);
Mesh *curve_to_wire_mesh(const CurveEval &curve);
} // namespace blender::bke

View File

@ -88,6 +88,7 @@ static void mark_edges_sharp(MutableSpan<MEdge> edges)
}
static void spline_extrude_to_mesh_data(const ResultInfo &info,
const bool fill_caps,
MutableSpan<MVert> r_verts,
MutableSpan<MEdge> r_edges,
MutableSpan<MLoop> r_loops,
@ -180,6 +181,36 @@ static void spline_extrude_to_mesh_data(const ResultInfo &info,
}
}
if (fill_caps && profile.is_cyclic()) {
const int poly_size = info.spline_edge_len * info.profile_edge_len;
const int cap_loop_offset = info.loop_offset + poly_size * 4;
const int cap_poly_offset = info.poly_offset + poly_size;
MPoly &poly_start = r_polys[cap_poly_offset];
poly_start.loopstart = cap_loop_offset;
poly_start.totloop = info.profile_edge_len;
MPoly &poly_end = r_polys[cap_poly_offset + 1];
poly_end.loopstart = cap_loop_offset + info.profile_edge_len;
poly_end.totloop = info.profile_edge_len;
const int last_ring_index = info.spline_vert_len - 1;
const int last_ring_vert_offset = info.vert_offset + info.profile_vert_len * last_ring_index;
const int last_ring_edge_offset = profile_edges_start +
info.profile_edge_len * last_ring_index;
for (const int i : IndexRange(info.profile_edge_len)) {
MLoop &loop_start = r_loops[cap_loop_offset + i];
loop_start.v = info.vert_offset + i;
loop_start.e = profile_edges_start + i;
MLoop &loop_end = r_loops[cap_loop_offset + info.profile_edge_len + i];
loop_end.v = last_ring_vert_offset + i;
loop_end.e = last_ring_edge_offset + i;
}
mark_edges_sharp(r_edges.slice(profile_edges_start, info.profile_edge_len));
mark_edges_sharp(r_edges.slice(last_ring_edge_offset, info.profile_edge_len));
}
/* Calculate the positions of each profile ring profile along the spline. */
Span<float3> positions = spline.evaluated_positions();
Span<float3> tangents = spline.evaluated_tangents();
@ -226,14 +257,22 @@ static inline int spline_extrude_edge_size(const Spline &curve, const Spline &pr
curve.evaluated_edges_size() * profile.evaluated_points_size();
}
static inline int spline_extrude_loop_size(const Spline &curve, const Spline &profile)
static inline int spline_extrude_loop_size(const Spline &curve,
const Spline &profile,
const bool fill_caps)
{
return curve.evaluated_edges_size() * profile.evaluated_edges_size() * 4;
const int tube = curve.evaluated_edges_size() * profile.evaluated_edges_size() * 4;
const int caps = (fill_caps && profile.is_cyclic()) ? profile.evaluated_edges_size() * 2 : 0;
return tube + caps;
}
static inline int spline_extrude_poly_size(const Spline &curve, const Spline &profile)
static inline int spline_extrude_poly_size(const Spline &curve,
const Spline &profile,
const bool fill_caps)
{
return curve.evaluated_edges_size() * profile.evaluated_edges_size();
const int tube = curve.evaluated_edges_size() * profile.evaluated_edges_size();
const int caps = (fill_caps && profile.is_cyclic()) ? 2 : 0;
return tube + caps;
}
struct ResultOffsets {
@ -242,7 +281,9 @@ struct ResultOffsets {
Array<int> loop;
Array<int> poly;
};
static ResultOffsets calculate_result_offsets(Span<SplinePtr> profiles, Span<SplinePtr> curves)
static ResultOffsets calculate_result_offsets(Span<SplinePtr> profiles,
Span<SplinePtr> curves,
const bool fill_caps)
{
const int total = profiles.size() * curves.size();
Array<int> vert(total + 1);
@ -263,8 +304,8 @@ static ResultOffsets calculate_result_offsets(Span<SplinePtr> profiles, Span<Spl
poly[mesh_index] = poly_offset;
vert_offset += spline_extrude_vert_size(*curves[i_spline], *profiles[i_profile]);
edge_offset += spline_extrude_edge_size(*curves[i_spline], *profiles[i_profile]);
loop_offset += spline_extrude_loop_size(*curves[i_spline], *profiles[i_profile]);
poly_offset += spline_extrude_poly_size(*curves[i_spline], *profiles[i_profile]);
loop_offset += spline_extrude_loop_size(*curves[i_spline], *profiles[i_profile], fill_caps);
poly_offset += spline_extrude_poly_size(*curves[i_spline], *profiles[i_profile], fill_caps);
mesh_index++;
}
}
@ -652,12 +693,12 @@ static void copy_spline_domain_attributes_to_mesh(const CurveEval &curve,
* changed anyway in a way that affects the normals. So currently this code uses the safer /
* simpler solution of deferring normal calculation to the rest of Blender.
*/
Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile)
Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile, const bool fill_caps)
{
Span<SplinePtr> profiles = profile.splines();
Span<SplinePtr> curves = curve.splines();
const ResultOffsets offsets = calculate_result_offsets(profiles, curves);
const ResultOffsets offsets = calculate_result_offsets(profiles, curves, fill_caps);
if (offsets.vert.last() == 0) {
return nullptr;
}
@ -696,6 +737,7 @@ Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile)
};
spline_extrude_to_mesh_data(info,
fill_caps,
{mesh->mvert, mesh->totvert},
{mesh->medge, mesh->totedge},
{mesh->mloop, mesh->totloop},
@ -733,7 +775,7 @@ static CurveEval get_curve_single_vert()
Mesh *curve_to_wire_mesh(const CurveEval &curve)
{
static const CurveEval vert_curve = get_curve_single_vert();
return curve_to_mesh_sweep(curve, vert_curve);
return curve_to_mesh_sweep(curve, vert_curve, false);
}
} // namespace blender::bke

View File

@ -29,19 +29,25 @@ static void geo_node_curve_to_mesh_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Curve");
b.add_input<decl::Geometry>("Profile Curve");
b.add_input<decl::Bool>("Fill Caps")
.description(
"If the profile spline is cyclic, fill the ends of the generated mesh with N-gons");
b.add_output<decl::Geometry>("Mesh");
}
static void geometry_set_curve_to_mesh(GeometrySet &geometry_set, const GeometrySet &profile_set)
static void geometry_set_curve_to_mesh(GeometrySet &geometry_set,
const GeometrySet &profile_set,
const bool fill_caps)
{
const CurveEval *curve = geometry_set.get_curve_for_read();
const CurveEval *profile_curve = profile_set.get_curve_for_read();
if (profile_curve == nullptr) {
Mesh *mesh = bke::curve_to_wire_mesh(*geometry_set.get_curve_for_read());
Mesh *mesh = bke::curve_to_wire_mesh(*curve);
geometry_set.replace_mesh(mesh);
}
else {
Mesh *mesh = bke::curve_to_mesh_sweep(*geometry_set.get_curve_for_read(), *profile_curve);
Mesh *mesh = bke::curve_to_mesh_sweep(*curve, *profile_curve, fill_caps);
geometry_set.replace_mesh(mesh);
}
}
@ -50,6 +56,7 @@ static void geo_node_curve_to_mesh_exec(GeoNodeExecParams params)
{
GeometrySet curve_set = params.extract_input<GeometrySet>("Curve");
GeometrySet profile_set = params.extract_input<GeometrySet>("Profile Curve");
const bool fill_caps = params.extract_input<bool>("Fill Caps");
if (profile_set.has_instances()) {
params.error_message_add(NodeWarningType::Error,
@ -67,7 +74,7 @@ static void geo_node_curve_to_mesh_exec(GeoNodeExecParams params)
curve_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
if (geometry_set.has_curve()) {
has_curve = true;
geometry_set_curve_to_mesh(geometry_set, profile_set);
geometry_set_curve_to_mesh(geometry_set, profile_set, fill_caps);
}
geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES});
});