Curves: Port curve to mesh node to the new data-block

This commit changes the Curve to Mesh node to work with `Curves`
instead of `CurveEval`. The change ends up basically completely
rewriting the node, since the different attribute storage means that
the decisions made previously don't make much sense anymore.

The main loops are now "for each attribute: for each curve combination"
rather than the other way around, with the goal of taking advantage
of the locality of curve attributes. This improvement is quite
noticeable with many small curves; I measured a 4-5x improvement
(around 4-5s to <1s) when converting millions of curves to tens of
millions of faces. I didn't obverse any change in performance compared
to 3.1 with fewer curves though.

The changes also solve an algorithmic flaw where any interpolated
attributes would be evaluated for every curve combination instead
of just once per curve. This can be a large improvement when there
are many profile curves.

The code relies heavily on a function `foreach_curve_combination`
which calculates some basic information about each combination and
calls a templated function. I made assumptions about unnecessary reads
being removed by compiler optimizations. For further performance
improvements in the future that might be an area to investigate.
Another might be using a "for a group of curves: for each attribute:
for each curve" pattern to increase the locality of memory access.

Differential Revision: https://developer.blender.org/D14642
This commit is contained in:
Hans Goudey 2022-04-15 10:14:54 -05:00
parent cc6db8921b
commit 7484f274dc
Notes: blender-bot 2023-04-14 09:18:04 +02:00
Referenced by issue #99850, Regression: Unexpected result with curve to mesh on cyclic bezier curve with automatic or vector handles
Referenced by issue #97365, Regression Tests: Run-Time Check Failure #3 - The variable 'ls' is being used without being initialized.
Referenced by issue #95443, Refactor curve nodes to use new data structure
7 changed files with 673 additions and 533 deletions

View File

@ -2,7 +2,7 @@
#pragma once
struct CurveEval;
struct CurvesGeometry;
struct Mesh;
/** \file
@ -21,11 +21,13 @@ namespace blender::bke {
* changed anyway in a way that affects the normals. So currently this code uses the safer /
* simpler solution of deferring normal calculation to the rest of Blender.
*/
Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile, bool fill_caps);
Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
const CurvesGeometry &profile,
bool fill_caps);
/**
* Create a loose-edge mesh based on the evaluated path of the curve's splines.
* Transfer curve attributes to the mesh.
*/
Mesh *curve_to_wire_mesh(const CurveEval &curve);
Mesh *curve_to_wire_mesh(const CurvesGeometry &curve);
} // namespace blender::bke

View File

@ -328,6 +328,10 @@ class CurvesGeometry : public ::CurvesGeometry {
* calculated. That can be ensured with #ensure_evaluated_offsets.
*/
void interpolate_to_evaluated(int curve_index, GSpan src, GMutableSpan dst) const;
/**
* Evaluate generic data for curve control points to the standard evaluated points of the curves.
*/
void interpolate_to_evaluated(GSpan src, GMutableSpan dst) const;
private:
/**
@ -445,6 +449,13 @@ bool segment_is_vector(Span<int8_t> handle_types_left,
*/
bool last_cylic_segment_is_vector(Span<int8_t> handle_types_left, Span<int8_t> handle_types_right);
/**
* Return true if the handle types at the index are free (#BEZIER_HANDLE_FREE) or vector
* (#BEZIER_HANDLE_VECTOR). In these cases, directional continuitity from the previous and next
* evaluated segments is assumed not to be desired.
*/
bool point_is_sharp(Span<int8_t> handle_types_left, Span<int8_t> handle_types_right, int index);
/**
* Calculate offsets into the curve's evaluated points for each control point. While most control
* point edges generate the number of edges specified by the resolution, vector segments only
@ -715,4 +726,22 @@ inline float CurvesGeometry::evaluated_length_total_for_curve(const int curve_in
/** \} */
/* -------------------------------------------------------------------- */
/** \name Bezier Inline Methods
* \{ */
namespace curves::bezier {
inline bool point_is_sharp(const Span<int8_t> handle_types_left,
const Span<int8_t> handle_types_right,
const int index)
{
return ELEM(handle_types_left[index], BEZIER_HANDLE_VECTOR, BEZIER_HANDLE_FREE) ||
ELEM(handle_types_right[index], BEZIER_HANDLE_VECTOR, BEZIER_HANDLE_FREE);
}
} // namespace curves::bezier
/** \} */
} // namespace blender::bke

