Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender')
-rw-r--r--source/blender/blenkernel/BKE_attribute_math.hh2
-rw-r--r--source/blender/blenkernel/BKE_geometry_set.h1
-rw-r--r--source/blender/blenkernel/BKE_geometry_set.hh42
-rw-r--r--source/blender/blenkernel/BKE_node.h1
-rw-r--r--source/blender/blenkernel/BKE_spline.hh478
-rw-r--r--source/blender/blenkernel/CMakeLists.txt7
-rw-r--r--source/blender/blenkernel/intern/DerivedMesh.cc8
-rw-r--r--source/blender/blenkernel/intern/attribute_access.cc2
-rw-r--r--source/blender/blenkernel/intern/curve_eval.cc184
-rw-r--r--source/blender/blenkernel/intern/geometry_component_curve.cc299
-rw-r--r--source/blender/blenkernel/intern/geometry_set.cc45
-rw-r--r--source/blender/blenkernel/intern/geometry_set_instances.cc43
-rw-r--r--source/blender/blenkernel/intern/node.cc1
-rw-r--r--source/blender/blenkernel/intern/spline_base.cc252
-rw-r--r--source/blender/blenkernel/intern/spline_bezier.cc478
-rw-r--r--source/blender/blenkernel/intern/spline_nurbs.cc417
-rw-r--r--source/blender/blenkernel/intern/spline_poly.cc105
-rw-r--r--source/blender/blenlib/BLI_float3.hh7
-rw-r--r--source/blender/blenlib/BLI_float4x4.hh44
-rw-r--r--source/blender/makesrna/intern/rna_attribute.c3
-rw-r--r--source/blender/makesrna/intern/rna_space.c14
-rw-r--r--source/blender/modifiers/intern/MOD_nodes.cc2
-rw-r--r--source/blender/nodes/CMakeLists.txt1
-rw-r--r--source/blender/nodes/NOD_geometry.h1
-rw-r--r--source/blender/nodes/NOD_static_types.h1
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc3
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc3
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc3
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc3
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc3
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc8
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc3
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc3
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc3
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc3
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc4
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc8
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc4
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc3
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc3
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc3
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc312
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc36
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_point_instance.cc5
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc4
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_transform.cc21
46 files changed, 2869 insertions, 7 deletions
diff --git a/source/blender/blenkernel/BKE_attribute_math.hh b/source/blender/blenkernel/BKE_attribute_math.hh
index fc7498951f9..b0d32b20d44 100644
--- a/source/blender/blenkernel/BKE_attribute_math.hh
+++ b/source/blender/blenkernel/BKE_attribute_math.hh
@@ -14,6 +14,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+#pragma once
+
#include "BLI_array.hh"
#include "BLI_color.hh"
#include "BLI_float2.hh"
diff --git a/source/blender/blenkernel/BKE_geometry_set.h b/source/blender/blenkernel/BKE_geometry_set.h
index c00310408af..cb5526cd690 100644
--- a/source/blender/blenkernel/BKE_geometry_set.h
+++ b/source/blender/blenkernel/BKE_geometry_set.h
@@ -36,6 +36,7 @@ typedef enum GeometryComponentType {
GEO_COMPONENT_TYPE_POINT_CLOUD = 1,
GEO_COMPONENT_TYPE_INSTANCES = 2,
GEO_COMPONENT_TYPE_VOLUME = 3,
+ GEO_COMPONENT_TYPE_CURVE = 4,
} GeometryComponentType;
void BKE_geometry_set_free(struct GeometrySet *geometry_set);
diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh
index 89cdef34297..2d0f099e5ce 100644
--- a/source/blender/blenkernel/BKE_geometry_set.hh
+++ b/source/blender/blenkernel/BKE_geometry_set.hh
@@ -39,6 +39,7 @@ struct Mesh;
struct Object;
struct PointCloud;
struct Volume;
+class CurveEval;
enum class GeometryOwnershipType {
/* The geometry is owned. This implies that it can be changed. */
@@ -363,18 +364,25 @@ struct GeometrySet {
Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
static GeometrySet create_with_pointcloud(
PointCloud *pointcloud, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
+ static GeometrySet create_with_curve(
+ CurveEval *curve, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
/* Utility methods for access. */
bool has_mesh() const;
bool has_pointcloud() const;
bool has_instances() const;
bool has_volume() const;
+ bool has_curve() const;
+
const Mesh *get_mesh_for_read() const;
const PointCloud *get_pointcloud_for_read() const;
const Volume *get_volume_for_read() const;
+ const CurveEval *get_curve_for_read() const;
+
Mesh *get_mesh_for_write();
PointCloud *get_pointcloud_for_write();
Volume *get_volume_for_write();
+ CurveEval *get_curve_for_write();
/* Utility methods for replacement. */
void replace_mesh(Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
@@ -382,6 +390,8 @@ struct GeometrySet {
GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
void replace_volume(Volume *volume,
GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
+ void replace_curve(CurveEval *curve,
+ GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
};
/** A geometry component that can store a mesh. */
@@ -463,6 +473,38 @@ class PointCloudComponent : public GeometryComponent {
const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final;
};
+/** A geometry component that stores curve data, in other words, a group of splines. */
+class CurveComponent : public GeometryComponent {
+ private:
+ CurveEval *curve_ = nullptr;
+ GeometryOwnershipType ownership_ = GeometryOwnershipType::Owned;
+
+ public:
+ CurveComponent();
+ ~CurveComponent();
+ GeometryComponent *copy() const override;
+
+ void clear();
+ bool has_curve() const;
+ void replace(CurveEval *curve, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
+ CurveEval *release();
+
+ const CurveEval *get_for_read() const;
+ CurveEval *get_for_write();
+
+ int attribute_domain_size(const AttributeDomain domain) const final;
+
+ bool is_empty() const final;
+
+ bool owns_direct_data() const override;
+ void ensure_owns_direct_data() override;
+
+ static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_CURVE;
+
+ private:
+ const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final;
+};
+
/** A geometry component that stores instances. */
class InstancesComponent : public GeometryComponent {
private:
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index 819c148f0ad..bcab456b36e 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -1418,6 +1418,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_BOUNDING_BOX 1042
#define GEO_NODE_SWITCH 1043
#define GEO_NODE_ATTRIBUTE_TRANSFER 1044
+#define GEO_NODE_CURVE_TO_MESH 1045
/** \} */
diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh
new file mode 100644
index 00000000000..5409eb52d02
--- /dev/null
+++ b/source/blender/blenkernel/BKE_spline.hh
@@ -0,0 +1,478 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup bke
+ */
+
+#include <mutex>
+
+#include "FN_generic_virtual_array.hh"
+
+#include "BLI_float3.hh"
+#include "BLI_float4x4.hh"
+#include "BLI_vector.hh"
+
+#include "BKE_attribute_math.hh"
+
+struct Curve;
+
+class Spline;
+using SplinePtr = std::unique_ptr<Spline>;
+
+/**
+ * A spline is an abstraction of a single branch-less curve section, its evaluation methods,
+ * and data. The spline data itself is just control points and a set of attributes by the set
+ * of "evaluated" data is often used instead.
+ *
+ * Any derived class of Spline has to manage two things:
+ * 1. Interpolating arbitrary attribute data from the control points to evaluated points.
+ * 2. Evaluating the positions based on the stored control point data.
+ *
+ * Beyond that, everything is the base class's responsibility, with minor exceptions. Further
+ * evaluation happens in a layer on top of the evaluated points generated by the derived types.
+ *
+ * There are a few methods to evaluate a spline:
+ * 1. #evaluated_positions and #interpolate_to_evaluated_points give data at the initial
+ * evaluated points, depending on the resolution.
+ * 2. #lookup_evaluated_factor and #lookup_evaluated_factor are meant for one-off lookups
+ * along the length of a curve.
+ *
+ * Commonly used evaluated data is stored in caches on the spline itself so that operations on
+ * splines don't need to worry about taking ownership of evaluated data when they don't need to.
+ */
+class Spline {
+ public:
+ enum class Type {
+ Bezier,
+ NURBS,
+ Poly,
+ };
+
+ protected:
+ Type type_;
+ bool is_cyclic_ = false;
+
+ public:
+ enum NormalCalculationMode {
+ ZUp,
+ Minimum,
+ Tangent,
+ };
+ /* Only #Zup is supported at the moment. */
+ NormalCalculationMode normal_mode;
+
+ protected:
+ /** Direction of the spline at each evaluated point. */
+ mutable blender::Vector<blender::float3> evaluated_tangents_cache_;
+ mutable std::mutex tangent_cache_mutex_;
+ mutable bool tangent_cache_dirty_ = true;
+
+ /** Normal direction vectors for each evaluated point. */
+ mutable blender::Vector<blender::float3> evaluated_normals_cache_;
+ mutable std::mutex normal_cache_mutex_;
+ mutable bool normal_cache_dirty_ = true;
+
+ /** Accumulated lengths along the evaluated points. */
+ mutable blender::Vector<float> evaluated_lengths_cache_;
+ mutable std::mutex length_cache_mutex_;
+ mutable bool length_cache_dirty_ = true;
+
+ public:
+ virtual ~Spline() = default;
+ Spline(const Type type) : type_(type)
+ {
+ }
+ Spline(Spline &other)
+ : type_(other.type_), is_cyclic_(other.is_cyclic_), normal_mode(other.normal_mode)
+ {
+ }
+
+ virtual SplinePtr copy() const = 0;
+
+ Spline::Type type() const;
+
+ /** Return the number of control points. */
+ virtual int size() const = 0;
+ int segments_size() const;
+ bool is_cyclic() const;
+ void set_cyclic(const bool value);
+
+ virtual blender::MutableSpan<blender::float3> positions() = 0;
+ virtual blender::Span<blender::float3> positions() const = 0;
+ virtual blender::MutableSpan<float> radii() = 0;
+ virtual blender::Span<float> radii() const = 0;
+ virtual blender::MutableSpan<float> tilts() = 0;
+ virtual blender::Span<float> tilts() const = 0;
+
+ virtual void translate(const blender::float3 &translation);
+ virtual void transform(const blender::float4x4 &matrix);
+
+ /**
+ * Mark all caches for re-computation. This must be called after any operation that would
+ * change the generated positions, tangents, normals, mapping, etc. of the evaluated points.
+ */
+ virtual void mark_cache_invalid() = 0;
+ virtual int evaluated_points_size() const = 0;
+ int evaluated_edges_size() const;
+
+ float length() const;
+
+ virtual blender::Span<blender::float3> evaluated_positions() const = 0;
+
+ blender::Span<float> evaluated_lengths() const;
+ blender::Span<blender::float3> evaluated_tangents() const;
+ blender::Span<blender::float3> evaluated_normals() const;
+
+ void bounds_min_max(blender::float3 &min, blender::float3 &max, const bool use_evaluated) const;
+
+ struct LookupResult {
+ /**
+ * The index of the evaluated point before the result location. In other words, the index of
+ * the edge that the result lies on. If the sampled factor/length is the very end of the
+ * spline, this will be the second to last index, if it's the very beginning, this will be 0.
+ */
+ int evaluated_index;
+ /**
+ * The index of the evaluated point after the result location, accounting for wrapping when
+ * the spline is cyclic. If the sampled factor/length is the very end of the spline, this will
+ * be the last index (#evaluated_points_size - 1).
+ */
+ int next_evaluated_index;
+ /**
+ * The portion of the way from the evaluated point at #evaluated_index to the next point.
+ * If the sampled factor/length is the very end of the spline, this will be the 1.0f
+ */
+ float factor;
+ };
+ LookupResult lookup_evaluated_factor(const float factor) const;
+ LookupResult lookup_evaluated_length(const float length) const;
+
+ /**
+ * Interpolate a virtual array of data with the size of the number of control points to the
+ * evaluated points. For poly splines, the lifetime of the returned virtual array must not
+ * exceed the lifetime of the input data.
+ */
+ virtual blender::fn::GVArrayPtr interpolate_to_evaluated_points(
+ const blender::fn::GVArray &source_data) const = 0;
+
+ protected:
+ virtual void correct_end_tangents() const = 0;
+};
+
+/**
+ * A Bézier spline is made up of a many curve segments, possibly achieving continuity of curvature
+ * by constraining the alignment of curve handles. Evaluation stores the positions and a map of
+ * factors and indices in a list of floats, which is then used to interpolate any other data.
+ */
+class BezierSpline final : public Spline {
+ public:
+ enum class HandleType {
+ /** The handle can be moved anywhere, and doesn't influence the point's other handle. */
+ Free,
+ /** The location is automatically calculated to be smooth. */
+ Auto,
+ /** The location is calculated to point to the next/previous control point. */
+ Vector,
+ /** The location is constrained to point in the opposite direction as the other handle. */
+ Align,
+ };
+
+ private:
+ blender::Vector<HandleType> handle_types_left_;
+ blender::Vector<blender::float3> handle_positions_left_;
+ blender::Vector<blender::float3> positions_;
+ blender::Vector<HandleType> handle_types_right_;
+ blender::Vector<blender::float3> handle_positions_right_;
+ blender::Vector<float> radii_;
+ blender::Vector<float> tilts_;
+ int resolution_;
+
+ /** Start index in evaluated points array for every control point. */
+ mutable blender::Vector<int> offset_cache_;
+ mutable std::mutex offset_cache_mutex_;
+ mutable bool offset_cache_dirty_ = true;
+
+ /** Cache of evaluated positions. */
+ mutable blender::Vector<blender::float3> evaluated_position_cache_;
+ mutable std::mutex position_cache_mutex_;
+ mutable bool position_cache_dirty_ = true;
+
+ /** Cache of "index factors" based calculated from the evaluated positions. */
+ mutable blender::Vector<float> evaluated_mapping_cache_;
+ mutable std::mutex mapping_cache_mutex_;
+ mutable bool mapping_cache_dirty_ = true;
+
+ public:
+ virtual SplinePtr copy() const final;
+ BezierSpline() : Spline(Type::Bezier)
+ {
+ }
+ BezierSpline(const BezierSpline &other)
+ : Spline((Spline &)other),
+ handle_types_left_(other.handle_types_left_),
+ handle_positions_left_(other.handle_positions_left_),
+ positions_(other.positions_),
+ handle_types_right_(other.handle_types_right_),
+ handle_positions_right_(other.handle_positions_right_),
+ radii_(other.radii_),
+ tilts_(other.tilts_),
+ resolution_(other.resolution_)
+ {
+ }
+
+ int size() const final;
+ int resolution() const;
+ void set_resolution(const int value);
+
+ void add_point(const blender::float3 position,
+ const HandleType handle_type_start,
+ const blender::float3 handle_position_start,
+ const HandleType handle_type_end,
+ const blender::float3 handle_position_end,
+ const float radius,
+ const float tilt);
+
+ blender::MutableSpan<blender::float3> positions() final;
+ blender::Span<blender::float3> positions() const final;
+ blender::MutableSpan<float> radii() final;
+ blender::Span<float> radii() const final;
+ blender::MutableSpan<float> tilts() final;
+ blender::Span<float> tilts() const final;
+ blender::Span<HandleType> handle_types_left() const;
+ blender::MutableSpan<HandleType> handle_types_left();
+ blender::Span<blender::float3> handle_positions_left() const;
+ blender::MutableSpan<blender::float3> handle_positions_left();
+ blender::Span<HandleType> handle_types_right() const;
+ blender::MutableSpan<HandleType> handle_types_right();
+ blender::Span<blender::float3> handle_positions_right() const;
+ blender::MutableSpan<blender::float3> handle_positions_right();
+
+ void translate(const blender::float3 &translation) override;
+ void transform(const blender::float4x4 &matrix) override;
+
+ bool point_is_sharp(const int index) const;
+
+ void mark_cache_invalid() final;
+ int evaluated_points_size() const final;
+
+ blender::Span<int> control_point_offsets() const;
+ blender::Span<float> evaluated_mappings() const;
+ blender::Span<blender::float3> evaluated_positions() const final;
+ struct InterpolationData {
+ int control_point_index;
+ int next_control_point_index;
+ /**
+ * Linear interpolation weight between the two indices, from 0 to 1.
+ * Higher means next control point.
+ */
+ float factor;
+ };
+ InterpolationData interpolation_data_from_index_factor(const float index_factor) const;
+
+ virtual blender::fn::GVArrayPtr interpolate_to_evaluated_points(
+ const blender::fn::GVArray &source_data) const;
+
+ private:
+ void correct_end_tangents() const final;
+ bool segment_is_vector(const int start_index) const;
+ void evaluate_bezier_segment(const int index,
+ const int next_index,
+ blender::MutableSpan<blender::float3> positions) const;
+ blender::Array<int> evaluated_point_offsets() const;
+};
+
+/**
+ * Data for Non-Uniform Rational B-Splines. The mapping from control points to evaluated points is
+ * influenced by a vector of knots, weights for each point, and the order of the spline. Every
+ * mapping of data to evaluated points is handled the same way, but the positions are cached in
+ * the spline.
+ */
+class NURBSpline final : public Spline {
+ public:
+ enum class KnotsMode {
+ Normal,
+ EndPoint,
+ Bezier,
+ };
+ KnotsMode knots_mode;
+
+ struct BasisCache {
+ /** The influence at each control point `i + #start_index`. */
+ blender::Vector<float> weights;
+ /**
+ * An offset for the start of #weights: the first control point index with a non-zero weight.
+ */
+ int start_index;
+ };
+
+ private:
+ blender::Vector<blender::float3> positions_;
+ blender::Vector<float> radii_;
+ blender::Vector<float> tilts_;
+ blender::Vector<float> weights_;
+ int resolution_;
+ /**
+ * Defines the number of nearby control points that influence a given evaluated point. Higher
+ * orders give smoother results. The number of control points must be greater than or equal to
+ * this value.
+ */
+ uint8_t order_;
+
+ /**
+ * Determines where and how the control points affect the evaluated points. The length should
+ * always be the value returned by #knots_size(), and each value should be greater than or equal
+ * to the previous. Only invalidated when a point is added or removed.
+ */
+ mutable blender::Vector<float> knots_;
+ mutable std::mutex knots_mutex_;
+ mutable bool knots_dirty_ = true;
+
+ /** Cache of control point influences on each evaluated point. */
+ mutable blender::Vector<BasisCache> basis_cache_;
+ mutable std::mutex basis_cache_mutex_;
+ mutable bool basis_cache_dirty_ = true;
+
+ /**
+ * Cache of position data calculated from the basis cache. Though it is interpolated
+ * in the same way as any other attribute, it is stored to save unnecessary recalculation.
+ */
+ mutable blender::Vector<blender::float3> evaluated_position_cache_;
+ mutable std::mutex position_cache_mutex_;
+ mutable bool position_cache_dirty_ = true;
+
+ public:
+ SplinePtr copy() const final;
+ NURBSpline() : Spline(Type::NURBS)
+ {
+ }
+ NURBSpline(const NURBSpline &other)
+ : Spline((Spline &)other),
+ knots_mode(other.knots_mode),
+ positions_(other.positions_),
+ radii_(other.radii_),
+ tilts_(other.tilts_),
+ weights_(other.weights_),
+ resolution_(other.resolution_),
+ order_(other.order_)
+ {
+ }
+
+ int size() const final;
+ int resolution() const;
+ void set_resolution(const int value);
+ uint8_t order() const;
+ void set_order(const uint8_t value);
+
+ void add_point(const blender::float3 position,
+ const float radius,
+ const float tilt,
+ const float weight);
+
+ bool check_valid_size_and_order() const;
+ int knots_size() const;
+
+ blender::MutableSpan<blender::float3> positions() final;
+ blender::Span<blender::float3> positions() const final;
+ blender::MutableSpan<float> radii() final;
+ blender::Span<float> radii() const final;
+ blender::MutableSpan<float> tilts() final;
+ blender::Span<float> tilts() const final;
+ blender::Span<float> knots() const;
+
+ blender::MutableSpan<float> weights();
+ blender::Span<float> weights() const;
+
+ void mark_cache_invalid() final;
+ int evaluated_points_size() const final;
+
+ blender::Span<blender::float3> evaluated_positions() const final;
+
+ blender::fn::GVArrayPtr interpolate_to_evaluated_points(
+ const blender::fn::GVArray &source_data) const final;
+
+ protected:
+ void correct_end_tangents() const final;
+ void calculate_knots() const;
+ void calculate_basis_cache() const;
+};
+
+/**
+ * A Poly spline is like a bezier spline with a resolution of one. The main reason to distinguish
+ * the two is for reduced complexity and increased performance, since interpolating data to control
+ * points does not change it.
+ */
+class PolySpline final : public Spline {
+ public:
+ blender::Vector<blender::float3> positions_;
+ blender::Vector<float> radii_;
+ blender::Vector<float> tilts_;
+
+ private:
+ public:
+ SplinePtr copy() const final;
+ PolySpline() : Spline(Type::Bezier)
+ {
+ }
+ PolySpline(const PolySpline &other)
+ : Spline((Spline &)other),
+ positions_(other.positions_),
+ radii_(other.radii_),
+ tilts_(other.tilts_)
+ {
+ }
+
+ int size() const final;
+
+ void add_point(const blender::float3 position, const float radius, const float tilt);
+
+ blender::MutableSpan<blender::float3> positions() final;
+ blender::Span<blender::float3> positions() const final;
+ blender::MutableSpan<float> radii() final;
+ blender::Span<float> radii() const final;
+ blender::MutableSpan<float> tilts() final;
+ blender::Span<float> tilts() const final;
+
+ void mark_cache_invalid() final;
+ int evaluated_points_size() const final;
+
+ blender::Span<blender::float3> evaluated_positions() const final;
+
+ blender::fn::GVArrayPtr interpolate_to_evaluated_points(
+ const blender::fn::GVArray &source_data) const final;
+
+ protected:
+ void correct_end_tangents() const final;
+};
+
+/**
+ * A #CurveEval corresponds to the #Curve object data. The name is different for clarity, since
+ * more of the data is stored in the splines, but also just to be different than the name in DNA.
+ */
+class CurveEval {
+ public:
+ blender::Vector<SplinePtr> splines;
+
+ CurveEval *copy();
+
+ void translate(const blender::float3 &translation);
+ void transform(const blender::float4x4 &matrix);
+ void bounds_min_max(blender::float3 &min, blender::float3 &max, const bool use_evaluated) const;
+};
+
+std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &curve);
diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt
index 69df29527dd..0944bbed2f3 100644
--- a/source/blender/blenkernel/CMakeLists.txt
+++ b/source/blender/blenkernel/CMakeLists.txt
@@ -112,6 +112,7 @@ set(SRC
intern/curve_convert.c
intern/curve_decimate.c
intern/curve_deform.c
+ intern/curve_eval.cc
intern/curveprofile.c
intern/customdata.c
intern/customdata_file.c
@@ -133,6 +134,7 @@ set(SRC
intern/fmodifier.c
intern/font.c
intern/freestyle.c
+ intern/geometry_component_curve.cc
intern/geometry_component_instances.cc
intern/geometry_component_mesh.cc
intern/geometry_component_pointcloud.cc
@@ -241,6 +243,10 @@ set(SRC
intern/softbody.c
intern/sound.c
intern/speaker.c
+ intern/spline_base.cc
+ intern/spline_bezier.cc
+ intern/spline_nurbs.cc
+ intern/spline_poly.cc
intern/studiolight.c
intern/subdiv.c
intern/subdiv_ccg.c
@@ -322,6 +328,7 @@ set(SRC
BKE_customdata_file.h
BKE_data_transfer.h
BKE_deform.h
+ BKE_spline.hh
BKE_displist.h
BKE_displist_tangent.h
BKE_duplilist.h
diff --git a/source/blender/blenkernel/intern/DerivedMesh.cc b/source/blender/blenkernel/intern/DerivedMesh.cc
index c8416ac897e..fcdfeef5db4 100644
--- a/source/blender/blenkernel/intern/DerivedMesh.cc
+++ b/source/blender/blenkernel/intern/DerivedMesh.cc
@@ -1859,9 +1859,11 @@ static void editbmesh_calc_modifiers(struct Depsgraph *depsgraph,
BKE_id_free(nullptr, mesh_orco);
}
- /* Ensure normals calculation below is correct. */
- BLI_assert((mesh_input->flag & ME_AUTOSMOOTH) == (mesh_final->flag & ME_AUTOSMOOTH));
- BLI_assert(mesh_input->smoothresh == mesh_final->smoothresh);
+ /* Ensure normals calculation below is correct (normal settings have transferred properly).
+ * However, nodes modifiers might create meshes from scratch or transfer meshes from other
+ * objects with different settings, and in general it doesn't make sense to guarentee that
+ * the settings are the same as the original mesh. If necessary, this could become a modifier
+ * type flag. */
BLI_assert(mesh_input->smoothresh == mesh_cage->smoothresh);
/* Compute normals. */
diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc
index 5b2f588959f..c24630c5666 100644
--- a/source/blender/blenkernel/intern/attribute_access.cc
+++ b/source/blender/blenkernel/intern/attribute_access.cc
@@ -143,10 +143,8 @@ CustomDataType attribute_data_type_highest_complexity(Span<CustomDataType> data_
static int attribute_domain_priority(const AttributeDomain domain)
{
switch (domain) {
-#if 0
case ATTR_DOMAIN_CURVE:
return 0;
-#endif
case ATTR_DOMAIN_FACE:
return 1;
case ATTR_DOMAIN_EDGE:
diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc
new file mode 100644
index 00000000000..4c63c7f05ee
--- /dev/null
+++ b/source/blender/blenkernel/intern/curve_eval.cc
@@ -0,0 +1,184 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BLI_array.hh"
+#include "BLI_listbase.h"
+#include "BLI_span.hh"
+
+#include "DNA_curve_types.h"
+
+#include "BKE_curve.h"
+#include "BKE_spline.hh"
+
+using blender::float3;
+using blender::float4x4;
+using blender::Span;
+
+CurveEval *CurveEval::copy()
+{
+ CurveEval *new_curve = new CurveEval();
+
+ for (SplinePtr &spline : this->splines) {
+ new_curve->splines.append(spline->copy());
+ }
+
+ return new_curve;
+}
+
+void CurveEval::translate(const float3 &translation)
+{
+ for (SplinePtr &spline : this->splines) {
+ spline->translate(translation);
+ spline->mark_cache_invalid();
+ }
+}
+
+void CurveEval::transform(const float4x4 &matrix)
+{
+ for (SplinePtr &spline : this->splines) {
+ spline->transform(matrix);
+ }
+}
+
+void CurveEval::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated) const
+{
+ for (const SplinePtr &spline : this->splines) {
+ spline->bounds_min_max(min, max, use_evaluated);
+ }
+}
+
+static BezierSpline::HandleType handle_type_from_dna_bezt(const eBezTriple_Handle dna_handle_type)
+{
+ switch (dna_handle_type) {
+ case HD_FREE:
+ return BezierSpline::HandleType::Free;
+ case HD_AUTO:
+ return BezierSpline::HandleType::Auto;
+ case HD_VECT:
+ return BezierSpline::HandleType::Vector;
+ case HD_ALIGN:
+ return BezierSpline::HandleType::Align;
+ case HD_AUTO_ANIM:
+ return BezierSpline::HandleType::Auto;
+ case HD_ALIGN_DOUBLESIDE:
+ return BezierSpline::HandleType::Align;
+ }
+ BLI_assert_unreachable();
+ return BezierSpline::HandleType::Auto;
+}
+
+static Spline::NormalCalculationMode normal_mode_from_dna_curve(const int twist_mode)
+{
+ switch (twist_mode) {
+ case CU_TWIST_Z_UP:
+ return Spline::NormalCalculationMode::ZUp;
+ case CU_TWIST_MINIMUM:
+ return Spline::NormalCalculationMode::Minimum;
+ case CU_TWIST_TANGENT:
+ return Spline::NormalCalculationMode::Tangent;
+ }
+ BLI_assert_unreachable();
+ return Spline::NormalCalculationMode::Minimum;
+}
+
+static NURBSpline::KnotsMode knots_mode_from_dna_nurb(const short flag)
+{
+ switch (flag & (CU_NURB_ENDPOINT | CU_NURB_BEZIER)) {
+ case CU_NURB_ENDPOINT:
+ return NURBSpline::KnotsMode::EndPoint;
+ case CU_NURB_BEZIER:
+ return NURBSpline::KnotsMode::Bezier;
+ default:
+ return NURBSpline::KnotsMode::Normal;
+ }
+
+ BLI_assert_unreachable();
+ return NURBSpline::KnotsMode::Normal;
+}
+
+std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve)
+{
+ std::unique_ptr<CurveEval> curve = std::make_unique<CurveEval>();
+
+ const ListBase *nurbs = BKE_curve_nurbs_get(&const_cast<Curve &>(dna_curve));
+
+ curve->splines.reserve(BLI_listbase_count(nurbs));
+
+ /* TODO: Optimize by reserving the correct points size. */
+ LISTBASE_FOREACH (const Nurb *, nurb, nurbs) {
+ switch (nurb->type) {
+ case CU_BEZIER: {
+ std::unique_ptr<BezierSpline> spline = std::make_unique<BezierSpline>();
+ spline->set_resolution(nurb->resolu);
+ spline->set_cyclic(nurb->flagu & CU_NURB_CYCLIC);
+
+ for (const BezTriple &bezt : Span(nurb->bezt, nurb->pntsu)) {
+ spline->add_point(bezt.vec[1],
+ handle_type_from_dna_bezt((eBezTriple_Handle)bezt.h1),
+ bezt.vec[0],
+ handle_type_from_dna_bezt((eBezTriple_Handle)bezt.h2),
+ bezt.vec[2],
+ bezt.radius,
+ bezt.tilt);
+ }
+
+ curve->splines.append(std::move(spline));
+ break;
+ }
+ case CU_NURBS: {
+ std::unique_ptr<NURBSpline> spline = std::make_unique<NURBSpline>();
+ spline->set_resolution(nurb->resolu);
+ spline->set_cyclic(nurb->flagu & CU_NURB_CYCLIC);
+ spline->set_order(nurb->orderu);
+ spline->knots_mode = knots_mode_from_dna_nurb(nurb->flagu);
+
+ for (const BPoint &bp : Span(nurb->bp, nurb->pntsu)) {
+ spline->add_point(bp.vec, bp.radius, bp.tilt, bp.vec[3]);
+ }
+
+ curve->splines.append(std::move(spline));
+ break;
+ }
+ case CU_POLY: {
+ std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>();
+ spline->set_cyclic(nurb->flagu & CU_NURB_CYCLIC);
+
+ for (const BPoint &bp : Span(nurb->bp, nurb->pntsu)) {
+ spline->add_point(bp.vec, bp.radius, bp.tilt);
+ }
+
+ curve->splines.append(std::move(spline));
+ break;
+ }
+ default: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+ }
+
+ /* Note: Normal mode is stored separately in each spline to facilitate combining splines
+ * from multiple curve objects, where the value may be different. */
+ const Spline::NormalCalculationMode normal_mode = normal_mode_from_dna_curve(
+ dna_curve.twist_mode);
+ for (SplinePtr &spline : curve->splines) {
+ spline->normal_mode = normal_mode;
+ }
+
+ return curve;
+}
+
+/** \} */
diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc
new file mode 100644
index 00000000000..0490d577b88
--- /dev/null
+++ b/source/blender/blenkernel/intern/geometry_component_curve.cc
@@ -0,0 +1,299 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BKE_spline.hh"
+
+#include "BKE_attribute_access.hh"
+#include "BKE_attribute_math.hh"
+#include "BKE_geometry_set.hh"
+
+#include "attribute_access_intern.hh"
+
+/* -------------------------------------------------------------------- */
+/** \name Geometry Component Implementation
+ * \{ */
+
+CurveComponent::CurveComponent() : GeometryComponent(GEO_COMPONENT_TYPE_CURVE)
+{
+}
+
+CurveComponent::~CurveComponent()
+{
+ this->clear();
+}
+
+GeometryComponent *CurveComponent::copy() const
+{
+ CurveComponent *new_component = new CurveComponent();
+ if (curve_ != nullptr) {
+ new_component->curve_ = curve_->copy();
+ new_component->ownership_ = GeometryOwnershipType::Owned;
+ }
+ return new_component;
+}
+
+void CurveComponent::clear()
+{
+ BLI_assert(this->is_mutable());
+ if (curve_ != nullptr) {
+ if (ownership_ == GeometryOwnershipType::Owned) {
+ delete curve_;
+ }
+ curve_ = nullptr;
+ }
+}
+
+bool CurveComponent::has_curve() const
+{
+ return curve_ != nullptr;
+}
+
+/* Clear the component and replace it with the new curve. */
+void CurveComponent::replace(CurveEval *curve, GeometryOwnershipType ownership)
+{
+ BLI_assert(this->is_mutable());
+ this->clear();
+ curve_ = curve;
+ ownership_ = ownership;
+}
+
+CurveEval *CurveComponent::release()
+{
+ BLI_assert(this->is_mutable());
+ CurveEval *curve = curve_;
+ curve_ = nullptr;
+ return curve;
+}
+
+const CurveEval *CurveComponent::get_for_read() const
+{
+ return curve_;
+}
+
+CurveEval *CurveComponent::get_for_write()
+{
+ BLI_assert(this->is_mutable());
+ if (ownership_ == GeometryOwnershipType::ReadOnly) {
+ curve_ = curve_->copy();
+ ownership_ = GeometryOwnershipType::Owned;
+ }
+ return curve_;
+}
+
+bool CurveComponent::is_empty() const
+{
+ return curve_ == nullptr;
+}
+
+bool CurveComponent::owns_direct_data() const
+{
+ return ownership_ == GeometryOwnershipType::Owned;
+}
+
+void CurveComponent::ensure_owns_direct_data()
+{
+ BLI_assert(this->is_mutable());
+ if (ownership_ != GeometryOwnershipType::Owned) {
+ curve_ = curve_->copy();
+ ownership_ = GeometryOwnershipType::Owned;
+ }
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Attribute Access
+ * \{ */
+
+int CurveComponent::attribute_domain_size(const AttributeDomain domain) const
+{
+ if (curve_ == nullptr) {
+ return 0;
+ }
+ if (domain == ATTR_DOMAIN_POINT) {
+ int total = 0;
+ for (const SplinePtr &spline : curve_->splines) {
+ total += spline->size();
+ }
+ return total;
+ }
+ if (domain == ATTR_DOMAIN_CURVE) {
+ return curve_->splines.size();
+ }
+ return 0;
+}
+
+namespace blender::bke {
+
+class BuiltinSplineAttributeProvider final : public BuiltinAttributeProvider {
+ using AsReadAttribute = GVArrayPtr (*)(const CurveEval &data);
+ using AsWriteAttribute = GVMutableArrayPtr (*)(CurveEval &data);
+ using UpdateOnWrite = void (*)(Spline &spline);
+ const AsReadAttribute as_read_attribute_;
+ const AsWriteAttribute as_write_attribute_;
+
+ public:
+ BuiltinSplineAttributeProvider(std::string attribute_name,
+ const CustomDataType attribute_type,
+ const WritableEnum writable,
+ const AsReadAttribute as_read_attribute,
+ const AsWriteAttribute as_write_attribute)
+ : BuiltinAttributeProvider(std::move(attribute_name),
+ ATTR_DOMAIN_CURVE,
+ attribute_type,
+ BuiltinAttributeProvider::NonCreatable,
+ writable,
+ BuiltinAttributeProvider::NonDeletable),
+ as_read_attribute_(as_read_attribute),
+ as_write_attribute_(as_write_attribute)
+ {
+ }
+
+ GVArrayPtr try_get_for_read(const GeometryComponent &component) const final
+ {
+ const CurveComponent &curve_component = static_cast<const CurveComponent &>(component);
+ const CurveEval *curve = curve_component.get_for_read();
+ if (curve == nullptr) {
+ return {};
+ }
+
+ return as_read_attribute_(*curve);
+ }
+
+ GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const final
+ {
+ if (writable_ != Writable) {
+ return {};
+ }
+ CurveComponent &curve_component = static_cast<CurveComponent &>(component);
+ CurveEval *curve = curve_component.get_for_write();
+ if (curve == nullptr) {
+ return {};
+ }
+
+ return as_write_attribute_(*curve);
+ }
+
+ bool try_delete(GeometryComponent &UNUSED(component)) const final
+ {
+ return false;
+ }
+
+ bool try_create(GeometryComponent &UNUSED(component),
+ const AttributeInit &UNUSED(initializer)) const final
+ {
+ return false;
+ }
+
+ bool exists(const GeometryComponent &component) const final
+ {
+ return component.attribute_domain_size(ATTR_DOMAIN_CURVE) != 0;
+ }
+};
+
+static int get_spline_resolution(const SplinePtr &spline)
+{
+ if (const BezierSpline *bezier_spline = dynamic_cast<const BezierSpline *>(spline.get())) {
+ return bezier_spline->resolution();
+ }
+ if (const NURBSpline *nurb_spline = dynamic_cast<const NURBSpline *>(spline.get())) {
+ return nurb_spline->resolution();
+ }
+ return 1;
+}
+
+static void set_spline_resolution(SplinePtr &spline, const int resolution)
+{
+ if (BezierSpline *bezier_spline = dynamic_cast<BezierSpline *>(spline.get())) {
+ bezier_spline->set_resolution(std::max(resolution, 1));
+ }
+ if (NURBSpline *nurb_spline = dynamic_cast<NURBSpline *>(spline.get())) {
+ nurb_spline->set_resolution(std::max(resolution, 1));
+ }
+}
+
+static GVArrayPtr make_resolution_read_attribute(const CurveEval &curve)
+{
+ return std::make_unique<fn::GVArray_For_DerivedSpan<SplinePtr, int, get_spline_resolution>>(
+ curve.splines.as_span());
+}
+
+static GVMutableArrayPtr make_resolution_write_attribute(CurveEval &curve)
+{
+ return std::make_unique<fn::GVMutableArray_For_DerivedSpan<SplinePtr,
+ int,
+ get_spline_resolution,
+ set_spline_resolution>>(
+ curve.splines.as_mutable_span());
+}
+
+static bool get_cyclic_value(const SplinePtr &spline)
+{
+ return spline->is_cyclic();
+}
+
+static void set_cyclic_value(SplinePtr &spline, const bool value)
+{
+ if (spline->is_cyclic() != value) {
+ spline->set_cyclic(value);
+ spline->mark_cache_invalid();
+ }
+}
+
+static GVArrayPtr make_cyclic_read_attribute(const CurveEval &curve)
+{
+ return std::make_unique<fn::GVArray_For_DerivedSpan<SplinePtr, bool, get_cyclic_value>>(
+ curve.splines.as_span());
+}
+
+static GVMutableArrayPtr make_cyclic_write_attribute(CurveEval &curve)
+{
+ return std::make_unique<
+ fn::GVMutableArray_For_DerivedSpan<SplinePtr, bool, get_cyclic_value, set_cyclic_value>>(
+ curve.splines.as_mutable_span());
+}
+
+/**
+ * In this function all the attribute providers for a curve component are created. Most data
+ * in this function is statically allocated, because it does not change over time.
+ */
+static ComponentAttributeProviders create_attribute_providers_for_curve()
+{
+ static BuiltinSplineAttributeProvider resolution("resolution",
+ CD_PROP_INT32,
+ BuiltinAttributeProvider::Writable,
+ make_resolution_read_attribute,
+ make_resolution_write_attribute);
+
+ static BuiltinSplineAttributeProvider cyclic("cyclic",
+ CD_PROP_BOOL,
+ BuiltinAttributeProvider::Writable,
+ make_cyclic_read_attribute,
+ make_cyclic_write_attribute);
+
+ return ComponentAttributeProviders({&resolution, &cyclic}, {});
+}
+
+} // namespace blender::bke
+
+const blender::bke::ComponentAttributeProviders *CurveComponent::get_attribute_providers() const
+{
+ static blender::bke::ComponentAttributeProviders providers =
+ blender::bke::create_attribute_providers_for_curve();
+ return &providers;
+}
+
+/** \} */
diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc
index 6ae78343fea..3d85118deee 100644
--- a/source/blender/blenkernel/intern/geometry_set.cc
+++ b/source/blender/blenkernel/intern/geometry_set.cc
@@ -24,6 +24,7 @@
#include "BKE_mesh_wrapper.h"
#include "BKE_modifier.h"
#include "BKE_pointcloud.h"
+#include "BKE_spline.hh"
#include "BKE_volume.h"
#include "DNA_collection_types.h"
@@ -60,6 +61,8 @@ GeometryComponent *GeometryComponent::create(GeometryComponentType component_typ
return new InstancesComponent();
case GEO_COMPONENT_TYPE_VOLUME:
return new VolumeComponent();
+ case GEO_COMPONENT_TYPE_CURVE:
+ return new CurveComponent();
}
BLI_assert_unreachable();
return nullptr;
@@ -182,6 +185,11 @@ void GeometrySet::compute_boundbox_without_instances(float3 *r_min, float3 *r_ma
if (volume != nullptr) {
BKE_volume_min_max(volume, *r_min, *r_max);
}
+ const CurveEval *curve = this->get_curve_for_read();
+ if (curve != nullptr) {
+ /* Using the evaluated positions is somewhat arbitrary, but it is probably expected. */
+ curve->bounds_min_max(*r_min, *r_max, true);
+ }
}
std::ostream &operator<<(std::ostream &stream, const GeometrySet &geometry_set)
@@ -252,6 +260,13 @@ const Volume *GeometrySet::get_volume_for_read() const
return (component == nullptr) ? nullptr : component->get_for_read();
}
+/* Returns a read-only curve or null. */
+const CurveEval *GeometrySet::get_curve_for_read() const
+{
+ const CurveComponent *component = this->get_component_for_read<CurveComponent>();
+ return (component == nullptr) ? nullptr : component->get_for_read();
+}
+
/* Returns true when the geometry set has a point cloud component that has a point cloud. */
bool GeometrySet::has_pointcloud() const
{
@@ -273,6 +288,13 @@ bool GeometrySet::has_volume() const
return component != nullptr && component->has_volume();
}
+/* Returns true when the geometry set has a curve component that has a curve. */
+bool GeometrySet::has_curve() const
+{
+ const CurveComponent *component = this->get_component_for_read<CurveComponent>();
+ return component != nullptr && component->has_curve();
+}
+
/* Create a new geometry set that only contains the given mesh. */
GeometrySet GeometrySet::create_with_mesh(Mesh *mesh, GeometryOwnershipType ownership)
{
@@ -292,6 +314,15 @@ GeometrySet GeometrySet::create_with_pointcloud(PointCloud *pointcloud,
return geometry_set;
}
+/* Create a new geometry set that only contains the given curve. */
+GeometrySet GeometrySet::create_with_curve(CurveEval *curve, GeometryOwnershipType ownership)
+{
+ GeometrySet geometry_set;
+ CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>();
+ component.replace(curve, ownership);
+ return geometry_set;
+}
+
/* Clear the existing mesh and replace it with the given one. */
void GeometrySet::replace_mesh(Mesh *mesh, GeometryOwnershipType ownership)
{
@@ -299,6 +330,13 @@ void GeometrySet::replace_mesh(Mesh *mesh, GeometryOwnershipType ownership)
component.replace(mesh, ownership);
}
+/* Clear the existing curve and replace it with the given one. */
+void GeometrySet::replace_curve(CurveEval *curve, GeometryOwnershipType ownership)
+{
+ CurveComponent &component = this->get_component_for_write<CurveComponent>();
+ component.replace(curve, ownership);
+}
+
/* Clear the existing point cloud and replace with the given one. */
void GeometrySet::replace_pointcloud(PointCloud *pointcloud, GeometryOwnershipType ownership)
{
@@ -334,6 +372,13 @@ Volume *GeometrySet::get_volume_for_write()
return component.get_for_write();
}
+/* Returns a mutable curve or null. No ownership is transferred. */
+CurveEval *GeometrySet::get_curve_for_write()
+{
+ CurveComponent &component = this->get_component_for_write<CurveComponent>();
+ return component.get_for_write();
+}
+
/** \} */
/* -------------------------------------------------------------------- */
diff --git a/source/blender/blenkernel/intern/geometry_set_instances.cc b/source/blender/blenkernel/intern/geometry_set_instances.cc
index 47db5d1f901..7e994b32ded 100644
--- a/source/blender/blenkernel/intern/geometry_set_instances.cc
+++ b/source/blender/blenkernel/intern/geometry_set_instances.cc
@@ -19,6 +19,7 @@
#include "BKE_mesh_wrapper.h"
#include "BKE_modifier.h"
#include "BKE_pointcloud.h"
+#include "BKE_spline.hh"
#include "DNA_collection_types.h"
#include "DNA_mesh_types.h"
@@ -50,6 +51,16 @@ static void add_final_mesh_as_geometry_component(const Object &object, GeometryS
}
}
+static void add_curve_data_as_geometry_component(const Object &object, GeometrySet &geometry_set)
+{
+ BLI_assert(object.type == OB_CURVE);
+ if (object.data != nullptr) {
+ std::unique_ptr<CurveEval> curve = curve_eval_from_dna_curve(*(Curve *)object.data);
+ CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>();
+ curve_component.replace(curve.release(), GeometryOwnershipType::Owned);
+ }
+}
+
/**
* \note This doesn't extract instances from the "dupli" system for non-geometry-nodes instances.
*/
@@ -73,6 +84,9 @@ static GeometrySet object_get_geometry_set_for_read(const Object &object)
if (object.type == OB_MESH) {
add_final_mesh_as_geometry_component(object, geometry_set);
}
+ else if (object.type == OB_CURVE) {
+ add_curve_data_as_geometry_component(object, geometry_set);
+ }
/* TODO: Cover the case of point-clouds without modifiers-- they may not be covered by the
* #geometry_set_eval case above. */
@@ -492,6 +506,28 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups,
}
}
+static void join_curve_splines(Span<GeometryInstanceGroup> set_groups, CurveComponent &result)
+{
+ CurveEval *new_curve = new CurveEval();
+ for (const GeometryInstanceGroup &set_group : set_groups) {
+ const GeometrySet &set = set_group.geometry_set;
+ if (!set.has_curve()) {
+ continue;
+ }
+
+ const CurveEval &source_curve = *set.get_curve_for_read();
+ for (const SplinePtr &source_spline : source_curve.splines) {
+ for (const float4x4 &transform : set_group.transforms) {
+ SplinePtr new_spline = source_spline->copy();
+ new_spline->transform(transform);
+ new_curve->splines.append(std::move(new_spline));
+ }
+ }
+ }
+
+ result.replace(new_curve);
+}
+
static void join_instance_groups_mesh(Span<GeometryInstanceGroup> set_groups,
bool convert_points_to_vertices,
GeometrySet &result)
@@ -558,6 +594,12 @@ static void join_instance_groups_volume(Span<GeometryInstanceGroup> set_groups,
UNUSED_VARS(set_groups, dst_component);
}
+static void join_instance_groups_curve(Span<GeometryInstanceGroup> set_groups, GeometrySet &result)
+{
+ CurveComponent &dst_component = result.get_component_for_write<CurveComponent>();
+ join_curve_splines(set_groups, dst_component);
+}
+
GeometrySet geometry_set_realize_mesh_for_modifier(const GeometrySet &geometry_set)
{
if (!geometry_set.has_instances() && !geometry_set.has_pointcloud()) {
@@ -589,6 +631,7 @@ GeometrySet geometry_set_realize_instances(const GeometrySet &geometry_set)
join_instance_groups_mesh(set_groups, false, new_geometry_set);
join_instance_groups_pointcloud(set_groups, new_geometry_set);
join_instance_groups_volume(set_groups, new_geometry_set);
+ join_instance_groups_curve(set_groups, new_geometry_set);
return new_geometry_set;
}
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index d940326cc2c..6634c42a274 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -4947,6 +4947,7 @@ static void registerGeometryNodes()
register_node_type_geo_boolean();
register_node_type_geo_bounding_box();
register_node_type_geo_collection_info();
+ register_node_type_geo_curve_to_mesh();
register_node_type_geo_edge_split();
register_node_type_geo_is_viewport();
register_node_type_geo_join_geometry();
diff --git a/source/blender/blenkernel/intern/spline_base.cc b/source/blender/blenkernel/intern/spline_base.cc
new file mode 100644
index 00000000000..acfd3eba912
--- /dev/null
+++ b/source/blender/blenkernel/intern/spline_base.cc
@@ -0,0 +1,252 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BLI_array.hh"
+#include "BLI_span.hh"
+
+#include "BKE_spline.hh"
+
+using blender::float3;
+using blender::IndexRange;
+using blender::MutableSpan;
+using blender::Span;
+
+Spline::Type Spline::type() const
+{
+ return type_;
+}
+
+void Spline::translate(const blender::float3 &translation)
+{
+ for (float3 &position : this->positions()) {
+ position += translation;
+ }
+ this->mark_cache_invalid();
+}
+
+void Spline::transform(const blender::float4x4 &matrix)
+{
+ for (float3 &position : this->positions()) {
+ position = matrix * position;
+ }
+ this->mark_cache_invalid();
+}
+
+int Spline::evaluated_edges_size() const
+{
+ const int eval_size = this->evaluated_points_size();
+ if (eval_size == 1) {
+ return 0;
+ }
+
+ return this->is_cyclic_ ? eval_size : eval_size - 1;
+}
+
+float Spline::length() const
+{
+ return this->evaluated_lengths().last();
+}
+
+int Spline::segments_size() const
+{
+ const int points_len = this->size();
+
+ return is_cyclic_ ? points_len : points_len - 1;
+}
+
+bool Spline::is_cyclic() const
+{
+ return is_cyclic_;
+}
+
+void Spline::set_cyclic(const bool value)
+{
+ is_cyclic_ = value;
+}
+
+static void accumulate_lengths(Span<float3> positions,
+ const bool is_cyclic,
+ MutableSpan<float> lengths)
+{
+ float length = 0.0f;
+ for (const int i : IndexRange(positions.size() - 1)) {
+ length += float3::distance(positions[i], positions[i + 1]);
+ lengths[i] = length;
+ }
+ if (is_cyclic) {
+ lengths.last() = length + float3::distance(positions.last(), positions.first());
+ }
+}
+
+/**
+ * Return non-owning access to the cache of accumulated lengths along the spline. Each item is the
+ * length of the subsequent segment, i.e. the first value is the length of the first segment rather
+ * than 0. This calculation is rather trivial, and only depends on the evaluated positions.
+ * However, the results are used often, so it makes sense to cache it.
+ */
+Span<float> Spline::evaluated_lengths() const
+{
+ if (!length_cache_dirty_) {
+ return evaluated_lengths_cache_;
+ }
+
+ std::lock_guard lock{length_cache_mutex_};
+ if (!length_cache_dirty_) {
+ return evaluated_lengths_cache_;
+ }
+
+ const int total = evaluated_edges_size();
+ evaluated_lengths_cache_.resize(total);
+
+ Span<float3> positions = this->evaluated_positions();
+ accumulate_lengths(positions, is_cyclic_, evaluated_lengths_cache_);
+
+ length_cache_dirty_ = false;
+ return evaluated_lengths_cache_;
+}
+
+static float3 direction_bisect(const float3 &prev, const float3 &middle, const float3 &next)
+{
+ const float3 dir_prev = (middle - prev).normalized();
+ const float3 dir_next = (next - middle).normalized();
+
+ return (dir_prev + dir_next).normalized();
+}
+
+static void calculate_tangents(Span<float3> positions,
+ const bool is_cyclic,
+ MutableSpan<float3> tangents)
+{
+ if (positions.size() == 1) {
+ 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() = (positions[1] - positions[0]).normalized();
+ tangents.last() = (positions.last() - positions[positions.size() - 2]).normalized();
+ }
+}
+
+/**
+ * Return non-owning access to the direction of the curve at each evaluated point.
+ */
+Span<float3> Spline::evaluated_tangents() const
+{
+ if (!tangent_cache_dirty_) {
+ return evaluated_tangents_cache_;
+ }
+
+ std::lock_guard lock{tangent_cache_mutex_};
+ if (!tangent_cache_dirty_) {
+ return evaluated_tangents_cache_;
+ }
+
+ const int eval_size = this->evaluated_points_size();
+ evaluated_tangents_cache_.resize(eval_size);
+
+ Span<float3> positions = this->evaluated_positions();
+
+ if (eval_size == 1) {
+ evaluated_tangents_cache_.first() = float3(1.0f, 0.0f, 0.0f);
+ }
+ else {
+ calculate_tangents(positions, is_cyclic_, evaluated_tangents_cache_);
+ this->correct_end_tangents();
+ }
+
+ tangent_cache_dirty_ = false;
+ return evaluated_tangents_cache_;
+}
+
+static void calculate_normals_z_up(Span<float3> tangents, MutableSpan<float3> normals)
+{
+ for (const int i : normals.index_range()) {
+ normals[i] = float3::cross(tangents[i], float3(0.0f, 0.0f, 1.0f)).normalized();
+ }
+}
+
+/**
+ * Return non-owning access to the direction vectors perpendicular to the tangents at every
+ * evaluated point. The method used to generate the normal vectors depends on Spline.normal_mode.
+ *
+ * TODO: Support changing the normal based on tilts.
+ */
+Span<float3> Spline::evaluated_normals() const
+{
+ if (!normal_cache_dirty_) {
+ return evaluated_normals_cache_;
+ }
+
+ std::lock_guard lock{normal_cache_mutex_};
+ if (!normal_cache_dirty_) {
+ return evaluated_normals_cache_;
+ }
+
+ const int eval_size = this->evaluated_points_size();
+ evaluated_normals_cache_.resize(eval_size);
+
+ Span<float3> tangents = evaluated_tangents();
+
+ /* Only Z up normals are supported at the moment. */
+ calculate_normals_z_up(tangents, evaluated_normals_cache_);
+
+ normal_cache_dirty_ = false;
+ return evaluated_normals_cache_;
+}
+
+Spline::LookupResult Spline::lookup_evaluated_factor(const float factor) const
+{
+ return this->lookup_evaluated_length(this->length() * factor);
+}
+
+/**
+ * \note This does not support extrapolation currently.
+ */
+Spline::LookupResult Spline::lookup_evaluated_length(const float length) const
+{
+ BLI_assert(length >= 0.0f && length <= this->length());
+
+ Span<float> lengths = this->evaluated_lengths();
+
+ const float *offset = std::lower_bound(lengths.begin(), lengths.end(), length);
+ const int index = offset - lengths.begin();
+ const int next_index = (index == this->size() - 1) ? 0 : index + 1;
+
+ const float previous_length = (index == 0) ? 0.0f : lengths[index - 1];
+ const float factor = (length - previous_length) / (lengths[index] - previous_length);
+
+ return LookupResult{index, next_index, factor};
+}
+
+void Spline::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated) const
+{
+ Span<float3> positions = use_evaluated ? this->evaluated_positions() : this->positions();
+ for (const float3 &position : positions) {
+ minmax_v3v3_v3(min, max, position);
+ }
+}
diff --git a/source/blender/blenkernel/intern/spline_bezier.cc b/source/blender/blenkernel/intern/spline_bezier.cc
new file mode 100644
index 00000000000..4981f441190
--- /dev/null
+++ b/source/blender/blenkernel/intern/spline_bezier.cc
@@ -0,0 +1,478 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BLI_array.hh"
+#include "BLI_span.hh"
+#include "BLI_task.hh"
+
+#include "BKE_spline.hh"
+
+using blender::Array;
+using blender::float3;
+using blender::IndexRange;
+using blender::MutableSpan;
+using blender::Span;
+
+SplinePtr BezierSpline::copy() const
+{
+ return std::make_unique<BezierSpline>(*this);
+}
+
+int BezierSpline::size() const
+{
+ const int size = positions_.size();
+ BLI_assert(size == handle_types_left_.size());
+ BLI_assert(size == handle_positions_left_.size());
+ BLI_assert(size == handle_types_right_.size());
+ BLI_assert(size == handle_positions_right_.size());
+ BLI_assert(size == radii_.size());
+ BLI_assert(size == tilts_.size());
+ return size;
+}
+
+int BezierSpline::resolution() const
+{
+ return resolution_;
+}
+
+void BezierSpline::set_resolution(const int value)
+{
+ BLI_assert(value > 0);
+ resolution_ = value;
+ this->mark_cache_invalid();
+}
+
+void BezierSpline::add_point(const float3 position,
+ const HandleType handle_type_start,
+ const float3 handle_position_start,
+ const HandleType handle_type_end,
+ const float3 handle_position_end,
+ const float radius,
+ const float tilt)
+{
+ handle_types_left_.append(handle_type_start);
+ handle_positions_left_.append(handle_position_start);
+ positions_.append(position);
+ handle_types_right_.append(handle_type_end);
+ handle_positions_right_.append(handle_position_end);
+ radii_.append(radius);
+ tilts_.append(tilt);
+ this->mark_cache_invalid();
+}
+
+MutableSpan<float3> BezierSpline::positions()
+{
+ return positions_;
+}
+Span<float3> BezierSpline::positions() const
+{
+ return positions_;
+}
+MutableSpan<float> BezierSpline::radii()
+{
+ return radii_;
+}
+Span<float> BezierSpline::radii() const
+{
+ return radii_;
+}
+MutableSpan<float> BezierSpline::tilts()
+{
+ return tilts_;
+}
+Span<float> BezierSpline::tilts() const
+{
+ return tilts_;
+}
+Span<BezierSpline::HandleType> BezierSpline::handle_types_left() const
+{
+ return handle_types_left_;
+}
+MutableSpan<BezierSpline::HandleType> BezierSpline::handle_types_left()
+{
+ return handle_types_left_;
+}
+Span<float3> BezierSpline::handle_positions_left() const
+{
+ return handle_positions_left_;
+}
+MutableSpan<float3> BezierSpline::handle_positions_left()
+{
+ return handle_positions_left_;
+}
+Span<BezierSpline::HandleType> BezierSpline::handle_types_right() const
+{
+ return handle_types_right_;
+}
+MutableSpan<BezierSpline::HandleType> BezierSpline::handle_types_right()
+{
+ return handle_types_right_;
+}
+Span<float3> BezierSpline::handle_positions_right() const
+{
+ return handle_positions_right_;
+}
+MutableSpan<float3> BezierSpline::handle_positions_right()
+{
+ return handle_positions_right_;
+}
+
+void BezierSpline::translate(const blender::float3 &translation)
+{
+ for (float3 &position : this->positions()) {
+ position += translation;
+ }
+ for (float3 &handle_position : this->handle_positions_left()) {
+ handle_position += translation;
+ }
+ for (float3 &handle_position : this->handle_positions_right()) {
+ handle_position += translation;
+ }
+ this->mark_cache_invalid();
+}
+
+void BezierSpline::transform(const blender::float4x4 &matrix)
+{
+ for (float3 &position : this->positions()) {
+ position = matrix * position;
+ }
+ for (float3 &handle_position : this->handle_positions_left()) {
+ handle_position = matrix * handle_position;
+ }
+ for (float3 &handle_position : this->handle_positions_right()) {
+ handle_position = matrix * handle_position;
+ }
+ this->mark_cache_invalid();
+}
+
+bool BezierSpline::point_is_sharp(const int index) const
+{
+ return ELEM(handle_types_left_[index], HandleType::Vector, HandleType::Free) ||
+ ELEM(handle_types_right_[index], HandleType::Vector, HandleType::Free);
+}
+
+bool BezierSpline::segment_is_vector(const int index) const
+{
+ if (index == this->size() - 1) {
+ BLI_assert(is_cyclic_);
+ return handle_types_right_.last() == HandleType::Vector &&
+ handle_types_left_.first() == HandleType::Vector;
+ }
+ return handle_types_right_[index] == HandleType::Vector &&
+ handle_types_left_[index + 1] == HandleType::Vector;
+}
+
+void BezierSpline::mark_cache_invalid()
+{
+ offset_cache_dirty_ = true;
+ position_cache_dirty_ = true;
+ mapping_cache_dirty_ = true;
+ tangent_cache_dirty_ = true;
+ normal_cache_dirty_ = true;
+ length_cache_dirty_ = true;
+}
+
+int BezierSpline::evaluated_points_size() const
+{
+ const int points_len = this->size();
+ BLI_assert(points_len > 0);
+
+ const int last_offset = this->control_point_offsets().last();
+ if (is_cyclic_ && points_len > 1) {
+ return last_offset + (this->segment_is_vector(points_len - 1) ? 0 : resolution_);
+ }
+
+ return last_offset + 1;
+}
+
+/**
+ * If the spline is not cyclic, the direction for the first and last points is just the
+ * direction formed by the corresponding handles and control points. In the unlikely situation
+ * that the handles define a zero direction, fallback to using the direction defined by the
+ * first and last evaluated segments already calculated in #Spline::evaluated_tangents().
+ */
+void BezierSpline::correct_end_tangents() const
+{
+ if (is_cyclic_) {
+ return;
+ }
+
+ MutableSpan<float3> tangents(evaluated_tangents_cache_);
+
+ if (handle_positions_left_.first() != positions_.first()) {
+ tangents.first() = (positions_.first() - handle_positions_left_.first()).normalized();
+ }
+ if (handle_positions_right_.last() != positions_.last()) {
+ tangents.last() = (handle_positions_right_.last() - positions_.last()).normalized();
+ }
+}
+
+static void bezier_forward_difference_3d(const float3 &point_0,
+ const float3 &point_1,
+ const float3 &point_2,
+ const float3 &point_3,
+ MutableSpan<float3> result)
+{
+ BLI_assert(result.size() > 0);
+ const float inv_len = 1.0f / static_cast<float>(result.size());
+ const float inv_len_squared = inv_len * inv_len;
+ const float inv_len_cubed = inv_len_squared * inv_len;
+
+ const float3 rt1 = 3.0f * (point_1 - point_0) * inv_len;
+ const float3 rt2 = 3.0f * (point_0 - 2.0f * point_1 + point_2) * inv_len_squared;
+ const float3 rt3 = (point_3 - point_0 + 3.0f * (point_1 - point_2)) * inv_len_cubed;
+
+ float3 q0 = point_0;
+ float3 q1 = rt1 + rt2 + rt3;
+ float3 q2 = 2.0f * rt2 + 6.0f * rt3;
+ float3 q3 = 6.0f * rt3;
+ for (const int i : result.index_range()) {
+ result[i] = q0;
+ q0 += q1;
+ q1 += q2;
+ q2 += q3;
+ }
+}
+
+void BezierSpline::evaluate_bezier_segment(const int index,
+ const int next_index,
+ MutableSpan<float3> positions) const
+{
+ if (this->segment_is_vector(index)) {
+ BLI_assert(positions.size() == 1);
+ positions.first() = positions_[index];
+ }
+ else {
+ bezier_forward_difference_3d(positions_[index],
+ handle_positions_right_[index],
+ handle_positions_left_[next_index],
+ positions_[next_index],
+ positions);
+ }
+}
+
+/**
+ * Returns access to a cache of offsets into the evaluated point array for each control point.
+ * This is important because while most control point edges generate the number of edges specified
+ * by the resolution, vector segments only generate one edge.
+ */
+Span<int> BezierSpline::control_point_offsets() const
+{
+ if (!offset_cache_dirty_) {
+ return offset_cache_;
+ }
+
+ std::lock_guard lock{offset_cache_mutex_};
+ if (!offset_cache_dirty_) {
+ return offset_cache_;
+ }
+
+ const int points_len = this->size();
+ offset_cache_.resize(points_len);
+
+ MutableSpan<int> offsets = offset_cache_;
+
+ int offset = 0;
+ for (const int i : IndexRange(points_len - 1)) {
+ offsets[i] = offset;
+ offset += this->segment_is_vector(i) ? 1 : resolution_;
+ }
+ offsets.last() = offset;
+
+ offset_cache_dirty_ = false;
+ return offsets;
+}
+
+/**
+ * Returns non-owning access to an array of values containing the information necessary to
+ * interpolate values from the original control points to evaluated points. The control point
+ * index is the integer part of each value, and the factor used for interpolating to the next
+ * control point is the remaining factional part.
+ */
+Span<float> BezierSpline::evaluated_mappings() const
+{
+ if (!mapping_cache_dirty_) {
+ return evaluated_mapping_cache_;
+ }
+
+ std::lock_guard lock{mapping_cache_mutex_};
+ if (!mapping_cache_dirty_) {
+ return evaluated_mapping_cache_;
+ }
+
+ const int size = this->size();
+ const int eval_size = this->evaluated_points_size();
+ evaluated_mapping_cache_.resize(eval_size);
+ MutableSpan<float> mappings = evaluated_mapping_cache_;
+
+ if (eval_size == 1) {
+ mappings.first() = 0.0f;
+ mapping_cache_dirty_ = false;
+ return mappings;
+ }
+
+ Span<int> offsets = this->control_point_offsets();
+ Span<float> lengths = this->evaluated_lengths();
+
+ /* Subtract one from the index into the lengths array to get the length
+ * at the start point rather than the length at the end of the edge. */
+
+ const float first_segment_len = lengths[offsets[1] - 1];
+ for (const int eval_index : IndexRange(0, offsets[1])) {
+ const float point_len = eval_index == 0 ? 0.0f : lengths[eval_index - 1];
+ const float length_factor = (first_segment_len == 0.0f) ? 0.0f : 1.0f / first_segment_len;
+
+ mappings[eval_index] = point_len * length_factor;
+ }
+
+ const int grain_size = std::max(512 / resolution_, 1);
+ blender::parallel_for(IndexRange(1, size - 2), grain_size, [&](IndexRange range) {
+ for (const int i : range) {
+ const float segment_start_len = lengths[offsets[i] - 1];
+ const float segment_end_len = lengths[offsets[i + 1] - 1];
+ const float segment_len = segment_end_len - segment_start_len;
+ const float length_factor = (segment_len == 0.0f) ? 0.0f : 1.0f / segment_len;
+
+ for (const int eval_index : IndexRange(offsets[i], offsets[i + 1] - offsets[i])) {
+ const float factor = (lengths[eval_index - 1] - segment_start_len) * length_factor;
+ mappings[eval_index] = i + factor;
+ }
+ }
+ });
+
+ if (is_cyclic_) {
+ const float segment_start_len = lengths[offsets.last() - 1];
+ const float segment_end_len = this->length();
+ const float segment_len = segment_end_len - segment_start_len;
+ const float length_factor = (segment_len == 0.0f) ? 0.0f : 1.0f / segment_len;
+
+ for (const int eval_index : IndexRange(offsets.last(), eval_size - offsets.last())) {
+ const float factor = (lengths[eval_index - 1] - segment_start_len) * length_factor;
+ mappings[eval_index] = size - 1 + factor;
+ }
+ mappings.last() = 0.0f;
+ }
+ else {
+ mappings.last() = size - 1;
+ }
+
+ mapping_cache_dirty_ = false;
+ return mappings;
+}
+
+Span<float3> BezierSpline::evaluated_positions() const
+{
+ if (!position_cache_dirty_) {
+ return evaluated_position_cache_;
+ }
+
+ std::lock_guard lock{position_cache_mutex_};
+ if (!position_cache_dirty_) {
+ return evaluated_position_cache_;
+ }
+
+ const int eval_size = this->evaluated_points_size();
+ evaluated_position_cache_.resize(eval_size);
+
+ MutableSpan<float3> positions = evaluated_position_cache_;
+
+ Span<int> offsets = this->control_point_offsets();
+ BLI_assert(offsets.last() <= eval_size);
+
+ const int grain_size = std::max(512 / resolution_, 1);
+ blender::parallel_for(IndexRange(this->size() - 1), grain_size, [&](IndexRange range) {
+ for (const int i : range) {
+ this->evaluate_bezier_segment(
+ i, i + 1, positions.slice(offsets[i], offsets[i + 1] - offsets[i]));
+ }
+ });
+
+ const int i_last = this->size() - 1;
+ if (is_cyclic_) {
+ this->evaluate_bezier_segment(i_last, 0, positions.slice(offsets.last(), resolution_));
+ }
+ else {
+ /* Since evualating the bezier segment doesn't add the final point,
+ * it must be added manually in the non-cyclic case. */
+ positions.last() = positions_.last();
+ }
+
+ position_cache_dirty_ = false;
+ return positions;
+}
+
+/**
+ * Convert the data encoded in #evaulated_mappings into its parts-- the information necessary
+ * to interpolate data from control points to evaluated points between them. The next control
+ * point index result will not overflow the size of the vector.
+ */
+BezierSpline::InterpolationData BezierSpline::interpolation_data_from_index_factor(
+ const float index_factor) const
+{
+ const int points_len = this->size();
+ const int index = std::floor(index_factor);
+ if (index == points_len) {
+ BLI_assert(is_cyclic_);
+ return InterpolationData{points_len - 1, 0, 1.0f};
+ }
+ if (index == points_len - 1) {
+ return InterpolationData{points_len - 2, points_len - 1, 1.0f};
+ }
+ return InterpolationData{index, index + 1, index_factor - index};
+}
+
+/* Use a spline argument to avoid adding this to the header. */
+template<typename T>
+static void interpolate_to_evaluated_points_impl(const BezierSpline &spline,
+ const blender::VArray<T> &source_data,
+ MutableSpan<T> result_data)
+{
+ Span<float> mappings = spline.evaluated_mappings();
+
+ for (const int i : result_data.index_range()) {
+ BezierSpline::InterpolationData interp = spline.interpolation_data_from_index_factor(
+ mappings[i]);
+
+ const T &value = source_data[interp.control_point_index];
+ const T &next_value = source_data[interp.next_control_point_index];
+
+ result_data[i] = blender::attribute_math::mix2(interp.factor, value, next_value);
+ }
+}
+
+blender::fn::GVArrayPtr BezierSpline::interpolate_to_evaluated_points(
+ const blender::fn::GVArray &source_data) const
+{
+ BLI_assert(source_data.size() == this->size());
+
+ const int eval_size = this->evaluated_points_size();
+ if (eval_size == 1) {
+ return source_data.shallow_copy();
+ }
+
+ blender::fn::GVArrayPtr new_varray;
+ blender::attribute_math::convert_to_static_type(source_data.type(), [&](auto dummy) {
+ using T = decltype(dummy);
+ if constexpr (!std::is_void_v<blender::attribute_math::DefaultMixer<T>>) {
+ Array<T> values(eval_size);
+ interpolate_to_evaluated_points_impl<T>(*this, source_data.typed<T>(), values);
+ new_varray = std::make_unique<blender::fn::GVArray_For_ArrayContainer<Array<T>>>(
+ std::move(values));
+ }
+ });
+
+ return new_varray;
+}
diff --git a/source/blender/blenkernel/intern/spline_nurbs.cc b/source/blender/blenkernel/intern/spline_nurbs.cc
new file mode 100644
index 00000000000..37d1232cfeb
--- /dev/null
+++ b/source/blender/blenkernel/intern/spline_nurbs.cc
@@ -0,0 +1,417 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BLI_array.hh"
+#include "BLI_span.hh"
+#include "BLI_virtual_array.hh"
+
+#include "BKE_attribute_math.hh"
+#include "BKE_spline.hh"
+
+using blender::Array;
+using blender::float3;
+using blender::IndexRange;
+using blender::MutableSpan;
+using blender::Span;
+
+SplinePtr NURBSpline::copy() const
+{
+ return std::make_unique<NURBSpline>(*this);
+}
+
+int NURBSpline::size() const
+{
+ const int size = positions_.size();
+ BLI_assert(size == radii_.size());
+ BLI_assert(size == tilts_.size());
+ BLI_assert(size == weights_.size());
+ return size;
+}
+
+int NURBSpline::resolution() const
+{
+ return resolution_;
+}
+
+void NURBSpline::set_resolution(const int value)
+{
+ BLI_assert(value > 0);
+ resolution_ = value;
+ this->mark_cache_invalid();
+}
+
+uint8_t NURBSpline::order() const
+{
+ return order_;
+}
+
+void NURBSpline::set_order(const uint8_t value)
+{
+ BLI_assert(value >= 2 && value <= 6);
+ order_ = value;
+ this->mark_cache_invalid();
+}
+
+void NURBSpline::add_point(const float3 position,
+ const float radius,
+ const float tilt,
+ const float weight)
+{
+ positions_.append(position);
+ radii_.append(radius);
+ tilts_.append(tilt);
+ weights_.append(weight);
+ knots_dirty_ = true;
+ this->mark_cache_invalid();
+}
+
+MutableSpan<float3> NURBSpline::positions()
+{
+ return positions_;
+}
+Span<float3> NURBSpline::positions() const
+{
+ return positions_;
+}
+MutableSpan<float> NURBSpline::radii()
+{
+ return radii_;
+}
+Span<float> NURBSpline::radii() const
+{
+ return radii_;
+}
+MutableSpan<float> NURBSpline::tilts()
+{
+ return tilts_;
+}
+Span<float> NURBSpline::tilts() const
+{
+ return tilts_;
+}
+MutableSpan<float> NURBSpline::weights()
+{
+ return weights_;
+}
+Span<float> NURBSpline::weights() const
+{
+ return weights_;
+}
+
+void NURBSpline::mark_cache_invalid()
+{
+ basis_cache_dirty_ = true;
+ position_cache_dirty_ = true;
+ tangent_cache_dirty_ = true;
+ normal_cache_dirty_ = true;
+ length_cache_dirty_ = true;
+}
+
+int NURBSpline::evaluated_points_size() const
+{
+ if (!this->check_valid_size_and_order()) {
+ return 0;
+ }
+ return resolution_ * this->segments_size();
+}
+
+void NURBSpline::correct_end_tangents() const
+{
+}
+
+bool NURBSpline::check_valid_size_and_order() const
+{
+ if (this->size() < order_) {
+ return false;
+ }
+
+ if (!is_cyclic_ && this->knots_mode == KnotsMode::Bezier) {
+ if (order_ == 4) {
+ if (this->size() < 5) {
+ return false;
+ }
+ }
+ else if (order_ != 3) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+int NURBSpline::knots_size() const
+{
+ const int size = this->size() + order_;
+ return is_cyclic_ ? size + order_ - 1 : size;
+}
+
+void NURBSpline::calculate_knots() const
+{
+ const KnotsMode mode = this->knots_mode;
+ const int length = this->size();
+ const int order = order_;
+
+ knots_.resize(this->knots_size());
+
+ MutableSpan<float> knots = knots_;
+
+ if (mode == NURBSpline::KnotsMode::Normal || is_cyclic_) {
+ for (const int i : knots.index_range()) {
+ knots[i] = static_cast<float>(i);
+ }
+ }
+ else if (mode == NURBSpline::KnotsMode::EndPoint) {
+ float k = 0.0f;
+ for (const int i : IndexRange(1, knots.size())) {
+ knots[i - 1] = k;
+ if (i >= order && i <= length) {
+ k += 1.0f;
+ }
+ }
+ }
+ else if (mode == NURBSpline::KnotsMode::Bezier) {
+ BLI_assert(ELEM(order, 3, 4));
+ if (order == 3) {
+ float k = 0.6f;
+ for (const int i : knots.index_range()) {
+ if (i >= order && i <= length) {
+ k += 0.5f;
+ }
+ knots[i] = std::floor(k);
+ }
+ }
+ else {
+ float k = 0.34f;
+ for (const int i : knots.index_range()) {
+ knots[i] = std::floor(k);
+ k += 1.0f / 3.0f;
+ }
+ }
+ }
+
+ if (is_cyclic_) {
+ const int b = length + order - 1;
+ if (order > 2) {
+ for (const int i : IndexRange(1, order - 2)) {
+ if (knots[b] != knots[b - i]) {
+ if (i == order - 1) {
+ knots[length + order - 2] += 1.0f;
+ break;
+ }
+ }
+ }
+ }
+
+ int c = order;
+ for (int i = b; i < this->knots_size(); i++) {
+ knots[i] = knots[i - 1] + (knots[c] - knots[c - 1]);
+ c--;
+ }
+ }
+}
+
+Span<float> NURBSpline::knots() const
+{
+ if (!knots_dirty_) {
+ BLI_assert(knots_.size() == this->size() + order_);
+ return knots_;
+ }
+
+ std::lock_guard lock{knots_mutex_};
+ if (!knots_dirty_) {
+ BLI_assert(knots_.size() == this->size() + order_);
+ return knots_;
+ }
+
+ this->calculate_knots();
+
+ knots_dirty_ = false;
+
+ return knots_;
+}
+
+static void calculate_basis_for_point(const float parameter,
+ const int points_len,
+ const int order,
+ Span<float> knots,
+ MutableSpan<float> basis_buffer,
+ NURBSpline::BasisCache &basis_cache)
+{
+ /* Clamp parameter due to floating point inaccuracy. TODO: Look into using doubles. */
+ const float t = std::clamp(parameter, knots[0], knots[points_len + order - 1]);
+
+ int start = 0;
+ int end = 0;
+ for (const int i : IndexRange(points_len + order - 1)) {
+ const bool knots_equal = knots[i] == knots[i + 1];
+ if (knots_equal || t < knots[i] || t > knots[i + 1]) {
+ basis_buffer[i] = 0.0f;
+ continue;
+ }
+
+ basis_buffer[i] = 1.0f;
+ start = std::max(i - order - 1, 0);
+ end = i;
+ basis_buffer.slice(i + 1, points_len + order - 1 - i).fill(0.0f);
+ break;
+ }
+ basis_buffer[points_len + order - 1] = 0.0f;
+
+ for (const int i_order : IndexRange(2, order - 1)) {
+ if (end + i_order >= points_len + order) {
+ end = points_len + order - 1 - i_order;
+ }
+ for (const int i : IndexRange(start, end - start + 1)) {
+ float new_basis = 0.0f;
+ if (basis_buffer[i] != 0.0f) {
+ new_basis += ((t - knots[i]) * basis_buffer[i]) / (knots[i + i_order - 1] - knots[i]);
+ }
+
+ if (basis_buffer[i + 1] != 0.0f) {
+ new_basis += ((knots[i + i_order] - t) * basis_buffer[i + 1]) /
+ (knots[i + i_order] - knots[i + 1]);
+ }
+
+ basis_buffer[i] = new_basis;
+ }
+ }
+
+ /* Shrink the range of calculated values to avoid storing unnecessary zeros. */
+ while (basis_buffer[start] == 0.0f && start < end) {
+ start++;
+ }
+ while (basis_buffer[end] == 0.0f && end > start) {
+ end--;
+ }
+
+ basis_cache.weights.clear();
+ basis_cache.weights.extend(basis_buffer.slice(start, end - start + 1));
+ basis_cache.start_index = start;
+}
+
+void NURBSpline::calculate_basis_cache() const
+{
+ if (!basis_cache_dirty_) {
+ return;
+ }
+
+ std::lock_guard lock{basis_cache_mutex_};
+ if (!basis_cache_dirty_) {
+ return;
+ }
+
+ const int points_len = this->size();
+ const int eval_size = this->evaluated_points_size();
+ BLI_assert(this->evaluated_edges_size() > 0);
+ basis_cache_.resize(eval_size);
+
+ const int order = this->order();
+ Span<float> control_weights = this->weights();
+ Span<float> knots = this->knots();
+
+ MutableSpan<BasisCache> basis_cache(basis_cache_);
+
+ /* This buffer is reused by each basis calculation to store temporary values.
+ * Theoretically it could be optimized away in the future. */
+ Array<float> basis_buffer(this->knots_size());
+
+ const float start = knots[order - 1];
+ const float end = is_cyclic_ ? knots[points_len + order - 1] : knots[points_len];
+ const float step = (end - start) / this->evaluated_edges_size();
+ float parameter = start;
+ for (const int i : IndexRange(eval_size)) {
+ BasisCache &basis = basis_cache[i];
+ calculate_basis_for_point(
+ parameter, points_len + (is_cyclic_ ? order - 1 : 0), order, knots, basis_buffer, basis);
+ BLI_assert(basis.weights.size() <= order);
+
+ for (const int j : basis.weights.index_range()) {
+ const int point_index = (basis.start_index + j) % points_len;
+ basis.weights[j] *= control_weights[point_index];
+ }
+
+ parameter += step;
+ }
+
+ basis_cache_dirty_ = false;
+}
+
+template<typename T>
+void interpolate_to_evaluated_points_impl(Span<NURBSpline::BasisCache> weights,
+ const blender::VArray<T> &source_data,
+ MutableSpan<T> result_data)
+{
+ const int points_len = source_data.size();
+ BLI_assert(result_data.size() == weights.size());
+ blender::attribute_math::DefaultMixer<T> mixer(result_data);
+
+ for (const int i : result_data.index_range()) {
+ Span<float> point_weights = weights[i].weights;
+ const int start_index = weights[i].start_index;
+
+ for (const int j : point_weights.index_range()) {
+ const int point_index = (start_index + j) % points_len;
+ mixer.mix_in(i, source_data[point_index], point_weights[j]);
+ }
+ }
+
+ mixer.finalize();
+}
+
+blender::fn::GVArrayPtr NURBSpline::interpolate_to_evaluated_points(
+ const blender::fn::GVArray &source_data) const
+{
+ BLI_assert(source_data.size() == this->size());
+
+ this->calculate_basis_cache();
+ Span<BasisCache> weights(basis_cache_);
+
+ blender::fn::GVArrayPtr new_varray;
+ blender::attribute_math::convert_to_static_type(source_data.type(), [&](auto dummy) {
+ using T = decltype(dummy);
+ if constexpr (!std::is_void_v<blender::attribute_math::DefaultMixer<T>>) {
+ Array<T> values(this->evaluated_points_size());
+ interpolate_to_evaluated_points_impl<T>(weights, source_data.typed<T>(), values);
+ new_varray = std::make_unique<blender::fn::GVArray_For_ArrayContainer<Array<T>>>(
+ std::move(values));
+ }
+ });
+
+ return new_varray;
+}
+
+Span<float3> NURBSpline::evaluated_positions() const
+{
+ if (!position_cache_dirty_) {
+ return evaluated_position_cache_;
+ }
+
+ std::lock_guard lock{position_cache_mutex_};
+ if (!position_cache_dirty_) {
+ return evaluated_position_cache_;
+ }
+
+ const int eval_size = this->evaluated_points_size();
+ evaluated_position_cache_.resize(eval_size);
+
+ blender::fn::GVArray_Typed<float3> evaluated_positions{
+ this->interpolate_to_evaluated_points(blender::fn::GVArray_For_Span<float3>(positions_))};
+
+ evaluated_positions->materialize(evaluated_position_cache_);
+
+ position_cache_dirty_ = false;
+ return evaluated_position_cache_;
+}
diff --git a/source/blender/blenkernel/intern/spline_poly.cc b/source/blender/blenkernel/intern/spline_poly.cc
new file mode 100644
index 00000000000..09c578c0503
--- /dev/null
+++ b/source/blender/blenkernel/intern/spline_poly.cc
@@ -0,0 +1,105 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BLI_span.hh"
+#include "BLI_virtual_array.hh"
+
+#include "BKE_spline.hh"
+
+using blender::float3;
+using blender::MutableSpan;
+using blender::Span;
+
+SplinePtr PolySpline::copy() const
+{
+ return std::make_unique<PolySpline>(*this);
+}
+
+int PolySpline::size() const
+{
+ const int size = positions_.size();
+ BLI_assert(size == radii_.size());
+ BLI_assert(size == tilts_.size());
+ return size;
+}
+
+void PolySpline::add_point(const float3 position, const float radius, const float tilt)
+{
+ positions_.append(position);
+ radii_.append(radius);
+ tilts_.append(tilt);
+ this->mark_cache_invalid();
+}
+
+MutableSpan<float3> PolySpline::positions()
+{
+ return positions_;
+}
+Span<float3> PolySpline::positions() const
+{
+ return positions_;
+}
+MutableSpan<float> PolySpline::radii()
+{
+ return radii_;
+}
+Span<float> PolySpline::radii() const
+{
+ return radii_;
+}
+MutableSpan<float> PolySpline::tilts()
+{
+ return tilts_;
+}
+Span<float> PolySpline::tilts() const
+{
+ return tilts_;
+}
+
+void PolySpline::mark_cache_invalid()
+{
+ tangent_cache_dirty_ = true;
+ normal_cache_dirty_ = true;
+ length_cache_dirty_ = true;
+}
+
+int PolySpline::evaluated_points_size() const
+{
+ return this->size();
+}
+
+void PolySpline::correct_end_tangents() const
+{
+}
+
+Span<float3> PolySpline::evaluated_positions() const
+{
+ return this->positions();
+}
+
+/**
+ * Poly spline interpolation from control points to evaluated points is a special case, since
+ * the result data is the same as the input data. This function returns a GVArray that points to
+ * the original data. Therefore the lifetime of the returned virtual array must not be longer than
+ * the source data.
+ */
+blender::fn::GVArrayPtr PolySpline::interpolate_to_evaluated_points(
+ const blender::fn::GVArray &source_data) const
+{
+ BLI_assert(source_data.size() == this->size());
+
+ return source_data.shallow_copy();
+}
diff --git a/source/blender/blenlib/BLI_float3.hh b/source/blender/blenlib/BLI_float3.hh
index cbc4d4ed366..04aae375889 100644
--- a/source/blender/blenlib/BLI_float3.hh
+++ b/source/blender/blenlib/BLI_float3.hh
@@ -245,6 +245,13 @@ struct float3 {
return result;
}
+ static float3 cross(const float3 &a, const float3 &b)
+ {
+ float3 result;
+ cross_v3_v3v3(result, a, b);
+ return result;
+ }
+
static float3 project(const float3 &a, const float3 &b)
{
float3 result;
diff --git a/source/blender/blenlib/BLI_float4x4.hh b/source/blender/blenlib/BLI_float4x4.hh
index f5ba91bc12c..396b0b1bd21 100644
--- a/source/blender/blenlib/BLI_float4x4.hh
+++ b/source/blender/blenlib/BLI_float4x4.hh
@@ -45,6 +45,37 @@ struct float4x4 {
return mat;
}
+ static float4x4 from_normalized_axis_data(const float3 location,
+ const float3 forward,
+ const float3 up)
+ {
+ BLI_ASSERT_UNIT_V3(forward);
+ BLI_ASSERT_UNIT_V3(up);
+ float4x4 matrix;
+ const float3 cross = float3::cross(forward, up);
+ matrix.values[0][0] = forward.x;
+ matrix.values[1][0] = cross.x;
+ matrix.values[2][0] = up.x;
+ matrix.values[3][0] = location.x;
+
+ matrix.values[0][1] = forward.y;
+ matrix.values[1][1] = cross.y;
+ matrix.values[2][1] = up.y;
+ matrix.values[3][1] = location.y;
+
+ matrix.values[0][2] = forward.z;
+ matrix.values[1][2] = cross.z;
+ matrix.values[2][2] = up.z;
+ matrix.values[3][2] = location.z;
+
+ matrix.values[0][3] = 0.0f;
+ matrix.values[1][3] = 0.0f;
+ matrix.values[2][3] = 0.0f;
+ matrix.values[3][3] = 1.0f;
+
+ return matrix;
+ }
+
static float4x4 identity()
{
float4x4 mat;
@@ -116,6 +147,19 @@ struct float4x4 {
return scale;
}
+ void apply_scale(const float scale)
+ {
+ values[0][0] *= scale;
+ values[0][1] *= scale;
+ values[0][2] *= scale;
+ values[1][0] *= scale;
+ values[1][1] *= scale;
+ values[1][2] *= scale;
+ values[2][0] *= scale;
+ values[2][1] *= scale;
+ values[2][2] *= scale;
+ }
+
float4x4 inverted() const
{
float4x4 result;
diff --git a/source/blender/makesrna/intern/rna_attribute.c b/source/blender/makesrna/intern/rna_attribute.c
index b5d39157164..a256002ffc1 100644
--- a/source/blender/makesrna/intern/rna_attribute.c
+++ b/source/blender/makesrna/intern/rna_attribute.c
@@ -71,7 +71,7 @@ const EnumPropertyItem rna_enum_attribute_domain_items[] = {
{ATTR_DOMAIN_CORNER, "CORNER", 0, "Face Corner", "Attribute on mesh face corner"},
/* Not implement yet */
// {ATTR_DOMAIN_GRIDS, "GRIDS", 0, "Grids", "Attribute on mesh multires grids"},
- {ATTR_DOMAIN_CURVE, "CURVE", 0, "Curve", "Attribute on hair curve"},
+ {ATTR_DOMAIN_CURVE, "CURVE", 0, "Spline", "Attribute on spline"},
{0, NULL, 0, NULL, NULL},
};
@@ -81,6 +81,7 @@ const EnumPropertyItem rna_enum_attribute_domain_with_auto_items[] = {
{ATTR_DOMAIN_EDGE, "EDGE", 0, "Edge", "Attribute on mesh edge"},
{ATTR_DOMAIN_FACE, "FACE", 0, "Face", "Attribute on mesh faces"},
{ATTR_DOMAIN_CORNER, "CORNER", 0, "Face Corner", "Attribute on mesh face corner"},
+ {ATTR_DOMAIN_CURVE, "CURVE", 0, "Spline", "Attribute on spline"},
{0, NULL, 0, NULL, NULL},
};
diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c
index 2a513691762..aa07ff4006a 100644
--- a/source/blender/makesrna/intern/rna_space.c
+++ b/source/blender/makesrna/intern/rna_space.c
@@ -3046,6 +3046,10 @@ static void rna_SpaceSpreadsheet_geometry_component_type_update(Main *UNUSED(bma
if (sspreadsheet->geometry_component_type == GEO_COMPONENT_TYPE_POINT_CLOUD) {
sspreadsheet->attribute_domain = ATTR_DOMAIN_POINT;
}
+ if (sspreadsheet->geometry_component_type == GEO_COMPONENT_TYPE_CURVE &&
+ !ELEM(sspreadsheet->attribute_domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE)) {
+ sspreadsheet->attribute_domain = ATTR_DOMAIN_POINT;
+ }
}
const EnumPropertyItem *rna_SpaceSpreadsheet_attribute_domain_itemf(bContext *UNUSED(C),
@@ -3091,6 +3095,11 @@ const EnumPropertyItem *rna_SpaceSpreadsheet_attribute_domain_itemf(bContext *UN
continue;
}
}
+ if (component_type == GEO_COMPONENT_TYPE_CURVE) {
+ if (!ELEM(item->value, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE)) {
+ continue;
+ }
+ }
if (item->value == ATTR_DOMAIN_POINT && component_type == GEO_COMPONENT_TYPE_MESH) {
RNA_enum_item_add(&item_array, &items_len, &mesh_vertex_domain_item);
}
@@ -7485,6 +7494,11 @@ static void rna_def_space_spreadsheet(BlenderRNA *brna)
ICON_POINTCLOUD_DATA,
"Point Cloud",
"Point cloud component containing only point data"},
+ {GEO_COMPONENT_TYPE_CURVE,
+ "CURVE",
+ ICON_CURVE_DATA,
+ "Curve",
+ "Curve component containing spline and control point data"},
{GEO_COMPONENT_TYPE_INSTANCES,
"INSTANCES",
ICON_EMPTY_AXIS,
diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc
index 6f674798a59..63541329563 100644
--- a/source/blender/modifiers/intern/MOD_nodes.cc
+++ b/source/blender/modifiers/intern/MOD_nodes.cc
@@ -182,7 +182,7 @@ static void add_object_relation(const ModifierUpdateDepsgraphContext *ctx, Objec
if (object.type == OB_EMPTY && object.instance_collection != nullptr) {
add_collection_relation(ctx, *object.instance_collection);
}
- else if (ELEM(object.type, OB_MESH, OB_POINTCLOUD, OB_VOLUME)) {
+ else if (ELEM(object.type, OB_MESH, OB_POINTCLOUD, OB_VOLUME, OB_CURVE)) {
DEG_add_object_relation(ctx->node, &object, DEG_OB_COMP_GEOMETRY, "Nodes Modifier");
DEG_add_customdata_mask(ctx->node, &object, &dependency_data_mask);
}
diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt
index 38814646d75..c116ceb8968 100644
--- a/source/blender/nodes/CMakeLists.txt
+++ b/source/blender/nodes/CMakeLists.txt
@@ -160,6 +160,7 @@ set(SRC
geometry/nodes/node_geo_bounding_box.cc
geometry/nodes/node_geo_collection_info.cc
geometry/nodes/node_geo_common.cc
+ geometry/nodes/node_geo_curve_to_mesh.cc
geometry/nodes/node_geo_edge_split.cc
geometry/nodes/node_geo_is_viewport.cc
geometry/nodes/node_geo_join_geometry.cc
diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h
index af2a1665b3f..14c9e235bb6 100644
--- a/source/blender/nodes/NOD_geometry.h
+++ b/source/blender/nodes/NOD_geometry.h
@@ -48,6 +48,7 @@ void register_node_type_geo_attribute_remove(void);
void register_node_type_geo_boolean(void);
void register_node_type_geo_bounding_box(void);
void register_node_type_geo_collection_info(void);
+void register_node_type_geo_curve_to_mesh(void);
void register_node_type_geo_edge_split(void);
void register_node_type_geo_is_viewport(void);
void register_node_type_geo_join_geometry(void);
diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h
index 8b498df0a81..2e4187229f4 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -312,6 +312,7 @@ DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "ATTRIB
DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "")
DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "")
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_TRANSFER, def_geo_attribute_transfer, "ATTRIBUTE_TRANSFER", AttributeTransfer, "Attribute Transfer", "")
+DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "")
/* undefine macros */
#undef DefNode
diff --git a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc b/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc
index f5b79141703..720ca9731a8 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc
@@ -188,6 +188,9 @@ static void geo_node_align_rotation_to_vector_exec(GeoNodeExecParams params)
align_rotations_on_component(geometry_set.get_component_for_write<PointCloudComponent>(),
params);
}
+ if (geometry_set.has<CurveComponent>()) {
+ align_rotations_on_component(geometry_set.get_component_for_write<CurveComponent>(), params);
+ }
params.set_output("Geometry", geometry_set);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc
index 1943971d49f..21538db5455 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc
@@ -255,6 +255,9 @@ static void geo_node_attribute_clamp_exec(GeoNodeExecParams params)
if (geometry_set.has<PointCloudComponent>()) {
clamp_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params);
}
+ if (geometry_set.has<CurveComponent>()) {
+ clamp_attribute(geometry_set.get_component_for_write<CurveComponent>(), params);
+ }
params.set_output("Geometry", geometry_set);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc
index f8c27ff55f4..26ddb0da515 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc
@@ -108,6 +108,9 @@ static void geo_node_attribute_color_ramp_exec(GeoNodeExecParams params)
if (geometry_set.has<PointCloudComponent>()) {
execute_on_component(params, geometry_set.get_component_for_write<PointCloudComponent>());
}
+ if (geometry_set.has<CurveComponent>()) {
+ execute_on_component(params, geometry_set.get_component_for_write<CurveComponent>());
+ }
params.set_output("Geometry", std::move(geometry_set));
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc
index d7ed8b6caf8..d8c52d16f41 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc
@@ -127,6 +127,9 @@ static void geo_node_attribute_combine_xyz_exec(GeoNodeExecParams params)
if (geometry_set.has<PointCloudComponent>()) {
combine_attributes(geometry_set.get_component_for_write<PointCloudComponent>(), params);
}
+ if (geometry_set.has<CurveComponent>()) {
+ combine_attributes(geometry_set.get_component_for_write<CurveComponent>(), params);
+ }
params.set_output("Geometry", geometry_set);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc
index ccfaeb9bb47..a2ff1668a06 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc
@@ -334,6 +334,9 @@ static void geo_node_attribute_compare_exec(GeoNodeExecParams params)
if (geometry_set.has<PointCloudComponent>()) {
attribute_compare_calc(geometry_set.get_component_for_write<PointCloudComponent>(), params);
}
+ if (geometry_set.has<CurveComponent>()) {
+ attribute_compare_calc(geometry_set.get_component_for_write<CurveComponent>(), params);
+ }
params.set_output("Geometry", geometry_set);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc
index 28fd06a8cc1..7b40456b180 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc
@@ -167,6 +167,14 @@ static void geo_node_attribute_convert_exec(GeoNodeExecParams params)
data_type,
domain);
}
+ if (geometry_set.has<CurveComponent>()) {
+ attribute_convert_calc(geometry_set.get_component_for_write<CurveComponent>(),
+ params,
+ source_name,
+ result_name,
+ data_type,
+ domain);
+ }
params.set_output("Geometry", geometry_set);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc
index 3df64625b99..60522fd0f72 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc
@@ -143,6 +143,9 @@ static void geo_node_attribute_fill_exec(GeoNodeExecParams params)
if (geometry_set.has<PointCloudComponent>()) {
fill_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params);
}
+ if (geometry_set.has<CurveComponent>()) {
+ fill_attribute(geometry_set.get_component_for_write<CurveComponent>(), params);
+ }
params.set_output("Geometry", geometry_set);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc
index 55b44db7afd..40fe675bd6c 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc
@@ -410,6 +410,9 @@ static void geo_node_attribute_map_range_exec(GeoNodeExecParams params)
if (geometry_set.has<PointCloudComponent>()) {
map_range_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params);
}
+ if (geometry_set.has<CurveComponent>()) {
+ map_range_attribute(geometry_set.get_component_for_write<CurveComponent>(), params);
+ }
params.set_output("Geometry", geometry_set);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc
index b5855038395..ce0ca31cc2b 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc
@@ -279,6 +279,9 @@ static void geo_node_attribute_math_exec(GeoNodeExecParams params)
if (geometry_set.has<PointCloudComponent>()) {
attribute_math_calc(geometry_set.get_component_for_write<PointCloudComponent>(), params);
}
+ if (geometry_set.has<CurveComponent>()) {
+ attribute_math_calc(geometry_set.get_component_for_write<CurveComponent>(), params);
+ }
params.set_output("Geometry", geometry_set);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc
index e6dd53d8e7f..3e5326edbf6 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc
@@ -209,6 +209,9 @@ static void geo_node_attribute_mix_exec(GeoNodeExecParams params)
if (geometry_set.has<PointCloudComponent>()) {
attribute_mix_calc(geometry_set.get_component_for_write<PointCloudComponent>(), params);
}
+ if (geometry_set.has<CurveComponent>()) {
+ attribute_mix_calc(geometry_set.get_component_for_write<CurveComponent>(), params);
+ }
params.set_output("Geometry", geometry_set);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc
index 8e9892f00b6..9c22b7fa87f 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc
@@ -253,6 +253,10 @@ static void geo_node_attribute_proximity_exec(GeoNodeExecParams params)
attribute_calc_proximity(
geometry_set.get_component_for_write<PointCloudComponent>(), geometry_set_target, params);
}
+ if (geometry_set.has<CurveComponent>()) {
+ attribute_calc_proximity(
+ geometry_set.get_component_for_write<CurveComponent>(), geometry_set_target, params);
+ }
params.set_output("Geometry", geometry_set);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc
index 9bb3cbd976c..286411b7d28 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc
@@ -315,6 +315,14 @@ static void geo_node_random_attribute_exec(GeoNodeExecParams params)
operation,
seed);
}
+ if (geometry_set.has<CurveComponent>()) {
+ randomize_attribute_on_component(geometry_set.get_component_for_write<CurveComponent>(),
+ params,
+ attribute_name,
+ data_type,
+ operation,
+ seed);
+ }
params.set_output("Geometry", geometry_set);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc
index 837f0c3629a..e4f3230ebb9 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc
@@ -70,6 +70,10 @@ static void geo_node_attribute_remove_exec(GeoNodeExecParams params)
remove_attribute(
geometry_set.get_component_for_write<PointCloudComponent>(), params, attribute_names);
}
+ if (geometry_set.has<CurveComponent>()) {
+ remove_attribute(
+ geometry_set.get_component_for_write<CurveComponent>(), params, attribute_names);
+ }
params.set_output("Geometry", geometry_set);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc
index 2fada0282ea..59790e5e46c 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc
@@ -122,6 +122,9 @@ static void geo_node_attribute_sample_texture_exec(GeoNodeExecParams params)
if (geometry_set.has<PointCloudComponent>()) {
execute_on_component(geometry_set.get_component_for_write<PointCloudComponent>(), params);
}
+ if (geometry_set.has<CurveComponent>()) {
+ execute_on_component(geometry_set.get_component_for_write<CurveComponent>(), params);
+ }
params.set_output("Geometry", geometry_set);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc
index bbc6cb71032..137a72bb707 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc
@@ -148,6 +148,9 @@ static void geo_node_attribute_separate_xyz_exec(GeoNodeExecParams params)
if (geometry_set.has<PointCloudComponent>()) {
separate_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params);
}
+ if (geometry_set.has<CurveComponent>()) {
+ separate_attribute(geometry_set.get_component_for_write<CurveComponent>(), params);
+ }
params.set_output("Geometry", geometry_set);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc
index 434befa6146..8877af445f9 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc
@@ -525,6 +525,9 @@ static void geo_node_attribute_vector_math_exec(GeoNodeExecParams params)
attribute_vector_math_calc(geometry_set.get_component_for_write<PointCloudComponent>(),
params);
}
+ if (geometry_set.has<CurveComponent>()) {
+ attribute_vector_math_calc(geometry_set.get_component_for_write<CurveComponent>(), params);
+ }
params.set_output("Geometry", geometry_set);
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc
new file mode 100644
index 00000000000..071504ad8df
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc
@@ -0,0 +1,312 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BLI_array.hh"
+#include "BLI_float4x4.hh"
+#include "BLI_timeit.hh"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BKE_mesh.h"
+#include "BKE_spline.hh"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "node_geometry_util.hh"
+
+static bNodeSocketTemplate geo_node_curve_to_mesh_in[] = {
+ {SOCK_GEOMETRY, N_("Curve")},
+ {SOCK_GEOMETRY, N_("Profile Curve")},
+ {-1, ""},
+};
+
+static bNodeSocketTemplate geo_node_curve_to_mesh_out[] = {
+ {SOCK_GEOMETRY, N_("Mesh")},
+ {-1, ""},
+};
+
+namespace blender::nodes {
+
+static void vert_extrude_to_mesh_data(const Spline &spline,
+ const float3 profile_vert,
+ MutableSpan<MVert> r_verts,
+ MutableSpan<MEdge> r_edges,
+ int &vert_offset,
+ int &edge_offset)
+{
+ Span<float3> positions = spline.evaluated_positions();
+
+ for (const int i : IndexRange(positions.size() - 1)) {
+ MEdge &edge = r_edges[edge_offset++];
+ edge.v1 = vert_offset + i;
+ edge.v2 = vert_offset + i + 1;
+ edge.flag = ME_LOOSEEDGE;
+ }
+
+ if (spline.is_cyclic()) {
+ MEdge &edge = r_edges[edge_offset++];
+ edge.v1 = vert_offset;
+ edge.v2 = vert_offset + positions.size() - 1;
+ edge.flag = ME_LOOSEEDGE;
+ }
+
+ for (const int i : positions.index_range()) {
+ MVert &vert = r_verts[vert_offset++];
+ copy_v3_v3(vert.co, positions[i] + profile_vert);
+ }
+}
+
+static void mark_edges_sharp(MutableSpan<MEdge> edges)
+{
+ for (MEdge &edge : edges) {
+ edge.flag |= ME_SHARP;
+ }
+}
+
+static void spline_extrude_to_mesh_data(const Spline &spline,
+ const Spline &profile_spline,
+ MutableSpan<MVert> r_verts,
+ MutableSpan<MEdge> r_edges,
+ MutableSpan<MLoop> r_loops,
+ MutableSpan<MPoly> r_polys,
+ int &vert_offset,
+ int &edge_offset,
+ int &loop_offset,
+ int &poly_offset)
+{
+ const int spline_vert_len = spline.evaluated_points_size();
+ const int spline_edge_len = spline.evaluated_edges_size();
+ const int profile_vert_len = profile_spline.evaluated_points_size();
+ const int profile_edge_len = profile_spline.evaluated_edges_size();
+ if (spline_vert_len == 0) {
+ return;
+ }
+
+ if (profile_vert_len == 1) {
+ vert_extrude_to_mesh_data(spline,
+ profile_spline.evaluated_positions()[0],
+ r_verts,
+ r_edges,
+ vert_offset,
+ edge_offset);
+ return;
+ }
+
+ /* Add the edges running along the length of the curve, starting at each profile vertex. */
+ const int spline_edges_start = edge_offset;
+ for (const int i_profile : IndexRange(profile_vert_len)) {
+ for (const int i_ring : IndexRange(spline_edge_len)) {
+ const int i_next_ring = (i_ring == spline_vert_len - 1) ? 0 : i_ring + 1;
+
+ const int ring_vert_offset = vert_offset + profile_vert_len * i_ring;
+ const int next_ring_vert_offset = vert_offset + profile_vert_len * i_next_ring;
+
+ MEdge &edge = r_edges[edge_offset++];
+ edge.v1 = ring_vert_offset + i_profile;
+ edge.v2 = next_ring_vert_offset + i_profile;
+ edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
+ }
+ }
+
+ /* Add the edges running along each profile ring. */
+ const int profile_edges_start = edge_offset;
+ for (const int i_ring : IndexRange(spline_vert_len)) {
+ const int ring_vert_offset = vert_offset + profile_vert_len * i_ring;
+
+ for (const int i_profile : IndexRange(profile_edge_len)) {
+ const int i_next_profile = (i_profile == profile_vert_len - 1) ? 0 : i_profile + 1;
+
+ MEdge &edge = r_edges[edge_offset++];
+ edge.v1 = ring_vert_offset + i_profile;
+ edge.v2 = ring_vert_offset + i_next_profile;
+ edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
+ }
+ }
+
+ /* Calculate poly and corner indices. */
+ for (const int i_ring : IndexRange(spline_edge_len)) {
+ const int i_next_ring = (i_ring == spline_vert_len - 1) ? 0 : i_ring + 1;
+
+ const int ring_vert_offset = vert_offset + profile_vert_len * i_ring;
+ const int next_ring_vert_offset = vert_offset + profile_vert_len * i_next_ring;
+
+ const int ring_edge_start = profile_edges_start + profile_edge_len * i_ring;
+ const int next_ring_edge_offset = profile_edges_start + profile_edge_len * i_next_ring;
+
+ for (const int i_profile : IndexRange(profile_edge_len)) {
+ const int i_next_profile = (i_profile == profile_vert_len - 1) ? 0 : i_profile + 1;
+
+ const int spline_edge_start = spline_edges_start + spline_edge_len * i_profile;
+ const int next_spline_edge_start = spline_edges_start + spline_edge_len * i_next_profile;
+
+ MPoly &poly = r_polys[poly_offset++];
+ poly.loopstart = loop_offset;
+ poly.totloop = 4;
+ poly.flag = ME_SMOOTH;
+
+ MLoop &loop_a = r_loops[loop_offset++];
+ loop_a.v = ring_vert_offset + i_profile;
+ loop_a.e = ring_edge_start + i_profile;
+ MLoop &loop_b = r_loops[loop_offset++];
+ loop_b.v = ring_vert_offset + i_next_profile;
+ loop_b.e = next_spline_edge_start + i_ring;
+ MLoop &loop_c = r_loops[loop_offset++];
+ loop_c.v = next_ring_vert_offset + i_next_profile;
+ loop_c.e = next_ring_edge_offset + i_profile;
+ MLoop &loop_d = r_loops[loop_offset++];
+ loop_d.v = next_ring_vert_offset + i_profile;
+ loop_d.e = spline_edge_start + i_ring;
+ }
+ }
+
+ /* Calculate the positions of each profile ring profile along the spline. */
+ Span<float3> positions = spline.evaluated_positions();
+ Span<float3> tangents = spline.evaluated_tangents();
+ Span<float3> normals = spline.evaluated_normals();
+ Span<float3> profile_positions = profile_spline.evaluated_positions();
+
+ GVArray_Typed<float> radii{
+ spline.interpolate_to_evaluated_points(blender::fn::GVArray_For_Span(spline.radii()))};
+ for (const int i_ring : IndexRange(spline_vert_len)) {
+ float4x4 point_matrix = float4x4::from_normalized_axis_data(
+ positions[i_ring], normals[i_ring], tangents[i_ring]);
+
+ point_matrix.apply_scale(radii[i_ring]);
+
+ for (const int i_profile : IndexRange(profile_vert_len)) {
+ MVert &vert = r_verts[vert_offset++];
+ copy_v3_v3(vert.co, point_matrix * profile_positions[i_profile]);
+ }
+ }
+
+ /* Mark edge loops from sharp vector control points sharp. */
+ if (profile_spline.type() == Spline::Type::Bezier) {
+ const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(profile_spline);
+ Span<int> control_point_offsets = bezier_spline.control_point_offsets();
+ for (const int i : control_point_offsets.index_range()) {
+ if (bezier_spline.point_is_sharp(i)) {
+ mark_edges_sharp(r_edges.slice(
+ spline_edges_start + spline_edge_len * control_point_offsets[i], spline_edge_len));
+ }
+ }
+ }
+}
+
+static Mesh *curve_to_mesh_calculate(const CurveEval &curve, const CurveEval &profile_curve)
+{
+ int profile_vert_total = 0;
+ int profile_edge_total = 0;
+ for (const SplinePtr &profile_spline : profile_curve.splines) {
+ profile_vert_total += profile_spline->evaluated_points_size();
+ profile_edge_total += profile_spline->evaluated_edges_size();
+ }
+
+ int vert_total = 0;
+ int edge_total = 0;
+ int poly_total = 0;
+ for (const SplinePtr &spline : curve.splines) {
+ const int spline_vert_len = spline->evaluated_points_size();
+ const int spline_edge_len = spline->evaluated_edges_size();
+ vert_total += spline_vert_len * profile_vert_total;
+ poly_total += spline_edge_len * profile_edge_total;
+
+ /* Add the ring edges, with one ring for every curve vertex, and the edge loops
+ * that run along the length of the curve, starting on the first profile. */
+ edge_total += profile_edge_total * spline_vert_len + profile_vert_total * spline_edge_len;
+ }
+ const int corner_total = poly_total * 4;
+
+ if (vert_total == 0) {
+ return nullptr;
+ }
+
+ Mesh *mesh = BKE_mesh_new_nomain(vert_total, edge_total, 0, corner_total, poly_total);
+ MutableSpan<MVert> verts{mesh->mvert, mesh->totvert};
+ MutableSpan<MEdge> edges{mesh->medge, mesh->totedge};
+ MutableSpan<MLoop> loops{mesh->mloop, mesh->totloop};
+ MutableSpan<MPoly> polys{mesh->mpoly, mesh->totpoly};
+ mesh->flag |= ME_AUTOSMOOTH;
+ mesh->smoothresh = DEG2RADF(180.0f);
+
+ int vert_offset = 0;
+ int edge_offset = 0;
+ int loop_offset = 0;
+ int poly_offset = 0;
+ for (const SplinePtr &spline : curve.splines) {
+ for (const SplinePtr &profile_spline : profile_curve.splines) {
+ spline_extrude_to_mesh_data(*spline,
+ *profile_spline,
+ verts,
+ edges,
+ loops,
+ polys,
+ vert_offset,
+ edge_offset,
+ loop_offset,
+ poly_offset);
+ }
+ }
+
+ BKE_mesh_calc_normals(mesh);
+
+ return mesh;
+}
+
+static CurveEval get_curve_single_vert()
+{
+ CurveEval curve;
+ std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>();
+ spline->add_point(float3(0), 0, 0.0f);
+ curve.splines.append(std::move(spline));
+
+ return curve;
+}
+
+static void geo_node_curve_to_mesh_exec(GeoNodeExecParams params)
+{
+ GeometrySet curve_set = params.extract_input<GeometrySet>("Curve");
+ GeometrySet profile_set = params.extract_input<GeometrySet>("Profile Curve");
+
+ curve_set = bke::geometry_set_realize_instances(curve_set);
+ profile_set = bke::geometry_set_realize_instances(profile_set);
+
+ if (!curve_set.has_curve()) {
+ params.set_output("Mesh", GeometrySet());
+ return;
+ }
+
+ const CurveEval *profile_curve = profile_set.get_curve_for_read();
+
+ static const CurveEval vert_curve = get_curve_single_vert();
+
+ Mesh *mesh = curve_to_mesh_calculate(*curve_set.get_curve_for_read(),
+ (profile_curve == nullptr) ? vert_curve : *profile_curve);
+ params.set_output("Mesh", GeometrySet::create_with_mesh(mesh));
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_curve_to_mesh()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(&ntype, GEO_NODE_CURVE_TO_MESH, "Curve to Mesh", NODE_CLASS_GEOMETRY, 0);
+ node_type_socket_templates(&ntype, geo_node_curve_to_mesh_in, geo_node_curve_to_mesh_out);
+ ntype.geometry_node_execute = blender::nodes::geo_node_curve_to_mesh_exec;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc
index 6696e2ccee8..edc2fb58e01 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc
@@ -17,6 +17,7 @@
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_pointcloud.h"
+#include "BKE_spline.hh"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
@@ -261,6 +262,40 @@ static void join_components(Span<const VolumeComponent *> src_components, Geomet
UNUSED_VARS(src_components, dst_component);
}
+static void join_curve_components(MutableSpan<GeometrySet> src_geometry_sets, GeometrySet &result)
+{
+ Vector<CurveComponent *> src_components;
+ for (GeometrySet &geometry_set : src_geometry_sets) {
+ if (geometry_set.has_curve()) {
+ /* Retrieving with write access seems counterintuitive, but it can allow avoiding a copy
+ * in the case where the input spline has no other users, because the splines can be
+ * moved from the source curve rather than copied from a read-only source. Retrieving
+ * the curve for write will make a copy only when it has a user elsewhere. */
+ CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>();
+ src_components.append(&component);
+ }
+ }
+
+ if (src_components.size() == 0) {
+ return;
+ }
+ if (src_components.size() == 1) {
+ result.add(*src_components[0]);
+ return;
+ }
+
+ CurveComponent &dst_component = result.get_component_for_write<CurveComponent>();
+ CurveEval *dst_curve = new CurveEval();
+ for (CurveComponent *component : src_components) {
+ CurveEval *src_curve = component->get_for_write();
+ for (SplinePtr &spline : src_curve->splines) {
+ dst_curve->splines.append(std::move(spline));
+ }
+ }
+
+ dst_component.replace(dst_curve);
+}
+
template<typename Component>
static void join_component_type(Span<GeometrySet> src_geometry_sets, GeometrySet &result)
{
@@ -291,6 +326,7 @@ static void geo_node_join_geometry_exec(GeoNodeExecParams params)
join_component_type<PointCloudComponent>(geometry_sets, geometry_set_result);
join_component_type<InstancesComponent>(geometry_sets, geometry_set_result);
join_component_type<VolumeComponent>(geometry_sets, geometry_set_result);
+ join_curve_components(geometry_sets, geometry_set_result);
params.set_output("Geometry", std::move(geometry_set_result));
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc
index 65b7d068003..f199ed71067 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc
@@ -209,6 +209,11 @@ static void geo_node_point_instance_exec(GeoNodeExecParams params)
instances, *geometry_set.get_component_for_read<PointCloudComponent>(), params);
}
+ if (geometry_set.has<CurveComponent>()) {
+ add_instances_from_geometry_component(
+ instances, *geometry_set.get_component_for_read<CurveComponent>(), params);
+ }
+
params.set_output("Geometry", std::move(geometry_set_out));
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc
index d73c83b4caf..e85f6aaee54 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc
@@ -186,6 +186,10 @@ static void initialize_volume_component_from_points(const GeometrySet &geometry_
gather_point_data_from_component(
params, *geometry_set_in.get_component_for_read<PointCloudComponent>(), positions, radii);
}
+ if (geometry_set_in.has<CurveComponent>()) {
+ gather_point_data_from_component(
+ params, *geometry_set_in.get_component_for_read<CurveComponent>(), positions, radii);
+ }
const float max_radius = *std::max_element(radii.begin(), radii.end());
const float voxel_size = compute_voxel_size(params, positions, max_radius);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_transform.cc b/source/blender/nodes/geometry/nodes/node_geo_transform.cc
index d54982d16c2..cba2c53907f 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_transform.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_transform.cc
@@ -24,6 +24,7 @@
#include "DNA_volume_types.h"
#include "BKE_mesh.h"
+#include "BKE_spline.hh"
#include "BKE_volume.h"
#include "DEG_depsgraph_query.h"
@@ -152,6 +153,21 @@ static void transform_volume(Volume *volume,
#endif
}
+static void transform_curve(CurveEval &curve,
+ const float3 translation,
+ const float3 rotation,
+ const float3 scale)
+{
+
+ if (use_translate(rotation, scale)) {
+ curve.translate(translation);
+ }
+ else {
+ const float4x4 matrix = float4x4::from_loc_eul_scale(translation, rotation, scale);
+ curve.transform(matrix);
+ }
+}
+
static void geo_node_transform_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
@@ -179,6 +195,11 @@ static void geo_node_transform_exec(GeoNodeExecParams params)
transform_volume(volume, translation, rotation, scale, params);
}
+ if (geometry_set.has_curve()) {
+ CurveEval *curve = geometry_set.get_curve_for_write();
+ transform_curve(*curve, translation, rotation, scale);
+ }
+
params.set_output("Geometry", std::move(geometry_set));
}
} // namespace blender::nodes