Cycles: new Microfacet-based Hair BSDF with elliptical cross-section support #105600
|
@ -1 +1 @@
|
|||
Subproject commit e398d3c4969a37ae2ecff388344dd780bc1cfe82
|
||||
Subproject commit 7084c4ecd97d93459d9d23fd90f81589b09be5df
|
|
@ -1 +1 @@
|
|||
Subproject commit 90c87dd771e027e0ffa157a0e294399bfd605d99
|
||||
Subproject commit 042c799b7aef9634a33d24e78d4922706aca9a2b
|
|
@ -1 +1 @@
|
|||
Subproject commit 96143b1a8b037ea3c81f065f557025db9fe1ace3
|
||||
Subproject commit bdcfdd47ec3451822b21d1cff2ea2db751093c9a
|
|
@ -520,6 +520,13 @@ 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).
|
||||
|
@ -696,6 +703,16 @@ void interpolate_to_evaluated(GSpan src, Span<int> evaluated_offsets, GMutableSp
|
|||
|
||||
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,
|
||||
Span<float3> tangents,
|
||||
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.
|
||||
|
@ -719,6 +736,8 @@ 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.
|
||||
|
@ -727,11 +746,24 @@ void calculate_basis(const float parameter, float4 &r_weights);
|
|||
* \param parameter: Parameter in range [0, 1] to compute the interpolation for.
|
||||
*/
|
||||
template<typename T>
|
||||
T interpolate(const T &a, const T &b, const T &c, const T &d, const float parameter)
|
||||
T interpolate(
|
||||
const int order, 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;
|
||||
calculate_basis(parameter, 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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -31,13 +31,37 @@ 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;
|
||||
const float s = 1.0f - parameter;
|
||||
r_weights[0] = -6.0f * t + 4.0f;
|
||||
r_weights[1] = 18.0f * t - 10.0f;
|
||||
r_weights[2] = 18.0f * s - 10.0f;
|
||||
r_weights[3] = 6.0f * t - 2.0f;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void evaluate_segment(const T &a, const T &b, const T &c, const T &d, MutableSpan<T> dst)
|
||||
static void evaluate_segment(
|
||||
const int order, 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;
|
||||
for (const int i : dst.index_range().drop_front(1)) {
|
||||
dst[i] = interpolate<T>(a, b, c, d, i * step);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,9 +69,11 @@ static void evaluate_segment(const T &a, const T &b, const T &c, const T &d, Mut
|
|||
* \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 Span<T> src,
|
||||
static void interpolate_to_evaluated(const int order,
|
||||
const Span<T> src,
|
||||
const bool cyclic,
|
||||
const RangeForSegmentFn &range_fn,
|
||||
MutableSpan<T> dst)
|
||||
|
@ -59,20 +85,30 @@ static void interpolate_to_evaluated(const Span<T> src,
|
|||
* - Finally evaluate all of the segments in the middle in parallel. */
|
||||
|
||||
if (src.size() == 1) {
|
||||
dst.first() = src.first();
|
||||
switch (order) {
|
||||
case 1:
|
||||
dst.first() = float3(0.0f, 0.0f, 1.0f);
|
||||
break;
|
||||
case 2:
|
||||
dst.first() = float3(1.0f, 0.0f, 0.0f);
|
||||
break;
|
||||
default:
|
||||
dst.first() = src.first();
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const IndexRange first = range_fn(0);
|
||||
|
||||
if (src.size() == 2) {
|
||||
evaluate_segment(src.first(), src.first(), src.last(), src.last(), dst.slice(first));
|
||||
evaluate_segment(order, src.first(), src.first(), src.last(), src.last(), dst.slice(first));
|
||||
if (cyclic) {
|
||||
const IndexRange last = range_fn(1);
|
||||
evaluate_segment(src.last(), src.last(), src.first(), src.first(), dst.slice(last));
|
||||
evaluate_segment(order, src.last(), src.last(), src.first(), src.first(), dst.slice(last));
|
||||
}
|
||||
else {
|
||||
dst.last() = src.last();
|
||||
dst.last() = interpolate(order, src.first(), src.first(), src.last(), src.last(), 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -80,17 +116,19 @@ static void interpolate_to_evaluated(const Span<T> src,
|
|||
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(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));
|
||||
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));
|
||||
}
|
||||
else {
|
||||
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));
|
||||
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));
|
||||
/* 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() = src.last();
|
||||
dst.last() = interpolate(order, src.last(2), src.last(1), src.last(), src.last(), 1);
|
||||
}
|
||||
|
||||
/* Evaluate every segment that isn't the first or last. */
|
||||
|
@ -98,7 +136,7 @@ static void interpolate_to_evaluated(const Span<T> src,
|
|||
threading::parallel_for(inner_range, 512, [&](IndexRange range) {
|
||||
for (const int i : range) {
|
||||
const IndexRange segment = range_fn(i);
|
||||
evaluate_segment(src[i - 1], src[i], src[i + 1], src[i + 2], dst.slice(segment));
|
||||
evaluate_segment(order, src[i - 1], src[i], src[i + 1], src[i + 2], dst.slice(segment));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -112,6 +150,7 @@ static void interpolate_to_evaluated(const Span<T> src,
|
|||
{
|
||||
BLI_assert(dst.size() == calculate_evaluated_num(src.size(), cyclic, resolution));
|
||||
interpolate_to_evaluated(
|
||||
0,
|
||||
src,
|
||||
cyclic,
|
||||
[resolution](const int segment_i) -> IndexRange {
|
||||
|
@ -128,6 +167,7 @@ static void interpolate_to_evaluated(const Span<T> src,
|
|||
|
||||
{
|
||||
interpolate_to_evaluated(
|
||||
0,
|
||||
src,
|
||||
cyclic,
|
||||
[evaluated_offsets](const int segment_i) -> IndexRange {
|
||||
|
@ -158,4 +198,76 @@ 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)
|
||||
{
|
||||
interpolate_to_evaluated(
|
||||
1,
|
||||
positions,
|
||||
is_cyclic,
|
||||
[resolution](const int segment_i) -> IndexRange {
|
||||
return {segment_i * resolution, resolution};
|
||||
},
|
||||
evaluated_tangents);
|
||||
|
||||
for (const int i : evaluated_tangents.index_range()) {
|
||||
evaluated_tangents[i] = math::normalize(evaluated_tangents[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Calculate analytical normals from the first and the second derivative. */
|
||||
void calculate_normals(const Span<float3> positions,
|
||||
const bool is_cyclic,
|
||||
const int resolution,
|
||||
const Span<float3> evaluated_tangents,
|
||||
MutableSpan<float3> evaluated_normals)
|
||||
{
|
||||
/* Compute the second derivative (r'') of the control points, evaluated at t = 0. */
|
||||
interpolate_to_evaluated(
|
||||
2,
|
||||
positions,
|
||||
is_cyclic,
|
||||
[resolution](const int segment_i) -> IndexRange {
|
||||
return {segment_i * resolution, 1};
|
||||
},
|
||||
evaluated_normals);
|
||||
|
||||
/* The normals along the segment is interpolated between the control points. */
|
||||
const float step = 1.0f / resolution;
|
||||
for (const int i : positions.index_range()) {
|
||||
if (i == positions.size() - 1 && !is_cyclic) {
|
||||
break;
|
||||
}
|
||||
const float3 last_normal = evaluated_normals[i * resolution];
|
||||
float3 next_normal = (i == positions.size() - 1) ? evaluated_normals[0] :
|
||||
evaluated_normals[(i + 1) * resolution];
|
||||
if (!is_cyclic && math::dot(last_normal, next_normal) < 0) {
|
||||
/* Prevent sudden normal changes. */
|
||||
next_normal = -next_normal;
|
||||
evaluated_normals[(i + 1) * resolution] = next_normal;
|
||||
}
|
||||
for (int j = 1; j < resolution; j++) {
|
||||
const float t = j * step;
|
||||
evaluated_normals[i * resolution + j] = (1.0f - t) * last_normal + t * next_normal;
|
||||
}
|
||||
}
|
||||
|
||||
MutableSpan<float3> binormals(evaluated_normals);
|
||||
|
||||
/* Compute evaluated_normals of the control points. */
|
||||
for (const int i : evaluated_normals.index_range()) {
|
||||
/* B = normalize(r' x r'') */
|
||||
binormals[i] = math::cross(evaluated_tangents[i], evaluated_normals[i]);
|
||||
/* N = B x T */
|
||||
evaluated_normals[i] = math::normalize(math::cross(binormals[i], evaluated_tangents[i]));
|
||||
}
|
||||
|
||||
if (positions.size() == 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::bke::curves::catmull_rom
|
||||
|
|
|
@ -50,6 +50,7 @@ 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:
|
||||
|
|
|
@ -199,4 +199,77 @@ 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
|
||||
|
|
|
@ -673,17 +673,31 @@ Span<float3> CurvesGeometry::evaluated_tangents() const
|
|||
{
|
||||
this->runtime->tangent_cache_mutex.ensure([&]() {
|
||||
const Span<float3> evaluated_positions = this->evaluated_positions();
|
||||
Span<float3> positions = this->positions();
|
||||
VArray<int> resolution = this->resolution();
|
||||
const VArray<bool> cyclic = this->cyclic();
|
||||
const VArray<int8_t> types = this->curve_types();
|
||||
|
||||
this->runtime->evaluated_tangent_cache.resize(this->evaluated_points_num());
|
||||
MutableSpan<float3> tangents = this->runtime->evaluated_tangent_cache;
|
||||
|
||||
threading::parallel_for(this->curves_range(), 128, [&](IndexRange curves_range) {
|
||||
for (const int curve_index : curves_range) {
|
||||
const IndexRange points = this->points_for_curve(curve_index);
|
||||
const IndexRange evaluated_points = this->evaluated_points_for_curve(curve_index);
|
||||
curves::poly::calculate_tangents(evaluated_positions.slice(evaluated_points),
|
||||
cyclic[curve_index],
|
||||
tangents.slice(evaluated_points));
|
||||
switch (types[curve_index]) {
|
||||
case CURVE_TYPE_CATMULL_ROM:
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -720,6 +734,7 @@ Span<float3> CurvesGeometry::evaluated_tangents() const
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
return this->runtime->evaluated_tangent_cache;
|
||||
}
|
||||
|
||||
|
@ -737,6 +752,8 @@ Span<float3> CurvesGeometry::evaluated_normals() const
|
|||
this->runtime->normal_cache_mutex.ensure([&]() {
|
||||
const Span<float3> evaluated_tangents = this->evaluated_tangents();
|
||||
const VArray<bool> cyclic = this->cyclic();
|
||||
Span<float3> positions = this->positions();
|
||||
VArray<int> resolution = this->resolution();
|
||||
const VArray<int8_t> normal_mode = this->normal_mode();
|
||||
const VArray<int8_t> types = this->curve_types();
|
||||
const VArray<float> tilt = this->tilt();
|
||||
|
@ -749,6 +766,7 @@ Span<float3> CurvesGeometry::evaluated_normals() const
|
|||
Vector<float> evaluated_tilts;
|
||||
|
||||
for (const int curve_index : curves_range) {
|
||||
const IndexRange points = this->points_for_curve(curve_index);
|
||||
const IndexRange evaluated_points = this->evaluated_points_for_curve(curve_index);
|
||||
switch (normal_mode[curve_index]) {
|
||||
case NORMAL_MODE_Z_UP:
|
||||
|
@ -760,6 +778,25 @@ 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],
|
||||
evaluated_tangents.slice(evaluated_points),
|
||||
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
|
||||
|
|
|
@ -199,6 +199,10 @@ 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;
|
||||
}
|
||||
|
@ -504,7 +508,7 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
|
|||
static const fn::CustomMF_SI_SO<int8_t, int8_t> normal_mode_clamp{
|
||||
"Normal Mode Validate",
|
||||
[](int8_t value) {
|
||||
return std::clamp<int8_t>(value, NORMAL_MODE_MINIMUM_TWIST, NORMAL_MODE_Z_UP);
|
||||
return std::clamp<int8_t>(value, NORMAL_MODE_MINIMUM_TWIST, NORMAL_MODE_CURVATURE_VECTOR);
|
||||
},
|
||||
fn::CustomMF_presets::AllSpanOrSingle()};
|
||||
static BuiltinCustomDataLayerProvider normal_mode("normal_mode",
|
||||
|
|
|
@ -373,6 +373,7 @@ 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);
|
||||
|
||||
/* Explicitly set all other attributes besides those processed above to default values. */
|
||||
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
|
||||
|
|
|
@ -262,11 +262,12 @@ 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<T>(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(0,
|
||||
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(
|
||||
|
|
|
@ -80,6 +80,7 @@ typedef enum KnotsMode {
|
|||
typedef enum NormalMode {
|
||||
NORMAL_MODE_MINIMUM_TWIST = 0,
|
||||
NORMAL_MODE_Z_UP = 1,
|
||||
NORMAL_MODE_CURVATURE_VECTOR = 2,
|
||||
} NormalMode;
|
||||
|
||||
/**
|
||||
|
|
|
@ -38,6 +38,11 @@ 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 aligned with the local curvature vectors."},
|
||||
{0, NULL, 0, NULL, NULL},
|
||||
};
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit fdfa2fcb9495d87571f2dfe2ae9fa0e032536600
|
||||
Subproject commit e1744b9bd82527cf7e8af63362b61bd309b5711b
|
Loading…
Reference in New Issue