File diff suppressed because it is too large Load Diff

View File

@ -840,6 +840,48 @@ void CurvesGeometry::interpolate_to_evaluated(const int curve_index,
BLI_assert_unreachable();
}
void CurvesGeometry::interpolate_to_evaluated(const GSpan src, GMutableSpan dst) const
{
BLI_assert(!this->runtime->offsets_cache_dirty);
BLI_assert(!this->runtime->nurbs_basis_cache_dirty);
const VArray<int8_t> types = this->curve_types();
const VArray<int> resolution = this->resolution();
const VArray<bool> cyclic = this->cyclic();
const VArray<int8_t> nurbs_orders = this->nurbs_orders();
const Span<float> nurbs_weights = this->nurbs_weights();
threading::parallel_for(this->curves_range(), 512, [&](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);
switch (types[curve_index]) {
case CURVE_TYPE_CATMULL_ROM:
curves::catmull_rom::interpolate_to_evaluated(src.slice(points),
cyclic[curve_index],
resolution[curve_index],
dst.slice(evaluated_points));
continue;
case CURVE_TYPE_POLY:
dst.slice(evaluated_points).copy_from(src.slice(points));
continue;
case CURVE_TYPE_BEZIER:
curves::bezier::interpolate_to_evaluated(
src.slice(points),
this->runtime->bezier_evaluated_offsets.as_span().slice(points),
dst.slice(evaluated_points));
continue;
case CURVE_TYPE_NURBS:
curves::nurbs::interpolate_to_evaluated(this->runtime->nurbs_basis_cache[curve_index],
nurbs_orders[curve_index],
nurbs_weights.slice(points),
src.slice(points),
dst.slice(evaluated_points));
continue;
}
}
});
}
void CurvesGeometry::ensure_evaluated_lengths() const
{
if (!this->runtime->length_cache_dirty) {

View File

@ -25,6 +25,7 @@
#include "BLI_utildefines.h"
#include "BKE_DerivedMesh.h"
#include "BKE_curves.hh"
#include "BKE_deform.h"
#include "BKE_displist.h"
#include "BKE_editmesh.h"
@ -970,8 +971,7 @@ static Mesh *mesh_new_from_evaluated_curve_type_object(const Object *evaluated_o
}
const Curves *curves = get_evaluated_curves_from_object(evaluated_object);
if (curves) {
std::unique_ptr<CurveEval> curve = curves_to_curve_eval(*curves);
return blender::bke::curve_to_wire_mesh(*curve);
return blender::bke::curve_to_wire_mesh(blender::bke::CurvesGeometry::wrap(curves->geometry));
}
return nullptr;
}

View File

@ -386,6 +386,16 @@ class Vector {
UPDATE_VECTOR_SIZE(this);
}
/**
* Reset the size of the vector so that it contains new_size elements.
* All existing elements are destructed, and not copied if the data must be reallocated.
*/
void reinitialize(const int64_t new_size)
{
this->clear();
this->resize(new_size);
}
/**
* Afterwards the vector has 0 elements, but will still have
* memory to be refilled again.

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_spline.hh"
#include "BKE_curves.hh"
#include "BKE_curve_to_mesh.hh"
@ -27,17 +27,18 @@ static void geometry_set_curve_to_mesh(GeometrySet &geometry_set,
const GeometrySet &profile_set,
const bool fill_caps)
{
const std::unique_ptr<CurveEval> curve = curves_to_curve_eval(
*geometry_set.get_curves_for_read());
const Curves &curves = *geometry_set.get_curves_for_read();
const Curves *profile_curves = profile_set.get_curves_for_read();
if (profile_curves == nullptr) {
Mesh *mesh = bke::curve_to_wire_mesh(*curve);
Mesh *mesh = bke::curve_to_wire_mesh(bke::CurvesGeometry::wrap(curves.geometry));
geometry_set.replace_mesh(mesh);
}
else {
const std::unique_ptr<CurveEval> profile_curve = curves_to_curve_eval(*profile_curves);
Mesh *mesh = bke::curve_to_mesh_sweep(*curve, *profile_curve, fill_caps);
Mesh *mesh = bke::curve_to_mesh_sweep(bke::CurvesGeometry::wrap(curves.geometry),
bke::CurvesGeometry::wrap(profile_curves->geometry),
fill_caps);
geometry_set.replace_mesh(mesh);
}
}