diff options
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 |