WIP: Curve-to-Mesh node Even Thickness #108700
|
@ -28,6 +28,7 @@ class AnonymousAttributePropagationInfo;
|
|||
Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
|
||||
const CurvesGeometry &profile,
|
||||
bool fill_caps,
|
||||
bool even_thickness,
|
||||
const AnonymousAttributePropagationInfo &propagation_info);
|
||||
/**
|
||||
* Create a loose-edge mesh based on the evaluated path of the curve's splines.
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_rotation_legacy.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
|
@ -165,7 +166,7 @@ static void mark_bezier_vector_edges_sharp(const int profile_point_num,
|
|||
}
|
||||
}
|
||||
|
||||
static void fill_mesh_positions(const int main_point_num,
|
||||
static void fill_mesh_positions_default(const int main_point_num,
|
||||
const int profile_point_num,
|
||||
const Span<float3> main_positions,
|
||||
const Span<float3> profile_positions,
|
||||
|
@ -646,6 +647,194 @@ static void copy_curve_domain_attribute_to_mesh(const ResultOffsets &mesh_offset
|
|||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------- */
|
||||
/** \name Even Thickness / Miter Joints
|
||||
*
|
||||
* Addresses T80979 Account for curvature in curve to mesh node
|
||||
*
|
||||
* Encapsulates info and methods to calculate profiles for the 'Even Thickness'
|
||||
* option of the `Curve to Mesh` node
|
||||
*
|
||||
* NOTE: `EvenThicknessInfo::fill_mesh_positions_even_thickness()` is the entry point called from
|
||||
* `curve_to_mesh_sweep()`
|
||||
*
|
||||
* \{ */
|
||||
class EvenThicknessInfo {
|
||||
private:
|
||||
static constexpr float SHEAR_EPSILON = 0.000001f;
|
||||
int id_; // Keeps track of which main position we're at.
|
||||
float3 pos_; // Curve point position.
|
||||
float3 tangent_;
|
||||
float3 ih_; // Forward vector of Miter Joint basis.
|
||||
float3 kh_; // Up vector of Miter Joint basis.
|
||||
float tilt_;
|
||||
float radius_;
|
||||
/**************************/
|
||||
EvenThicknessInfo(const int i_ring,
|
||||
const int main_point_num,
|
||||
const Span<float3> main_positions,
|
||||
const Span<float3> tangents,
|
||||
const Span<float3> normals,
|
||||
const Span<float> radii,
|
||||
const Span<float> tilts,
|
||||
const bool is_cyclic,
|
||||
const float3 prev_ih)
|
||||
{
|
||||
id_ = i_ring;
|
||||
pos_ = main_positions[id_];
|
||||
tangent_ = tangents[id_];
|
||||
kh_ = calculate_kh(id_, main_point_num, main_positions, is_cyclic);
|
||||
tilt_ = tilts.is_empty() || id_ >= tilts.size() ? 0.f : tilts[id_];
|
||||
ih_ = id_ == 0 ? calculate_first_ih(normals[id_], kh_, tilt_) :
|
||||
calculate_ih_from_prev_pt(prev_ih, tangents[id_ - 1]);
|
||||
radius_ = radii.is_empty() ? 1.f : radii[i_ring];
|
||||
};
|
||||
|
||||
public:
|
||||
/**************************/
|
||||
static void fill_mesh_positions_even_thickness(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,
|
||||
const Span<float> tilts,
|
||||
const bool is_cyclic,
|
||||
MutableSpan<float3> mesh_positions)
|
||||
{
|
||||
float3 prev_ih;
|
||||
for (const int i_ring : IndexRange(main_point_num)) {
|
||||
const EvenThicknessInfo info(i_ring,
|
||||
main_point_num,
|
||||
main_positions,
|
||||
tangents,
|
||||
normals,
|
||||
radii,
|
||||
tilts,
|
||||
is_cyclic,
|
||||
prev_ih);
|
||||
float4x4 matrix = info.calculate_orientation_and_scale_matrix();
|
||||
matrix *= info.calculate_tilt_and_shear_matrix();
|
||||
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(
|
||||
matrix, profile_positions[i_profile]);
|
||||
}
|
||||
prev_ih = info.ih_;
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG
|
||||
/***************************/
|
||||
friend std::ostream &operator<<(std::ostream &os, const EvenThicknessInfo &c)
|
||||
{
|
||||
return os << "{ /*EvenThicknessInfo*/\n"
|
||||
<< " id: " << c.id_ << ",\n"
|
||||
<< " pos: " << c.pos_ << ",\n"
|
||||
<< " tan: " << c.tangent_ << ",\n"
|
||||
<< " ih: " << c.ih_ << ",\n"
|
||||
<< " kh: " << c.kh_ << ",\n"
|
||||
<< " tilt: " << c.tilt_ << ",\n"
|
||||
<< " radius: " << c.radius_ << ",\n"
|
||||
<< "}\n";
|
||||
}
|
||||
#endif
|
||||
private:
|
||||
/**************************/
|
||||
static const float3 calculate_first_ih(const float3 normal, const float3 kh, const float tilt)
|
||||
{
|
||||
// The `ih/forward` axis for the first point in curve is the perpendicular to kh_/UP in the
|
||||
// `kh/Normal` plane.
|
||||
const float3 nih = math::normalize(normal - kh * math::dot(normal, kh));
|
||||
// Compensates for tilt which is already applied by the default evaluated normal (to let
|
||||
// tilt be re-applied later by the rotation matrix).
|
||||
return math::rotate_direction_around_axis(nih, kh, -tilt);
|
||||
}
|
||||
/**************************/
|
||||
static const float3 calculate_ih_from_prev_pt(const float3 prev_ih, const float3 prev_tangent)
|
||||
{
|
||||
// Calculates `new ih` from reflection of `previous ih` mirrored over the previous tangent
|
||||
// plane.
|
||||
float new_ih[3];
|
||||
reflect_v3_v3v3(new_ih, prev_ih, prev_tangent);
|
||||
return float3(new_ih);
|
||||
}
|
||||
/**************************/
|
||||
static const float3 calculate_kh(const int i_ring,
|
||||
const int main_point_num,
|
||||
const Span<float3> main_positions,
|
||||
const bool is_cyclic)
|
||||
{
|
||||
const float3 current_pos = main_positions[i_ring];
|
||||
const float3 prev_pos = is_cyclic ?
|
||||
main_positions[i_ring > 0 ? i_ring - 1 : main_point_num - 1] :
|
||||
main_positions[std::max(i_ring - 1, 0)];
|
||||
if (i_ring > 0) {
|
||||
return math::normalize(current_pos - prev_pos);
|
||||
}
|
||||
const float3 next_pos = is_cyclic ?
|
||||
main_positions[i_ring < main_point_num - 1 ? i_ring + 1 : 0] :
|
||||
main_positions[std::min(i_ring + 1, main_point_num - 1)];
|
||||
return is_cyclic ? math::normalize(current_pos - prev_pos) :
|
||||
math::normalize(next_pos - current_pos);
|
||||
}
|
||||
/**************************/
|
||||
const float4x4 calculate_orientation_and_scale_matrix() const
|
||||
{
|
||||
float4x4 orientation_matrix = math::from_orthonormal_axes<float4x4>(pos_, ih_, kh_);
|
||||
if (radius_ != 1.f) {
|
||||
orientation_matrix = math::scale(orientation_matrix, float3(radius_));
|
||||
}
|
||||
return orientation_matrix;
|
||||
};
|
||||
/**************************/
|
||||
const float4x4 calculate_tilt_and_shear_matrix() const
|
||||
{
|
||||
const float3 jh = math::normalize(math::cross(kh_, ih_));
|
||||
const float dot_ti = math::dot(tangent_, ih_);
|
||||
const float dot_tj = math::dot(tangent_, jh);
|
||||
const float dot_tk = math::dot(tangent_, kh_);
|
||||
// Shear values
|
||||
// Shear to change `khat = f(distance from ihat)`.
|
||||
const float tan_alpha = std::abs(dot_tk) > SHEAR_EPSILON ? dot_ti / dot_tk : 0.f;
|
||||
// Shear to change `khat = f(distance from jhat)`.
|
||||
const float tan_omega = std::abs(dot_tk) > SHEAR_EPSILON ? dot_tj / dot_tk : 0.f;
|
||||
// Shear to change `ihat = f(distance from jhat)`.
|
||||
const float tan_beta = std::abs(dot_ti) > SHEAR_EPSILON ? dot_tj / dot_ti : 0.f;
|
||||
const float a = tan_alpha;
|
||||
const float b = tan_beta;
|
||||
const float o = tan_omega;
|
||||
if (tilt_ == 0.f) {
|
||||
// clang-format off
|
||||
// Returns shear-only matrix:
|
||||
const float shear_m4[4][4] = {{1, 0, -a, 0},
|
||||
{0, 1, -o, 0},
|
||||
{0, -b, 1, 0},
|
||||
{0, 0, 0, 1}};
|
||||
return float4x4(shear_m4);
|
||||
// clang-format on
|
||||
}
|
||||
const float c = std::cos(-tilt_);
|
||||
const float s = std::sin(-tilt_);
|
||||
// clang-format off
|
||||
// NOTE: this matrix 4x4 is left commented for reference only
|
||||
// and represents the rotation on the Z/UP-axis that's combined with the shear matrix just above:
|
||||
//
|
||||
// const float4x4 rotz_matrix((float[4][4]){{ c, -s, 0, 0},
|
||||
// { s, c, 0, 0},
|
||||
// { 0, 0, 1, 0},
|
||||
// { 0, 0, 0, 1}});
|
||||
// The combo result matrix is the result of shear * rotz_matrix:
|
||||
const float combo[4][4] = {{ c, -s, s*o-c*a, 0},
|
||||
{ s, c, -s*a-c*o, 0},
|
||||
{ 0, -b, 1, 0},
|
||||
{ 0, 0, 0, 1}};
|
||||
// clang-format on
|
||||
return float4x4(combo);
|
||||
}
|
||||
};
|
||||
/** \} */
|
||||
|
||||
static void write_sharp_bezier_edges(const CurvesInfo &curves_info,
|
||||
const ResultOffsets &offsets,
|
||||
MutableAttributeAccessor mesh_attributes,
|
||||
|
@ -683,6 +872,7 @@ static void write_sharp_bezier_edges(const CurvesInfo &curves_info,
|
|||
Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
|
||||
const CurvesGeometry &profile,
|
||||
const bool fill_caps,
|
||||
const bool even_thickness,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const CurvesInfo curves_info = get_curves_info(main, profile);
|
||||
|
@ -756,8 +946,9 @@ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
|
|||
.typed<float>();
|
||||
}
|
||||
|
||||
if (!even_thickness) {
|
||||
foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
|
||||
fill_mesh_positions(info.main_points.size(),
|
||||
fill_mesh_positions_default(info.main_points.size(),
|
||||
info.profile_points.size(),
|
||||
main_positions.slice(info.main_points),
|
||||
profile_positions.slice(info.profile_points),
|
||||
|
@ -766,6 +957,32 @@ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
|
|||
radii.is_empty() ? radii : radii.slice(info.main_points),
|
||||
positions.slice(info.vert_range));
|
||||
});
|
||||
}
|
||||
else {
|
||||
Vector<std::byte> eval_buffer_tilts;
|
||||
Span<float> tilts = {};
|
||||
if (main_attributes.contains("tilt")) {
|
||||
tilts = evaluated_attribute_if_necessary(
|
||||
*main_attributes.lookup_or_default<float>("tilt", ATTR_DOMAIN_POINT, 0.0f),
|
||||
main,
|
||||
main.curve_type_counts(),
|
||||
eval_buffer_tilts)
|
||||
.typed<float>();
|
||||
}
|
||||
foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
|
||||
EvenThicknessInfo::fill_mesh_positions_even_thickness(
|
||||
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),
|
||||
tilts.is_empty() ? tilts : tilts.slice(info.main_points),
|
||||
info.main_cyclic,
|
||||
positions.slice(info.vert_range));
|
||||
});
|
||||
}
|
||||
|
||||
if (!offsets.any_single_point_main) {
|
||||
/* If there are no single point curves, every combination will have at least loose edges. */
|
||||
|
@ -892,7 +1109,7 @@ 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);
|
||||
return curve_to_mesh_sweep(curve, vert_curve, false, false, propagation_info);
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
|
|
@ -22,12 +22,17 @@ static void node_declare(NodeDeclarationBuilder &b)
|
|||
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_input<decl::Bool>("Even Thickness")
|
||||
.description(
|
||||
"Keeps the extruded profile at a constant size along the curve segments and uses miter "
|
||||
"joints at segment elbows");
|
||||
b.add_output<decl::Geometry>("Mesh").propagate_all();
|
||||
}
|
||||
|
||||
static void geometry_set_curve_to_mesh(GeometrySet &geometry_set,
|
||||
const GeometrySet &profile_set,
|
||||
const bool fill_caps,
|
||||
const bool even_thickness,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const Curves &curves = *geometry_set.get_curves_for_read();
|
||||
|
@ -40,8 +45,11 @@ static void geometry_set_curve_to_mesh(GeometrySet &geometry_set,
|
|||
geometry_set.replace_mesh(mesh);
|
||||
}
|
||||
else {
|
||||
Mesh *mesh = bke::curve_to_mesh_sweep(
|
||||
curves.geometry.wrap(), profile_curves->geometry.wrap(), fill_caps, propagation_info);
|
||||
Mesh *mesh = bke::curve_to_mesh_sweep(curves.geometry.wrap(),
|
||||
profile_curves->geometry.wrap(),
|
||||
fill_caps,
|
||||
even_thickness,
|
||||
propagation_info);
|
||||
geometry_set.replace_mesh(mesh);
|
||||
}
|
||||
}
|
||||
|
@ -51,11 +59,15 @@ static void node_geo_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");
|
||||
const bool even_thickness = params.extract_input<bool>("Even Thickness");
|
||||
|
||||
curve_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
|
||||
if (geometry_set.has_curves()) {
|
||||
geometry_set_curve_to_mesh(
|
||||
geometry_set, profile_set, fill_caps, params.get_output_propagation_info("Mesh"));
|
||||
geometry_set_curve_to_mesh(geometry_set,
|
||||
profile_set,
|
||||
fill_caps,
|
||||
even_thickness,
|
||||
params.get_output_propagation_info("Mesh"));
|
||||
}
|
||||
geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue