Interpolate between the curvature vector and the minimal twist vector
with custom weight
This commit is contained in:
parent
74cab3ea05
commit
8ee7e626a5
|
@ -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);
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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},
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue