Interpolate between the curvature vector and the minimal twist vector

with custom weight
This commit is contained in:
Weizhen Huang 2023-01-09 17:32:17 +01:00
parent 74cab3ea05
commit 8ee7e626a5
5 changed files with 109 additions and 33 deletions

View File

@ -242,6 +242,13 @@ class CurvesGeometry : public ::CurvesGeometry {
VArray<int8_t> normal_mode() const;
MutableSpan<int8_t> normal_mode_for_write();
/**
* Used for normal mode curvature vector, specifying the weight as opposed to the minimal twist
* normal.
*/
VArray<float> curvature_vector_weight() const;
MutableSpan<float> curvature_vector_weight_for_write();
/**
* Handle types for Bezier control points. Call #tag_topology_changed after changes.
*/
@ -707,6 +714,7 @@ void calculate_tangents(Span<float3> positions,
void calculate_normals(Span<float3> positions,
bool is_cyclic,
int resolution,
float weight_bending,
MutableSpan<float3> normals);
/**

View File

@ -251,6 +251,7 @@ static float find_root_newton_bisection(float x_begin,
void calculate_normals(const Span<float3> positions,
const bool is_cyclic,
const int resolution,
const float weight_bending,
MutableSpan<float3> evaluated_normals)
{
if (evaluated_normals.size() == 1) {
@ -258,6 +259,8 @@ void calculate_normals(const Span<float3> positions,
return;
}
const float weight_twist = 1.0f - clamp_f(weight_bending, 0.0f, 1.0f);
/* TODO: check if derivatives == 0.*/
Vector<float3> first_derivatives_(evaluated_normals.size());
MutableSpan<float3> first_derivatives = first_derivatives_;
@ -265,21 +268,35 @@ void calculate_normals(const Span<float3> positions,
Vector<float3> second_derivatives_(evaluated_normals.size());
MutableSpan<float3> second_derivatives = second_derivatives_;
interpolate_to_evaluated(2, positions, is_cyclic, resolution, second_derivatives);
if (weight_twist < 1.0f) {
interpolate_to_evaluated(2, positions, is_cyclic, resolution, second_derivatives);
}
else {
/* TODO: Actually only the first entry needs to be evaluated. */
interpolate_to_evaluated(
2,
positions,
is_cyclic,
[resolution](const int segment_i) -> IndexRange {
return {segment_i * resolution, 1};
},
second_derivatives);
}
/* TODO: below are hair-specific values, assuming elliptical cross-section. Maybe make them more
* general by specifying user-defined weight? */
/* Values taken from Table 9.5 in book Chemical and Physical Behavior of Human Hair by Clarence
* R. Robbins, 5th edition. Unit: GPa. */
const float youngs_modulus = 3.89f;
const float shear_modulus = 0.89f;
/* Assuming major axis a = 1. */
const float aspect_ratio = 0.5f; /* Minimal realistic value. */
const float aspect_ratio_squared = aspect_ratio * aspect_ratio;
const float torsion_constant = M_PI * aspect_ratio_squared * aspect_ratio /
(1.0f + aspect_ratio_squared);
const float moment_of_inertia_coefficient = M_PI * aspect_ratio * 0.25f *
(1.0f - aspect_ratio_squared);
/* Hair-specific values, taken from Table 9.5 in book Chemical and Physical Behavior of Human
* Hair by Clarence R. Robbins, 5th edition. Unit: GPa. */
// const float youngs_modulus = 3.89f;
// const float shear_modulus = 0.89f;
// /* Assuming major axis a = 1. */
// const float aspect_ratio = 0.5f; /* Minimal realistic value. */
// const float aspect_ratio_squared = aspect_ratio * aspect_ratio;
// const float torsion_constant = M_PI * aspect_ratio_squared * aspect_ratio /
// (1.0f + aspect_ratio_squared);
// const float moment_of_inertia_coefficient = M_PI * aspect_ratio * 0.25f *
// (1.0f - aspect_ratio_squared);
// const float weight_bending = 0.5f * youngs_modulus * moment_of_inertia_coefficient;
// const float weight_twist = 0.5f * shear_modulus * torsion_constant;
const float dt = 1.0f / resolution;
/* Compute evaluated_normals. */
@ -296,15 +313,15 @@ void calculate_normals(const Span<float3> positions,
continue;
}
if (weight_twist == 0.0f) {
if (angle_normalized_v3v3(curvature_vector, evaluated_normals[i - 1]) < 0) {
curvature_vector = -curvature_vector;
}
evaluated_normals[i] = curvature_vector;
continue;
}
const float first_derivative_norm = math::length(first_derivatives[i]);
/* Arc length depends on the unit, assuming meter. */
const float arc_length = first_derivative_norm * dt;
const float curvature = math::length(binormal) / pow3f(first_derivative_norm);
const float weight_twist = 0.5f * shear_modulus * torsion_constant / arc_length;
const float weight_bending = 0.5f * arc_length * curvature * curvature * youngs_modulus *
moment_of_inertia_coefficient;
const float3 last_tangent = math::normalize(first_derivatives[i - 1]);
const float3 current_tangent = first_derivatives[i] / first_derivative_norm;
const float angle = angle_normalized_v3v3(last_tangent, current_tangent);
@ -315,6 +332,18 @@ void calculate_normals(const Span<float3> positions,
last_normal, math::normalize(math::cross(last_tangent, current_tangent)), angle) :
last_normal;
if (weight_twist == 1.0f) {
evaluated_normals[i] = minimal_twist_normal;
continue;
}
/* Arc length depends on the unit, assuming meter. */
const float arc_length = first_derivative_norm * dt;
const float curvature = math::length(binormal) / pow3f(first_derivative_norm);
const float coefficient_twist = weight_twist / arc_length;
const float coefficient_bending = weight_bending * arc_length * curvature * curvature;
/* Angle between the curvature vector and the minimal twist normal. */
float theta_t = angle_normalized_v3v3(curvature_vector, minimal_twist_normal);
if (theta_t > M_PI_2) {
@ -326,18 +355,18 @@ void calculate_normals(const Span<float3> positions,
* equation: 2 * wt * (θ - θt) + wb * sin(2θ) = 0, with θ being the angle between the computed
* normal and the curvature vector. */
const float theta_begin = 0.0f;
const float theta_end = weight_twist + weight_bending * cosf(2.0f * theta_t) < 0 ?
0.5f * acosf(-weight_twist / weight_bending) :
const float theta_end = coefficient_twist + coefficient_bending * cosf(2.0f * theta_t) < 0 ?
0.5f * acosf(-coefficient_twist / coefficient_bending) :
theta_t;
const float theta = find_root_newton_bisection(
theta_begin,
theta_end,
[weight_bending, weight_twist, theta_t](float t) -> float {
return 2.0f * weight_twist * (t - theta_t) + weight_bending * sinf(2.0f * t);
[coefficient_bending, coefficient_twist, theta_t](float t) -> float {
return 2.0f * coefficient_twist * (t - theta_t) + coefficient_bending * sinf(2.0f * t);
},
[weight_bending, weight_twist](float t) -> float {
return 2.0f * (weight_twist + weight_bending * cosf(2.0f * t));
[coefficient_bending, coefficient_twist](float t) -> float {
return 2.0f * (coefficient_twist + coefficient_bending * cosf(2.0f * t));
},
1e-5f);
@ -347,6 +376,8 @@ void calculate_normals(const Span<float3> positions,
-current_tangent;
evaluated_normals[i] = math::rotate_direction_around_axis(curvature_vector, axis, theta);
}
/* TODO: correct normals along the cyclic curve. */
}
} // namespace blender::bke::curves::catmull_rom

View File

@ -31,6 +31,7 @@ static const std::string ATTR_CURVE_TYPE = "curve_type";
static const std::string ATTR_CYCLIC = "cyclic";
static const std::string ATTR_RESOLUTION = "resolution";
static const std::string ATTR_NORMAL_MODE = "normal_mode";
static const std::string ATTR_CURVATURE_VECTOR_WEIGHT = "curvature_vector_weight";
static const std::string ATTR_HANDLE_TYPE_LEFT = "handle_type_left";
static const std::string ATTR_HANDLE_TYPE_RIGHT = "handle_type_right";
static const std::string ATTR_HANDLE_POSITION_LEFT = "handle_left";
@ -349,6 +350,15 @@ MutableSpan<int8_t> CurvesGeometry::normal_mode_for_write()
return get_mutable_attribute<int8_t>(*this, ATTR_DOMAIN_CURVE, ATTR_NORMAL_MODE);
}
VArray<float> CurvesGeometry::curvature_vector_weight() const
{
return get_varray_attribute<float>(*this, ATTR_DOMAIN_CURVE, ATTR_CURVATURE_VECTOR_WEIGHT, 0.8f);
}
MutableSpan<float> CurvesGeometry::curvature_vector_weight_for_write()
{
return get_mutable_attribute<float>(*this, ATTR_DOMAIN_CURVE, ATTR_CURVATURE_VECTOR_WEIGHT);
}
VArray<float> CurvesGeometry::tilt() const
{
return get_varray_attribute<float>(*this, ATTR_DOMAIN_POINT, ATTR_TILT, 0.0f);
@ -733,6 +743,7 @@ Span<float3> CurvesGeometry::evaluated_normals() const
const VArray<bool> cyclic = this->cyclic();
Span<float3> positions = this->positions();
VArray<int> resolution = this->resolution();
const VArray<float> weight = this->curvature_vector_weight();
const VArray<int8_t> normal_mode = this->normal_mode();
const VArray<int8_t> types = this->curve_types();
const VArray<float> tilt = this->tilt();
@ -763,6 +774,7 @@ Span<float3> CurvesGeometry::evaluated_normals() const
curves::catmull_rom::calculate_normals(positions.slice(points),
cyclic[curve_index],
resolution[curve_index],
weight[curve_index],
evaluated_normals.slice(evaluated_points));
break;
default:

View File

@ -42,7 +42,8 @@ const EnumPropertyItem rna_enum_curve_normal_modes[] = {
"CURVATURE_VECTOR",
ICON_NONE,
"Curvature Vector",
"Calculate normals aligned with the local curvature vectors."},
"Calculate normals that are linearly interpolated between minimal twist normals and local "
"curvature vectors."},
{0, NULL, 0, NULL, NULL},
};

View File

@ -13,6 +13,14 @@ static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>(N_("Curve")).supported_type(GEO_COMPONENT_TYPE_CURVE);
b.add_input<decl::Bool>(N_("Selection")).default_value(true).hide_value().field_on_all();
b.add_input<decl::Float>(N_("Weight"))
.default_value(0.8f)
.min(0.0f)
.max(1.0f)
.description(
"With a larger weight, the normals are more aligned with the curvature vector, but "
"might be less stable when animated. Cross-sections with smaller aspect ratios should "
"have larger weights.");
b.add_output<decl::Geometry>(N_("Curve")).propagate_all();
}
@ -26,13 +34,23 @@ static void node_init(bNodeTree * /*tree*/, bNode *node)
node->custom1 = NORMAL_MODE_MINIMUM_TWIST;
}
static void set_normal_mode(bke::CurvesGeometry &curves,
const NormalMode mode,
const Field<bool> &selection_field)
static void node_update(bNodeTree *ntree, bNode *node)
{
bNodeSocket *weight_socket = static_cast<bNodeSocket *>(node->inputs.first)->next->next;
nodeSetSocketAvailability(ntree, weight_socket, node->custom1 == NORMAL_MODE_CURVATURE_VECTOR);
}
static void set_normal_mode_and_weight(bke::CurvesGeometry &curves,
const NormalMode mode,
const Field<bool> &selection_field,
const Field<float> &weight_field)
{
bke::CurvesFieldContext field_context{curves, ATTR_DOMAIN_CURVE};
fn::FieldEvaluator evaluator{field_context, curves.curves_num()};
evaluator.set_selection(selection_field);
if (mode == NORMAL_MODE_CURVATURE_VECTOR) {
evaluator.add_with_destination(weight_field, curves.curvature_vector_weight_for_write());
}
evaluator.evaluate();
const IndexMask selection = evaluator.get_evaluated_selection_as_mask();
curves.normal_mode_for_write().fill_indices(selection, mode);
@ -45,11 +63,16 @@ static void node_geo_exec(GeoNodeExecParams params)
GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve");
Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
Field<float> weight_field;
if (mode == NORMAL_MODE_CURVATURE_VECTOR) {
weight_field = params.extract_input<Field<float>>("Weight");
}
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
if (Curves *curves_id = geometry_set.get_curves_for_write()) {
bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id->geometry);
set_normal_mode(curves, mode, selection_field);
set_normal_mode_and_weight(curves, mode, selection_field, weight_field);
}
});
@ -67,6 +90,7 @@ void register_node_type_geo_set_curve_normal()
ntype.declare = file_ns::node_declare;
ntype.geometry_node_execute = file_ns::node_geo_exec;
ntype.initfunc = file_ns::node_init;
ntype.updatefunc = file_ns::node_update;
ntype.draw_buttons = file_ns::node_layout;
nodeRegisterType(&ntype);