Cycles: new Microfacet-based Hair BSDF with elliptical cross-section support #105600

Merged
Weizhen Huang merged 114 commits from weizhen/blender:microfacet_hair into main 2023-08-18 12:46:20 +02:00
11 changed files with 32 additions and 461 deletions
Showing only changes of commit 9039f76928 - Show all commits

View File

@ -204,13 +204,6 @@ 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.
*/
@ -488,13 +481,6 @@ void calculate_tangents(Span<float3> positions, bool is_cyclic, MutableSpan<floa
*/
void calculate_normals_minimum(Span<float3> tangents, bool cyclic, MutableSpan<float3> normals);
/**
* Calculate curvature vectors. TODO: more description. */
void calculate_curvature_vectors(Span<float3> positions,
Span<float3> tangents,
bool cyclic,
MutableSpan<float3> normals);
/**
* Calculate a vector perpendicular to every tangent on the X-Y plane (unless the tangent is
* vertical, in that case use the X direction).
@ -672,16 +658,6 @@ void interpolate_to_evaluated(GSpan src, OffsetIndices<int> evaluated_offsets, G
namespace catmull_rom {
void calculate_tangents(Span<float3> positions,
bool is_cyclic,
int resolution,
MutableSpan<float3> tangents);
void calculate_normals(Span<float3> positions,
bool is_cyclic,
int resolution,
float weight_bending,
MutableSpan<float3> normals);
/**
* Calculate the number of evaluated points that #interpolate_to_evaluated is expected to produce.
* \param points_num: The number of points in the curve.
@ -705,8 +681,6 @@ void interpolate_to_evaluated(const GSpan src,
GMutableSpan dst);
void calculate_basis(const float parameter, float4 &r_weights);
void calculate_basis_derivative(const float parameter, float4 &r_weights);
void calculate_basis_derivative2(const float parameter, float4 &r_weights);
/**
* Interpolate the control point values for the given parameter on the piecewise segment.
@ -715,24 +689,11 @@ void calculate_basis_derivative2(const float parameter, float4 &r_weights);
* \param parameter: Parameter in range [0, 1] to compute the interpolation for.
*/
template<typename T>
T interpolate(
const int order, const T &a, const T &b, const T &c, const T &d, const float parameter)
T interpolate(const T &a, const T &b, const T &c, const T &d, const float parameter)
{
BLI_assert(0.0f <= parameter && parameter <= 1.0f);
float4 n;
switch (order) {
case 1:
calculate_basis_derivative(parameter, n);
break;
case 2:
calculate_basis_derivative2(parameter, n);
break;
default:
calculate_basis(parameter, n);
break;
}
calculate_basis(parameter, n);
if constexpr (is_same_any_v<T, float, float2, float3>) {
/* Save multiplications by adjusting weights after mix. */
return 0.5f * attribute_math::mix4<T>(n, a, b, c, d);

View File

@ -8,8 +8,6 @@
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BLI_math_rotation_legacy.hh"
#include "BLI_math_vector.hh"
namespace blender::bke::curves::catmull_rom {
@ -35,36 +33,13 @@ void calculate_basis(const float parameter, float4 &r_weights)
r_weights[3] = -s * t * t;
}
/* Adapted from Cycles #catmull_rom_basis_derivative function. */
void calculate_basis_derivative(const float parameter, float4 &r_weights)
{
const float t = parameter;
const float s = 1.0f - parameter;
r_weights[0] = s * (2.0f * t - s);
r_weights[1] = t * (9.0f * t - 10.0f);
r_weights[2] = -s * (9.0f * s - 10.0f);
r_weights[3] = t * (t - 2.0f * s);
}
/* Adapted from Cycles #catmull_rom_basis_derivative2 function. */
void calculate_basis_derivative2(const float parameter, float4 &r_weights)
{
const float t = parameter;
r_weights[0] = -6.0f * t + 4.0f;
r_weights[1] = 18.0f * t - 10.0f;
r_weights[2] = -18.0f * t + 8.0f;
r_weights[3] = 6.0f * t - 2.0f;
}
template<typename T>
static void evaluate_segment(
const int order, const T &a, const T &b, const T &c, const T &d, MutableSpan<T> dst)
static void evaluate_segment(const T &a, const T &b, const T &c, const T &d, MutableSpan<T> dst)
{
const float step = 1.0f / dst.size();
dst.first() = b;
IndexRange index_range = (order == 0) ? dst.index_range().drop_front(1) : dst.index_range();
for (const int i : index_range) {
dst[i] = interpolate(order, a, b, c, d, i * step);
for (const int i : dst.index_range().drop_front(1)) {
dst[i] = interpolate<T>(a, b, c, d, i * step);
}
}
@ -72,11 +47,9 @@ static void evaluate_segment(
* \param range_fn: Returns an index range describing where in the #dst span each segment should be
* evaluated to, and how many points to add to it. This is used to avoid the need to allocate an
* actual offsets array in typical evaluation use cases where the resolution is per-curve.
* \param order: The order of the derivative. order == 0 is the default.
*/
template<typename T, typename RangeForSegmentFn>
static void interpolate_to_evaluated(const int order,
const Span<T> src,
static void interpolate_to_evaluated(const Span<T> src,
const bool cyclic,
const RangeForSegmentFn &range_fn,
MutableSpan<T> dst)
@ -88,27 +61,20 @@ static void interpolate_to_evaluated(const int order,
* - Finally evaluate all of the segments in the middle in parallel. */
if (src.size() == 1) {
switch (order) {
case 0:
dst.first() = src.first();
break;
default:
dst.first() = T(0);
break;
}
dst.first() = src.first();
return;
}
const IndexRange first = range_fn(0);
if (src.size() == 2) {
evaluate_segment(order, src.first(), src.first(), src.last(), src.last(), dst.slice(first));
evaluate_segment(src.first(), src.first(), src.last(), src.last(), dst.slice(first));
if (cyclic) {
const IndexRange last = range_fn(1);
evaluate_segment(order, src.last(), src.last(), src.first(), src.first(), dst.slice(last));
evaluate_segment(src.last(), src.last(), src.first(), src.first(), dst.slice(last));
}
else {
dst.last() = interpolate(order, src.first(), src.first(), src.last(), src.last(), 1);
dst.last() = src.last();
}
return;
}
@ -116,19 +82,17 @@ static void interpolate_to_evaluated(const int order,
const IndexRange second_to_last = range_fn(src.index_range().last(1));
const IndexRange last = range_fn(src.index_range().last());
if (cyclic) {
evaluate_segment(order, src.last(), src[0], src[1], src[2], dst.slice(first));
evaluate_segment(
order, src.last(2), src.last(1), src.last(), src.first(), dst.slice(second_to_last));
evaluate_segment(order, src.last(1), src.last(), src[0], src[1], dst.slice(last));
evaluate_segment(src.last(), src[0], src[1], src[2], dst.slice(first));
evaluate_segment(src.last(2), src.last(1), src.last(), src.first(), dst.slice(second_to_last));
evaluate_segment(src.last(1), src.last(), src[0], src[1], dst.slice(last));
}
else {
evaluate_segment(order, src[0], src[0], src[1], src[2], dst.slice(first));
evaluate_segment(
order, src.last(2), src.last(1), src.last(), src.last(), dst.slice(second_to_last));
evaluate_segment(src[0], src[0], src[1], src[2], dst.slice(first));
evaluate_segment(src.last(2), src.last(1), src.last(), src.last(), dst.slice(second_to_last));
/* For non-cyclic curves, the last segment should always just have a single point. We could
* assert that the size of the provided range is 1 here, but that would require specializing
* the #range_fn implementation for the last point, which may have a performance cost. */
dst.last() = interpolate(order, src.last(2), src.last(1), src.last(), src.last(), 1);
dst.last() = src.last();
}
/* Evaluate every segment that isn't the first or last. */
@ -136,14 +100,13 @@ static void interpolate_to_evaluated(const int order,
threading::parallel_for(inner_range, 512, [&](IndexRange range) {
for (const int i : range) {
const IndexRange segment = range_fn(i);
evaluate_segment(order, src[i - 1], src[i], src[i + 1], src[i + 2], dst.slice(segment));
evaluate_segment(src[i - 1], src[i], src[i + 1], src[i + 2], dst.slice(segment));
}
});
}
template<typename T>
static void interpolate_to_evaluated(const int order,
const Span<T> src,
static void interpolate_to_evaluated(const Span<T> src,
const bool cyclic,
const int resolution,
MutableSpan<T> dst)
@ -151,7 +114,6 @@ static void interpolate_to_evaluated(const int order,
{
BLI_assert(dst.size() == calculate_evaluated_num(src.size(), cyclic, resolution));
interpolate_to_evaluated(
order,
src,
cyclic,
[resolution](const int segment_i) -> IndexRange {
@ -168,7 +130,6 @@ static void interpolate_to_evaluated(const Span<T> src,
{
interpolate_to_evaluated(
0,
src,
cyclic,
[evaluated_offsets](const int segment_i) -> IndexRange {
@ -184,7 +145,7 @@ void interpolate_to_evaluated(const GSpan src,
{
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
interpolate_to_evaluated(0, src.typed<T>(), cyclic, resolution, dst.typed<T>());
interpolate_to_evaluated(src.typed<T>(), cyclic, resolution, dst.typed<T>());
});
}
@ -199,196 +160,4 @@ void interpolate_to_evaluated(const GSpan src,
});
}
/* Calculate analytical tangents from the first derivative. */
void calculate_tangents(const Span<float3> positions,
const bool is_cyclic,
const int resolution,
MutableSpan<float3> evaluated_tangents)
{
if (evaluated_tangents.size() == 1) {
evaluated_tangents[0] = float3(0.0f, 0.0f, 1.0f);
return;
}
interpolate_to_evaluated(1, positions, is_cyclic, resolution, evaluated_tangents);
for (const int i : evaluated_tangents.index_range()) {
evaluated_tangents[i] = math::normalize(evaluated_tangents[i]);
}
}
/* Use a combination of Newton iterations and bisection to find the root in the monotonic interval
* [x_begin, x_end], given precision. Idea taken from the paper High-Performance Polynomial Root
* Finding for Graphics by Cem Yuksel, HPG 2022. */
/* TODO: move this function elsewhere as a utility function? Also test corner cases. */
static float find_root_newton_bisection(float x_begin,
float x_end,
std::function<float(float)> eval,
std::function<float(float)> eval_derivative,
const float precision)
{
float x_mid = 0.5f * (x_begin + x_end);
float y_begin = eval(x_begin);
if (y_begin == 0.0f) {
return x_begin;
}
float y_end = eval(x_end);
if (y_end == 0.0f) {
return x_end;
}
BLI_assert(signf(y_begin) != signf(eval(x_end)));
while (x_mid - x_begin > precision) {
const float y_mid = eval(x_mid);
if (signf(y_begin) == signf(y_mid)) {
x_begin = x_mid;
y_begin = y_mid;
}
else {
x_end = x_mid;
}
const float x_newton = x_mid - y_mid / eval_derivative(x_mid);
x_mid = (x_newton > x_begin && x_newton < x_end) ? x_newton : 0.5f * (x_begin + x_end);
}
return x_mid;
}
/* Calculate normals by minimizing the potential energy due to twist and bending. Global
* optimization would involve integration and is too costly. Instead, we start with the first
* curvature vector and propogate it along the curve. */
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) {
evaluated_normals[0] = float3(1.0f, 0.0f, 0.0f);
return;
}
const float weight_twist = 1.0f - clamp_f(weight_bending, 0.0f, 1.0f);
Vector<float3> first_derivatives_(evaluated_normals.size());
MutableSpan<float3> first_derivatives = first_derivatives_;
interpolate_to_evaluated(1, positions, is_cyclic, resolution, first_derivatives);
Vector<float3> second_derivatives_(evaluated_normals.size());
MutableSpan<float3> second_derivatives = 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);
}
/* 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. */
for (const int i : evaluated_normals.index_range()) {
/* B = r' x r'' */
const float3 binormal = math::cross(first_derivatives[i], second_derivatives[i]);
/* N = B x T */
/* TODO: Catmull-Rom is not C2 continuous. Some smoothening maybe? */
float3 curvature_vector = math::normalize(math::cross(binormal, first_derivatives[i]));
const float first_derivative_norm = math::length(first_derivatives[i]);
const float curvature = math::length(binormal) / pow3f(first_derivative_norm);
if (i == 0) {
/* TODO: Does it make sense to propogate from where the curvature is the largest? */
evaluated_normals[i] = curvature > 1e-4f ? curvature_vector : float3(1.0f, 0.0f, 0.0f);
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 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);
const float3 last_normal = evaluated_normals[i - 1];
const float3 minimal_twist_normal =
angle > 0 && first_derivative_norm > 0 ?
math::rotate_direction_around_axis(
last_normal, math::normalize(math::cross(last_tangent, current_tangent)), angle) :
last_normal;
/* Arc length depends on the unit, assuming meter. */
const float arc_length = first_derivative_norm * dt;
const float coefficient_twist = weight_twist / arc_length;
const float coefficient_bending = weight_bending * arc_length * curvature * curvature;
if (weight_twist == 1.0f || !(coefficient_bending > 0)) {
evaluated_normals[i] = minimal_twist_normal;
continue;
}
/* 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) {
curvature_vector = -curvature_vector;
theta_t = M_PI - theta_t;
}
/* Minimizing the total potential energy U(θ) = wt * (θ - θt)^2 + wb * sin^2(θ) by solving the
* 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 = 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,
[coefficient_bending, coefficient_twist, theta_t](float t) -> float {
return 2.0f * coefficient_twist * (t - theta_t) + coefficient_bending * sinf(2.0f * t);
},
[coefficient_bending, coefficient_twist](float t) -> float {
return 2.0f * (coefficient_twist + coefficient_bending * cosf(2.0f * t));
},
1e-4f);
const float3 axis = math::dot(current_tangent,
math::cross(curvature_vector, minimal_twist_normal)) > 0 ?
current_tangent :
-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

@ -50,7 +50,6 @@ static HandleType handle_type_from_legacy(const uint8_t handle_type_legacy)
static NormalMode normal_mode_from_legacy(const short twist_mode)
{
/* TODO: add curvature vector mode. */
switch (twist_mode) {
case CU_TWIST_Z_UP:
case CU_TWIST_TANGENT:

View File

@ -199,77 +199,4 @@ void calculate_normals_minimum(const Span<float3> tangents,
}
}
void calculate_curvature_vectors(const Span<float3> positions,
const Span<float3> tangents,
const bool cyclic,
MutableSpan<float3> normals)
{
const int size = positions.size();
BLI_assert(normals.size() == size);
BLI_assert(tangents.size() == size);
if (normals.is_empty()) {
return;
}
const float epsilon = 1e-4f;
/* Fill in the first normal, in case the following computations are invalid. */
const float3 &first_tangent = tangents.first();
if (fabs(first_tangent.x) + fabs(first_tangent.y) < epsilon) {
normals.first() = {1.0f, 0.0f, 0.0f};
}
else {
normals.first() = math::normalize(float3(first_tangent.y, -first_tangent.x, 0.0f));
}
/* Computing normals from the second point to the second to last point, or from the first point
* to the last point for cyclic curve. */
/* TODO: maybe better to compute only for control points. */
int first_valid_normal_index = -1;
const int start_index = cyclic ? 0 : 1;
const int end_index = cyclic ? size - 1 : size - 2;
for (int i = start_index; i < end_index; i++) {
const float3 previous_segment = (i == 0) ? positions.last() - positions.first() :
positions[i - 1] - positions[i];
const float3 next_segment = (i == size - 1) ? positions.first() - positions.last() :
positions[i + 1] - positions[i];
const float3 binormal = math::cross(math::normalize(next_segment),
math::normalize(previous_segment));
float normal_len;
float3 normal = math::normalize_and_get_length(math::cross(tangents[i], binormal), normal_len);
if (normal_len > epsilon) {
if (first_valid_normal_index != -1 && math::dot(normal, normals[i - 1]) < 0) {
/* Prevent sudden normal changes. */
normal = -normal;
}
if (first_valid_normal_index == -1) {
first_valid_normal_index = i;
}
normals[i] = normal;
}
else {
if (first_valid_normal_index != -1) {
normals[i] = normals[i - 1];
}
}
}
if (first_valid_normal_index == -1) {
normals.fill(normals.first());
}
else {
const float3 &first_valid_normal = normals[first_valid_normal_index];
/* If the first few normals are invalid, use the normal from the first point with a valid
* normal. */
normals.take_front(first_valid_normal_index).fill(first_valid_normal);
if (!cyclic) {
/* The last normal is the same as the second to last normal. The normal is already filled if
* the curve is cyclic. */
normals.last() = normals[size - 2];
}
}
}
} // namespace blender::bke::curves::poly

View File

@ -32,7 +32,6 @@ 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";
@ -363,15 +362,6 @@ 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);
@ -691,35 +681,19 @@ Span<float3> CurvesGeometry::evaluated_tangents() const
{
const bke::CurvesGeometryRuntime &runtime = *this->runtime;
runtime.evaluated_tangent_cache.ensure([&](Vector<float3> &r_data) {
const OffsetIndices<int> points_by_curve = this->points_by_curve();
const OffsetIndices<int> evaluated_points_by_curve = this->evaluated_points_by_curve();
const Span<float3> evaluated_positions = this->evaluated_positions();
const Span<float3> positions = this->positions();
const VArray<int> resolution = this->resolution();
const VArray<bool> cyclic = this->cyclic();
const VArray<int8_t> types = this->curve_types();
r_data.resize(this->evaluated_points_num());
MutableSpan<float3> tangents = r_data;
threading::parallel_for(this->curves_range(), 128, [&](IndexRange curves_range) {
for (const int curve_index : curves_range) {
const IndexRange points = points_by_curve[curve_index];
const IndexRange evaluated_points = evaluated_points_by_curve[curve_index];
switch (types[curve_index]) {
case CURVE_TYPE_CATMULL_ROM:
/* NOTE: this causes test geo_node_curves_test_sample_curves to fail. */
curves::catmull_rom::calculate_tangents(positions.slice(points),
cyclic[curve_index],
resolution[curve_index],
tangents.slice(evaluated_points));
break;
default:
curves::poly::calculate_tangents(evaluated_positions.slice(evaluated_points),
cyclic[curve_index],
tangents.slice(evaluated_points));
break;
}
curves::poly::calculate_tangents(evaluated_positions.slice(evaluated_points),
cyclic[curve_index],
tangents.slice(evaluated_points));
}
});
@ -815,8 +789,6 @@ Span<float3> CurvesGeometry::evaluated_normals() const
const OffsetIndices<int> evaluated_points_by_curve = this->evaluated_points_by_curve();
const VArray<int8_t> types = this->curve_types();
const VArray<bool> cyclic = this->cyclic();
const Span<float3> positions = this->positions();
const VArray<float> weight = this->curvature_vector_weight();
const VArray<int8_t> normal_mode = this->normal_mode();
const VArray<int> resolution = this->resolution();
const VArray<int8_t> nurbs_orders = this->nurbs_orders();
@ -840,7 +812,6 @@ Span<float3> CurvesGeometry::evaluated_normals() const
Vector<float> evaluated_tilts;
for (const int curve_index : curves_range) {
const IndexRange points = points_by_curve[curve_index];
const IndexRange evaluated_points = evaluated_points_by_curve[curve_index];
switch (normal_mode[curve_index]) {
case NORMAL_MODE_Z_UP:
@ -852,25 +823,6 @@ Span<float3> CurvesGeometry::evaluated_normals() const
cyclic[curve_index],
evaluated_normals.slice(evaluated_points));
break;
case NORMAL_MODE_CURVATURE_VECTOR:
switch (types[curve_index]) {
case CURVE_TYPE_CATMULL_ROM:
curves::catmull_rom::calculate_normals(positions.slice(points),
cyclic[curve_index],
resolution[curve_index],
weight[curve_index],
evaluated_normals.slice(evaluated_points));
break;
default:
const Span<float3> evaluated_positions = this->evaluated_positions();
curves::poly::calculate_curvature_vectors(
evaluated_positions.slice(evaluated_points),
evaluated_tangents.slice(evaluated_points),
cyclic[curve_index],
evaluated_normals.slice(evaluated_points));
break;
}
break;
}
/* If the "tilt" attribute exists, rotate the normals around the tangents by the

View File

@ -201,10 +201,6 @@ static Array<float3> curve_normal_point_domain(const bke::CurvesGeometry &curves
case NORMAL_MODE_MINIMUM_TWIST:
bke::curves::poly::calculate_normals_minimum(nurbs_tangents, cyclic, curve_normals);
break;
case NORMAL_MODE_CURVATURE_VECTOR:
bke::curves::poly::calculate_curvature_vectors(
curve_positions, nurbs_tangents, cyclic, curve_normals);
break;
}
break;
}
@ -500,7 +496,7 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
static const auto normal_mode_clamp = mf::build::SI1_SO<int8_t, int8_t>(
"Normal Mode Validate",
[](int8_t value) {
return std::clamp<int8_t>(value, NORMAL_MODE_MINIMUM_TWIST, NORMAL_MODE_CURVATURE_VECTOR);
return std::clamp<int8_t>(value, NORMAL_MODE_MINIMUM_TWIST, NORMAL_MODE_Z_UP);
},
mf::build::exec_presets::AllSpanOrSingle());
static BuiltinCustomDataLayerProvider normal_mode("normal_mode",

View File

@ -377,7 +377,6 @@ AddCurvesOnMeshOutputs add_curves_on_mesh(CurvesGeometry &curves,
}
curves.fill_curve_types(new_curves_range, CURVE_TYPE_CATMULL_ROM);
curves.normal_mode_for_write().fill(NORMAL_MODE_CURVATURE_VECTOR);
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();

View File

@ -264,12 +264,11 @@ static T interpolate_catmull_rom(const Span<T> src_data,
if (i3 == src_data.size()) {
i3 = src_cyclic ? 0 : insertion_point.next_index;
}
return bke::curves::catmull_rom::interpolate(0,
src_data[i0],
src_data[insertion_point.index],
src_data[insertion_point.next_index],
src_data[i3],
insertion_point.parameter);
return bke::curves::catmull_rom::interpolate<T>(src_data[i0],
src_data[insertion_point.index],
src_data[insertion_point.next_index],
src_data[i3],
insertion_point.parameter);
}
static bke::curves::bezier::Insertion knot_insert_bezier(

View File

@ -86,7 +86,6 @@ typedef enum NormalMode {
* is vertical, the X axis is used.
*/
NORMAL_MODE_Z_UP = 1,
NORMAL_MODE_CURVATURE_VECTOR = 2,
} NormalMode;
/**

View File

@ -38,12 +38,6 @@ const EnumPropertyItem rna_enum_curve_normal_modes[] = {
"Z Up",
"Calculate normals perpendicular to the Z axis and the curve tangent. If a series of points "
"is vertical, the X axis is used"},
{NORMAL_MODE_CURVATURE_VECTOR,
"CURVATURE_VECTOR",
ICON_NONE,
"Curvature Vector",
"Calculate normals that are linearly interpolated between minimal twist normals and local "
"curvature vectors."},
{0, NULL, 0, NULL, NULL},
};

View File

@ -13,14 +13,6 @@ 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();
}
@ -34,23 +26,13 @@ static void node_init(bNodeTree * /*tree*/, bNode *node)
node->custom1 = NORMAL_MODE_MINIMUM_TWIST;
}
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)
static void set_normal_mode(bke::CurvesGeometry &curves,
const NormalMode mode,
const Field<bool> &selection_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);
@ -63,16 +45,11 @@ 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 = curves_id->geometry.wrap();
set_normal_mode_and_weight(curves, mode, selection_field, weight_field);
set_normal_mode(curves, mode, selection_field);
}
});
@ -90,7 +67,6 @@ 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);