Compare commits
109 Commits
temp-paral
...
geometry-n
Author | SHA1 | Date | |
---|---|---|---|
7030e3a016 | |||
936dfae3de | |||
399aed9a81 | |||
e7c1702ccb | |||
149078d580 | |||
47b1f9a4d3 | |||
8709d0cd5d | |||
46bfc3ec46 | |||
a29b9a73d2 | |||
39f21c2475 | |||
87036f5eeb | |||
9636e79c5a | |||
e1c04da36f | |||
45bac491eb | |||
837d630466 | |||
23eb05eba5 | |||
12951aecf6 | |||
46a037707b | |||
f2948faf97 | |||
d7aa481231 | |||
cd07fb6477 | |||
d0bdb2b48f | |||
35b0d177a2 | |||
441f8d98eb | |||
c21199a0ba | |||
26d4864ee6 | |||
da443d82ee | |||
330c14c26c | |||
34542eede5 | |||
2c73e16d1c | |||
dd886fcc6d | |||
6b44605cec | |||
2e76c3565d | |||
c3b5378978 | |||
645fa86b7d | |||
e43428eba3 | |||
52cb657e91 | |||
a80edb8865 | |||
b500099e5c | |||
c9c295ef25 | |||
d64dea8a7e | |||
476006ae64 | |||
51e72e4703 | |||
ba12548f24 | |||
a6f2da1caf | |||
91b44ebb52 | |||
0d203862ca | |||
371720cb6d | |||
be1a41878c | |||
53b2a1a0e3 | |||
d9cb1fa7dc | |||
e7516174c7 | |||
98c908d6f6 | |||
fcb7d2fcba | |||
d0b2ddefa3 | |||
bb04d1c2c8 | |||
839c796321 | |||
db731b0beb | |||
6cf3878765 | |||
1975dc96d1 | |||
fa83dbda06 | |||
d0b0cb5982 | |||
da3004a0f9 | |||
ec090a215b | |||
797331db86 | |||
652ee81662 | |||
b5bd676a51 | |||
b490e0838c | |||
7c77d769dd | |||
48316b7e08 | |||
ff34a3a612 | |||
583bc7a05d | |||
2a96dc80b0 | |||
2cdec6cbf4 | |||
f8604f18e8 | |||
4f0e6c3aa3 | |||
f3775ca2cf | |||
5e013d47a1 | |||
2f2bedafa1 | |||
b90aeb0a44 | |||
29e24974a6 | |||
c752884d0d | |||
797ff2f064 | |||
24d32a796a | |||
d1519df6a8 | |||
457e1f9d1f | |||
85165058ef | |||
54188678b4 | |||
073bf63a9d | |||
f91df2e3e3 | |||
c95b2cefab | |||
53f92279cb | |||
b3fc6ac07a | |||
968977d684 | |||
f49a499129 | |||
140828037b | |||
188e4d302c | |||
cc06b059c2 | |||
0c64850e43 | |||
d64e149a4f | |||
09846cdce8 | |||
a4ad4bc8d3 | |||
2622882d83 | |||
b4beba7f1c | |||
58cdb78ea0 | |||
25e95ccb1a | |||
9e91aea40a | |||
5f03931fc4 | |||
e1cef1b85d |
@@ -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"),
|
||||
|
@@ -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"
|
||||
|
@@ -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);
|
||||
|
@@ -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,18 +365,25 @@ struct GeometrySet {
|
||||
Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
|
||||
static GeometrySet create_with_pointcloud(
|
||||
PointCloud *pointcloud, GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
|
||||
static GeometrySet create_with_curve(
|
||||
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);
|
||||
@@ -382,6 +391,8 @@ struct GeometrySet {
|
||||
GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
|
||||
void replace_volume(Volume *volume,
|
||||
GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
|
||||
void replace_curve(SplineGroup *mesh,
|
||||
GeometryOwnershipType ownership = GeometryOwnershipType::Owned);
|
||||
};
|
||||
|
||||
/** A geometry component that can store a mesh. */
|
||||
@@ -463,6 +474,38 @@ class PointCloudComponent : public GeometryComponent {
|
||||
const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final;
|
||||
};
|
||||
|
||||
/** A geometry component that stores curve data, in other words, a group of splines. */
|
||||
class CurveComponent : public GeometryComponent {
|
||||
private:
|
||||
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:
|
||||
|
@@ -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
|
||||
|
||||
/** \} */
|
||||
|
||||
|
434
source/blender/blenkernel/BKE_spline.hh
Normal file
434
source/blender/blenkernel/BKE_spline.hh
Normal file
@@ -0,0 +1,434 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup bke
|
||||
*/
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "FN_generic_virtual_array.hh"
|
||||
|
||||
#include "BLI_float3.hh"
|
||||
#include "BLI_float4x4.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "BKE_attribute_math.hh"
|
||||
|
||||
struct Curve;
|
||||
|
||||
class Spline;
|
||||
using SplinePtr = std::unique_ptr<Spline>;
|
||||
|
||||
/**
|
||||
* A spline is an abstraction of a single branchless curve section, its evaluation methods,
|
||||
* and data. The spline data itself is just control points and a set of attributes.
|
||||
*
|
||||
* Any derived class of Spline has to manage two things:
|
||||
* 1. Evaluating the positions based on the stored control point data.
|
||||
* 2. Interpolating arbitrary attribute data from the control points to evaluated points.
|
||||
*
|
||||
* Beyond that, everything else is the bas class's responsibility, with minor exceptions. Futher
|
||||
* evaluation happens in a layer on top of the evaluated points generated by the derived types.
|
||||
* There are a few methods to evaluate a spline:
|
||||
*
|
||||
* Common evaluated data is stored in caches on the spline itself. That 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)
|
||||
{
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* A Bézier spline is made up of a many curve segments, possibly achieving continuity of curvature
|
||||
* by constraining the alignment of curve handles. Evaluation stores the positions and a map of
|
||||
* factors and indices in a list of floats, which is then used to interpolate any other data.
|
||||
*/
|
||||
class BezierSpline final : public Spline {
|
||||
public:
|
||||
enum 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_;
|
||||
|
||||
mutable bool offset_cache_dirty_ = true;
|
||||
mutable std::mutex offset_cache_mutex_;
|
||||
mutable blender::Vector<int> offset_cache_;
|
||||
|
||||
mutable bool position_cache_dirty_ = true;
|
||||
mutable std::mutex position_cache_mutex_;
|
||||
mutable blender::Vector<blender::float3> evaluated_position_cache_;
|
||||
|
||||
mutable bool mapping_cache_dirty_ = true;
|
||||
mutable std::mutex mapping_cache_mutex_;
|
||||
mutable blender::Vector<float> evaluated_mapping_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_(other.resolution_)
|
||||
{
|
||||
}
|
||||
|
||||
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<int> control_point_offsets() const;
|
||||
blender::Span<float> evaluated_mappings() const;
|
||||
blender::Span<blender::float3> evaluated_positions() const final;
|
||||
struct InterpolationData {
|
||||
int control_point_index;
|
||||
int next_control_point_index;
|
||||
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;
|
||||
|
||||
private:
|
||||
void correct_end_tangents() const final;
|
||||
bool segment_is_vector(const int start_index) const;
|
||||
void evaluate_bezier_segment(const int index,
|
||||
const int next_index,
|
||||
blender::MutableSpan<blender::float3> positions) const;
|
||||
blender::Array<int> evaluated_point_offsets() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Data for Non-Uniform Rational B-Splines. The mapping from control points to evaluated points is
|
||||
* influenced by a vector of knots, weights for each point, and the order of the spline. Every
|
||||
* mapping of data to evaluated points is handled the same way, but the posistions are cached in
|
||||
* the spline.
|
||||
*/
|
||||
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_;
|
||||
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_position_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_(other.resolution_),
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* A poly spline is like a bezier spline with a resolution of one. The main reason to distinguish
|
||||
* the two is for reduced complexity and increased performance, since interpolating data to control
|
||||
* points does not change it.
|
||||
*/
|
||||
class PolySpline final : public Spline {
|
||||
public:
|
||||
blender::Vector<blender::float3> positions_;
|
||||
blender::Vector<float> radii_;
|
||||
blender::Vector<float> tilts_;
|
||||
|
||||
private:
|
||||
public:
|
||||
SplinePtr copy() const final;
|
||||
PolySpline() : Spline(Type::Bezier){};
|
||||
PolySpline(const PolySpline &other)
|
||||
: Spline((Spline &)other),
|
||||
positions_(other.positions_),
|
||||
radii_(other.radii_),
|
||||
tilts_(other.tilts_)
|
||||
{
|
||||
}
|
||||
|
||||
int size() const final;
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* A #SplineGroup corresponds to the #Curve object data. The name is different for clarity, since
|
||||
* more of the data is stored in the splines, but also just to be different than the name in DNA.
|
||||
*/
|
||||
class 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);
|
@@ -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
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
||||
@@ -893,14 +895,59 @@ 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;
|
||||
@@ -1020,7 +1067,9 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph,
|
||||
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 */
|
||||
@@ -1096,6 +1145,10 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph,
|
||||
/* Pretty stupid to generate that whole mesh if it's unused, yet we have to free it. */
|
||||
BKE_id_free(nullptr, modified);
|
||||
}
|
||||
|
||||
if (r_geometry_set) {
|
||||
*r_geometry_set = new GeometrySet(std::move(geometry_set_final));
|
||||
}
|
||||
}
|
||||
|
||||
static void displist_surf_indices(DispList *dl)
|
||||
@@ -1235,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);
|
||||
@@ -1469,7 +1522,8 @@ 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 = (Curve *)ob->data;
|
||||
|
||||
@@ -1709,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) {
|
||||
@@ -1746,10 +1806,18 @@ void BKE_displist_make_curveTypes(Depsgraph *depsgraph,
|
||||
dispbase = &(ob->runtime.curve_cache->disp);
|
||||
|
||||
Mesh *mesh_eval = nullptr;
|
||||
do_makeDispListCurveTypes(depsgraph, scene, ob, dispbase, for_render, for_orco, &mesh_eval);
|
||||
GeometrySet *geometry_set_eval = nullptr;
|
||||
do_makeDispListCurveTypes(
|
||||
depsgraph, scene, ob, dispbase, for_render, for_orco, &mesh_eval, &geometry_set_eval);
|
||||
|
||||
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);
|
||||
@@ -1767,7 +1835,7 @@ void BKE_displist_make_curveTypes_forRender(Depsgraph *depsgraph,
|
||||
"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])
|
||||
|
403
source/blender/blenkernel/intern/geometry_component_curve.cc
Normal file
403
source/blender/blenkernel/intern/geometry_component_curve.cc
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
/** \} */
|
@@ -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)
|
||||
{
|
||||
@@ -334,6 +374,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();
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
@@ -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();
|
||||
|
339
source/blender/blenkernel/intern/spline_base.cc
Normal file
339
source/blender/blenkernel/intern/spline_base.cc
Normal file
@@ -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_span.hh"
|
||||
|
||||
#include "BKE_spline.hh"
|
||||
|
||||
using blender::float3;
|
||||
using blender::IndexRange;
|
||||
using blender::MutableSpan;
|
||||
using blender::Span;
|
||||
|
||||
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_;
|
||||
}
|
||||
|
||||
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_;
|
||||
}
|
||||
|
||||
#if 0 /* Not supported yet, has errors. */
|
||||
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);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
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();
|
||||
|
||||
#if 0 /* Not supported yet, has errors. */
|
||||
switch (this->normal_mode) {
|
||||
case NormalCalculationMode::Minimum:
|
||||
calculate_normals_minimum_twist(tangents, is_cyclic, this->evaluated_normals_cache_);
|
||||
break;
|
||||
case NormalCalculationMode::ZUp:
|
||||
break;
|
||||
case NormalCalculationMode::Tangent:
|
||||
calculate_normals_tangent(tangents, this->evaluated_normals_cache_);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
calculate_normals_z_up(tangents, this->evaluated_normals_cache_);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* \note This does not support extrapolation currently.
|
||||
*/
|
||||
Spline::LookupResult Spline::lookup_evaluated_length(const float length) const
|
||||
{
|
||||
BLI_assert(length >= 0.0f && length <= this->length());
|
||||
|
||||
Span<float> lengths = this->evaluated_lengths();
|
||||
|
||||
const float *offset = std::lower_bound(lengths.begin(), lengths.end(), length);
|
||||
const int index = offset - lengths.begin();
|
||||
const int next_index = (index == this->size() - 1) ? 0 : index + 1;
|
||||
|
||||
const float previous_length = (index == 0) ? 0.0f : lengths[index - 1];
|
||||
const float factor = (length - previous_length) / (lengths[index] - previous_length);
|
||||
|
||||
return LookupResult{index, next_index, factor};
|
||||
}
|
||||
|
||||
void Spline::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated) const
|
||||
{
|
||||
Span<float3> positions = use_evaluated ? this->evaluated_positions() : this->positions();
|
||||
for (const float3 &position : positions) {
|
||||
minmax_v3v3_v3(min, max, position);
|
||||
}
|
||||
}
|
483
source/blender/blenkernel/intern/spline_bezier.cc
Normal file
483
source/blender/blenkernel/intern/spline_bezier.cc
Normal file
@@ -0,0 +1,483 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "BKE_spline.hh"
|
||||
|
||||
using blender::Array;
|
||||
using blender::float3;
|
||||
using blender::IndexRange;
|
||||
using blender::MutableSpan;
|
||||
using blender::Span;
|
||||
|
||||
SplinePtr BezierSpline::copy() const
|
||||
{
|
||||
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_;
|
||||
}
|
||||
|
||||
void BezierSpline::set_resolution(const int value)
|
||||
{
|
||||
this->resolution_ = 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 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->offset_cache_dirty_ = true;
|
||||
this->position_cache_dirty_ = true;
|
||||
this->mapping_cache_dirty_ = true;
|
||||
this->tangent_cache_dirty_ = true;
|
||||
this->normal_cache_dirty_ = true;
|
||||
this->length_cache_dirty_ = true;
|
||||
}
|
||||
|
||||
int BezierSpline::evaluated_points_size() const
|
||||
{
|
||||
const int points_len = this->size();
|
||||
BLI_assert(points_len > 0);
|
||||
|
||||
const int last_offset = this->control_point_offsets().last();
|
||||
if (this->is_cyclic) {
|
||||
return last_offset + (this->segment_is_vector(points_len - 1) ? 0 : this->resolution_);
|
||||
}
|
||||
|
||||
return last_offset + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the spline is not cyclic, the direction for the first and last points is just the
|
||||
* direction formed by the corresponding handles and control points. In the unlikely situation
|
||||
* that the handles define a zero direction, fallback to using the direction defined by the
|
||||
* first and last evaluated segments already calculated in #Spline::evaluated_tangents().
|
||||
*/
|
||||
void BezierSpline::correct_end_tangents() const
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void BezierSpline::evaluate_bezier_segment(const int index,
|
||||
const int next_index,
|
||||
MutableSpan<float3> positions) const
|
||||
{
|
||||
if (this->segment_is_vector(index)) {
|
||||
positions.first() = this->positions_[index];
|
||||
}
|
||||
else {
|
||||
bezier_forward_difference_3d(this->positions_[index],
|
||||
this->handle_positions_end_[index],
|
||||
this->handle_positions_start_[next_index],
|
||||
this->positions_[next_index],
|
||||
positions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns access to a cache of offsets into the evaluated point array for each control point.
|
||||
* This is important because while most control point edges generate the number of edges specified
|
||||
* by the resolution, vector segments only generate one edge.
|
||||
*/
|
||||
Span<int> BezierSpline::control_point_offsets() const
|
||||
{
|
||||
if (!this->offset_cache_dirty_) {
|
||||
return this->offset_cache_;
|
||||
}
|
||||
|
||||
std::lock_guard lock{this->offset_cache_mutex_};
|
||||
if (!this->offset_cache_dirty_) {
|
||||
return this->offset_cache_;
|
||||
}
|
||||
|
||||
const int points_len = this->size();
|
||||
this->offset_cache_.resize(points_len);
|
||||
|
||||
MutableSpan<int> offsets = this->offset_cache_;
|
||||
|
||||
int offset = 0;
|
||||
for (const int i : IndexRange(points_len - 1)) {
|
||||
offsets[i] = offset;
|
||||
offset += this->segment_is_vector(i) ? 1 : this->resolution_;
|
||||
}
|
||||
offsets.last() = offset;
|
||||
|
||||
this->offset_cache_dirty_ = false;
|
||||
return offsets;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
if (!this->mapping_cache_dirty_) {
|
||||
return this->evaluated_mapping_cache_;
|
||||
}
|
||||
|
||||
std::lock_guard lock{this->mapping_cache_mutex_};
|
||||
if (!this->mapping_cache_dirty_) {
|
||||
return this->evaluated_mapping_cache_;
|
||||
}
|
||||
|
||||
const int size = this->size();
|
||||
const int eval_size = this->evaluated_points_size();
|
||||
this->evaluated_mapping_cache_.resize(eval_size);
|
||||
MutableSpan<float> mappings = this->evaluated_mapping_cache_;
|
||||
|
||||
Span<int> offsets = this->control_point_offsets();
|
||||
Span<float> lengths = this->evaluated_lengths();
|
||||
|
||||
/* Subtract one from the index into the lengths array to get the length
|
||||
* at the start point rather than the length at the end of the edge. */
|
||||
|
||||
const float first_segment_len = lengths[offsets[1] - 1];
|
||||
for (const int eval_index : IndexRange(0, offsets[1])) {
|
||||
const float point_len = eval_index == 0 ? 0.0f : lengths[eval_index - 1];
|
||||
const float length_factor = (first_segment_len == 0.0f) ? 0.0f : 1.0f / first_segment_len;
|
||||
|
||||
mappings[eval_index] = point_len * length_factor;
|
||||
}
|
||||
|
||||
const int grain_size = std::max(512 / this->resolution_, 1);
|
||||
blender::parallel_for(IndexRange(1, size - 2), grain_size, [&](IndexRange range) {
|
||||
for (const int i : range) {
|
||||
const float segment_start_len = lengths[offsets[i] - 1];
|
||||
const float segment_end_len = lengths[offsets[i + 1] - 1];
|
||||
const float segment_len = segment_end_len - segment_start_len;
|
||||
const float length_factor = (segment_len == 0.0f) ? 0.0f : 1.0f / segment_len;
|
||||
|
||||
for (const int eval_index : IndexRange(offsets[i], offsets[i + 1] - offsets[i])) {
|
||||
const float factor = (lengths[eval_index - 1] - segment_start_len) * length_factor;
|
||||
mappings[eval_index] = i + factor;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (this->is_cyclic) {
|
||||
const float segment_start_len = lengths[offsets.last() - 1];
|
||||
const float segment_end_len = this->length();
|
||||
const float segment_len = segment_end_len - segment_start_len;
|
||||
const float length_factor = (segment_len == 0.0f) ? 0.0f : 1.0f / segment_len;
|
||||
|
||||
for (const int eval_index : IndexRange(offsets.last(), eval_size - offsets.last())) {
|
||||
const float factor = (lengths[eval_index - 1] - segment_start_len) * length_factor;
|
||||
mappings[eval_index] = size - 1 + factor;
|
||||
}
|
||||
mappings.last() = 0.0f;
|
||||
}
|
||||
else {
|
||||
mappings.last() = size - 1;
|
||||
}
|
||||
|
||||
this->mapping_cache_dirty_ = false;
|
||||
return this->evaluated_mapping_cache_;
|
||||
}
|
||||
|
||||
Span<float3> BezierSpline::evaluated_positions() const
|
||||
{
|
||||
if (!this->position_cache_dirty_) {
|
||||
return this->evaluated_position_cache_;
|
||||
}
|
||||
|
||||
std::lock_guard lock{this->position_cache_mutex_};
|
||||
if (!this->position_cache_dirty_) {
|
||||
return this->evaluated_position_cache_;
|
||||
}
|
||||
|
||||
const int eval_total = this->evaluated_points_size();
|
||||
this->evaluated_position_cache_.resize(eval_total);
|
||||
|
||||
MutableSpan<float3> positions = this->evaluated_position_cache_;
|
||||
|
||||
Span<int> offsets = this->control_point_offsets();
|
||||
BLI_assert(offsets.last() <= eval_total);
|
||||
|
||||
const int grain_size = std::max(512 / this->resolution_, 1);
|
||||
blender::parallel_for(IndexRange(this->size() - 1), grain_size, [&](IndexRange range) {
|
||||
for (const int i : range) {
|
||||
this->evaluate_bezier_segment(
|
||||
i, i + 1, positions.slice(offsets[i], offsets[i + 1] - offsets[i]));
|
||||
}
|
||||
});
|
||||
|
||||
const int i_last = this->size() - 1;
|
||||
if (this->is_cyclic) {
|
||||
this->evaluate_bezier_segment(i_last, 0, positions.slice(offsets.last(), this->resolution_));
|
||||
}
|
||||
else {
|
||||
/* Since evaulating the bezier segment doesn't add the final point,
|
||||
* it must be added manually in the non-cyclic case. */
|
||||
positions.last() = this->positions_.last();
|
||||
}
|
||||
|
||||
this->position_cache_dirty_ = false;
|
||||
return this->evaluated_position_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();
|
||||
/* TODO: Use a set of functions mix2 in attribute_math instead of DefaultMixer. */
|
||||
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;
|
||||
}
|
205
source/blender/blenkernel/intern/spline_group.cc
Normal file
205
source/blender/blenkernel/intern/spline_group.cc
Normal file
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_span.hh"
|
||||
|
||||
#include "DNA_curve_types.h"
|
||||
|
||||
#include "BKE_curve.h"
|
||||
#include "BKE_spline.hh"
|
||||
|
||||
using blender::float3;
|
||||
using blender::float4x4;
|
||||
using blender::Span;
|
||||
|
||||
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) {
|
||||
for (float3 &position : spline->positions()) {
|
||||
position += translation;
|
||||
}
|
||||
if (BezierSpline *bezier_spline = dynamic_cast<BezierSpline *>(spline.get())) {
|
||||
for (float3 &handle_position : bezier_spline->handle_positions_start()) {
|
||||
handle_position += translation;
|
||||
}
|
||||
for (float3 &handle_position : bezier_spline->handle_positions_end()) {
|
||||
handle_position += translation;
|
||||
}
|
||||
}
|
||||
spline->mark_cache_invalid();
|
||||
}
|
||||
}
|
||||
|
||||
void SplineGroup::transform(const float4x4 &matrix)
|
||||
{
|
||||
for (SplinePtr &spline : this->splines) {
|
||||
for (float3 &position : spline->positions()) {
|
||||
position = matrix * position;
|
||||
}
|
||||
if (BezierSpline *bezier_spline = dynamic_cast<BezierSpline *>(spline.get())) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Note: Normal mode is stored separately in each spline to facilitate combining splines
|
||||
* from multiple curve objects, where the value may be different. */
|
||||
const Spline::NormalCalculationMode normal_mode = normal_mode_from_dna_curve(
|
||||
dna_curve.twist_mode);
|
||||
for (SplinePtr &spline : curve->splines) {
|
||||
spline->normal_mode = normal_mode;
|
||||
}
|
||||
|
||||
return curve;
|
||||
}
|
||||
|
||||
/** \} */
|
444
source/blender/blenkernel/intern/spline_nurbs.cc
Normal file
444
source/blender/blenkernel/intern/spline_nurbs.cc
Normal file
@@ -0,0 +1,444 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_virtual_array.hh"
|
||||
|
||||
#include "BKE_attribute_math.hh"
|
||||
#include "BKE_spline.hh"
|
||||
|
||||
using blender::Array;
|
||||
using blender::float3;
|
||||
using blender::IndexRange;
|
||||
using blender::MutableSpan;
|
||||
using blender::Span;
|
||||
|
||||
SplinePtr NURBSpline::copy() const
|
||||
{
|
||||
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_;
|
||||
}
|
||||
|
||||
void NURBSpline::set_resolution(const int value)
|
||||
{
|
||||
this->resolution_ = value;
|
||||
this->mark_cache_invalid();
|
||||
}
|
||||
|
||||
uint8_t NURBSpline::order() const
|
||||
{
|
||||
return this->order_;
|
||||
}
|
||||
|
||||
void NURBSpline::set_order(const uint8_t value)
|
||||
{
|
||||
BLI_assert(value >= 2 && value <= 6);
|
||||
this->order_ = value;
|
||||
this->mark_cache_invalid();
|
||||
}
|
||||
|
||||
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_ * 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 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_position_cache_;
|
||||
}
|
||||
|
||||
std::lock_guard lock{this->position_cache_mutex_};
|
||||
if (!this->position_cache_dirty_) {
|
||||
return this->evaluated_position_cache_;
|
||||
}
|
||||
|
||||
const int total = this->evaluated_points_size();
|
||||
this->evaluated_position_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_position_cache_[i] = evaluated_positions[i];
|
||||
}
|
||||
|
||||
this->position_cache_dirty_ = false;
|
||||
return this->evaluated_position_cache_;
|
||||
}
|
144
source/blender/blenkernel/intern/spline_poly.cc
Normal file
144
source/blender/blenkernel/intern/spline_poly.cc
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_virtual_array.hh"
|
||||
|
||||
#include "BKE_spline.hh"
|
||||
|
||||
using blender::float3;
|
||||
using blender::MutableSpan;
|
||||
using blender::Span;
|
||||
|
||||
SplinePtr PolySpline::copy() const
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Poly spline interpolation from control points to evaluated points is a special case, since the
|
||||
* result data is the same as the input data.
|
||||
*/
|
||||
blender::fn::GVArrayPtr PolySpline::interpolate_to_evaluated_points(
|
||||
const blender::fn::GVArray &source_data) const
|
||||
{
|
||||
BLI_assert(source_data.size() == this->size());
|
||||
|
||||
/* TODO: Must make sure this ownership idea works. */
|
||||
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 {};
|
||||
}
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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. */
|
||||
|
@@ -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,
|
||||
|
@@ -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},
|
||||
};
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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,
|
||||
|
@@ -185,7 +185,7 @@ static void add_object_relation(const ModifierUpdateDepsgraphContext *ctx, Objec
|
||||
if (object.type == OB_EMPTY && object.instance_collection != nullptr) {
|
||||
add_collection_relation(ctx, *object.instance_collection);
|
||||
}
|
||||
else if (ELEM(object.type, OB_MESH, OB_POINTCLOUD, OB_VOLUME)) {
|
||||
else if (ELEM(object.type, OB_MESH, OB_POINTCLOUD, OB_VOLUME, OB_CURVE)) {
|
||||
DEG_add_object_relation(ctx->node, &object, DEG_OB_COMP_GEOMETRY, "Nodes Modifier");
|
||||
DEG_add_customdata_mask(ctx->node, &object, &dependency_data_mask);
|
||||
}
|
||||
@@ -1577,9 +1577,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,
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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));
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
309
source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc
Normal file
309
source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc
Normal file
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* Add the edges running along the length of the curve, starting at each profile vertex. */
|
||||
const int spline_edges_start = edge_offset;
|
||||
for (const int i_profile : IndexRange(profile_vert_len)) {
|
||||
for (const int i_ring : IndexRange(spline_edge_len)) {
|
||||
const int i_next_ring = (i_ring == spline_vert_len - 1) ? 0 : i_ring + 1;
|
||||
|
||||
const int ring_vert_offset = vert_offset + profile_vert_len * i_ring;
|
||||
const int next_ring_vert_offset = vert_offset + profile_vert_len * i_next_ring;
|
||||
|
||||
MEdge &edge = edges[edge_offset++];
|
||||
edge.v1 = ring_vert_offset + i_profile;
|
||||
edge.v2 = next_ring_vert_offset + i_profile;
|
||||
edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add the edges running along each profile ring. */
|
||||
const int profile_edges_start = edge_offset;
|
||||
for (const int i_ring : IndexRange(spline_vert_len)) {
|
||||
const int ring_vert_offset = vert_offset + profile_vert_len * i_ring;
|
||||
|
||||
for (const int i_profile : IndexRange(profile_edge_len)) {
|
||||
const int i_next_profile = (i_profile == profile_vert_len - 1) ? 0 : i_profile + 1;
|
||||
|
||||
MEdge &edge = edges[edge_offset++];
|
||||
edge.v1 = ring_vert_offset + i_profile;
|
||||
edge.v2 = ring_vert_offset + i_next_profile;
|
||||
edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
|
||||
}
|
||||
}
|
||||
|
||||
/* Calculate poly and face indices. */
|
||||
for (const int i_ring : IndexRange(spline_edge_len)) {
|
||||
const int i_next_ring = (i_ring == spline_vert_len - 1) ? 0 : i_ring + 1;
|
||||
|
||||
const int ring_vert_offset = vert_offset + profile_vert_len * i_ring;
|
||||
const int next_ring_vert_offset = vert_offset + profile_vert_len * i_next_ring;
|
||||
|
||||
const int ring_edge_start = profile_edges_start + profile_edge_len * i_ring;
|
||||
const int next_ring_edge_offset = profile_edges_start + profile_edge_len * i_next_ring;
|
||||
|
||||
for (const int i_profile : IndexRange(profile_edge_len)) {
|
||||
const int i_next_profile = (i_profile == profile_vert_len - 1) ? 0 : i_profile + 1;
|
||||
|
||||
const int spline_edge_start = spline_edges_start + spline_edge_len * i_profile;
|
||||
const int next_spline_edge_start = spline_edges_start + spline_edge_len * i_next_profile;
|
||||
|
||||
MPoly &poly = 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_next_profile;
|
||||
loop_b.e = next_spline_edge_start + i_ring;
|
||||
MLoop &loop_c = loops[loop_offset++];
|
||||
loop_c.v = next_ring_vert_offset + i_next_profile;
|
||||
loop_c.e = next_ring_edge_offset + i_profile;
|
||||
MLoop &loop_d = 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], normals[i_ring], tangents[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<int> control_point_offsets = bezier_spline.control_point_offsets();
|
||||
for (const int i : control_point_offsets.index_range()) {
|
||||
if (bezier_spline.point_is_sharp(i)) {
|
||||
mark_edges_sharp(edges.slice(
|
||||
spline_edges_start + spline_edge_len * control_point_offsets[i], 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();
|
||||
profile_edge_total += profile_spline->evaluated_edges_size();
|
||||
}
|
||||
|
||||
int vert_total = 0;
|
||||
int edge_total = 0;
|
||||
int poly_total = 0;
|
||||
for (const SplinePtr &spline : curve.splines) {
|
||||
const int spline_vert_len = spline->evaluated_points_size();
|
||||
const int spline_edge_len = spline->evaluated_edges_size();
|
||||
vert_total += spline_vert_len * profile_vert_total;
|
||||
poly_total += spline_edge_len * profile_edge_total;
|
||||
|
||||
/* Add the ring edges, with one ring for every curve vertex, and the edge loops
|
||||
* that run along the length of the curve, starting on the first profile. */
|
||||
edge_total += profile_edge_total * spline_vert_len + profile_vert_total * spline_edge_len;
|
||||
}
|
||||
const int corner_total = poly_total * 4;
|
||||
|
||||
if (vert_total == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Mesh *mesh = BKE_mesh_new_nomain(vert_total, edge_total, 0, corner_total, poly_total);
|
||||
MutableSpan<MVert> verts{mesh->mvert, mesh->totvert};
|
||||
MutableSpan<MEdge> edges{mesh->medge, mesh->totedge};
|
||||
MutableSpan<MLoop> loops{mesh->mloop, mesh->totloop};
|
||||
MutableSpan<MPoly> polys{mesh->mpoly, mesh->totpoly};
|
||||
mesh->flag |= ME_AUTOSMOOTH;
|
||||
mesh->smoothresh = DEG2RADF(180.0f);
|
||||
|
||||
int vert_offset = 0;
|
||||
int edge_offset = 0;
|
||||
int loop_offset = 0;
|
||||
int poly_offset = 0;
|
||||
for (const SplinePtr &spline : curve.splines) {
|
||||
for (const SplinePtr &profile_spline : profile_curve.splines) {
|
||||
spline_extrude_to_mesh_data(*spline,
|
||||
*profile_spline,
|
||||
verts,
|
||||
edges,
|
||||
loops,
|
||||
polys,
|
||||
vert_offset,
|
||||
edge_offset,
|
||||
loop_offset,
|
||||
poly_offset);
|
||||
}
|
||||
}
|
||||
|
||||
BKE_mesh_calc_normals(mesh);
|
||||
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);
|
||||
}
|
@@ -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);
|
||||
}
|
230
source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
Normal file
230
source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
Normal file
@@ -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);
|
||||
}
|
@@ -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));
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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));
|
||||
}
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user