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:
-rw-r--r--release/scripts/startup/nodeitems_builtins.py6
-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.h4
-rw-r--r--source/blender/blenkernel/BKE_spline.hh421
-rw-r--r--source/blender/blenkernel/CMakeLists.txt9
-rw-r--r--source/blender/blenkernel/intern/attribute_access.cc2
-rw-r--r--source/blender/blenkernel/intern/displist.cc (renamed from source/blender/blenkernel/intern/displist.c)308
-rw-r--r--source/blender/blenkernel/intern/geometry_component_curve.cc403
-rw-r--r--source/blender/blenkernel/intern/geometry_set.cc47
-rw-r--r--source/blender/blenkernel/intern/node.cc4
-rw-r--r--source/blender/blenkernel/intern/spline_base.cc339
-rw-r--r--source/blender/blenkernel/intern/spline_bezier.cc463
-rw-r--r--source/blender/blenkernel/intern/spline_group.cc218
-rw-r--r--source/blender/blenkernel/intern/spline_nurbs.cc448
-rw-r--r--source/blender/blenkernel/intern/spline_poly.cc151
-rw-r--r--source/blender/blenlib/BLI_float3.hh7
-rw-r--r--source/blender/blenlib/BLI_float4x4.hh44
-rw-r--r--source/blender/functions/FN_generic_virtual_array.hh4
-rw-r--r--source/blender/makesdna/DNA_node_types.h20
-rw-r--r--source/blender/makesrna/intern/rna_attribute.c3
-rw-r--r--source/blender/makesrna/intern/rna_nodetree.c52
-rw-r--r--source/blender/makesrna/intern/rna_space.c14
-rw-r--r--source/blender/modifiers/intern/MOD_nodes.cc9
-rw-r--r--source/blender/nodes/CMakeLists.txt4
-rw-r--r--source/blender/nodes/NOD_geometry.h4
-rw-r--r--source/blender/nodes/NOD_static_types.h4
-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_sample_points.cc161
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc348
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_transform_test.cc73
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc230
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc44
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_object_info.cc28
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_point_instance.cc4
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_transform.cc21
52 files changed, 3869 insertions, 133 deletions
diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index ad2de08bcf2..5518ab9cde5 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -504,6 +504,12 @@ geometry_node_categories = [
NodeItem("ShaderNodeSeparateRGB"),
NodeItem("ShaderNodeCombineRGB"),
]),
+ GeometryNodeCategory("GEO_CURVE", "Curve", items=[
+ NodeItem("GeometryNodeCurveToMesh"),
+ NodeItem("GeometryNodeTransformTest"),
+ NodeItem("GeometryNodeCurveTrim"),
+ NodeItem("GeometryNodeCurveSamplePoints"),
+ ]),
GeometryNodeCategory("GEO_GEOMETRY", "Geometry", items=[
NodeItem("GeometryNodeBoundBox"),
NodeItem("GeometryNodeTransform"),
diff --git a/source/blender/blenkernel/BKE_attribute_math.hh b/source/blender/blenkernel/BKE_attribute_math.hh
index 65ac5b5bfa8..2557f23190b 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 027338a5d5c..fc3adde0178 100644
--- a/source/blender/blenkernel/BKE_geometry_set.hh
+++ b/source/blender/blenkernel/BKE_geometry_set.hh
@@ -34,11 +34,13 @@
#include "BKE_attribute_access.hh"
#include "BKE_geometry_set.h"
+struct SplineGroup;
struct Collection;
struct Mesh;
struct Object;
struct PointCloud;
struct Volume;
+struct SplineGroup;
enum class GeometryOwnershipType {
/* The geometry is owned. This implies that it can be changed. */
@@ -363,23 +365,32 @@ struct GeometrySet {
Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
static GeometrySet create_with_pointcloud(
PointCloud *pointcloud, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
+ static GeometrySet create_with_curve(
+ SplineGroup *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 SplineGroup *get_curve_for_read() const;
+
Mesh *get_mesh_for_write();
PointCloud *get_pointcloud_for_write();
Volume *get_volume_for_write();
+ SplineGroup *get_curve_for_write();
/* Utility methods for replacement. */
void replace_mesh(Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
void replace_pointcloud(PointCloud *pointcloud,
GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
+ void replace_curve(SplineGroup *mesh,
+ GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
};
/** A geometry component that can store a mesh. */
@@ -461,6 +472,37 @@ class PointCloudComponent : public GeometryComponent {
const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final;
};
+class CurveComponent : public GeometryComponent {
+ private:
+ SplineGroup *curve_ = nullptr;
+ GeometryOwnershipType ownership_ = GeometryOwnershipType::Owned;
+
+ public:
+ CurveComponent();
+ ~CurveComponent();
+ GeometryComponent *copy() const override;
+
+ void clear();
+ bool has_curve() const;
+ void replace(SplineGroup *curve, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
+ SplineGroup *release();
+
+ const SplineGroup *get_for_read() const;
+ SplineGroup *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 2a33c4819e3..a36e25956b4 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -1414,6 +1414,10 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_ATTRIBUTE_CLAMP 1041
#define GEO_NODE_BOUNDING_BOX 1042
#define GEO_NODE_SWITCH 1043
+#define GEO_NODE_CURVE_TO_MESH 1044
+#define GEO_NODE_CURVE_TRANSFORM_TEST 1045
+#define GEO_NODE_CURVE_TRIM 1046
+#define GEO_NODE_CURVE_SAMPLE_POINTS 1047
/** \} */
diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh
new file mode 100644
index 00000000000..7f58794a1d3
--- /dev/null
+++ b/source/blender/blenkernel/BKE_spline.hh
@@ -0,0 +1,421 @@
+/*
+ * 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 curve section, its evaluation methods, and data.
+ * The spline data itself is just control points and a set of attributes.
+ *
+ * Common evaluated data is stored in caches on the spline itself. This way operations on splines
+ * don't need to worry about taking ownership of evaluated data when they don't need to.
+ */
+class Spline {
+ public:
+ enum Type {
+ Bezier,
+ NURBS,
+ Poly,
+ };
+
+ private:
+ Type type_;
+
+ public:
+ bool is_cyclic = false;
+
+ enum NormalCalculationMode {
+ ZUp,
+ Minimum,
+ Tangent,
+ };
+ NormalCalculationMode normal_mode;
+
+ protected:
+ mutable bool tangent_cache_dirty_ = true;
+ mutable std::mutex tangent_cache_mutex_;
+ mutable blender::Vector<blender::float3> evaluated_tangents_cache_;
+
+ mutable bool normal_cache_dirty_ = true;
+ mutable std::mutex normal_cache_mutex_;
+ mutable blender::Vector<blender::float3> evaluated_normals_cache_;
+
+ mutable bool length_cache_dirty_ = true;
+ mutable std::mutex length_cache_mutex_;
+ mutable blender::Vector<float> evaluated_lengths_cache_;
+
+ 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)
+ {
+ if (!other.tangent_cache_dirty_) {
+ evaluated_tangents_cache_ = other.evaluated_tangents_cache_;
+ tangent_cache_dirty_ = false;
+ }
+ if (!other.normal_cache_dirty_) {
+ evaluated_normals_cache_ = other.evaluated_normals_cache_;
+ normal_cache_dirty_ = false;
+ }
+ if (!other.length_cache_dirty_) {
+ evaluated_lengths_cache_ = other.evaluated_lengths_cache_;
+ length_cache_dirty_ = false;
+ }
+ }
+
+ virtual SplinePtr copy() const = 0;
+
+ Spline::Type type() const;
+
+ virtual int size() const = 0;
+ int segments_size() const;
+ virtual int resolution() const = 0;
+ virtual void set_resolution(const int value) = 0;
+
+ virtual void drop_front(const int count) = 0;
+ virtual void drop_back(const int count) = 0;
+
+ 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;
+
+ /**
+ * Mark all caches for recomputation. 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.
+ */
+ int evaluated_index;
+ /*
+ * The index of the evaluated point after the result location,
+ * accounting for wrapping when the spline is cyclic.
+ */
+ int next_evaluated_index;
+ /**
+ * The portion of the way from the evaluated point at #evaluated_index to the next point.
+ */
+ float factor;
+ };
+ LookupResult lookup_evaluated_factor(const float factor) const;
+ LookupResult lookup_evaluated_length(const float length) const;
+
+ virtual blender::fn::GVArrayPtr interpolate_to_evaluated_points(
+ const blender::fn::GVArray &source_data) const = 0;
+
+ protected:
+ virtual void correct_end_tangents() const = 0;
+};
+
+class BezierSpline final : public Spline {
+ public:
+ enum HandleType {
+ Free,
+ Auto,
+ Vector,
+ Align,
+ };
+
+ private:
+ blender::Vector<HandleType> handle_types_start_;
+ blender::Vector<blender::float3> handle_positions_start_;
+ blender::Vector<blender::float3> positions_;
+ blender::Vector<HandleType> handle_types_end_;
+ blender::Vector<blender::float3> handle_positions_end_;
+ blender::Vector<float> radii_;
+ blender::Vector<float> tilts_;
+ int resolution_u_;
+
+ mutable bool base_cache_dirty_ = true;
+ mutable std::mutex base_cache_mutex_;
+ mutable blender::Vector<blender::float3> evaluated_positions_cache_;
+ mutable blender::Vector<float> evaluated_mappings_cache_;
+
+ public:
+ virtual SplinePtr copy() const final;
+ BezierSpline() : Spline(Type::Bezier){};
+ BezierSpline(const BezierSpline &other)
+ : Spline((Spline &)other),
+ handle_types_start_(other.handle_types_start_),
+ handle_positions_start_(other.handle_positions_start_),
+ positions_(other.positions_),
+ handle_types_end_(other.handle_types_end_),
+ handle_positions_end_(other.handle_positions_end_),
+ radii_(other.radii_),
+ tilts_(other.tilts_),
+ resolution_u_(other.resolution_u_)
+ {
+ if (!other.base_cache_dirty_) {
+ evaluated_positions_cache_ = other.evaluated_positions_cache_;
+ evaluated_mappings_cache_ = other.evaluated_mappings_cache_;
+ base_cache_dirty_ = false;
+ }
+ }
+
+ int size() const final;
+ int resolution() const final;
+ void set_resolution(const int value) final;
+
+ 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);
+
+ void drop_front(const int count) final;
+ void drop_back(const int count) final;
+
+ 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_start() const;
+ blender::MutableSpan<HandleType> handle_types_start();
+ blender::Span<blender::float3> handle_positions_start() const;
+ blender::MutableSpan<blender::float3> handle_positions_start();
+ blender::Span<HandleType> handle_types_end() const;
+ blender::MutableSpan<HandleType> handle_types_end();
+ blender::Span<blender::float3> handle_positions_end() const;
+ blender::MutableSpan<blender::float3> handle_positions_end();
+
+ bool point_is_sharp(const int index) const;
+ bool handle_start_is_automatic(const int index) const;
+ bool handle_end_is_automatic(const int index) const;
+
+ void move_control_point(const int index, const blender::float3 new_position);
+
+ void mark_cache_invalid() final;
+ int evaluated_points_size() const final;
+
+ 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;
+ float factor;
+ };
+ InterpolationData interpolation_data_from_map(const float map) const;
+
+ virtual blender::fn::GVArrayPtr interpolate_to_evaluated_points(
+ const blender::fn::GVArray &source_data) const;
+
+ protected:
+ void correct_final_tangents() 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,
+ int &offset,
+ blender::MutableSpan<blender::float3> positions,
+ blender::MutableSpan<float> mappings) const;
+ void evaluate_bezier_position_and_mapping() const;
+};
+
+class NURBSpline final : public Spline {
+ public:
+ enum KnotsMode {
+ Normal,
+ EndPoint,
+ Bezier,
+ };
+ KnotsMode knots_mode;
+
+ struct BasisCache {
+ blender::Vector<float> weights;
+ int start_index;
+ };
+
+ private:
+ blender::Vector<blender::float3> positions_;
+ blender::Vector<float> radii_;
+ blender::Vector<float> tilts_;
+ blender::Vector<float> weights_;
+ int resolution_u_;
+ uint8_t order_;
+
+ mutable bool knots_dirty_ = true;
+ mutable std::mutex knots_mutex_;
+ mutable blender::Vector<float> knots_;
+
+ mutable bool position_cache_dirty_ = true;
+ mutable std::mutex position_cache_mutex_;
+ mutable blender::Vector<blender::float3> evaluated_positions_cache_;
+
+ mutable bool basis_cache_dirty_ = true;
+ mutable std::mutex basis_cache_mutex_;
+ mutable blender::Vector<BasisCache> basis_cache_;
+
+ public:
+ SplinePtr copy() const final;
+ NURBSpline() : Spline(Type::NURBS){};
+ NURBSpline(const NURBSpline &other)
+ : Spline((Spline &)other),
+ positions_(other.positions_),
+ radii_(other.radii_),
+ tilts_(other.tilts_),
+ weights_(other.weights_),
+ resolution_u_(other.resolution_u_),
+ order_(other.order_)
+ {
+ }
+
+ int size() const final;
+ int resolution() const final;
+ void set_resolution(const int value) final;
+ 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);
+
+ void drop_front(const int count) final;
+ void drop_back(const int count) final;
+
+ 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;
+};
+
+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;
+ int resolution() const final;
+ void set_resolution(const int value) final;
+
+ void add_point(const blender::float3 position, const float radius, const float tilt);
+
+ void drop_front(const int count) final;
+ void drop_back(const int count) final;
+
+ 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;
+};
+
+/* Proposed name to be different from DNA type. */
+class SplineGroup {
+ public:
+ blender::Vector<SplinePtr> splines;
+
+ SplineGroup *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;
+};
+
+SplineGroup *dcurve_from_dna_curve(const Curve &curve); \ No newline at end of file
diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt
index adf321da8f0..7e0138ff85d 100644
--- a/source/blender/blenkernel/CMakeLists.txt
+++ b/source/blender/blenkernel/CMakeLists.txt
@@ -117,7 +117,7 @@ set(SRC
intern/customdata_file.c
intern/data_transfer.c
intern/deform.c
- intern/displist.c
+ intern/displist.cc
intern/displist_tangent.c
intern/dynamicpaint.c
intern/editlattice.c
@@ -133,6 +133,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 +242,11 @@ set(SRC
intern/softbody.c
intern/sound.c
intern/speaker.c
+ intern/spline_base.cc
+ intern/spline_bezier.cc
+ intern/spline_group.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/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/displist.c b/source/blender/blenkernel/intern/displist.cc
index ad8939fa5d1..0b960add98d 100644
--- a/source/blender/blenkernel/intern/displist.c
+++ b/source/blender/blenkernel/intern/displist.cc
@@ -21,9 +21,9 @@
* \ingroup bke
*/
-#include <math.h>
-#include <stdio.h>
-#include <string.h>
+#include <cmath>
+#include <cstdio>
+#include <cstring>
#include "MEM_guardedalloc.h"
@@ -46,6 +46,7 @@
#include "BKE_curve.h"
#include "BKE_displist.h"
#include "BKE_font.h"
+#include "BKE_geometry_set.hh"
#include "BKE_key.h"
#include "BKE_lattice.h"
#include "BKE_lib_id.h"
@@ -54,6 +55,7 @@
#include "BKE_mesh.h"
#include "BKE_modifier.h"
#include "BKE_object.h"
+#include "BKE_spline.hh"
#include "BLI_sys_types.h" // for intptr_t support
@@ -82,7 +84,7 @@ void BKE_displist_free(ListBase *lb)
{
DispList *dl;
- while ((dl = BLI_pophead(lb))) {
+ while ((dl = (DispList *)BLI_pophead(lb))) {
BKE_displist_elem_free(dl);
}
}
@@ -95,7 +97,7 @@ DispList *BKE_displist_find_or_create(ListBase *lb, int type)
}
}
- DispList *dl = MEM_callocN(sizeof(DispList), "find_disp");
+ DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), "find_disp");
dl->type = type;
BLI_addtail(lb, dl);
@@ -110,7 +112,7 @@ DispList *BKE_displist_find(ListBase *lb, int type)
}
}
- return NULL;
+ return nullptr;
}
bool BKE_displist_has_faces(const ListBase *lb)
@@ -129,11 +131,11 @@ void BKE_displist_copy(ListBase *lbn, const ListBase *lb)
BKE_displist_free(lbn);
LISTBASE_FOREACH (const DispList *, dl, lb) {
- DispList *dln = MEM_dupallocN(dl);
+ DispList *dln = (DispList *)MEM_dupallocN(dl);
BLI_addtail(lbn, dln);
- dln->verts = MEM_dupallocN(dl->verts);
- dln->nors = MEM_dupallocN(dl->nors);
- dln->index = MEM_dupallocN(dl->index);
+ dln->verts = (float *)MEM_dupallocN(dl->verts);
+ dln->nors = (float *)MEM_dupallocN(dl->nors);
+ dln->index = (int *)MEM_dupallocN(dl->index);
}
}
@@ -146,8 +148,8 @@ void BKE_displist_normals_add(ListBase *lb)
LISTBASE_FOREACH (DispList *, dl, lb) {
if (dl->type == DL_INDEX3) {
- if (dl->nors == NULL) {
- dl->nors = MEM_callocN(sizeof(float[3]), "dlnors");
+ if (dl->nors == nullptr) {
+ dl->nors = (float *)MEM_callocN(sizeof(float[3]), "dlnors");
if (dl->flag & DL_BACK_CURVE) {
dl->nors[2] = -1.0f;
@@ -158,8 +160,8 @@ void BKE_displist_normals_add(ListBase *lb)
}
}
else if (dl->type == DL_SURF) {
- if (dl->nors == NULL) {
- dl->nors = MEM_callocN(sizeof(float[3]) * dl->nr * dl->parts, "dlnors");
+ if (dl->nors == nullptr) {
+ dl->nors = (float *)MEM_callocN(sizeof(float[3]) * dl->nr * dl->parts, "dlnors");
vdata = dl->verts;
ndata = dl->nors;
@@ -338,9 +340,9 @@ static void curve_to_displist(const Curve *cu,
* and resolution > 1. */
const bool use_cyclic_sample = is_cyclic && (samples_len != 2);
- DispList *dl = MEM_callocN(sizeof(DispList), __func__);
+ DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), __func__);
/* Add one to the length because of 'BKE_curve_forward_diff_bezier'. */
- dl->verts = MEM_mallocN(sizeof(float[3]) * (samples_len + 1), "dlverts");
+ dl->verts = (float *)MEM_mallocN(sizeof(float[3]) * (samples_len + 1), "dlverts");
BLI_addtail(r_dispbase, dl);
dl->parts = 1;
dl->nr = samples_len;
@@ -393,8 +395,8 @@ static void curve_to_displist(const Curve *cu,
}
else if (nu->type == CU_NURBS) {
const int len = (resolution * SEGMENTSU(nu));
- DispList *dl = MEM_callocN(sizeof(DispList), __func__);
- dl->verts = MEM_mallocN(len * sizeof(float[3]), "dlverts");
+ DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), __func__);
+ dl->verts = (float *)MEM_mallocN(len * sizeof(float[3]), "dlverts");
BLI_addtail(r_dispbase, dl);
dl->parts = 1;
dl->nr = len;
@@ -402,12 +404,12 @@ static void curve_to_displist(const Curve *cu,
dl->charidx = nu->charidx;
dl->type = is_cyclic ? DL_POLY : DL_SEGM;
- BKE_nurb_makeCurve(nu, dl->verts, NULL, NULL, NULL, resolution, sizeof(float[3]));
+ BKE_nurb_makeCurve(nu, dl->verts, nullptr, nullptr, nullptr, resolution, sizeof(float[3]));
}
else if (nu->type == CU_POLY) {
const int len = nu->pntsu;
- DispList *dl = MEM_callocN(sizeof(DispList), __func__);
- dl->verts = MEM_mallocN(len * sizeof(float[3]), "dlverts");
+ DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), __func__);
+ dl->verts = (float *)MEM_mallocN(len * sizeof(float[3]), "dlverts");
BLI_addtail(r_dispbase, dl);
dl->parts = 1;
dl->nr = len;
@@ -435,7 +437,7 @@ void BKE_displist_fill(const ListBase *dispbase,
const float normal_proj[3],
const bool flip_normal)
{
- if (dispbase == NULL) {
+ if (dispbase == nullptr) {
return;
}
if (BLI_listbase_is_empty(dispbase)) {
@@ -471,14 +473,14 @@ void BKE_displist_fill(const ListBase *dispbase,
sf_ctx.poly_nr++;
/* Make verts and edges. */
- ScanFillVert *sf_vert = NULL;
- ScanFillVert *sf_vert_last = NULL;
- ScanFillVert *sf_vert_new = NULL;
+ ScanFillVert *sf_vert = nullptr;
+ ScanFillVert *sf_vert_last = nullptr;
+ ScanFillVert *sf_vert_new = nullptr;
for (int i = 0; i < dl->nr; i++) {
sf_vert_last = sf_vert;
sf_vert = BLI_scanfill_vert_add(&sf_ctx, &dl->verts[3 * i]);
totvert++;
- if (sf_vert_last == NULL) {
+ if (sf_vert_last == nullptr) {
sf_vert_new = sf_vert;
}
else {
@@ -486,7 +488,7 @@ void BKE_displist_fill(const ListBase *dispbase,
}
}
- if (sf_vert != NULL && sf_vert_new != NULL) {
+ if (sf_vert != nullptr && sf_vert_new != nullptr) {
BLI_scanfill_edge_add(&sf_ctx, sf_vert, sf_vert_new);
}
}
@@ -503,7 +505,7 @@ void BKE_displist_fill(const ListBase *dispbase,
const int triangles_len = BLI_scanfill_calc_ex(&sf_ctx, scanfill_flag, normal_proj);
if (totvert != 0 && triangles_len != 0) {
- DispList *dlnew = MEM_callocN(sizeof(DispList), "filldisplist");
+ DispList *dlnew = (DispList *)MEM_callocN(sizeof(DispList), "filldisplist");
dlnew->type = DL_INDEX3;
dlnew->flag = (dl_flag_accum & (DL_BACK_CURVE | DL_FRONT_CURVE));
dlnew->rt = (dl_rt_accum & CU_SMOOTH);
@@ -511,8 +513,8 @@ void BKE_displist_fill(const ListBase *dispbase,
dlnew->nr = totvert;
dlnew->parts = triangles_len;
- dlnew->index = MEM_mallocN(sizeof(int[3]) * triangles_len, "dlindex");
- dlnew->verts = MEM_mallocN(sizeof(float[3]) * totvert, "dlverts");
+ dlnew->index = (int *)MEM_mallocN(sizeof(int[3]) * triangles_len, "dlindex");
+ dlnew->verts = (float *)MEM_mallocN(sizeof(float[3]) * totvert, "dlverts");
/* vert data */
int i;
@@ -551,16 +553,16 @@ void BKE_displist_fill(const ListBase *dispbase,
static void bevels_to_filledpoly(const Curve *cu, ListBase *dispbase)
{
- ListBase front = {NULL, NULL};
- ListBase back = {NULL, NULL};
+ ListBase front = {nullptr, nullptr};
+ ListBase back = {nullptr, nullptr};
LISTBASE_FOREACH (const DispList *, dl, dispbase) {
if (dl->type == DL_SURF) {
if ((dl->flag & DL_CYCL_V) && (dl->flag & DL_CYCL_U) == 0) {
if ((cu->flag & CU_BACK) && (dl->flag & DL_BACK_CURVE)) {
- DispList *dlnew = MEM_callocN(sizeof(DispList), __func__);
+ DispList *dlnew = (DispList *)MEM_callocN(sizeof(DispList), __func__);
BLI_addtail(&front, dlnew);
- dlnew->verts = MEM_mallocN(sizeof(float[3]) * dl->parts, __func__);
+ dlnew->verts = (float *)MEM_mallocN(sizeof(float[3]) * dl->parts, __func__);
dlnew->nr = dl->parts;
dlnew->parts = 1;
dlnew->type = DL_POLY;
@@ -577,9 +579,9 @@ static void bevels_to_filledpoly(const Curve *cu, ListBase *dispbase)
}
}
if ((cu->flag & CU_FRONT) && (dl->flag & DL_FRONT_CURVE)) {
- DispList *dlnew = MEM_callocN(sizeof(DispList), __func__);
+ DispList *dlnew = (DispList *)MEM_callocN(sizeof(DispList), __func__);
BLI_addtail(&back, dlnew);
- dlnew->verts = MEM_mallocN(sizeof(float[3]) * dl->parts, __func__);
+ dlnew->verts = (float *)MEM_mallocN(sizeof(float[3]) * dl->parts, __func__);
dlnew->nr = dl->parts;
dlnew->parts = 1;
dlnew->type = DL_POLY;
@@ -634,16 +636,16 @@ static float displist_calc_taper(Depsgraph *depsgraph,
Object *taperobj,
float fac)
{
- DispList *dl;
-
- if (taperobj == NULL || taperobj->type != OB_CURVE) {
+ if (taperobj == nullptr || taperobj->type != OB_CURVE) {
return 1.0;
}
- dl = taperobj->runtime.curve_cache ? taperobj->runtime.curve_cache->disp.first : NULL;
- if (dl == NULL) {
+ DispList *dl = taperobj->runtime.curve_cache ?
+ (DispList *)taperobj->runtime.curve_cache->disp.first :
+ nullptr;
+ if (dl == nullptr) {
BKE_displist_make_curveTypes(depsgraph, scene, taperobj, false, false);
- dl = taperobj->runtime.curve_cache->disp.first;
+ dl = (DispList *)taperobj->runtime.curve_cache->disp.first;
}
if (dl) {
float minx, dx, *fp;
@@ -693,7 +695,8 @@ void BKE_displist_make_mball(Depsgraph *depsgraph, Scene *scene, Object *ob)
BKE_displist_free(&(ob->runtime.curve_cache->disp));
}
else {
- ob->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for MBall");
+ ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache),
+ "CurveCache for MBall");
}
BKE_mball_polygonize(depsgraph, scene, ob, &ob->runtime.curve_cache->disp);
@@ -738,9 +741,9 @@ static ModifierData *curve_get_tessellate_point(const Scene *scene,
required_mode |= eModifierMode_Editmode;
}
- pretessellatePoint = NULL;
+ pretessellatePoint = nullptr;
for (; md; md = md->next) {
- const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type);
+ const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type);
if (!BKE_modifier_is_enabled(scene, md, required_mode)) {
continue;
@@ -777,22 +780,22 @@ bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph,
VirtualModifierData virtualModifierData;
ModifierData *md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData);
ModifierData *pretessellatePoint;
- Curve *cu = ob->data;
+ Curve *cu = (Curve *)ob->data;
int numElems = 0, numVerts = 0;
const bool editmode = (!for_render && (cu->editnurb || cu->editfont));
- ModifierApplyFlag apply_flag = 0;
- float(*deformedVerts)[3] = NULL;
- float *keyVerts = NULL;
+ ModifierApplyFlag apply_flag = (ModifierApplyFlag)0;
+ float(*deformedVerts)[3] = nullptr;
+ float *keyVerts = nullptr;
int required_mode;
bool modified = false;
BKE_modifiers_clear_errors(ob);
if (editmode) {
- apply_flag |= MOD_APPLY_USECACHE;
+ apply_flag = MOD_APPLY_USECACHE;
}
if (for_render) {
- apply_flag |= MOD_APPLY_RENDER;
+ apply_flag = MOD_APPLY_RENDER;
required_mode = eModifierMode_Render;
}
else {
@@ -823,7 +826,7 @@ bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph,
if (pretessellatePoint) {
for (; md; md = md->next) {
- const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type);
+ const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type);
if (!BKE_modifier_is_enabled(scene, md, required_mode)) {
continue;
@@ -836,7 +839,7 @@ bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph,
deformedVerts = BKE_curve_nurbs_vert_coords_alloc(source_nurb, &numVerts);
}
- mti->deformVerts(md, &mectx, NULL, deformedVerts, numVerts);
+ mti->deformVerts(md, &mectx, nullptr, deformedVerts, numVerts);
modified = true;
if (md == pretessellatePoint) {
@@ -869,7 +872,7 @@ static float (*displist_vert_coords_alloc(ListBase *dispbase, int *r_vert_len))[
*r_vert_len += (dl->type == DL_INDEX3) ? dl->nr : dl->parts * dl->nr;
}
- allverts = MEM_mallocN(sizeof(float[3]) * (*r_vert_len), "displist_vert_coords_alloc allverts");
+ allverts = (float(*)[3])MEM_mallocN(sizeof(float[3]) * (*r_vert_len), __func__);
fp = (float *)allverts;
LISTBASE_FOREACH (DispList *, dl, dispbase) {
int ofs = 3 * ((dl->type == DL_INDEX3) ? dl->nr : dl->parts * dl->nr);
@@ -892,27 +895,72 @@ static void displist_vert_coords_apply(ListBase *dispbase, const float (*allvert
}
}
+static Mesh *modifier_modify_mesh_and_geometry_set(ModifierData *md,
+ const ModifierEvalContext &mectx,
+ const Curve *curve,
+ Object *ob,
+ Mesh *input_mesh,
+ GeometrySet &geometry_set)
+{
+ Mesh *mesh_output = nullptr;
+ const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type);
+ if (mti->modifyGeometrySet == nullptr) {
+ mesh_output = BKE_modifier_modify_mesh(md, &mectx, input_mesh);
+ }
+ else {
+ /* Adds a new mesh component to the geometry set based on the #input_mesh. */
+ BLI_assert(!geometry_set.has<MeshComponent>());
+ MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
+ mesh_component.replace(input_mesh, GeometryOwnershipType::Editable);
+ mesh_component.copy_vertex_group_names_from_object(*ob);
+
+ CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>();
+ curve_component.replace(dcurve_from_dna_curve(*curve));
+
+ /* Let the modifier change the geometry set. */
+ mti->modifyGeometrySet(md, &mectx, &geometry_set);
+
+ /* Release the mesh from the geometry set again. */
+ if (geometry_set.has<MeshComponent>()) {
+ MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
+ mesh_output = mesh_component.release();
+ geometry_set.remove<MeshComponent>();
+ }
+
+ /* Return an empty mesh instead of null. */
+ if (mesh_output == nullptr) {
+ mesh_output = BKE_mesh_new_nomain(0, 0, 0, 0, 0);
+ BKE_mesh_copy_settings(mesh_output, input_mesh);
+ }
+ }
+
+ return mesh_output;
+}
+
static void curve_calc_modifiers_post(Depsgraph *depsgraph,
const Scene *scene,
Object *ob,
ListBase *dispbase,
Mesh **r_final,
const bool for_render,
- const bool force_mesh_conversion)
+ const bool force_mesh_conversion,
+ GeometrySet **r_geometry_set)
{
+ GeometrySet geometry_set_final;
+
VirtualModifierData virtualModifierData;
ModifierData *md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData);
ModifierData *pretessellatePoint;
- const Curve *cu = ob->data;
+ const Curve *cu = (const Curve *)ob->data;
int required_mode = 0, totvert = 0;
const bool editmode = (!for_render && (cu->editnurb || cu->editfont));
- Mesh *modified = NULL, *mesh_applied;
- float(*vertCos)[3] = NULL;
+ Mesh *modified = nullptr, *mesh_applied;
+ float(*vertCos)[3] = nullptr;
int useCache = !for_render;
- ModifierApplyFlag apply_flag = 0;
+ ModifierApplyFlag apply_flag = (ModifierApplyFlag)0;
if (for_render) {
- apply_flag |= MOD_APPLY_RENDER;
+ apply_flag = MOD_APPLY_RENDER;
required_mode = eModifierMode_Render;
}
else {
@@ -920,9 +968,9 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph,
}
const ModifierEvalContext mectx_deform = {
- depsgraph, ob, editmode ? apply_flag | MOD_APPLY_USECACHE : apply_flag};
+ depsgraph, ob, editmode ? (ModifierApplyFlag)(apply_flag | MOD_APPLY_USECACHE) : apply_flag};
const ModifierEvalContext mectx_apply = {
- depsgraph, ob, useCache ? apply_flag | MOD_APPLY_USECACHE : apply_flag};
+ depsgraph, ob, useCache ? (ModifierApplyFlag)(apply_flag | MOD_APPLY_USECACHE) : apply_flag};
pretessellatePoint = curve_get_tessellate_point(scene, ob, for_render, editmode);
@@ -935,22 +983,22 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph,
}
if (r_final && *r_final) {
- BKE_id_free(NULL, *r_final);
+ BKE_id_free(nullptr, *r_final);
}
for (; md; md = md->next) {
- const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type);
+ const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type);
if (!BKE_modifier_is_enabled(scene, md, required_mode)) {
continue;
}
/* If we need normals, no choice, have to convert to mesh now. */
- bool need_normal = mti->dependsOnNormals != NULL && mti->dependsOnNormals(md);
+ bool need_normal = mti->dependsOnNormals != nullptr && mti->dependsOnNormals(md);
/* XXX 2.8 : now that batch cache is stored inside the ob->data
* we need to create a Mesh for each curve that uses modifiers. */
- if (modified == NULL /* && need_normal */) {
- if (vertCos != NULL) {
+ if (modified == nullptr /* && need_normal */) {
+ if (vertCos != nullptr) {
displist_vert_coords_apply(dispbase, vertCos);
}
@@ -976,7 +1024,7 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph,
if (!vertCos) {
vertCos = displist_vert_coords_alloc(dispbase, &totvert);
}
- mti->deformVerts(md, &mectx_deform, NULL, vertCos, totvert);
+ mti->deformVerts(md, &mectx_deform, nullptr, vertCos, totvert);
}
}
else {
@@ -991,8 +1039,8 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph,
if (modified) {
if (vertCos) {
Mesh *temp_mesh = (Mesh *)BKE_id_copy_ex(
- NULL, &modified->id, NULL, LIB_ID_COPY_LOCALIZE);
- BKE_id_free(NULL, modified);
+ nullptr, &modified->id, nullptr, LIB_ID_COPY_LOCALIZE);
+ BKE_id_free(nullptr, modified);
modified = temp_mesh;
BKE_mesh_vert_coords_apply(modified, vertCos);
@@ -1013,19 +1061,21 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph,
if (vertCos) {
/* Vertex coordinates were applied to necessary data, could free it */
MEM_freeN(vertCos);
- vertCos = NULL;
+ vertCos = nullptr;
}
if (need_normal) {
BKE_mesh_ensure_normals(modified);
}
- mesh_applied = mti->modifyMesh(md, &mectx_apply, modified);
+
+ mesh_applied = modifier_modify_mesh_and_geometry_set(
+ md, mectx_apply, cu, ob, modified, geometry_set_final);
if (mesh_applied) {
/* Modifier returned a new derived mesh */
if (modified && modified != mesh_applied) { /* Modifier */
- BKE_id_free(NULL, modified);
+ BKE_id_free(nullptr, modified);
}
modified = mesh_applied;
}
@@ -1034,8 +1084,9 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph,
if (vertCos) {
if (modified) {
- Mesh *temp_mesh = (Mesh *)BKE_id_copy_ex(NULL, &modified->id, NULL, LIB_ID_COPY_LOCALIZE);
- BKE_id_free(NULL, modified);
+ Mesh *temp_mesh = (Mesh *)BKE_id_copy_ex(
+ nullptr, &modified->id, nullptr, LIB_ID_COPY_LOCALIZE);
+ BKE_id_free(nullptr, modified);
modified = temp_mesh;
BKE_mesh_vert_coords_apply(modified, vertCos);
@@ -1046,7 +1097,7 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph,
else {
displist_vert_coords_apply(dispbase, vertCos);
MEM_freeN(vertCos);
- vertCos = NULL;
+ vertCos = nullptr;
}
}
@@ -1081,18 +1132,22 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph,
MEM_SAFE_FREE(modified->mat);
/* Set flag which makes it easier to see what's going on in a debugger. */
modified->id.tag |= LIB_TAG_COPIED_ON_WRITE_EVAL_RESULT;
- modified->mat = MEM_dupallocN(cu->mat);
+ modified->mat = (Material **)MEM_dupallocN(cu->mat);
modified->totcol = cu->totcol;
(*r_final) = modified;
}
else {
- (*r_final) = NULL;
+ (*r_final) = nullptr;
}
}
- else if (modified != NULL) {
+ else if (modified != nullptr) {
/* Pretty stupid to generate that whole mesh if it's unused, yet we have to free it. */
- BKE_id_free(NULL, modified);
+ BKE_id_free(nullptr, modified);
+ }
+
+ if (r_geometry_set) {
+ *r_geometry_set = new GeometrySet(std::move(geometry_set_final));
}
}
@@ -1103,8 +1158,8 @@ static void displist_surf_indices(DispList *dl)
dl->totindex = 0;
- index = dl->index = MEM_mallocN(sizeof(int[4]) * (dl->parts + 1) * (dl->nr + 1),
- "index array nurbs");
+ index = dl->index = (int *)MEM_mallocN(sizeof(int[4]) * (dl->parts + 1) * (dl->nr + 1),
+ "index array nurbs");
for (a = 0; a < dl->parts; a++) {
@@ -1136,8 +1191,8 @@ void BKE_displist_make_surf(Depsgraph *depsgraph,
const bool for_render,
const bool for_orco)
{
- ListBase nubase = {NULL, NULL};
- Curve *cu = ob->data;
+ ListBase nubase = {nullptr, nullptr};
+ Curve *cu = (Curve *)ob->data;
DispList *dl;
float *data;
int len;
@@ -1174,8 +1229,8 @@ void BKE_displist_make_surf(Depsgraph *depsgraph,
if (nu->pntsv == 1) {
len = SEGMENTSU(nu) * resolu;
- dl = MEM_callocN(sizeof(DispList), "makeDispListsurf");
- dl->verts = MEM_mallocN(len * sizeof(float[3]), "dlverts");
+ dl = (DispList *)MEM_callocN(sizeof(DispList), "makeDispListsurf");
+ dl->verts = (float *)MEM_mallocN(len * sizeof(float[3]), "dlverts");
BLI_addtail(dispbase, dl);
dl->parts = 1;
@@ -1195,13 +1250,13 @@ void BKE_displist_make_surf(Depsgraph *depsgraph,
dl->type = DL_SEGM;
}
- BKE_nurb_makeCurve(nu, data, NULL, NULL, NULL, resolu, sizeof(float[3]));
+ BKE_nurb_makeCurve(nu, data, nullptr, nullptr, nullptr, resolu, sizeof(float[3]));
}
else {
len = (nu->pntsu * resolu) * (nu->pntsv * resolv);
- dl = MEM_callocN(sizeof(DispList), "makeDispListsurf");
- dl->verts = MEM_mallocN(len * sizeof(float[3]), "dlverts");
+ dl = (DispList *)MEM_callocN(sizeof(DispList), "makeDispListsurf");
+ dl->verts = (float *)MEM_mallocN(len * sizeof(float[3]), "dlverts");
BLI_addtail(dispbase, dl);
dl->col = nu->mat_nr;
@@ -1233,7 +1288,7 @@ void BKE_displist_make_surf(Depsgraph *depsgraph,
if (!for_orco) {
BKE_nurbList_duplicate(&ob->runtime.curve_cache->deformed_nurbs, &nubase);
curve_calc_modifiers_post(
- depsgraph, scene, ob, dispbase, r_final, for_render, force_mesh_conversion);
+ depsgraph, scene, ob, dispbase, r_final, for_render, force_mesh_conversion, nullptr);
}
BKE_nurbList_free(&nubase);
@@ -1258,7 +1313,7 @@ static void rotateBevelPiece(const Curve *cu,
vec[1] = fp[2];
vec[2] = 0.0;
- if (nbevp == NULL) {
+ if (nbevp == nullptr) {
copy_v3_v3(data, bevp->vec);
copy_qt_qt(quat, bevp->quat);
}
@@ -1276,7 +1331,7 @@ static void rotateBevelPiece(const Curve *cu,
else {
float sina, cosa;
- if (nbevp == NULL) {
+ if (nbevp == nullptr) {
copy_v3_v3(data, bevp->vec);
sina = bevp->sina;
cosa = bevp->cosa;
@@ -1307,8 +1362,8 @@ static void fillBevelCap(const Nurb *nu,
{
DispList *dl;
- dl = MEM_callocN(sizeof(DispList), "makeDispListbev2");
- dl->verts = MEM_mallocN(sizeof(float[3]) * dlb->nr, "dlverts");
+ dl = (DispList *)MEM_callocN(sizeof(DispList), "makeDispListbev2");
+ dl->verts = (float *)MEM_mallocN(sizeof(float[3]) * dlb->nr, "dlverts");
memcpy(dl->verts, prev_fp, sizeof(float[3]) * dlb->nr);
dl->type = DL_POLY;
@@ -1467,9 +1522,10 @@ static void do_makeDispListCurveTypes(Depsgraph *depsgraph,
ListBase *dispbase,
const bool for_render,
const bool for_orco,
- Mesh **r_final)
+ Mesh **r_final,
+ GeometrySet **r_geometry_set)
{
- Curve *cu = ob->data;
+ Curve *cu = (Curve *)ob->data;
/* we do allow duplis... this is only displist on curve level */
if (!ELEM(ob->type, OB_SURF, OB_CURVE, OB_FONT)) {
@@ -1481,7 +1537,7 @@ static void do_makeDispListCurveTypes(Depsgraph *depsgraph,
}
else if (ELEM(ob->type, OB_CURVE, OB_FONT)) {
ListBase dlbev;
- ListBase nubase = {NULL, NULL};
+ ListBase nubase = {nullptr, nullptr};
bool force_mesh_conversion = false;
BKE_curve_bevelList_free(&ob->runtime.curve_cache->bev);
@@ -1520,8 +1576,8 @@ static void do_makeDispListCurveTypes(Depsgraph *depsgraph,
}
else {
const float widfac = cu->width - 1.0f;
- BevList *bl = ob->runtime.curve_cache->bev.first;
- Nurb *nu = nubase.first;
+ BevList *bl = (BevList *)ob->runtime.curve_cache->bev.first;
+ Nurb *nu = (Nurb *)nubase.first;
for (; bl && nu; bl = bl->next, nu = nu->next) {
float *data;
@@ -1532,8 +1588,8 @@ static void do_makeDispListCurveTypes(Depsgraph *depsgraph,
/* exception handling; curve without bevel or extrude, with width correction */
if (BLI_listbase_is_empty(&dlbev)) {
- DispList *dl = MEM_callocN(sizeof(DispList), "makeDispListbev");
- dl->verts = MEM_mallocN(sizeof(float[3]) * bl->nr, "dlverts");
+ DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), "makeDispListbev");
+ dl->verts = (float *)MEM_mallocN(sizeof(float[3]) * bl->nr, "dlverts");
BLI_addtail(dispbase, dl);
if (bl->poly != -1) {
@@ -1565,8 +1621,8 @@ static void do_makeDispListCurveTypes(Depsgraph *depsgraph,
}
}
else {
- ListBase bottom_capbase = {NULL, NULL};
- ListBase top_capbase = {NULL, NULL};
+ ListBase bottom_capbase = {nullptr, nullptr};
+ ListBase top_capbase = {nullptr, nullptr};
float bottom_no[3] = {0.0f};
float top_no[3] = {0.0f};
float first_blend = 0.0f, last_blend = 0.0f;
@@ -1585,8 +1641,8 @@ static void do_makeDispListCurveTypes(Depsgraph *depsgraph,
LISTBASE_FOREACH (DispList *, dlb, &dlbev) {
/* for each part of the bevel use a separate displblock */
- DispList *dl = MEM_callocN(sizeof(DispList), "makeDispListbev1");
- dl->verts = data = MEM_mallocN(sizeof(float[3]) * dlb->nr * steps, "dlverts");
+ DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), "makeDispListbev1");
+ dl->verts = data = (float *)MEM_mallocN(sizeof(float[3]) * dlb->nr * steps, "dlverts");
BLI_addtail(dispbase, dl);
dl->type = DL_SURF;
@@ -1616,7 +1672,7 @@ static void do_makeDispListCurveTypes(Depsgraph *depsgraph,
float radius_factor = 1.0;
float *cur_data = data;
- if (cu->taperobj == NULL) {
+ if (cu->taperobj == nullptr) {
radius_factor = bevp->radius;
}
else {
@@ -1666,7 +1722,7 @@ static void do_makeDispListCurveTypes(Depsgraph *depsgraph,
cu, bevp, bevp - 1, dlb, 1.0f - last_blend, widfac, radius_factor, &data);
}
else {
- rotateBevelPiece(cu, bevp, NULL, dlb, 0.0f, widfac, radius_factor, &data);
+ rotateBevelPiece(cu, bevp, nullptr, dlb, 0.0f, widfac, radius_factor, &data);
}
if ((cu->flag & CU_FILL_CAPS) && !(nu->flagu & CU_NURB_CYCLIC)) {
@@ -1707,8 +1763,14 @@ static void do_makeDispListCurveTypes(Depsgraph *depsgraph,
}
BKE_nurbList_duplicate(&ob->runtime.curve_cache->deformed_nurbs, &nubase);
- curve_calc_modifiers_post(
- depsgraph, scene, ob, dispbase, r_final, for_render, force_mesh_conversion);
+ curve_calc_modifiers_post(depsgraph,
+ scene,
+ ob,
+ dispbase,
+ r_final,
+ for_render,
+ force_mesh_conversion,
+ r_geometry_set);
}
if (cu->flag & CU_DEFORM_FILL && !ob->runtime.data_eval) {
@@ -1737,16 +1799,25 @@ void BKE_displist_make_curveTypes(Depsgraph *depsgraph,
BKE_object_free_derived_caches(ob);
if (!ob->runtime.curve_cache) {
- ob->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for curve types");
+ ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache),
+ "CurveCache for curve types");
}
dispbase = &(ob->runtime.curve_cache->disp);
- Mesh *mesh_eval = NULL;
- do_makeDispListCurveTypes(depsgraph, scene, ob, dispbase, for_render, for_orco, &mesh_eval);
+ Mesh *mesh_eval = nullptr;
+ GeometrySet *geometry_set_eval = nullptr;
+ do_makeDispListCurveTypes(
+ depsgraph, scene, ob, dispbase, for_render, for_orco, &mesh_eval, &geometry_set_eval);
- if (mesh_eval != NULL) {
+ if (mesh_eval != nullptr) {
BKE_object_eval_assign_data(ob, &mesh_eval->id, true);
+
+ /* Add the final mesh as read-only non-owning component to the geometry set. */
+ BLI_assert(!geometry_set_eval->has<MeshComponent>());
+ MeshComponent &mesh_component = geometry_set_eval->get_component_for_write<MeshComponent>();
+ mesh_component.replace(mesh_eval, GeometryOwnershipType::ReadOnly);
+ ob->runtime.geometry_set_eval = geometry_set_eval;
}
boundbox_displist_object(ob);
@@ -1759,11 +1830,12 @@ void BKE_displist_make_curveTypes_forRender(Depsgraph *depsgraph,
Mesh **r_final,
const bool for_orco)
{
- if (ob->runtime.curve_cache == NULL) {
- ob->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for Curve");
+ if (ob->runtime.curve_cache == nullptr) {
+ ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache),
+ "CurveCache for Curve");
}
- do_makeDispListCurveTypes(depsgraph, scene, ob, dispbase, true, for_orco, r_final);
+ do_makeDispListCurveTypes(depsgraph, scene, ob, dispbase, true, for_orco, r_final, nullptr);
}
void BKE_displist_minmax(const ListBase *dispbase, float min[3], float max[3])
@@ -1797,8 +1869,8 @@ static void boundbox_displist_object(Object *ob)
*/
/* object's BB is calculated from final displist */
- if (ob->runtime.bb == NULL) {
- ob->runtime.bb = MEM_callocN(sizeof(BoundBox), "boundbox");
+ if (ob->runtime.bb == nullptr) {
+ ob->runtime.bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), "boundbox");
}
Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob);
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..e0938d2101b
--- /dev/null
+++ b/source/blender/blenkernel/intern/geometry_component_curve.cc
@@ -0,0 +1,403 @@
+/*
+ * 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(SplineGroup *curve, GeometryOwnershipType ownership)
+{
+ BLI_assert(this->is_mutable());
+ this->clear();
+ curve_ = curve;
+ ownership_ = ownership;
+}
+
+SplineGroup *CurveComponent::release()
+{
+ BLI_assert(this->is_mutable());
+ SplineGroup *curve = curve_;
+ curve_ = nullptr;
+ return curve;
+}
+
+const SplineGroup *CurveComponent::get_for_read() const
+{
+ return curve_;
+}
+
+SplineGroup *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 SplineGroup &data);
+ using AsWriteAttribute = GVMutableArrayPtr (*)(SplineGroup &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 SplineGroup *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);
+ SplineGroup *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)
+{
+ return spline->resolution();
+}
+
+static void set_spline_resolution(SplinePtr &spline, const int resolution)
+{
+ spline->set_resolution(std::max(resolution, 1));
+}
+
+static GVArrayPtr make_resolution_read_attribute(const SplineGroup &curve)
+{
+ return std::make_unique<fn::GVArray_For_DerivedSpan<SplinePtr, int, get_spline_resolution>>(
+ curve.splines.as_span());
+}
+
+static GVMutableArrayPtr make_resolution_write_attribute(SplineGroup &curve)
+{
+ return std::make_unique<fn::GVMutableArray_For_DerivedSpan<SplinePtr,
+ int,
+ get_spline_resolution,
+ set_spline_resolution>>(
+ curve.splines.as_mutable_span());
+}
+
+static float get_spline_length(const SplinePtr &spline)
+{
+ return spline->length();
+}
+
+static GVArrayPtr make_length_attribute(const SplineGroup &curve)
+{
+ return std::make_unique<fn::GVArray_For_DerivedSpan<SplinePtr, float, get_spline_length>>(
+ curve.splines.as_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->is_cyclic = value;
+ spline->mark_cache_invalid();
+ }
+}
+
+static GVArrayPtr make_cyclic_read_attribute(const SplineGroup &curve)
+{
+ return std::make_unique<fn::GVArray_For_DerivedSpan<SplinePtr, bool, get_cyclic_value>>(
+ curve.splines.as_span());
+}
+
+static GVMutableArrayPtr make_cyclic_write_attribute(SplineGroup &curve)
+{
+ return std::make_unique<
+ fn::GVMutableArray_For_DerivedSpan<SplinePtr, bool, get_cyclic_value, set_cyclic_value>>(
+ curve.splines.as_mutable_span());
+}
+
+/**
+ * \note Currently this uses an inefficient method, copying data from each spline into a single
+ * array and then passing that as the attribute. Also, currently attributes are only read-only.
+ */
+class BuiltinPointAttributeProvider final : public BuiltinAttributeProvider {
+ using GetSplineData = void (*)(const Spline &spline, fn::GMutableSpan r_data);
+ using SetSplineData = void (*)(Spline &spline, fn::GSpan data);
+ const GetSplineData get_spline_data_;
+ const SetSplineData set_spline_data_;
+
+ public:
+ BuiltinPointAttributeProvider(std::string attribute_name,
+ const CustomDataType attribute_type,
+ const WritableEnum writable,
+ const GetSplineData get_spline_data,
+ const SetSplineData set_spline_data)
+ : BuiltinAttributeProvider(std::move(attribute_name),
+ ATTR_DOMAIN_POINT,
+ attribute_type,
+ BuiltinAttributeProvider::NonCreatable,
+ writable,
+ BuiltinAttributeProvider::NonDeletable),
+ get_spline_data_(get_spline_data),
+ set_spline_data_(set_spline_data)
+ {
+ }
+
+ GVArrayPtr try_get_for_read(const GeometryComponent &component) const final
+ {
+ const CurveComponent &curve_component = static_cast<const CurveComponent &>(component);
+ const SplineGroup *curve = curve_component.get_for_read();
+ if (curve == nullptr) {
+ return {};
+ }
+
+ GVArrayPtr varray;
+ attribute_math::convert_to_static_type(data_type_, [&](auto dummy) {
+ using T = decltype(dummy);
+ Array<T> values(curve_component.attribute_domain_size(ATTR_DOMAIN_POINT));
+
+ int offset = 0;
+ for (const SplinePtr &spline : curve->splines) {
+ const int points_len = spline->size();
+ MutableSpan<T> spline_data = values.as_mutable_span().slice(offset, points_len);
+ fn::GMutableSpan generic_spline_data(spline_data);
+ get_spline_data_(*spline, generic_spline_data);
+ offset += points_len;
+ }
+
+ varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values));
+ });
+
+ return varray;
+ }
+
+ GVMutableArrayPtr try_get_for_write(GeometryComponent &UNUSED(component)) const final
+ {
+ return {};
+ }
+
+ 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_POINT) != 0;
+ }
+};
+
+static void get_spline_radius_data(const Spline &spline, fn::GMutableSpan r_data)
+{
+ MutableSpan<float> r_span = r_data.typed<float>();
+ r_span.copy_from(spline.radii());
+}
+
+static void get_spline_position_data(const Spline &spline, fn::GMutableSpan r_data)
+{
+ MutableSpan<float3> r_span = r_data.typed<float3>();
+ r_span.copy_from(spline.positions());
+}
+
+/**
+ * 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 length(
+ "length", CD_PROP_FLOAT, BuiltinAttributeProvider::Readonly, make_length_attribute, nullptr);
+
+ static BuiltinSplineAttributeProvider cyclic("cyclic",
+ CD_PROP_BOOL,
+ BuiltinAttributeProvider::Writable,
+ make_cyclic_read_attribute,
+ make_cyclic_write_attribute);
+
+ static BuiltinPointAttributeProvider position("position",
+ CD_PROP_FLOAT3,
+ BuiltinAttributeProvider::Readonly,
+ get_spline_position_data,
+ nullptr);
+
+ static BuiltinPointAttributeProvider radius("radius",
+ CD_PROP_FLOAT,
+ BuiltinAttributeProvider::Readonly,
+ get_spline_radius_data,
+ nullptr);
+
+ return ComponentAttributeProviders({&resolution, &length, &cyclic, &position, &radius}, {});
+}
+
+} // 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 3e457e48076..c343fff51fa 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,13 @@ 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 SplineGroup *curve = this->get_curve_for_read();
+ if (curve != nullptr) {
+ /* Note the the choice of using the evaluated positions is somewhat arbitrary, and may counter
+ * the idea that the curve is the reduced set of control point information, but it may also be
+ * the expected result. */
+ curve->bounds_min_max(*r_min, *r_max, true);
+ }
}
std::ostream &operator<<(std::ostream &stream, const GeometrySet &geometry_set)
@@ -252,6 +262,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 SplineGroup *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 +290,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 +316,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(SplineGroup *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 +332,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(SplineGroup *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)
{
@@ -327,6 +367,13 @@ Volume *GeometrySet::get_volume_for_write()
return component.get_for_write();
}
+/* Returns a mutable curve or null. No ownership is transferred. */
+SplineGroup *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/node.cc b/source/blender/blenkernel/intern/node.cc
index 473be34d69a..8fc7c948a9a 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -4946,6 +4946,10 @@ 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_sample_points();
+ register_node_type_geo_curve_to_mesh();
+ register_node_type_geo_curve_transform_test();
+ register_node_type_geo_curve_trim();
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..93535fd4036
--- /dev/null
+++ b/source/blender/blenkernel/intern/spline_base.cc
@@ -0,0 +1,339 @@
+/*
+ * 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_spline.hh"
+
+using blender::Array;
+using blender::float3;
+using blender::float4x4;
+using blender::IndexRange;
+using blender::MutableSpan;
+using blender::Span;
+using blender::Vector;
+
+Spline::Type Spline::type() const
+{
+ return this->type_;
+}
+
+int Spline::evaluated_edges_size() const
+{
+ const int points_len = this->evaluated_points_size();
+
+ return this->is_cyclic ? points_len : points_len - 1;
+}
+
+float Spline::length() const
+{
+ return this->evaluated_lengths().last();
+}
+
+int Spline::segments_size() const
+{
+ const int points_len = this->size();
+
+ return this->is_cyclic ? points_len : points_len - 1;
+}
+
+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 (!this->length_cache_dirty_) {
+ return evaluated_lengths_cache_;
+ }
+
+ std::lock_guard lock{this->length_cache_mutex_};
+ if (!this->length_cache_dirty_) {
+ return evaluated_lengths_cache_;
+ }
+
+ const int total = this->evaluated_edges_size();
+ this->evaluated_lengths_cache_.resize(total);
+
+ Span<float3> positions = this->evaluated_positions();
+ accumulate_lengths(positions, this->is_cyclic, this->evaluated_lengths_cache_);
+
+ this->length_cache_dirty_ = false;
+ return evaluated_lengths_cache_;
+}
+
+/* TODO: Optimize this along with the function below. */
+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() - 1]).normalized();
+ }
+}
+
+/**
+ * Return non-owning access to the direction of the curve at each evaluated point.
+ */
+Span<float3> Spline::evaluated_tangents() const
+{
+ if (!this->tangent_cache_dirty_) {
+ return evaluated_tangents_cache_;
+ }
+
+ std::lock_guard lock{this->tangent_cache_mutex_};
+ if (!this->tangent_cache_dirty_) {
+ return evaluated_tangents_cache_;
+ }
+
+ const int total = this->evaluated_points_size();
+ this->evaluated_tangents_cache_.resize(total);
+
+ Span<float3> positions = this->evaluated_positions();
+
+ calculate_tangents(positions, this->is_cyclic, this->evaluated_tangents_cache_);
+
+ this->correct_end_tangents();
+
+ this->tangent_cache_dirty_ = false;
+ return evaluated_tangents_cache_;
+}
+
+static float3 initial_normal(const float3 first_tangent)
+{
+ /* TODO: Should be is "almost" zero. */
+ if (first_tangent.is_zero()) {
+ return float3(0.0f, 0.0f, 1.0f);
+ }
+
+ const float3 normal = float3::cross(first_tangent, float3(0.0f, 0.0f, 1.0f));
+ if (!normal.is_zero()) {
+ return normal.normalized();
+ }
+
+ return float3::cross(first_tangent, float3(0.0f, 1.0f, 0.0f)).normalized();
+}
+
+static float3 rotate_around_axis(const float3 dir, const float3 axis, const float angle)
+{
+ BLI_ASSERT_UNIT_V3(axis);
+ const float3 scaled_axis = axis * float3::dot(dir, axis);
+ const float3 sub = dir - scaled_axis;
+ const float3 cross = float3::cross(sub, sub);
+ const float sin = std::sin(angle);
+ const float cos = std::cos(angle);
+ return (scaled_axis + sub * cos + cross * sin).normalized();
+}
+
+static float3 project_on_center_plane(const float3 vector, const float3 plane_normal)
+{
+ BLI_ASSERT_UNIT_V3(plane_normal);
+ const float distance = float3::dot(vector, plane_normal);
+ const float3 projection_vector = plane_normal * -distance;
+ return vector + projection_vector;
+}
+
+static float3 propagate_normal(const float3 last_normal,
+ const float3 last_tangent,
+ const float3 current_tangent)
+{
+ const float angle = angle_normalized_v3v3(last_tangent, current_tangent);
+
+ if (angle == 0.0f) {
+ return last_normal;
+ }
+
+ const float3 axis = float3::cross(last_tangent, current_tangent).normalized();
+
+ const float3 new_normal = rotate_around_axis(last_normal, axis, angle);
+
+ return project_on_center_plane(new_normal, current_tangent).normalized();
+}
+
+static void apply_rotation_gradient(Span<float3> tangents,
+ MutableSpan<float3> normals,
+ const float full_angle)
+{
+
+ float remaining_rotation = full_angle;
+ float done_rotation = 0.0f;
+ for (const int i : IndexRange(1, normals.size() - 1)) {
+ if (angle_v3v3(tangents[i], tangents[i - 1]) < 0.001f) {
+ normals[i] = rotate_around_axis(normals[i], tangents[i], done_rotation);
+ }
+ else {
+ const float angle = remaining_rotation / (normals.size() - i);
+ normals[i] = rotate_around_axis(normals[i], tangents[i], angle + done_rotation);
+ remaining_rotation -= angle;
+ done_rotation += angle;
+ }
+ }
+}
+
+static void make_normals_cyclic(Span<float3> tangents, MutableSpan<float3> normals)
+{
+ const float3 last_normal = propagate_normal(normals.last(), tangents.last(), tangents.first());
+
+ float angle = angle_normalized_v3v3(normals.first(), last_normal);
+
+ const float3 cross = float3::cross(normals.first(), last_normal);
+ if (float3::dot(cross, tangents.first()) <= 0.0f) {
+ angle = -angle;
+ }
+
+ apply_rotation_gradient(tangents, normals, -angle);
+}
+
+/* This algorithm is a copy from animation nodes bezier normal calculation.
+ * TODO: Explore different methods, this also doesn't work right now. */
+static void calculate_normals_minimum_twist(Span<float3> tangents,
+ const bool is_cyclic,
+ MutableSpan<float3> normals)
+{
+ if (normals.size() == 1) {
+ normals.first() = float3(1.0f, 0.0f, 0.0f);
+ return;
+ }
+
+ /* Start by calculating a simple normal for the first point. */
+ normals[0] = initial_normal(tangents[0]);
+
+ /* Then propogate that normal along the spline. */
+ for (const int i : IndexRange(1, normals.size() - 1)) {
+ normals[i] = propagate_normal(normals[i - 1], tangents[i - 1], tangents[i]);
+ }
+
+ if (is_cyclic) {
+ make_normals_cyclic(tangents, normals);
+ }
+}
+
+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.
+ */
+Span<float3> Spline::evaluated_normals() const
+{
+ if (!this->normal_cache_dirty_) {
+ return evaluated_normals_cache_;
+ }
+
+ std::lock_guard lock{this->normal_cache_mutex_};
+ if (!this->normal_cache_dirty_) {
+ return evaluated_normals_cache_;
+ }
+
+ const int total = this->evaluated_points_size();
+ this->evaluated_normals_cache_.resize(total);
+
+ Span<float3> tangents = this->evaluated_tangents();
+ switch (this->normal_mode) {
+ case NormalCalculationMode::Minimum:
+ calculate_normals_minimum_twist(tangents, is_cyclic, this->evaluated_normals_cache_);
+ break;
+ case NormalCalculationMode::ZUp:
+ calculate_normals_z_up(tangents, this->evaluated_normals_cache_);
+ break;
+ case NormalCalculationMode::Tangent:
+ // calculate_normals_tangent(tangents, this->evaluated_normals_cache_);
+ break;
+ }
+
+ this->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);
+}
+
+/* TODO: Support extrapolation somehow. */
+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..ef77ce085c4
--- /dev/null
+++ b/source/blender/blenkernel/intern/spline_bezier.cc
@@ -0,0 +1,463 @@
+/*
+ * 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 "BKE_spline.hh"
+
+using blender::Array;
+using blender::float3;
+using blender::float4x4;
+using blender::IndexRange;
+using blender::MutableSpan;
+using blender::Span;
+using blender::Vector;
+
+SplinePtr BezierSpline::copy() const
+{
+ SplinePtr new_spline = std::make_unique<BezierSpline>(*this);
+
+ return new_spline;
+}
+
+int BezierSpline::size() const
+{
+ const int size = this->positions_.size();
+ BLI_assert(this->handle_types_start_.size() == size);
+ BLI_assert(this->handle_positions_start_.size() == size);
+ BLI_assert(this->handle_types_end_.size() == size);
+ BLI_assert(this->handle_positions_end_.size() == size);
+ BLI_assert(this->radii_.size() == size);
+ BLI_assert(this->tilts_.size() == size);
+ return size;
+}
+
+int BezierSpline::resolution() const
+{
+ return this->resolution_u_;
+}
+
+void BezierSpline::set_resolution(const int value)
+{
+ this->resolution_u_ = value;
+ this->mark_cache_invalid();
+}
+
+MutableSpan<float3> BezierSpline::positions()
+{
+ return this->positions_;
+}
+Span<float3> BezierSpline::positions() const
+{
+ return this->positions_;
+}
+MutableSpan<float> BezierSpline::radii()
+{
+ return this->radii_;
+}
+Span<float> BezierSpline::radii() const
+{
+ return this->radii_;
+}
+MutableSpan<float> BezierSpline::tilts()
+{
+ return this->tilts_;
+}
+Span<float> BezierSpline::tilts() const
+{
+ return this->tilts_;
+}
+Span<BezierSpline::HandleType> BezierSpline::handle_types_start() const
+{
+ return this->handle_types_start_;
+}
+MutableSpan<BezierSpline::HandleType> BezierSpline::handle_types_start()
+{
+ return this->handle_types_start_;
+}
+Span<float3> BezierSpline::handle_positions_start() const
+{
+ return this->handle_positions_start_;
+}
+MutableSpan<float3> BezierSpline::handle_positions_start()
+{
+ return this->handle_positions_start_;
+}
+Span<BezierSpline::HandleType> BezierSpline::handle_types_end() const
+{
+ return this->handle_types_end_;
+}
+MutableSpan<BezierSpline::HandleType> BezierSpline::handle_types_end()
+{
+ return this->handle_types_end_;
+}
+Span<float3> BezierSpline::handle_positions_end() const
+{
+ return this->handle_positions_end_;
+}
+MutableSpan<float3> BezierSpline::handle_positions_end()
+{
+ return this->handle_positions_end_;
+}
+
+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_start_.append(handle_type_start);
+ handle_positions_start_.append(handle_position_start);
+ positions_.append(position);
+ handle_types_end_.append(handle_type_end);
+ handle_positions_end_.append(handle_position_end);
+ radii_.append(radius);
+ tilts_.append(tilt);
+}
+
+void BezierSpline::drop_front(const int count)
+{
+ BLI_assert(this->size() - count > 0);
+ this->handle_types_start_.remove(0, count);
+ this->handle_positions_start_.remove(0, count);
+ this->positions_.remove(0, count);
+ this->handle_types_end_.remove(0, count);
+ this->handle_positions_end_.remove(0, count);
+ this->radii_.remove(0, count);
+ this->tilts_.remove(0, count);
+ this->mark_cache_invalid();
+}
+
+void BezierSpline::drop_back(const int count)
+{
+ const int new_size = this->size() - count;
+ BLI_assert(new_size > 0);
+ this->handle_types_start_.resize(new_size);
+ this->handle_positions_start_.resize(new_size);
+ this->positions_.resize(new_size);
+ this->handle_types_end_.resize(new_size);
+ this->handle_positions_end_.resize(new_size);
+ this->radii_.resize(new_size);
+ this->tilts_.resize(new_size);
+ this->mark_cache_invalid();
+}
+
+bool BezierSpline::point_is_sharp(const int index) const
+{
+ return ELEM(handle_types_start_[index], HandleType::Vector, HandleType::Free) ||
+ ELEM(handle_types_end_[index], HandleType::Vector, HandleType::Free);
+}
+
+bool BezierSpline::handle_start_is_automatic(const int index) const
+{
+ return ELEM(handle_types_start_[index], HandleType::Free, HandleType::Align);
+}
+
+bool BezierSpline::handle_end_is_automatic(const int index) const
+{
+ return ELEM(handle_types_end_[index], HandleType::Free, HandleType::Align);
+}
+
+void BezierSpline::move_control_point(const int index, const blender::float3 new_position)
+{
+ const float3 position_delta = new_position - positions_[index];
+ if (!this->handle_start_is_automatic(index)) {
+ handle_positions_start_[index] += position_delta;
+ }
+ if (!this->handle_end_is_automatic(index)) {
+ handle_positions_end_[index] += position_delta;
+ }
+ positions_[index] = new_position;
+}
+
+bool BezierSpline::segment_is_vector(const int index) const
+{
+ if (index == this->size() - 1) {
+ BLI_assert(this->is_cyclic);
+ return this->handle_types_end_.last() == HandleType::Vector &&
+ this->handle_types_start_.first() == HandleType::Vector;
+ }
+ return this->handle_types_end_[index] == HandleType::Vector &&
+ this->handle_types_start_[index + 1] == HandleType::Vector;
+}
+
+void BezierSpline::mark_cache_invalid()
+{
+ this->base_cache_dirty_ = true;
+ this->tangent_cache_dirty_ = true;
+ this->normal_cache_dirty_ = true;
+ this->length_cache_dirty_ = true;
+}
+
+int BezierSpline::evaluated_points_size() const
+{
+ BLI_assert(this->size() > 0);
+#ifndef DEBUG
+ if (!this->base_cache_dirty_) {
+ /* In a non-debug build, assume that the cache's size has not changed, and that any operation
+ * that would cause the cache to change its length would also mark the cache dirty. This is
+ * checked at the end of this function in a debug build. */
+ return this->evaluated_positions_cache_.size();
+ }
+#endif
+
+ int total_len = 0;
+ for (const int i : IndexRange(this->size() - 1)) {
+ if (this->segment_is_vector(i)) {
+ total_len += 1;
+ }
+ else {
+ total_len += this->resolution_u_;
+ }
+ }
+
+ if (this->is_cyclic) {
+ if (segment_is_vector(this->size() - 1)) {
+ total_len++;
+ }
+ else {
+ total_len += this->resolution_u_;
+ }
+ }
+ else {
+ /* Since evaulating the bezier doesn't add the final point's position,
+ * it must be added manually in the non-cyclic case. */
+ total_len++;
+ }
+
+ /* Assert that the cache has the correct length in debug mode. */
+ if (!this->base_cache_dirty_) {
+ BLI_assert(this->evaluated_positions_cache_.size() == total_len);
+ }
+
+ return total_len;
+}
+
+/**
+ * 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
+{
+ MutableSpan<float3> tangents(this->evaluated_tangents_cache_);
+
+ if (handle_positions_start_.first() != positions_.first()) {
+ tangents.first() = (positions_.first() - handle_positions_start_.first()).normalized();
+ }
+ if (handle_positions_end_.last() != positions_.last()) {
+ tangents.last() = (handle_positions_end_.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)
+{
+ const float len = static_cast<float>(result.size());
+ const float len_squared = len * len;
+ const float len_cubed = len_squared * len;
+ BLI_assert(len > 0.0f);
+
+ const float3 rt1 = 3.0f * (point_1 - point_0) / len;
+ const float3 rt2 = 3.0f * (point_0 - 2.0f * point_1 + point_2) / len_squared;
+ const float3 rt3 = (point_3 - point_0 + 3.0f * (point_1 - point_2)) / 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;
+ }
+}
+
+static void evaluate_segment_mapping(Span<float3> evaluated_positions,
+ MutableSpan<float> mappings,
+ const int index)
+{
+ float length = 0.0f;
+ mappings[0] = index;
+ for (const int i : IndexRange(1, mappings.size() - 1)) {
+ length += float3::distance(evaluated_positions[i - 1], evaluated_positions[i]);
+ mappings[i] = length;
+ }
+
+ /* To get the factors instead of the accumulated lengths, divide the mapping factors by the
+ * accumulated length. */
+ if (length != 0.0f) {
+ for (float &mapping : mappings) {
+ mapping = mapping / length + index;
+ }
+ }
+}
+
+void BezierSpline::evaluate_bezier_segment(const int index,
+ const int next_index,
+ int &offset,
+ MutableSpan<float3> positions,
+ MutableSpan<float> mappings) const
+{
+ if (this->segment_is_vector(index)) {
+ positions[offset] = positions_[index];
+ mappings[offset] = index;
+ offset++;
+ }
+ else {
+ bezier_forward_difference_3d(this->positions_[index],
+ this->handle_positions_end_[index],
+ this->handle_positions_start_[next_index],
+ this->positions_[next_index],
+ positions.slice(offset, this->resolution_u_));
+ evaluate_segment_mapping(positions.slice(offset, this->resolution_u_),
+ mappings.slice(offset, this->resolution_u_),
+ index);
+ offset += this->resolution_u_;
+ }
+}
+
+void BezierSpline::evaluate_bezier_position_and_mapping() const
+{
+ if (!this->base_cache_dirty_) {
+ return;
+ }
+
+ std::lock_guard lock{this->base_cache_mutex_};
+ if (!this->base_cache_dirty_) {
+ return;
+ }
+
+ const int total = this->evaluated_points_size();
+ this->evaluated_positions_cache_.resize(total);
+ this->evaluated_mappings_cache_.resize(total);
+
+ MutableSpan<float3> positions = this->evaluated_positions_cache_;
+ MutableSpan<float> mappings = this->evaluated_mappings_cache_;
+
+ /* TODO: It would also be possible to store an array of offsets to facilitate parallelism here,
+ * maybe it is worth it? */
+ int offset = 0;
+ for (const int i : IndexRange(this->size() - 1)) {
+ this->evaluate_bezier_segment(i, i + 1, offset, positions, mappings);
+ }
+
+ const int i_last = this->size() - 1;
+ if (this->is_cyclic) {
+ this->evaluate_bezier_segment(i_last, 0, offset, positions, mappings);
+ }
+ else {
+ /* Since evaulating the bezier doesn't add the final point's position,
+ * it must be added manually in the non-cyclic case. */
+ positions[offset] = this->positions_.last();
+ mappings[offset] = i_last;
+ offset++;
+ }
+
+ if (this->is_cyclic) {
+ if (mappings.last() >= this->size()) {
+ mappings.last() = 0.0f;
+ }
+ }
+
+ BLI_assert(offset == positions.size());
+
+ this->base_cache_dirty_ = false;
+}
+
+/**
+ * Returns non-owning access to an array of values ontains 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
+{
+ this->evaluate_bezier_position_and_mapping();
+ return this->evaluated_mappings_cache_;
+}
+
+Span<float3> BezierSpline::evaluated_positions() const
+{
+ this->evaluate_bezier_position_and_mapping();
+ return this->evaluated_positions_cache_;
+}
+
+BezierSpline::InterpolationData BezierSpline::interpolation_data_from_map(const float map) const
+{
+ const int points_len = this->size();
+ const int index = std::floor(map);
+ if (index == points_len) {
+ BLI_assert(this->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, map - index};
+}
+
+template<typename T>
+static void interpolate_to_evaluated_points_impl(Span<float> mappings,
+ const blender::VArray<T> &source_data,
+ MutableSpan<T> result_data)
+{
+ const int points_len = source_data.size();
+ blender::attribute_math::DefaultMixer<T> mixer(result_data);
+
+ for (const int i : result_data.index_range()) {
+ const int index = std::floor(mappings[i]);
+ const int next_index = (index == points_len - 1) ? 0 : index + 1;
+ const float factor = mappings[i] - index;
+
+ const T &value = source_data[index];
+ const T &next_value = source_data[next_index];
+
+ mixer.mix_in(i, value, 1.0f - factor);
+ mixer.mix_in(i, next_value, factor);
+ }
+
+ mixer.finalize();
+}
+
+blender::fn::GVArrayPtr BezierSpline::interpolate_to_evaluated_points(
+ const blender::fn::GVArray &source_data) const
+{
+ BLI_assert(source_data.size() == this->size());
+ Span<float> mappings = this->evaluated_mappings();
+
+ 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>(mappings, 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_group.cc b/source/blender/blenkernel/intern/spline_group.cc
new file mode 100644
index 00000000000..9b80cc6bf47
--- /dev/null
+++ b/source/blender/blenkernel/intern/spline_group.cc
@@ -0,0 +1,218 @@
+/*
+ * 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::Array;
+using blender::float3;
+using blender::float4x4;
+using blender::IndexRange;
+using blender::MutableSpan;
+using blender::Span;
+using blender::Vector;
+
+SplineGroup *SplineGroup::copy()
+{
+ SplineGroup *new_curve = new SplineGroup();
+
+ for (SplinePtr &spline : this->splines) {
+ new_curve->splines.append(spline->copy());
+ }
+
+ return new_curve;
+}
+
+void SplineGroup::translate(const float3 translation)
+{
+ for (SplinePtr &spline : this->splines) {
+ if (BezierSpline *bezier_spline = dynamic_cast<BezierSpline *>(spline.get())) {
+ for (float3 &position : bezier_spline->positions()) {
+ position += translation;
+ }
+ for (float3 &handle_position : bezier_spline->handle_positions_start()) {
+ handle_position += translation;
+ }
+ for (float3 &handle_position : bezier_spline->handle_positions_end()) {
+ handle_position += translation;
+ }
+ }
+ else if (PolySpline *poly_spline = dynamic_cast<PolySpline *>(spline.get())) {
+ for (float3 &position : poly_spline->positions()) {
+ position += translation;
+ }
+ }
+ spline->mark_cache_invalid();
+ }
+}
+
+void SplineGroup::transform(const float4x4 &matrix)
+{
+ for (SplinePtr &spline : this->splines) {
+ if (BezierSpline *bezier_spline = dynamic_cast<BezierSpline *>(spline.get())) {
+ for (float3 &position : bezier_spline->positions()) {
+ position = matrix * position;
+ }
+ for (float3 &handle_position : bezier_spline->handle_positions_start()) {
+ handle_position = matrix * handle_position;
+ }
+ for (float3 &handle_position : bezier_spline->handle_positions_end()) {
+ handle_position = matrix * handle_position;
+ }
+ }
+ else if (PolySpline *poly_spline = dynamic_cast<PolySpline *>(spline.get())) {
+ for (float3 &position : poly_spline->positions()) {
+ position = matrix * position;
+ }
+ }
+ spline->mark_cache_invalid();
+ }
+}
+
+void SplineGroup::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::Free;
+ case HD_AUTO:
+ return BezierSpline::Auto;
+ case HD_VECT:
+ return BezierSpline::Vector;
+ case HD_ALIGN:
+ return BezierSpline::Align;
+ case HD_AUTO_ANIM:
+ return BezierSpline::Auto;
+ case HD_ALIGN_DOUBLESIDE:
+ return BezierSpline::Align;
+ }
+ BLI_assert_unreachable();
+ return BezierSpline::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;
+}
+
+SplineGroup *dcurve_from_dna_curve(const Curve &dna_curve)
+{
+ SplineGroup *curve = new SplineGroup();
+
+ const ListBase *nurbs = BKE_curve_nurbs_get(&const_cast<Curve &>(dna_curve));
+
+ curve->splines.reserve(BLI_listbase_count(nurbs));
+
+ 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->is_cyclic = nurb->flagu & CU_NURB_CYCLIC;
+
+ /* TODO: Optimize by reserving the correct size. */
+ 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->is_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->is_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;
+ }
+ }
+ }
+
+ /* TODO: Decide whether to store this in the spline or the curve. */
+ 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/spline_nurbs.cc b/source/blender/blenkernel/intern/spline_nurbs.cc
new file mode 100644
index 00000000000..0e1342f8f08
--- /dev/null
+++ b/source/blender/blenkernel/intern/spline_nurbs.cc
@@ -0,0 +1,448 @@
+/*
+ * 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 "BLI_virtual_array.hh"
+
+#include "BKE_attribute_math.hh"
+#include "BKE_spline.hh"
+
+using blender::Array;
+using blender::float3;
+using blender::float4x4;
+using blender::IndexRange;
+using blender::MutableSpan;
+using blender::Span;
+using blender::Vector;
+
+SplinePtr NURBSpline::copy() const
+{
+ SplinePtr new_spline = std::make_unique<NURBSpline>(*this);
+
+ return new_spline;
+}
+
+int NURBSpline::size() const
+{
+ const int size = this->positions_.size();
+ BLI_assert(this->radii_.size() == size);
+ BLI_assert(this->tilts_.size() == size);
+ BLI_assert(this->weights_.size() == size);
+ return size;
+}
+
+int NURBSpline::resolution() const
+{
+ return this->resolution_u_;
+}
+
+void NURBSpline::set_resolution(const int value)
+{
+ this->resolution_u_ = value;
+ this->mark_cache_invalid();
+}
+
+uint8_t NURBSpline::order() const
+{
+ return this->order_;
+}
+
+void NURBSpline::set_order(const uint8_t value)
+{
+ /* TODO: Check the spline length. */
+ BLI_assert(value >= 2 && value <= 6);
+ this->order_ = value;
+}
+
+void NURBSpline::add_point(const float3 position,
+ const float radius,
+ const float tilt,
+ const float weight)
+{
+ this->positions_.append(position);
+ this->radii_.append(radius);
+ this->tilts_.append(tilt);
+ this->weights_.append(weight);
+ this->knots_dirty_ = true;
+}
+
+void NURBSpline::drop_front(const int count)
+{
+ BLI_assert(this->size() - count > 0);
+ this->positions_.remove(0, count);
+ this->radii_.remove(0, count);
+ this->tilts_.remove(0, count);
+ this->weights_.remove(0, count);
+ this->mark_cache_invalid();
+}
+
+void NURBSpline::drop_back(const int count)
+{
+ const int new_size = this->size() - count;
+ BLI_assert(new_size > 0);
+ this->positions_.resize(new_size);
+ this->radii_.resize(new_size);
+ this->tilts_.resize(new_size);
+ this->weights_.resize(new_size);
+ this->mark_cache_invalid();
+}
+
+MutableSpan<float3> NURBSpline::positions()
+{
+ return this->positions_;
+}
+Span<float3> NURBSpline::positions() const
+{
+ return this->positions_;
+}
+MutableSpan<float> NURBSpline::radii()
+{
+ return this->radii_;
+}
+Span<float> NURBSpline::radii() const
+{
+ return this->radii_;
+}
+MutableSpan<float> NURBSpline::tilts()
+{
+ return this->tilts_;
+}
+Span<float> NURBSpline::tilts() const
+{
+ return this->tilts_;
+}
+MutableSpan<float> NURBSpline::weights()
+{
+ return this->weights_;
+}
+Span<float> NURBSpline::weights() const
+{
+ return this->weights_;
+}
+
+void NURBSpline::mark_cache_invalid()
+{
+ this->basis_cache_dirty_ = true;
+ this->position_cache_dirty_ = true;
+ this->tangent_cache_dirty_ = true;
+ this->normal_cache_dirty_ = true;
+ this->length_cache_dirty_ = true;
+}
+
+int NURBSpline::evaluated_points_size() const
+{
+ return this->resolution_u_ * this->segments_size();
+}
+
+void NURBSpline::correct_end_tangents() const
+{
+}
+
+bool NURBSpline::check_valid_size_and_order() const
+{
+ if (this->size() < this->order_) {
+ return false;
+ }
+
+ if (!this->is_cyclic && this->knots_mode == KnotsMode::Bezier) {
+ if (this->order_ == 4) {
+ if (this->size() < 5) {
+ return false;
+ }
+ }
+ else if (this->order_ != 3) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+int NURBSpline::knots_size() const
+{
+ const int size = this->size() + this->order_;
+ return this->is_cyclic ? size + this->order_ - 1 : size;
+}
+
+void NURBSpline::calculate_knots() const
+{
+ const KnotsMode mode = this->knots_mode;
+ const int length = this->size();
+ const int order = this->order_;
+
+ this->knots_.resize(this->knots_size());
+
+ MutableSpan<float> knots = this->knots_;
+
+ if (mode == NURBSpline::KnotsMode::Normal || this->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 (this->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 (!this->knots_dirty_) {
+ BLI_assert(this->knots_.size() == this->size() + this->order_);
+ return this->knots_;
+ }
+
+ std::lock_guard lock{this->knots_mutex_};
+ if (!this->knots_dirty_) {
+ BLI_assert(this->knots_.size() == this->size() + this->order_);
+ return this->knots_;
+ }
+
+ this->calculate_knots();
+
+ this->knots_dirty_ = false;
+
+ return this->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 unecessary 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 (!this->basis_cache_dirty_) {
+ return;
+ }
+
+ std::lock_guard lock{this->basis_cache_mutex_};
+ if (!this->basis_cache_dirty_) {
+ return;
+ }
+
+ const int points_len = this->size();
+ const int evaluated_len = this->evaluated_points_size();
+ this->basis_cache_.resize(evaluated_len);
+
+ const int order = this->order();
+ Span<float> control_weights = this->weights();
+ Span<float> knots = this->knots();
+
+ MutableSpan<BasisCache> basis_cache(this->basis_cache_);
+
+ /* This buffer is reused by each basis calculation to store temporary values.
+ * Theoretically it could likely be optimized away in the future. */
+ Array<float> basis_buffer(this->knots_size());
+
+ const float start = knots[order - 1];
+ const float end = this->is_cyclic ? knots[points_len + order - 1] : knots[points_len];
+ const float step = (end - start) / (evaluated_len - (this->is_cyclic ? 0 : 1));
+ float parameter = start;
+ for (const int i : IndexRange(evaluated_len)) {
+ BasisCache &basis = basis_cache[i];
+ calculate_basis_for_point(parameter,
+ points_len + (this->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;
+ }
+
+ this->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(this->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 (!this->position_cache_dirty_) {
+ return this->evaluated_positions_cache_;
+ }
+
+ std::lock_guard lock{this->position_cache_mutex_};
+ if (!this->position_cache_dirty_) {
+ return this->evaluated_positions_cache_;
+ }
+
+ const int total = this->evaluated_points_size();
+ this->evaluated_positions_cache_.resize(total);
+
+ blender::fn::GVArray_For_Span<float3> positions_varray(this->positions_.as_span());
+ blender::fn::GVArrayPtr evaluated_positions_varray = this->interpolate_to_evaluated_points(
+ positions_varray);
+
+ /* TODO: Avoid copying. */
+ Span<float3> evaluated_positions =
+ evaluated_positions_varray->typed<float3>()->get_internal_span();
+ for (const int i : IndexRange(total)) {
+ this->evaluated_positions_cache_[i] = evaluated_positions[i];
+ }
+
+ this->position_cache_dirty_ = false;
+
+ return this->evaluated_positions_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..9868320d2a6
--- /dev/null
+++ b/source/blender/blenkernel/intern/spline_poly.cc
@@ -0,0 +1,151 @@
+/*
+ * 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 "BLI_virtual_array.hh"
+
+#include "BKE_curve.h"
+#include "BKE_spline.hh"
+
+using blender::Array;
+using blender::float3;
+using blender::float4x4;
+using blender::IndexRange;
+using blender::MutableSpan;
+using blender::Span;
+using blender::Vector;
+
+SplinePtr PolySpline::copy() const
+{
+ SplinePtr new_spline = std::make_unique<PolySpline>(*this);
+
+ return new_spline;
+}
+
+int PolySpline::size() const
+{
+ const int size = this->positions_.size();
+ BLI_assert(this->radii_.size() == size);
+ BLI_assert(this->tilts_.size() == size);
+ return size;
+}
+
+int PolySpline::resolution() const
+{
+ return 1;
+}
+
+void PolySpline::set_resolution(const int UNUSED(value))
+{
+ /* Poly curve has no resolution, there is just one evaluated point per control point. */
+}
+
+void PolySpline::add_point(const float3 position, const float radius, const float tilt)
+{
+ this->positions_.append(position);
+ this->radii_.append(radius);
+ this->tilts_.append(tilt);
+}
+
+void PolySpline::drop_front(const int count)
+{
+ BLI_assert(this->size() - count > 0);
+ this->positions_.remove(0, count);
+ this->radii_.remove(0, count);
+ this->tilts_.remove(0, count);
+ this->mark_cache_invalid();
+}
+
+void PolySpline::drop_back(const int count)
+{
+ const int new_size = this->size() - count;
+ BLI_assert(new_size > 0);
+ this->positions_.resize(new_size);
+ this->radii_.resize(new_size);
+ this->tilts_.resize(new_size);
+ this->mark_cache_invalid();
+}
+
+MutableSpan<float3> PolySpline::positions()
+{
+ return this->positions_;
+}
+Span<float3> PolySpline::positions() const
+{
+ return this->positions_;
+}
+MutableSpan<float> PolySpline::radii()
+{
+ return this->radii_;
+}
+Span<float> PolySpline::radii() const
+{
+ return this->radii_;
+}
+MutableSpan<float> PolySpline::tilts()
+{
+ return this->tilts_;
+}
+Span<float> PolySpline::tilts() const
+{
+ return this->tilts_;
+}
+
+void PolySpline::mark_cache_invalid()
+{
+ this->tangent_cache_dirty_ = true;
+ this->normal_cache_dirty_ = true;
+ this->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();
+}
+
+// static blender::fn::GVArrayPtr bad_hack_copy_varray(const blender::fn::GVArray &source_data)
+// {
+// }
+
+/* TODO: This function is hacky.. how to deal with poly spline interpolation? */
+blender::fn::GVArrayPtr PolySpline::interpolate_to_evaluated_points(
+ const blender::fn::GVArray &source_data) const
+{
+ BLI_assert(source_data.size() == this->size());
+
+ if (source_data.is_span()) {
+ return std::make_unique<blender::fn::GVArray_For_GSpan>(source_data.get_internal_span());
+ }
+ // if (source_data.is_single()) {
+ // BUFFER_FOR_CPP_TYPE_VALUE(source_data.type(), value);
+ // source_data.get_internal_single(value);
+ // return std::make_unique<blender::fn::GVArray_For_SingleValue>(
+ // source_data.type(), source_data.size(), value);
+ // }
+
+ return {};
+} \ No newline at end of file
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/functions/FN_generic_virtual_array.hh b/source/blender/functions/FN_generic_virtual_array.hh
index c1af00fd4cd..2a7d01a7423 100644
--- a/source/blender/functions/FN_generic_virtual_array.hh
+++ b/source/blender/functions/FN_generic_virtual_array.hh
@@ -714,6 +714,10 @@ class GVArray_For_Span : public GVArray_For_EmbeddedVArray<T, VArray_For_Span<T>
: GVArray_For_EmbeddedVArray<T, VArray_For_Span<T>>(data.size(), data)
{
}
+ GVArray_For_Span(const MutableSpan<T> data)
+ : GVArray_For_EmbeddedVArray<T, VArray_For_Span<T>>(data.size(), data.as_span())
+ {
+ }
};
/* Same as VMutableArray_For_MutableSpan, but for a generic virtual array. */
diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h
index f57515c0e93..9a063aa6e65 100644
--- a/source/blender/makesdna/DNA_node_types.h
+++ b/source/blender/makesdna/DNA_node_types.h
@@ -1196,6 +1196,16 @@ typedef struct NodeInputString {
char *string;
} NodeInputString;
+typedef struct NodeGeometryCurveTrim {
+ /* GeometryNodeCurveTrimMode. */
+ uint8_t mode;
+} NodeGeometryCurveTrim;
+
+typedef struct NodeGeometryCurveSamplePoints {
+ /* GeometryNodeCurveSamplePointsMode. */
+ uint8_t mode;
+} NodeGeometryCurveSamplePoints;
+
typedef struct NodeGeometryRotatePoints {
/* GeometryNodeRotatePointsType */
uint8_t type;
@@ -1708,6 +1718,16 @@ typedef enum GeometryNodeAttributeProximityTargetType {
GEO_NODE_ATTRIBUTE_PROXIMITY_TARGET_GEOMETRY_ELEMENT_FACES = 2,
} GeometryNodeAttributeProximityTargetType;
+typedef enum GeometryNodeCurveTrimMode {
+ GEO_NODE_CURVE_TRIM_FACTOR = 0,
+ GEO_NODE_CURVE_TRIM_LENGTH = 1,
+} GeometryNodeCurveTrimMode;
+
+typedef enum GeometryNodeCurveSamplePointsMode {
+ GEO_NODE_CURVE_SAMPLE_POINTS_COUNT = 0,
+ GEO_NODE_CURVE_SAMPLE_POINTS_LENGTH = 1,
+} GeometryNodeCurveSamplePointsMode;
+
/* Boolean Node */
typedef enum GeometryNodeBooleanOperation {
GEO_NODE_BOOLEAN_INTERSECT = 0,
diff --git a/source/blender/makesrna/intern/rna_attribute.c b/source/blender/makesrna/intern/rna_attribute.c
index 7976df3e4e4..ec29e81b07c 100644
--- a/source/blender/makesrna/intern/rna_attribute.c
+++ b/source/blender/makesrna/intern/rna_attribute.c
@@ -58,7 +58,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},
};
@@ -68,6 +68,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_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c
index 1da711f260c..c965aa9b598 100644
--- a/source/blender/makesrna/intern/rna_nodetree.c
+++ b/source/blender/makesrna/intern/rna_nodetree.c
@@ -9586,6 +9586,58 @@ static void def_geo_attribute_separate_xyz(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
}
+static void def_geo_curve_trim(StructRNA *srna)
+{
+ PropertyRNA *prop;
+
+ static EnumPropertyItem mode_items[] = {
+ {GEO_NODE_CURVE_TRIM_FACTOR,
+ "FACTOR",
+ 0,
+ "Factor",
+ "Remove a portion of each spline's total length"},
+ {GEO_NODE_CURVE_TRIM_LENGTH,
+ "LENGTH",
+ 0,
+ "Length",
+ "Remove the specified length from the beginning and end of the curve"},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ RNA_def_struct_sdna_from(srna, "NodeGeometryCurveTrim", "storage");
+
+ prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, mode_items);
+ RNA_def_property_ui_text(prop, "Mode", "How to specify the amount of each spline to remove");
+ RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
+}
+
+static void def_geo_curve_sample_points(StructRNA *srna)
+{
+ PropertyRNA *prop;
+
+ static EnumPropertyItem mode_items[] = {
+ {GEO_NODE_CURVE_SAMPLE_POINTS_COUNT,
+ "COUNT",
+ 0,
+ "Count",
+ "Distribute the specified number of points along the curve"},
+ {GEO_NODE_CURVE_SAMPLE_POINTS_LENGTH,
+ "LENGTH",
+ 0,
+ "Length",
+ "Add points to the curve at the specified distance from each other"},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ RNA_def_struct_sdna_from(srna, "NodeGeometryCurveTrim", "storage");
+
+ prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, mode_items);
+ RNA_def_property_ui_text(prop, "Mode", "How to specify the amount of points to sample");
+ RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
+}
+
static void def_geo_mesh_circle(StructRNA *srna)
{
PropertyRNA *prop;
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 57de629fc18..f72a21ffe1b 100644
--- a/source/blender/modifiers/intern/MOD_nodes.cc
+++ b/source/blender/modifiers/intern/MOD_nodes.cc
@@ -177,7 +177,7 @@ static void add_object_relation(const ModifierUpdateDepsgraphContext *ctx, Objec
add_collection_object_relations_recursive(ctx, *collection_instance);
}
}
- 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");
/* We don't know exactly what attributes from the other object we will need. */
CustomData_MeshMasks mask;
@@ -1591,9 +1591,10 @@ ModifierTypeInfo modifierType_Nodes = {
/* srna */ &RNA_NodesModifier,
/* type */ eModifierTypeType_Constructive,
/* flags */
- static_cast<ModifierTypeFlag>(
- eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_SupportsEditmode |
- eModifierTypeFlag_EnableInEditmode | eModifierTypeFlag_SupportsMapping),
+ static_cast<ModifierTypeFlag>(eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_AcceptsCVs |
+ eModifierTypeFlag_SupportsEditmode |
+ eModifierTypeFlag_EnableInEditmode |
+ eModifierTypeFlag_SupportsMapping),
/* icon */ ICON_NODETREE,
/* copyData */ copyData,
diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt
index 7a966b660b4..efef715947f 100644
--- a/source/blender/nodes/CMakeLists.txt
+++ b/source/blender/nodes/CMakeLists.txt
@@ -159,6 +159,10 @@ 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_sample_points.cc
+ geometry/nodes/node_geo_curve_to_mesh.cc
+ geometry/nodes/node_geo_curve_transform_test.cc
+ geometry/nodes/node_geo_curve_trim.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 74d77787eea..ec6e2f2ce50 100644
--- a/source/blender/nodes/NOD_geometry.h
+++ b/source/blender/nodes/NOD_geometry.h
@@ -47,6 +47,10 @@ 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_sample_points(void);
+void register_node_type_geo_curve_to_mesh(void);
+void register_node_type_geo_curve_transform_test(void);
+void register_node_type_geo_curve_trim(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 12e14768c35..0b3b5c4b05b 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -311,6 +311,10 @@ DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MAP_RANGE, def_geo_attribute_map_range,
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "ATTRIBUTE_CLAMP", AttributeClamp, "Attribute Clamp", "")
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_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "")
+DefNode(GeometryNode, GEO_NODE_CURVE_TRANSFORM_TEST, 0, "TRANSFORM_TEST", TransformTest, "Transform Test", "")
+DefNode(GeometryNode, GEO_NODE_CURVE_TRIM, def_geo_curve_trim, "CURVE_TRIM", CurveTrim, "Curve Trim", "")
+DefNode(GeometryNode, GEO_NODE_CURVE_SAMPLE_POINTS, def_geo_curve_sample_points, "CURVE_SAMPLE_POINTS", CurveSamplePoints, "Curve Sample Points", "")
/* 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 c4d37a82617..870d09da001 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
@@ -183,6 +183,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 9e697226a01..07f29b81bc5 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
@@ -104,6 +104,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 60bb27191b4..d084f806500 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc
@@ -163,6 +163,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 5d82897e9db..102649205db 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
@@ -388,6 +388,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 00ae6b373b2..d131437f364 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc
@@ -271,6 +271,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 7129679117d..bbea05fa6a1 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc
@@ -201,6 +201,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 cc6aacaca79..9fc8326457e 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc
@@ -302,6 +302,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 fa11ef936cd..5fbf894541f 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
@@ -118,6 +118,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..148ed212633 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<PointCloudComponent>()) {
+ separate_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), 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 86fbc9dc6d0..ba846b361f5 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
@@ -510,6 +510,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_sample_points.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_sample_points.cc
new file mode 100644
index 00000000000..0f5619d9287
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_sample_points.cc
@@ -0,0 +1,161 @@
+/*
+ * 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_pointcloud.h"
+#include "BKE_spline.hh"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "node_geometry_util.hh"
+
+static bNodeSocketTemplate geo_node_curve_sample_points_in[] = {
+ {SOCK_GEOMETRY, N_("Curve")},
+ {SOCK_INT, N_("Count"), 10.0f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX},
+ {SOCK_FLOAT, N_("Length"), 0.1f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX, PROP_DISTANCE},
+ {-1, ""},
+};
+
+static bNodeSocketTemplate geo_node_curve_sample_points_out[] = {
+ {SOCK_GEOMETRY, N_("Points")},
+ {-1, ""},
+};
+
+static void geo_node_curve_sample_points_layout(uiLayout *layout,
+ bContext *UNUSED(C),
+ PointerRNA *ptr)
+{
+ uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE);
+}
+
+static void geo_node_curve_sample_points_init(bNodeTree *UNUSED(tree), bNode *node)
+{
+ NodeGeometryCurveSamplePoints *data = (NodeGeometryCurveSamplePoints *)MEM_callocN(
+ sizeof(NodeGeometryCurveSamplePoints), __func__);
+
+ data->mode = GEO_NODE_CURVE_SAMPLE_POINTS_LENGTH;
+ node->storage = data;
+}
+
+static void geo_node_curve_sample_points_update(bNodeTree *UNUSED(ntree), bNode *node)
+{
+ NodeGeometryCurveSamplePoints &node_storage = *(NodeGeometryCurveSamplePoints *)node->storage;
+ const GeometryNodeCurveSamplePointsMode mode = (GeometryNodeCurveSamplePointsMode)
+ node_storage.mode;
+
+ bNodeSocket *count_socket = ((bNodeSocket *)node->inputs.first)->next;
+ bNodeSocket *length_socket = count_socket->next;
+
+ nodeSetSocketAvailability(count_socket, mode == GEO_NODE_CURVE_SAMPLE_POINTS_COUNT);
+ nodeSetSocketAvailability(length_socket, mode == GEO_NODE_CURVE_SAMPLE_POINTS_LENGTH);
+}
+
+namespace blender::nodes {
+
+static void sample_points_from_spline(const Spline &spline,
+ const int offset,
+ const float sample_length,
+ PointCloudComponent &point_component)
+{
+}
+
+static Array<int> get_result_point_offsets(const SplineGroup &curve,
+ const GeometryNodeCurveSamplePointsMode mode,
+ const int count,
+ const float length)
+{
+ Array<int> offsets(curve.splines.size() + 1);
+
+ if (mode == GEO_NODE_CURVE_SAMPLE_POINTS_COUNT) {
+ for (const int i : curve.splines.index_range()) {
+ offsets[i] = count * i;
+ }
+ offsets.last() = curve.splines.size() * count;
+ }
+ else {
+ int offset = 0;
+ for (const int i : curve.splines.index_range()) {
+ offsets[i] = offset;
+ offset += curve.splines[i]->length() / length;
+ }
+ offsets.last() = offset;
+ }
+
+ return offsets;
+}
+
+static void geo_node_curve_sample_points_exec(GeoNodeExecParams params)
+{
+ const GeometrySet input_geometry_set = params.extract_input<GeometrySet>("Curve");
+
+ const bNode &node = params.node();
+ const NodeGeometryCurveSamplePoints &node_storage = *(const NodeGeometryCurveSamplePoints *)
+ node.storage;
+ const GeometryNodeCurveSamplePointsMode mode = (GeometryNodeCurveSamplePointsMode)
+ node_storage.mode;
+
+ if (!input_geometry_set.has_curve()) {
+ params.set_output("Points", GeometrySet());
+ }
+
+ const SplineGroup &curve = *input_geometry_set.get_curve_for_read();
+
+ const int count = (mode == GEO_NODE_CURVE_SAMPLE_POINTS_COUNT) ?
+ params.extract_input<int>("Count") :
+ 0;
+ const float length = (mode == GEO_NODE_CURVE_SAMPLE_POINTS_LENGTH) ?
+ params.extract_input<float>("Length") :
+ 0.0f;
+
+ Array<int> offsets = get_result_point_offsets(curve, mode, count, length);
+
+ PointCloud *pointcloud = BKE_pointcloud_new_nomain(offsets.last());
+ GeometrySet result_geometry_set = GeometrySet::create_with_pointcloud(pointcloud);
+ PointCloudComponent &point_component =
+ result_geometry_set.get_component_for_write<PointCloudComponent>();
+
+ if (mode == GEO_NODE_CURVE_SAMPLE_POINTS_COUNT) {
+ const int count = params.extract_input<int>("Count");
+ for (const int i : curve.splines.index_range()) {
+ Spline &spline = *curve.splines[i];
+ sample_points_from_spline(spline, offsets[i], spline.length() / count, point_component);
+ }
+ }
+ else {
+ }
+
+ params.set_output("Geometry", result_geometry_set);
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_curve_sample_points()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(
+ &ntype, GEO_NODE_CURVE_SAMPLE_POINTS, "Sample Curve Points", NODE_CLASS_GEOMETRY, 0);
+ node_type_socket_templates(
+ &ntype, geo_node_curve_sample_points_in, geo_node_curve_sample_points_out);
+ node_type_init(&ntype, geo_node_curve_sample_points_init);
+ node_type_update(&ntype, geo_node_curve_sample_points_update);
+ node_type_storage(
+ &ntype, "NodeGeometryCurveTrim", node_free_standard_storage, node_copy_standard_storage);
+
+ ntype.geometry_node_execute = blender::nodes::geo_node_curve_sample_points_exec;
+ ntype.draw_buttons = geo_node_curve_sample_points_layout;
+ nodeRegisterType(&ntype);
+}
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..bd95dce9564
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc
@@ -0,0 +1,348 @@
+/*
+ * 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, ""},
+};
+
+using blender::Array;
+
+namespace blender::nodes {
+
+static void vert_extrude_to_mesh_data(const Spline &spline,
+ const float3 profile_vert,
+ MutableSpan<MVert> verts,
+ MutableSpan<MEdge> edges,
+ int &vert_offset,
+ int &edge_offset)
+{
+ Span<float3> positions = spline.evaluated_positions();
+
+ for (const int i : IndexRange(positions.size() - 1)) {
+ MEdge &edge = edges[edge_offset++];
+ edge.v1 = vert_offset + i;
+ edge.v2 = vert_offset + i + 1;
+ edge.flag = ME_LOOSEEDGE;
+ }
+ // for (const int i : IndexRange(positions.size() - 1)) {
+ // MEdge &edge = edges[edge_offset++];
+ // edge.v1 = vert_offset + i * 3;
+ // edge.v2 = vert_offset + i * 3 + 3;
+ // edge.flag = ME_LOOSEEDGE;
+
+ // MEdge &tangent_edge = edges[edge_offset++];
+ // tangent_edge.v1 = vert_offset + i * 3;
+ // tangent_edge.v2 = vert_offset + i * 3 + 1;
+ // tangent_edge.flag = ME_LOOSEEDGE;
+
+ // MEdge &normal_edge = edges[edge_offset++];
+ // normal_edge.v1 = vert_offset + i * 3;
+ // normal_edge.v2 = vert_offset + i * 3 + 2;
+ // normal_edge.flag = ME_LOOSEEDGE;
+ // }
+
+ if (spline.is_cyclic) {
+ MEdge &edge = 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 = verts[vert_offset++];
+ copy_v3_v3(vert.co, positions[i] + profile_vert);
+ }
+ // Span<float3> tangents = spline.evaluated_tangents();
+ // Span<float3> normals = spline.evaluated_normals();
+ // for (const int i : positions.index_range()) {
+ // MVert &vert = verts[vert_offset++];
+ // copy_v3_v3(vert.co, positions[i] + profile_vert);
+
+ // MVert &tangent_vert = verts[vert_offset++];
+ // copy_v3_v3(tangent_vert.co, tangents[i] + vert.co);
+
+ // MVert &normal_vert = verts[vert_offset++];
+ // copy_v3_v3(normal_vert.co, normals[i] + vert.co);
+ // }
+}
+
+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> verts,
+ MutableSpan<MEdge> edges,
+ MutableSpan<MLoop> loops,
+ MutableSpan<MPoly> 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], verts, edges, vert_offset, edge_offset);
+ return;
+ }
+
+ /* TODO: All of this could probably be generalized to something like:
+ * GEO_mesh_grid_topology(vert_offset,
+ * spline_vert_len,
+ * profile_vert_len,
+ * spline.is_cyclic,
+ * profile_spline.is_cyclic,
+ * edges,
+ * loops,
+ * polys,
+ * edge_offset,
+ * poly_offset);
+ */
+
+ /* 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 ring_vert_offset = vert_offset + profile_vert_len * i_ring;
+ const int next_ring_vert_offset = vert_offset +
+ profile_vert_len * ((i_ring + 1) % spline_vert_len);
+ MEdge &edge = 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)) {
+ MEdge &edge = edges[edge_offset++];
+ edge.v1 = ring_vert_offset + i_profile;
+ edge.v2 = ring_vert_offset + (i_profile + 1) % profile_vert_len;
+ edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
+ }
+ }
+
+ /* Calculate poly and face indices. */
+ for (const int i_ring : IndexRange(spline_edge_len)) {
+ const int i_next_ring = (i_ring + 1) % spline_vert_len;
+
+ 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 spline_edge_start = spline_edges_start + spline_edge_len * i_profile;
+ const int next_spline_edge_start = spline_edges_start +
+ spline_edge_len * ((i_profile + 1) % profile_vert_len);
+ MPoly &poly = polys[poly_offset++];
+ poly.loopstart = loop_offset;
+ poly.totloop = 4;
+ poly.flag = ME_SMOOTH;
+
+ MLoop &loop_a = loops[loop_offset++];
+ loop_a.v = ring_vert_offset + i_profile;
+ loop_a.e = ring_edge_start + i_profile;
+ MLoop &loop_b = loops[loop_offset++];
+ loop_b.v = ring_vert_offset + (i_profile + 1) % profile_vert_len;
+ loop_b.e = next_spline_edge_start + i_ring;
+ MLoop &loop_c = loops[loop_offset++];
+ loop_c.v = next_ring_vert_offset + (i_profile + 1) % profile_vert_len;
+ loop_c.e = next_ring_edge_offset + i_profile;
+ MLoop &loop_d = 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();
+
+ GVArrayPtr radii_varray = spline.interpolate_to_evaluated_points(
+ blender::fn::GVArray_For_Span(spline.radii()));
+ GVArray_Typed<float> radii = radii_varray->typed<float>();
+ for (const int i_ring : IndexRange(spline_vert_len)) {
+ float4x4 point_matrix = float4x4::from_normalized_axis_data(
+ positions[i_ring], tangents[i_ring], normals[i_ring]);
+
+ point_matrix.apply_scale(radii[i_ring]);
+
+ for (const int i_profile : IndexRange(profile_vert_len)) {
+ MVert &vert = 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::Bezier) {
+ const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(profile_spline);
+ Span<float> mappings = bezier_spline.evaluated_mappings();
+ for (const int i_profile : mappings.index_range()) {
+ const float index = std::floor(mappings[i_profile]);
+ if ((mappings[i_profile] - index) == 0.0f) {
+ if (bezier_spline.point_is_sharp(index)) {
+ mark_edges_sharp(
+ edges.slice(spline_edges_start + spline_edge_len * i_profile, spline_edge_len));
+ }
+ }
+ }
+ }
+}
+
+static Mesh *curve_to_mesh_calculate(const SplineGroup &curve, const SplineGroup &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() + 2;
+ 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() * 3;
+ 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);
+ // BLI_assert(BKE_mesh_is_valid(mesh));
+
+ return mesh;
+}
+
+static SplineGroup get_curve_single_vert()
+{
+ SplineGroup 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");
+
+ if (!curve_set.has_curve()) {
+ params.set_output("Mesh", GeometrySet());
+ return;
+ }
+
+ const SplineGroup *profile_curve = profile_set.get_curve_for_read();
+
+ const SplineGroup 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_curve_transform_test.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_transform_test.cc
new file mode 100644
index 00000000000..4073dc28f3f
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_transform_test.cc
@@ -0,0 +1,73 @@
+/*
+ * 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_float4x4.hh"
+
+#include "BKE_mesh.h"
+
+#include "node_geometry_util.hh"
+
+static bNodeSocketTemplate geo_node_transform_test_in[] = {
+ {SOCK_GEOMETRY, N_("Geometry")},
+ {SOCK_VECTOR, N_("Translation"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_TRANSLATION},
+ {SOCK_VECTOR, N_("Forward"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX},
+ {SOCK_VECTOR, N_("Up"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX},
+ {SOCK_FLOAT, N_("Radius"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX},
+ {-1, ""},
+};
+
+static bNodeSocketTemplate geo_node_transform_test_out[] = {
+ {SOCK_GEOMETRY, N_("Geometry")},
+ {-1, ""},
+};
+
+namespace blender::nodes {
+
+static void geo_node_transform_test_exec(GeoNodeExecParams params)
+{
+ GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
+ const float3 translation = params.extract_input<float3>("Translation");
+ const float3 forward = params.extract_input<float3>("Forward").normalized();
+ const float3 up = params.extract_input<float3>("Up").normalized();
+ const float radius = params.extract_input<float>("Radius");
+
+ float4x4 matrix = float4x4::from_normalized_axis_data(translation, forward, up);
+ // float4x4 scale_matrix;
+ // scale_m4_fl(scale_matrix.values, radius * 2.0f);
+ // const float4x4 final_matrix = matrix * scale_matrix;
+ matrix.apply_scale(radius);
+ // const float4x4 final_matrix = matrix;
+
+ if (geometry_set.has_mesh()) {
+ Mesh *mesh = geometry_set.get_mesh_for_write();
+ BKE_mesh_transform(mesh, matrix.values, false);
+ }
+
+ params.set_output("Geometry", geometry_set);
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_curve_transform_test()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(
+ &ntype, GEO_NODE_CURVE_TRANSFORM_TEST, "Transform Test", NODE_CLASS_GEOMETRY, 0);
+ node_type_socket_templates(&ntype, geo_node_transform_test_in, geo_node_transform_test_out);
+ ntype.geometry_node_execute = blender::nodes::geo_node_transform_test_exec;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
new file mode 100644
index 00000000000..e5fb6f66a21
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
@@ -0,0 +1,230 @@
+/*
+ * 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 "UI_interface.h"
+#include "UI_resources.h"
+
+#include "node_geometry_util.hh"
+
+static bNodeSocketTemplate geo_node_curve_trim_in[] = {
+ {SOCK_GEOMETRY, N_("Geometry")},
+ {SOCK_FLOAT, N_("Start"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR},
+ {SOCK_FLOAT, N_("Start"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX, PROP_DISTANCE},
+ {SOCK_FLOAT, N_("End"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR},
+ {SOCK_FLOAT, N_("End"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, FLT_MAX, PROP_DISTANCE},
+ {-1, ""},
+};
+
+static bNodeSocketTemplate geo_node_curve_trim_out[] = {
+ {SOCK_GEOMETRY, N_("Geometry")},
+ {-1, ""},
+};
+
+static void geo_node_curve_trim_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
+{
+ uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE);
+}
+
+static void geo_node_curve_trim_init(bNodeTree *UNUSED(tree), bNode *node)
+{
+ NodeGeometryCurveTrim *data = (NodeGeometryCurveTrim *)MEM_callocN(sizeof(NodeGeometryCurveTrim),
+ __func__);
+
+ data->mode = GEO_NODE_CURVE_TRIM_FACTOR;
+ node->storage = data;
+}
+
+static void geo_node_curve_trim_update(bNodeTree *UNUSED(ntree), bNode *node)
+{
+ NodeGeometryCurveTrim &node_storage = *(NodeGeometryCurveTrim *)node->storage;
+ const GeometryNodeCurveTrimMode mode = (GeometryNodeCurveTrimMode)node_storage.mode;
+
+ bNodeSocket *factor_start_socket = ((bNodeSocket *)node->inputs.first)->next;
+ bNodeSocket *length_start_socket = factor_start_socket->next;
+ bNodeSocket *factor_end_socket = length_start_socket->next;
+ bNodeSocket *length_end_socket = factor_end_socket->next;
+
+ nodeSetSocketAvailability(factor_start_socket, mode == GEO_NODE_CURVE_TRIM_FACTOR);
+ nodeSetSocketAvailability(length_start_socket, mode == GEO_NODE_CURVE_TRIM_LENGTH);
+ nodeSetSocketAvailability(factor_end_socket, mode == GEO_NODE_CURVE_TRIM_FACTOR);
+ nodeSetSocketAvailability(length_end_socket, mode == GEO_NODE_CURVE_TRIM_LENGTH);
+}
+
+namespace blender::nodes {
+
+static void interpolate_control_point(BezierSpline &spline,
+ const bool adjust_next,
+ const Spline::LookupResult lookup)
+{
+ using namespace ::blender::fn;
+
+ const int eval_index = lookup.evaluated_index;
+ const int next_eval_index = lookup.next_evaluated_index;
+ Span<float> mappings = spline.evaluated_mappings();
+
+ BezierSpline::InterpolationData interp_data = spline.interpolation_data_from_map(
+ mappings[eval_index]);
+
+ const int index = interp_data.control_point_index + (adjust_next ? 1 : 0);
+
+ Span<float3> evaluated_positions = spline.evaluated_positions();
+
+ spline.positions()[index] = float3::interpolate(
+ evaluated_positions[eval_index], evaluated_positions[next_eval_index], lookup.factor);
+
+ /* TODO: Do this interpolation with attributes instead. */
+
+ {
+ MutableSpan<float> radii = spline.radii();
+ GVArrayPtr radii_varray = spline.interpolate_to_evaluated_points(GVArray_For_Span(radii));
+ GVArray_Typed<float> radii_eval = radii_varray->typed<float>();
+
+ radii[index] = interpf(radii_eval[next_eval_index], radii_eval[eval_index], lookup.factor);
+ }
+
+ {
+ MutableSpan<float> tilts = spline.radii();
+ GVArrayPtr tilt_varray = spline.interpolate_to_evaluated_points(GVArray_For_Span(tilts));
+ GVArray_Typed<float> tilts_eval = tilt_varray->typed<float>();
+
+ tilts[index] = interpf(tilts_eval[next_eval_index], tilts_eval[eval_index], lookup.factor);
+ }
+
+ {
+ MutableSpan<float3> handle_positions_start = spline.handle_positions_start();
+ GVArrayPtr handle_positions_start_varray = spline.interpolate_to_evaluated_points(
+ GVArray_For_Span(handle_positions_start));
+ GVArray_Typed<float3> handle_positions_start_eval =
+ handle_positions_start_varray->typed<float3>();
+
+ handle_positions_start[index] = float3::interpolate(
+ handle_positions_start_eval[eval_index],
+ handle_positions_start_eval[next_eval_index],
+ lookup.factor);
+ }
+
+ {
+ MutableSpan<float3> handle_positions_end = spline.handle_positions_end();
+ GVArrayPtr handle_positions_end_varray = spline.interpolate_to_evaluated_points(
+ GVArray_For_Span(handle_positions_end));
+ GVArray_Typed<float3> handle_positions_end_eval = handle_positions_end_varray->typed<float3>();
+
+ handle_positions_end[index] = float3::interpolate(handle_positions_end_eval[eval_index],
+ handle_positions_end_eval[next_eval_index],
+ lookup.factor);
+ }
+}
+
+static void trim_spline(BezierSpline &spline,
+ const Spline::LookupResult start,
+ const Spline::LookupResult end)
+{
+ BLI_assert(!spline.is_cyclic);
+ BLI_assert(start.evaluated_index <= end.evaluated_index);
+
+ Span<float> mappings = spline.evaluated_mappings();
+
+ const int points_len = spline.size();
+ const int start_index = std::floor(mappings[start.evaluated_index]);
+ const int end_index = std::min((int)std::floor(mappings[end.evaluated_index]) + 2, points_len);
+
+ if (!(start.evaluated_index == 0 && start.factor == 0.0f)) {
+ interpolate_control_point(spline, false, start);
+ }
+ if (end.evaluated_index != spline.evaluated_points_size() - 1) {
+ interpolate_control_point(spline, true, end);
+ }
+
+ spline.drop_back(std::min(points_len - end_index, points_len));
+ spline.drop_front(std::max(start_index, 0));
+}
+
+static void geo_node_curve_trim_exec(GeoNodeExecParams params)
+{
+ GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
+
+ const bNode &node = params.node();
+ const NodeGeometryCurveTrim &node_storage = *(const NodeGeometryCurveTrim *)node.storage;
+ const GeometryNodeCurveTrimMode mode = (GeometryNodeCurveTrimMode)node_storage.mode;
+
+ if (!geometry_set.has_curve()) {
+ params.set_output("Geometry", geometry_set);
+ return;
+ }
+
+ SplineGroup &curve = *geometry_set.get_curve_for_write();
+
+ switch (mode) {
+ case GEO_NODE_CURVE_TRIM_FACTOR: {
+ const float factor_start = params.extract_input<float>("Start");
+ const float factor_end = params.extract_input<float>("End");
+ for (SplinePtr &spline : curve.splines) {
+ if (spline->type() != Spline::Type::Bezier) {
+ continue;
+ }
+ if (spline->is_cyclic) {
+ continue;
+ }
+ trim_spline(static_cast<BezierSpline &>(*spline),
+ spline->lookup_evaluated_factor(std::min(factor_start, factor_end)),
+ spline->lookup_evaluated_factor(std::max(factor_start, factor_end)));
+ }
+ break;
+ }
+ case GEO_NODE_CURVE_TRIM_LENGTH: {
+ const float length_start = params.extract_input<float>("Start_001");
+ const float length_from_end = params.extract_input<float>("End_001");
+ for (SplinePtr &spline : curve.splines) {
+ if (spline->type() != Spline::Type::Bezier) {
+ continue;
+ }
+ if (spline->is_cyclic) {
+ continue;
+ }
+ const float length_end = spline->length() - length_from_end;
+ trim_spline(static_cast<BezierSpline &>(*spline),
+ spline->lookup_evaluated_length(std::min(length_start, length_end)),
+ spline->lookup_evaluated_length(std::max(length_start, length_end)));
+ }
+ break;
+ }
+ default:
+ BLI_assert_unreachable();
+ break;
+ }
+
+ params.set_output("Geometry", geometry_set);
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_curve_trim()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(&ntype, GEO_NODE_CURVE_TRIM, "Curve Trim", NODE_CLASS_GEOMETRY, 0);
+ node_type_socket_templates(&ntype, geo_node_curve_trim_in, geo_node_curve_trim_out);
+ node_type_init(&ntype, geo_node_curve_trim_init);
+ node_type_update(&ntype, geo_node_curve_trim_update);
+ node_type_storage(
+ &ntype, "NodeGeometryCurveTrim", node_free_standard_storage, node_copy_standard_storage);
+
+ ntype.geometry_node_execute = blender::nodes::geo_node_curve_trim_exec;
+ ntype.draw_buttons = geo_node_curve_trim_layout;
+ 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 857b60dfb93..2e7731c39c5 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"
@@ -260,6 +261,48 @@ static void join_components(Span<const VolumeComponent *> src_components, Geomet
UNUSED_VARS(src_components, dst_component);
}
+/**
+ * Curve components are a special case. It's possibly to exploit the fact that they simply store
+ * splines by retrieved with write access is an optimization to avoid copying unecessarily when
+ * possible.
+ */
+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()) {
+ /* Getting write access for write access seems counterintuitive, but it can actually 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 copying them 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>();
+ SplineGroup *dst_curve = new SplineGroup();
+ for (CurveComponent *component : src_components) {
+ SplineGroup *src_curve = component->get_for_write();
+ for (SplinePtr &spline : src_curve->splines) {
+ dst_curve->splines.append(std::move(spline));
+ }
+ }
+
+ dst_component.replace(dst_curve);
+
+ /* TODO: Make sure generic attributes in different splines have the same type. */
+}
+
template<typename Component>
static void join_component_type(Span<GeometrySet> src_geometry_sets, GeometrySet &result)
{
@@ -290,6 +333,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_object_info.cc b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc
index bd42b4c11d6..52a44cc2cfe 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_object_info.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc
@@ -14,6 +14,11 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+#include "DNA_curve_types.h"
+#include "DNA_object_types.h"
+
+#include "BKE_spline.hh"
+
#include "BLI_math_matrix.h"
#include "UI_interface.h"
@@ -72,15 +77,24 @@ static void geo_node_object_info_exec(GeoNodeExecParams params)
quat_to_eul(rotation, quaternion);
if (object != self_object) {
- InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>();
-
- if (transform_space_relative) {
- instances.add_instance(object, transform);
+ if (object->type == OB_CURVE) {
+ SplineGroup *curve = dcurve_from_dna_curve(*(Curve *)object->data);
+ if (transform_space_relative) {
+ curve->transform(float4x4(transform));
+ }
+ geometry_set = GeometrySet::create_with_curve(curve);
}
else {
- float unit_transform[4][4];
- unit_m4(unit_transform);
- instances.add_instance(object, unit_transform);
+ InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>();
+
+ if (transform_space_relative) {
+ instances.add_instance(object, transform);
+ }
+ else {
+ float unit_transform[4][4];
+ unit_m4(unit_transform);
+ instances.add_instance(object, unit_transform);
+ }
}
}
}
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..b90d60a01ba 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc
@@ -208,6 +208,10 @@ static void geo_node_point_instance_exec(GeoNodeExecParams params)
add_instances_from_geometry_component(
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_transform.cc b/source/blender/nodes/geometry/nodes/node_geo_transform.cc
index d54982d16c2..3eada75b054 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(SplineGroup &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()) {
+ SplineGroup *curve = geometry_set.get_curve_for_write();
+ transform_curve(*curve, translation, rotation, scale);
+ }
+
params.set_output("Geometry", std::move(geometry_set));
}
} // namespace blender::nodes