Curves: Port tangent and normal calculation to the new data-block
Port the "Normal" and "Curve Tangent" nodes to the new curves data-block to avoid the conversion to `CurveEval`. This should make them faster by avoiding all that copying, but otherwise nothing else has changed. This also includes a fix to move the normal mode as a built-in curve attribute when converting to and from `CurveEval`. The attribute is needed because the option is used implicitly in many nodes currently. Differential Revision: https://developer.blender.org/D14609
This commit is contained in:
parent
69a4d113e8
commit
ceed37fc5c
Notes:
blender-bot
2023-02-14 19:45:25 +01:00
Referenced by commit 47d961a4b1
, Fix: Apply tilt in curves data-block normals calculation
Referenced by issue #95443, Refactor curve nodes to use new data structure
|
@ -158,6 +158,9 @@ class CurvesGeometry : public ::CurvesGeometry {
|
|||
/** Return the number of curves with each type. */
|
||||
std::array<int, CURVE_TYPES_NUM> count_curve_types() const;
|
||||
|
||||
/** Return true if all of the curves have the provided type. */
|
||||
bool is_single_type(CurveType type) const;
|
||||
|
||||
Span<float3> positions() const;
|
||||
MutableSpan<float3> positions_for_write();
|
||||
|
||||
|
@ -174,6 +177,13 @@ class CurvesGeometry : public ::CurvesGeometry {
|
|||
/** Mutable access to curve resolution. Call #tag_topology_changed after changes. */
|
||||
MutableSpan<int> resolution_for_write();
|
||||
|
||||
/**
|
||||
* Which method to use for calculating the normals of evaluated points (#NormalMode).
|
||||
* Call #tag_normals_changed after changes.
|
||||
*/
|
||||
VArray<int8_t> normal_mode() const;
|
||||
MutableSpan<int8_t> normal_mode_for_write();
|
||||
|
||||
/**
|
||||
* Handle types for Bezier control points. Call #tag_topology_changed after changes.
|
||||
*/
|
||||
|
@ -280,6 +290,8 @@ class CurvesGeometry : public ::CurvesGeometry {
|
|||
Span<int> bezier_evaluated_offsets_for_curve(int curve_index) const;
|
||||
|
||||
Span<float3> evaluated_positions() const;
|
||||
Span<float3> evaluated_tangents() const;
|
||||
Span<float3> evaluated_normals() const;
|
||||
|
||||
/**
|
||||
* Return a cache of accumulated lengths along the curve. Each item is the length of the
|
||||
|
@ -379,6 +391,31 @@ inline float3 decode_surface_bary_coord(const float2 &v)
|
|||
return {v.x, v.y, 1.0f - v.x - v.y};
|
||||
}
|
||||
|
||||
namespace poly {
|
||||
|
||||
/**
|
||||
* Calculate the direction at every point, defined as the normalized average of the two neighboring
|
||||
* segments (and if non-cyclic, the direction of the first and last segments). This is different
|
||||
* than evaluating the derivative of the basis functions for curve types like NURBS, Bezier, or
|
||||
* Catmull Rom, though the results may be similar.
|
||||
*/
|
||||
void calculate_tangents(Span<float3> positions, bool is_cyclic, MutableSpan<float3> tangents);
|
||||
|
||||
/**
|
||||
* Calculate directions perpendicular to the tangent at every point by rotating an arbitrary
|
||||
* starting vector by the same rotation of each tangent. If the curve is cylic, propagate a
|
||||
* correction through the entire to make sure the first and last normal align.
|
||||
*/
|
||||
void calculate_normals_minimum(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).
|
||||
*/
|
||||
void calculate_normals_z_up(Span<float3> tangents, MutableSpan<float3> normals);
|
||||
|
||||
} // namespace poly
|
||||
|
||||
namespace bezier {
|
||||
|
||||
/**
|
||||
|
@ -586,6 +623,11 @@ inline IndexRange CurvesGeometry::curves_range() const
|
|||
return IndexRange(this->curves_num());
|
||||
}
|
||||
|
||||
inline bool CurvesGeometry::is_single_type(const CurveType type) const
|
||||
{
|
||||
return this->count_curve_types()[type] == this->curves_num();
|
||||
}
|
||||
|
||||
inline IndexRange CurvesGeometry::points_for_curve(const int index) const
|
||||
{
|
||||
/* Offsets are not allocated when there are no curves. */
|
||||
|
|
|
@ -112,6 +112,7 @@ set(SRC
|
|||
intern/curve_deform.c
|
||||
intern/curve_eval.cc
|
||||
intern/curve_nurbs.cc
|
||||
intern/curve_poly.cc
|
||||
intern/curve_to_mesh_convert.cc
|
||||
intern/curveprofile.cc
|
||||
intern/curves.cc
|
||||
|
|
|
@ -381,6 +381,7 @@ std::unique_ptr<CurveEval> curves_to_curve_eval(const Curves &curves)
|
|||
curves.geometry);
|
||||
|
||||
VArray<int> resolution = geometry.resolution();
|
||||
VArray<int8_t> normal_mode = geometry.normal_mode();
|
||||
|
||||
VArray_Span<float> nurbs_weights{
|
||||
src_component.attribute_get_for_read<float>("nurbs_weight", ATTR_DOMAIN_POINT, 0.0f)};
|
||||
|
@ -436,6 +437,7 @@ std::unique_ptr<CurveEval> curves_to_curve_eval(const Curves &curves)
|
|||
spline->positions().fill(float3(0));
|
||||
spline->tilts().fill(0.0f);
|
||||
spline->radii().fill(1.0f);
|
||||
spline->normal_mode = static_cast<NormalMode>(normal_mode[curve_index]);
|
||||
curve_eval->add_spline(std::move(spline));
|
||||
}
|
||||
|
||||
|
@ -448,6 +450,7 @@ std::unique_ptr<CurveEval> curves_to_curve_eval(const Curves &curves)
|
|||
dst_component,
|
||||
{"curve_type",
|
||||
"resolution",
|
||||
"normal_mode",
|
||||
"nurbs_weight",
|
||||
"nurbs_order",
|
||||
"knots_mode",
|
||||
|
@ -468,6 +471,8 @@ Curves *curve_eval_to_curves(const CurveEval &curve_eval)
|
|||
geometry.offsets_for_write().copy_from(curve_eval.control_point_offsets());
|
||||
MutableSpan<int8_t> curve_types = geometry.curve_types_for_write();
|
||||
|
||||
OutputAttribute_Typed<int8_t> normal_mode =
|
||||
dst_component.attribute_try_get_for_output_only<int8_t>("normal_mode", ATTR_DOMAIN_CURVE);
|
||||
OutputAttribute_Typed<float> nurbs_weight;
|
||||
OutputAttribute_Typed<int> nurbs_order;
|
||||
OutputAttribute_Typed<int8_t> nurbs_knots_mode;
|
||||
|
@ -491,7 +496,7 @@ Curves *curve_eval_to_curves(const CurveEval &curve_eval)
|
|||
for (const int curve_index : curve_eval.splines().index_range()) {
|
||||
const Spline &spline = *curve_eval.splines()[curve_index];
|
||||
curve_types[curve_index] = curve_eval.splines()[curve_index]->type();
|
||||
|
||||
normal_mode.as_span()[curve_index] = curve_eval.splines()[curve_index]->normal_mode;
|
||||
const IndexRange point_range = geometry.points_for_curve(curve_index);
|
||||
|
||||
switch (spline.type()) {
|
||||
|
@ -517,6 +522,7 @@ Curves *curve_eval_to_curves(const CurveEval &curve_eval)
|
|||
}
|
||||
}
|
||||
|
||||
normal_mode.save();
|
||||
nurbs_weight.save();
|
||||
nurbs_order.save();
|
||||
nurbs_knots_mode.save();
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup bke
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_math_vector.hh"
|
||||
|
||||
#include "BKE_curves.hh"
|
||||
|
||||
namespace blender::bke::curves::poly {
|
||||
|
||||
static float3 direction_bisect(const float3 &prev, const float3 &middle, const float3 &next)
|
||||
{
|
||||
const float3 dir_prev = math::normalize(middle - prev);
|
||||
const float3 dir_next = math::normalize(next - middle);
|
||||
|
||||
const float3 result = math::normalize(dir_prev + dir_next);
|
||||
if (UNLIKELY(math::is_zero(result))) {
|
||||
return float3(0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void calculate_tangents(const Span<float3> positions,
|
||||
const bool is_cyclic,
|
||||
MutableSpan<float3> tangents)
|
||||
{
|
||||
BLI_assert(positions.size() == tangents.size());
|
||||
|
||||
if (positions.size() == 1) {
|
||||
tangents.first() = float3(0.0f, 0.0f, 1.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const int i : IndexRange(1, positions.size() - 2)) {
|
||||
tangents[i] = direction_bisect(positions[i - 1], positions[i], positions[i + 1]);
|
||||
}
|
||||
|
||||
if (is_cyclic) {
|
||||
const float3 &second_to_last = positions[positions.size() - 2];
|
||||
const float3 &last = positions.last();
|
||||
const float3 &first = positions.first();
|
||||
const float3 &second = positions[1];
|
||||
tangents.first() = direction_bisect(last, first, second);
|
||||
tangents.last() = direction_bisect(second_to_last, last, first);
|
||||
}
|
||||
else {
|
||||
tangents.first() = math::normalize(positions[1] - positions.first());
|
||||
tangents.last() = math::normalize(positions.last() - positions[positions.size() - 2]);
|
||||
}
|
||||
}
|
||||
|
||||
static float3 rotate_direction_around_axis(const float3 &direction,
|
||||
const float3 &axis,
|
||||
const float angle)
|
||||
{
|
||||
BLI_ASSERT_UNIT_V3(direction);
|
||||
BLI_ASSERT_UNIT_V3(axis);
|
||||
|
||||
const float3 axis_scaled = axis * math::dot(direction, axis);
|
||||
const float3 diff = direction - axis_scaled;
|
||||
const float3 cross = math::cross(axis, diff);
|
||||
|
||||
return axis_scaled + diff * std::cos(angle) + cross * std::sin(angle);
|
||||
}
|
||||
|
||||
void calculate_normals_z_up(const Span<float3> tangents, MutableSpan<float3> normals)
|
||||
{
|
||||
BLI_assert(normals.size() == tangents.size());
|
||||
|
||||
/* Same as in `vec_to_quat`. */
|
||||
const float epsilon = 1e-4f;
|
||||
for (const int i : normals.index_range()) {
|
||||
const float3 &tangent = tangents[i];
|
||||
if (std::abs(tangent.x) + std::abs(tangent.y) < epsilon) {
|
||||
normals[i] = {1.0f, 0.0f, 0.0f};
|
||||
}
|
||||
else {
|
||||
normals[i] = math::normalize(float3(tangent.y, -tangent.x, 0.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate the last normal in the same way the tangent has been rotated.
|
||||
*/
|
||||
static float3 calculate_next_normal(const float3 &last_normal,
|
||||
const float3 &last_tangent,
|
||||
const float3 ¤t_tangent)
|
||||
{
|
||||
if (math::is_zero(last_tangent) || math::is_zero(current_tangent)) {
|
||||
return last_normal;
|
||||
}
|
||||
const float angle = angle_normalized_v3v3(last_tangent, current_tangent);
|
||||
if (angle != 0.0) {
|
||||
const float3 axis = math::normalize(math::cross(last_tangent, current_tangent));
|
||||
return rotate_direction_around_axis(last_normal, axis, angle);
|
||||
}
|
||||
return last_normal;
|
||||
}
|
||||
|
||||
void calculate_normals_minimum(const Span<float3> tangents,
|
||||
const bool cyclic,
|
||||
MutableSpan<float3> normals)
|
||||
{
|
||||
BLI_assert(normals.size() == tangents.size());
|
||||
|
||||
if (normals.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float epsilon = 1e-4f;
|
||||
|
||||
/* Set initial normal. */
|
||||
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));
|
||||
}
|
||||
|
||||
/* Forward normal with minimum twist along the entire spline. */
|
||||
for (const int i : IndexRange(1, normals.size() - 1)) {
|
||||
normals[i] = calculate_next_normal(normals[i - 1], tangents[i - 1], tangents[i]);
|
||||
}
|
||||
|
||||
if (!cyclic) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Compute how much the first normal deviates from the normal that has been forwarded along the
|
||||
* entire cyclic spline. */
|
||||
const float3 uncorrected_last_normal = calculate_next_normal(
|
||||
normals.last(), tangents.last(), tangents.first());
|
||||
float correction_angle = angle_signed_on_axis_v3v3_v3(
|
||||
normals.first(), uncorrected_last_normal, tangents.first());
|
||||
if (correction_angle > M_PI) {
|
||||
correction_angle = correction_angle - 2 * M_PI;
|
||||
}
|
||||
|
||||
/* Gradually apply correction by rotating all normals slightly. */
|
||||
const float angle_step = correction_angle / normals.size();
|
||||
for (const int i : normals.index_range()) {
|
||||
const float angle = angle_step * i;
|
||||
normals[i] = rotate_direction_around_axis(normals[i], tangents[i], angle);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::bke::curves::poly
|
|
@ -25,6 +25,7 @@ static const std::string ATTR_RADIUS = "radius";
|
|||
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_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";
|
||||
|
@ -320,6 +321,15 @@ MutableSpan<int> CurvesGeometry::resolution_for_write()
|
|||
return get_mutable_attribute<int>(*this, ATTR_DOMAIN_CURVE, ATTR_RESOLUTION, 12);
|
||||
}
|
||||
|
||||
VArray<int8_t> CurvesGeometry::normal_mode() const
|
||||
{
|
||||
return get_varray_attribute<int8_t>(*this, ATTR_DOMAIN_CURVE, ATTR_NORMAL_MODE, 0);
|
||||
}
|
||||
MutableSpan<int8_t> CurvesGeometry::normal_mode_for_write()
|
||||
{
|
||||
return get_mutable_attribute<int8_t>(*this, ATTR_DOMAIN_CURVE, ATTR_NORMAL_MODE);
|
||||
}
|
||||
|
||||
VArray<int8_t> CurvesGeometry::handle_types_left() const
|
||||
{
|
||||
return get_varray_attribute<int8_t>(*this, ATTR_DOMAIN_POINT, ATTR_HANDLE_TYPE_LEFT, 0);
|
||||
|
@ -638,6 +648,113 @@ Span<float3> CurvesGeometry::evaluated_positions() const
|
|||
return this->runtime->evaluated_position_cache;
|
||||
}
|
||||
|
||||
Span<float3> CurvesGeometry::evaluated_tangents() const
|
||||
{
|
||||
if (!this->runtime->tangent_cache_dirty) {
|
||||
return this->runtime->evaluated_tangent_cache;
|
||||
}
|
||||
|
||||
/* A double checked lock. */
|
||||
std::scoped_lock lock{this->runtime->tangent_cache_mutex};
|
||||
if (!this->runtime->tangent_cache_dirty) {
|
||||
return this->runtime->evaluated_tangent_cache;
|
||||
}
|
||||
|
||||
threading::isolate_task([&]() {
|
||||
const Span<float3> evaluated_positions = this->evaluated_positions();
|
||||
const VArray<bool> cyclic = this->cyclic();
|
||||
|
||||
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 evaluated_points = this->evaluated_points_for_curve(curve_index);
|
||||
if (UNLIKELY(evaluated_points.is_empty())) {
|
||||
continue;
|
||||
}
|
||||
curves::poly::calculate_tangents(evaluated_positions.slice(evaluated_points),
|
||||
cyclic[curve_index],
|
||||
tangents.slice(evaluated_points));
|
||||
}
|
||||
});
|
||||
|
||||
/* Correct the first and last tangents of Bezier curves so that they align with the inner
|
||||
* handles. This is a separate loop to avoid the cost when Bezier type curves are not used. */
|
||||
Vector<int64_t> bezier_indices;
|
||||
const IndexMask bezier_mask = this->indices_for_curve_type(CURVE_TYPE_BEZIER, bezier_indices);
|
||||
if (!bezier_mask.is_empty()) {
|
||||
const Span<float3> positions = this->positions();
|
||||
const Span<float3> handles_left = this->handle_positions_left();
|
||||
const Span<float3> handles_right = this->handle_positions_right();
|
||||
|
||||
threading::parallel_for(bezier_mask.index_range(), 1024, [&](IndexRange range) {
|
||||
for (const int curve_index : bezier_mask.slice(range)) {
|
||||
const IndexRange points = this->points_for_curve(curve_index);
|
||||
const IndexRange evaluated_points = this->evaluated_points_for_curve(curve_index);
|
||||
|
||||
if (handles_right[points.first()] != positions[points.first()]) {
|
||||
tangents[evaluated_points.first()] = math::normalize(handles_right[points.first()] -
|
||||
positions[points.first()]);
|
||||
}
|
||||
if (handles_left[points.last()] != positions[points.last()]) {
|
||||
tangents[evaluated_points.last()] = math::normalize(positions[points.last()] -
|
||||
handles_left[points.last()]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this->runtime->tangent_cache_dirty = false;
|
||||
return this->runtime->evaluated_tangent_cache;
|
||||
}
|
||||
|
||||
Span<float3> CurvesGeometry::evaluated_normals() const
|
||||
{
|
||||
if (!this->runtime->normal_cache_dirty) {
|
||||
return this->runtime->evaluated_normal_cache;
|
||||
}
|
||||
|
||||
/* A double checked lock. */
|
||||
std::scoped_lock lock{this->runtime->normal_cache_mutex};
|
||||
if (!this->runtime->normal_cache_dirty) {
|
||||
return this->runtime->evaluated_normal_cache;
|
||||
}
|
||||
|
||||
threading::isolate_task([&]() {
|
||||
const Span<float3> evaluated_tangents = this->evaluated_tangents();
|
||||
const VArray<bool> cyclic = this->cyclic();
|
||||
const VArray<int8_t> normal_mode = this->normal_mode();
|
||||
|
||||
this->runtime->evaluated_normal_cache.resize(this->evaluated_points_num());
|
||||
MutableSpan<float3> evaluated_normals = this->runtime->evaluated_normal_cache;
|
||||
|
||||
threading::parallel_for(this->curves_range(), 128, [&](IndexRange curves_range) {
|
||||
for (const int curve_index : curves_range) {
|
||||
const IndexRange evaluated_points = this->evaluated_points_for_curve(curve_index);
|
||||
if (UNLIKELY(evaluated_points.is_empty())) {
|
||||
continue;
|
||||
}
|
||||
switch (normal_mode[curve_index]) {
|
||||
case NORMAL_MODE_Z_UP:
|
||||
curves::poly::calculate_normals_z_up(evaluated_tangents.slice(evaluated_points),
|
||||
evaluated_normals.slice(evaluated_points));
|
||||
break;
|
||||
case NORMAL_MODE_MINIMUM_TWIST:
|
||||
curves::poly::calculate_normals_minimum(evaluated_tangents.slice(evaluated_points),
|
||||
cyclic[curve_index],
|
||||
evaluated_normals.slice(evaluated_points));
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this->runtime->normal_cache_dirty = false;
|
||||
return this->runtime->evaluated_normal_cache;
|
||||
}
|
||||
|
||||
void CurvesGeometry::interpolate_to_evaluated(const int curve_index,
|
||||
const GSpan src,
|
||||
GMutableSpan dst) const
|
||||
|
|
|
@ -141,81 +141,97 @@ const Curve *CurveComponent::get_curve_for_render() const
|
|||
|
||||
namespace blender::bke {
|
||||
|
||||
static void calculate_bezier_normals(const BezierSpline &spline, MutableSpan<float3> normals)
|
||||
static Array<float3> curve_normal_point_domain(const bke::CurvesGeometry &curves)
|
||||
{
|
||||
Span<int> offsets = spline.control_point_offsets();
|
||||
Span<float3> evaluated_normals = spline.evaluated_normals();
|
||||
for (const int i : IndexRange(spline.size())) {
|
||||
normals[i] = evaluated_normals[offsets[i]];
|
||||
}
|
||||
}
|
||||
const VArray<int8_t> types = curves.curve_types();
|
||||
const VArray<int> resolutions = curves.resolution();
|
||||
const VArray<bool> curves_cyclic = curves.cyclic();
|
||||
|
||||
static void calculate_poly_normals(const PolySpline &spline, MutableSpan<float3> normals)
|
||||
{
|
||||
normals.copy_from(spline.evaluated_normals());
|
||||
}
|
||||
const Span<float3> positions = curves.positions();
|
||||
const VArray<int8_t> normal_modes = curves.normal_mode();
|
||||
|
||||
/**
|
||||
* Because NURBS control points are not necessarily on the path, the normal at the control points
|
||||
* is not well defined, so create a temporary poly spline to find the normals. This requires extra
|
||||
* copying currently, but may be more efficient in the future if attributes have some form of CoW.
|
||||
*/
|
||||
static void calculate_nurbs_normals(const NURBSpline &spline, MutableSpan<float3> normals)
|
||||
{
|
||||
PolySpline poly_spline;
|
||||
poly_spline.resize(spline.size());
|
||||
poly_spline.positions().copy_from(spline.positions());
|
||||
poly_spline.tilts().copy_from(spline.tilts());
|
||||
normals.copy_from(poly_spline.evaluated_normals());
|
||||
}
|
||||
const Span<float3> evaluated_normals = curves.evaluated_normals();
|
||||
|
||||
static Array<float3> curve_normal_point_domain(const CurveEval &curve)
|
||||
{
|
||||
Span<SplinePtr> splines = curve.splines();
|
||||
Array<int> offsets = curve.control_point_offsets();
|
||||
const int total_size = offsets.last();
|
||||
Array<float3> normals(total_size);
|
||||
Array<float3> results(curves.points_num());
|
||||
|
||||
threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) {
|
||||
for (const int i : range) {
|
||||
const Spline &spline = *splines[i];
|
||||
MutableSpan spline_normals{normals.as_mutable_span().slice(offsets[i], spline.size())};
|
||||
switch (splines[i]->type()) {
|
||||
case CURVE_TYPE_BEZIER:
|
||||
calculate_bezier_normals(static_cast<const BezierSpline &>(spline), spline_normals);
|
||||
threading::parallel_for(curves.curves_range(), 128, [&](IndexRange range) {
|
||||
Vector<float3> nurbs_tangents;
|
||||
|
||||
for (const int i_curve : range) {
|
||||
const IndexRange points = curves.points_for_curve(i_curve);
|
||||
const IndexRange evaluated_points = curves.evaluated_points_for_curve(i_curve);
|
||||
|
||||
MutableSpan<float3> curve_normals = results.as_mutable_span().slice(points);
|
||||
|
||||
switch (types[i_curve]) {
|
||||
case CURVE_TYPE_CATMULL_ROM: {
|
||||
const Span<float3> normals = evaluated_normals.slice(evaluated_points);
|
||||
const int resolution = resolutions[i_curve];
|
||||
for (const int i : IndexRange(points.size())) {
|
||||
curve_normals[i] = normals[resolution * i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CURVE_TYPE_POLY:
|
||||
calculate_poly_normals(static_cast<const PolySpline &>(spline), spline_normals);
|
||||
curve_normals.copy_from(evaluated_normals.slice(evaluated_points));
|
||||
break;
|
||||
case CURVE_TYPE_NURBS:
|
||||
calculate_nurbs_normals(static_cast<const NURBSpline &>(spline), spline_normals);
|
||||
case CURVE_TYPE_BEZIER: {
|
||||
const Span<float3> normals = evaluated_normals.slice(evaluated_points);
|
||||
curve_normals.first() = normals.first();
|
||||
const Span<int> offsets = curves.bezier_evaluated_offsets_for_curve(i_curve);
|
||||
for (const int i : IndexRange(points.size()).drop_front(1)) {
|
||||
curve_normals[i] = normals[offsets[i - 1]];
|
||||
}
|
||||
break;
|
||||
case CURVE_TYPE_CATMULL_ROM:
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
case CURVE_TYPE_NURBS: {
|
||||
/* For NURBS curves there is no obvious correspondence between specific evaluated points
|
||||
* and control points, so normals are determined by treating them as poly curves. */
|
||||
nurbs_tangents.clear();
|
||||
nurbs_tangents.resize(points.size());
|
||||
const bool cyclic = curves_cyclic[i_curve];
|
||||
const Span<float3> curve_positions = positions.slice(points);
|
||||
bke::curves::poly::calculate_tangents(curve_positions, cyclic, nurbs_tangents);
|
||||
switch (NormalMode(normal_modes[i_curve])) {
|
||||
case NORMAL_MODE_Z_UP:
|
||||
bke::curves::poly::calculate_normals_z_up(nurbs_tangents, curve_normals);
|
||||
break;
|
||||
case NORMAL_MODE_MINIMUM_TWIST:
|
||||
bke::curves::poly::calculate_normals_minimum(nurbs_tangents, cyclic, curve_normals);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return normals;
|
||||
return results;
|
||||
}
|
||||
|
||||
VArray<float3> curve_normals_varray(const CurveComponent &component, const AttributeDomain domain)
|
||||
{
|
||||
if (component.is_empty()) {
|
||||
return nullptr;
|
||||
if (!component.has_curves()) {
|
||||
return {};
|
||||
}
|
||||
const std::unique_ptr<CurveEval> curve = curves_to_curve_eval(*component.get_for_read());
|
||||
|
||||
const Curves &curves_id = *component.get_for_read();
|
||||
const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry);
|
||||
|
||||
const VArray<int8_t> types = curves.curve_types();
|
||||
if (curves.is_single_type(CURVE_TYPE_POLY)) {
|
||||
return component.attribute_try_adapt_domain<float3>(
|
||||
VArray<float3>::ForSpan(curves.evaluated_normals()), ATTR_DOMAIN_POINT, domain);
|
||||
}
|
||||
|
||||
Array<float3> normals = curve_normal_point_domain(curves);
|
||||
|
||||
if (domain == ATTR_DOMAIN_POINT) {
|
||||
Array<float3> normals = curve_normal_point_domain(*curve);
|
||||
return VArray<float3>::ForContainer(std::move(normals));
|
||||
}
|
||||
|
||||
if (domain == ATTR_DOMAIN_CURVE) {
|
||||
Array<float3> point_normals = curve_normal_point_domain(*curve);
|
||||
VArray<float3> varray = VArray<float3>::ForContainer(std::move(point_normals));
|
||||
return component.attribute_try_adapt_domain<float3>(
|
||||
std::move(varray), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE);
|
||||
VArray<float3>::ForContainer(std::move(normals)), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
@ -456,6 +472,18 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
|
|||
make_array_write_attribute<int>,
|
||||
tag_component_topology_changed);
|
||||
|
||||
static BuiltinCustomDataLayerProvider normal_mode("normal_mode",
|
||||
ATTR_DOMAIN_CURVE,
|
||||
CD_PROP_INT8,
|
||||
CD_PROP_INT8,
|
||||
BuiltinAttributeProvider::Creatable,
|
||||
BuiltinAttributeProvider::Writable,
|
||||
BuiltinAttributeProvider::Deletable,
|
||||
curve_access,
|
||||
make_array_read_attribute<int8_t>,
|
||||
make_array_write_attribute<int8_t>,
|
||||
tag_component_normals_changed);
|
||||
|
||||
static BuiltinCustomDataLayerProvider nurbs_knots_mode("knots_mode",
|
||||
ATTR_DOMAIN_CURVE,
|
||||
CD_PROP_INT8,
|
||||
|
@ -515,6 +543,7 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
|
|||
&handle_left,
|
||||
&handle_type_right,
|
||||
&handle_type_left,
|
||||
&normal_mode,
|
||||
&nurbs_order,
|
||||
&nurbs_weight,
|
||||
&curve_type,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "BKE_spline.hh"
|
||||
#include "BKE_curves.hh"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
|
@ -13,65 +13,54 @@ static void node_declare(NodeDeclarationBuilder &b)
|
|||
b.add_output<decl::Vector>(N_("Tangent")).field_source();
|
||||
}
|
||||
|
||||
static void calculate_bezier_tangents(const BezierSpline &spline, MutableSpan<float3> tangents)
|
||||
static Array<float3> curve_tangent_point_domain(const bke::CurvesGeometry &curves)
|
||||
{
|
||||
Span<int> offsets = spline.control_point_offsets();
|
||||
Span<float3> evaluated_tangents = spline.evaluated_tangents();
|
||||
for (const int i : IndexRange(spline.size())) {
|
||||
tangents[i] = evaluated_tangents[offsets[i]];
|
||||
}
|
||||
}
|
||||
const VArray<int8_t> types = curves.curve_types();
|
||||
const VArray<int> resolutions = curves.resolution();
|
||||
const VArray<bool> cyclic = curves.cyclic();
|
||||
const Span<float3> positions = curves.positions();
|
||||
|
||||
static void calculate_poly_tangents(const PolySpline &spline, MutableSpan<float3> tangents)
|
||||
{
|
||||
tangents.copy_from(spline.evaluated_tangents());
|
||||
}
|
||||
const Span<float3> evaluated_tangents = curves.evaluated_tangents();
|
||||
|
||||
/**
|
||||
* Because NURBS control points are not necessarily on the path, the tangent at the control points
|
||||
* is not well defined, so create a temporary poly spline to find the tangents. This requires extra
|
||||
* copying currently, but may be more efficient in the future if attributes have some form of CoW.
|
||||
*/
|
||||
static void calculate_nurbs_tangents(const NURBSpline &spline, MutableSpan<float3> tangents)
|
||||
{
|
||||
PolySpline poly_spline;
|
||||
poly_spline.resize(spline.size());
|
||||
poly_spline.positions().copy_from(spline.positions());
|
||||
tangents.copy_from(poly_spline.evaluated_tangents());
|
||||
}
|
||||
Array<float3> results(curves.points_num());
|
||||
|
||||
static Array<float3> curve_tangent_point_domain(const CurveEval &curve)
|
||||
{
|
||||
Span<SplinePtr> splines = curve.splines();
|
||||
Array<int> offsets = curve.control_point_offsets();
|
||||
const int total_size = offsets.last();
|
||||
Array<float3> tangents(total_size);
|
||||
threading::parallel_for(curves.curves_range(), 128, [&](IndexRange range) {
|
||||
for (const int i_curve : range) {
|
||||
const IndexRange points = curves.points_for_curve(i_curve);
|
||||
const IndexRange evaluated_points = curves.evaluated_points_for_curve(i_curve);
|
||||
|
||||
threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) {
|
||||
for (const int i : range) {
|
||||
const Spline &spline = *splines[i];
|
||||
MutableSpan spline_tangents{tangents.as_mutable_span().slice(offsets[i], spline.size())};
|
||||
switch (splines[i]->type()) {
|
||||
case CURVE_TYPE_BEZIER: {
|
||||
calculate_bezier_tangents(static_cast<const BezierSpline &>(spline), spline_tangents);
|
||||
MutableSpan<float3> curve_tangents = results.as_mutable_span().slice(points);
|
||||
|
||||
switch (types[i_curve]) {
|
||||
case CURVE_TYPE_CATMULL_ROM: {
|
||||
Span<float3> tangents = evaluated_tangents.slice(evaluated_points);
|
||||
const int resolution = resolutions[i_curve];
|
||||
for (const int i : IndexRange(points.size())) {
|
||||
curve_tangents[i] = tangents[resolution * i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CURVE_TYPE_POLY: {
|
||||
calculate_poly_tangents(static_cast<const PolySpline &>(spline), spline_tangents);
|
||||
case CURVE_TYPE_POLY:
|
||||
curve_tangents.copy_from(evaluated_tangents.slice(evaluated_points));
|
||||
break;
|
||||
case CURVE_TYPE_BEZIER: {
|
||||
Span<float3> tangents = evaluated_tangents.slice(evaluated_points);
|
||||
curve_tangents.first() = tangents.first();
|
||||
const Span<int> offsets = curves.bezier_evaluated_offsets_for_curve(i_curve);
|
||||
for (const int i : IndexRange(points.size()).drop_front(1)) {
|
||||
curve_tangents[i] = tangents[offsets[i - 1]];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CURVE_TYPE_NURBS: {
|
||||
calculate_nurbs_tangents(static_cast<const NURBSpline &>(spline), spline_tangents);
|
||||
break;
|
||||
}
|
||||
case CURVE_TYPE_CATMULL_ROM: {
|
||||
BLI_assert_unreachable();
|
||||
const Span<float3> curve_positions = positions.slice(points);
|
||||
bke::curves::poly::calculate_tangents(curve_positions, cyclic[i_curve], curve_tangents);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return tangents;
|
||||
return results;
|
||||
}
|
||||
|
||||
static VArray<float3> construct_curve_tangent_gvarray(const CurveComponent &component,
|
||||
|
@ -80,19 +69,25 @@ static VArray<float3> construct_curve_tangent_gvarray(const CurveComponent &comp
|
|||
if (!component.has_curves()) {
|
||||
return {};
|
||||
}
|
||||
const std::unique_ptr<CurveEval> curve = curves_to_curve_eval(*component.get_for_read());
|
||||
|
||||
const Curves &curves_id = *component.get_for_read();
|
||||
const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry);
|
||||
|
||||
const VArray<int8_t> types = curves.curve_types();
|
||||
if (curves.is_single_type(CURVE_TYPE_POLY)) {
|
||||
return component.attribute_try_adapt_domain<float3>(
|
||||
VArray<float3>::ForSpan(curves.evaluated_tangents()), ATTR_DOMAIN_POINT, domain);
|
||||
}
|
||||
|
||||
Array<float3> tangents = curve_tangent_point_domain(curves);
|
||||
|
||||
if (domain == ATTR_DOMAIN_POINT) {
|
||||
Array<float3> tangents = curve_tangent_point_domain(*curve);
|
||||
return VArray<float3>::ForContainer(std::move(tangents));
|
||||
}
|
||||
|
||||
if (domain == ATTR_DOMAIN_CURVE) {
|
||||
Array<float3> point_tangents = curve_tangent_point_domain(*curve);
|
||||
return component.attribute_try_adapt_domain<float3>(
|
||||
VArray<float3>::ForContainer(std::move(point_tangents)),
|
||||
ATTR_DOMAIN_POINT,
|
||||
ATTR_DOMAIN_CURVE);
|
||||
VArray<float3>::ForContainer(std::move(tangents)), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
|
Loading…
Reference in New Issue