diff options
Diffstat (limited to 'source/blender/blenkernel')
87 files changed, 5847 insertions, 1132 deletions
diff --git a/source/blender/blenkernel/BKE_action.h b/source/blender/blenkernel/BKE_action.h index e27cb2be8ee..3d81fcba37d 100644 --- a/source/blender/blenkernel/BKE_action.h +++ b/source/blender/blenkernel/BKE_action.h @@ -140,7 +140,7 @@ void BKE_pose_channel_free_bbone_cache(struct bPoseChannel_Runtime *runtime); void BKE_pose_channels_free(struct bPose *pose); void BKE_pose_channels_free_ex(struct bPose *pose, bool do_id_user); -void BKE_pose_channels_hash_make(struct bPose *pose); +void BKE_pose_channels_hash_ensure(struct bPose *pose); void BKE_pose_channels_hash_free(struct bPose *pose); void BKE_pose_channels_remove(struct Object *ob, @@ -161,7 +161,7 @@ void BKE_pose_channel_session_uuid_generate(struct bPoseChannel *pchan); struct bPoseChannel *BKE_pose_channel_find_name(const struct bPose *pose, const char *name); struct bPoseChannel *BKE_pose_channel_active(struct Object *ob); struct bPoseChannel *BKE_pose_channel_active_or_first_selected(struct Object *ob); -struct bPoseChannel *BKE_pose_channel_verify(struct bPose *pose, const char *name); +struct bPoseChannel *BKE_pose_channel_ensure(struct bPose *pose, const char *name); struct bPoseChannel *BKE_pose_channel_get_mirrored(const struct bPose *pose, const char *name); void BKE_pose_check_uuids_unique_and_report(const struct bPose *pose); diff --git a/source/blender/blenkernel/BKE_armature.h b/source/blender/blenkernel/BKE_armature.h index 0bd817f0da1..3002a9cc10d 100644 --- a/source/blender/blenkernel/BKE_armature.h +++ b/source/blender/blenkernel/BKE_armature.h @@ -178,6 +178,7 @@ void BKE_armature_where_is_bone(struct Bone *bone, void BKE_pose_clear_pointers(struct bPose *pose); void BKE_pose_remap_bone_pointers(struct bArmature *armature, struct bPose *pose); void BKE_pchan_rebuild_bbone_handles(struct bPose *pose, struct bPoseChannel *pchan); +void BKE_pose_channels_clear_with_null_bone(struct bPose *pose, const bool do_id_user); void BKE_pose_rebuild(struct Main *bmain, struct Object *ob, struct bArmature *arm, diff --git a/source/blender/blenkernel/BKE_attribute_access.hh b/source/blender/blenkernel/BKE_attribute_access.hh index f6a6de04b70..c3bc4d3ca4a 100644 --- a/source/blender/blenkernel/BKE_attribute_access.hh +++ b/source/blender/blenkernel/BKE_attribute_access.hh @@ -27,6 +27,85 @@ #include "BLI_color.hh" #include "BLI_float2.hh" #include "BLI_float3.hh" +#include "BLI_function_ref.hh" + +/** + * Contains information about an attribute in a geometry component. + * More information can be added in the future. E.g. whether the attribute is builtin and how it is + * stored (uv map, vertex group, ...). + */ +struct AttributeMetaData { + AttributeDomain domain; + CustomDataType data_type; + + constexpr friend bool operator==(AttributeMetaData a, AttributeMetaData b) + { + return (a.domain == b.domain) && (a.data_type == b.data_type); + } +}; + +/** + * Base class for the attribute initializer types described below. + */ +struct AttributeInit { + enum class Type { + Default, + VArray, + MoveArray, + }; + Type type; + AttributeInit(const Type type) : type(type) + { + } +}; + +/** + * Create an attribute using the default value for the data type. + * The default values may depend on the attribute provider implementation. + */ +struct AttributeInitDefault : public AttributeInit { + AttributeInitDefault() : AttributeInit(Type::Default) + { + } +}; + +/** + * Create an attribute by copying data from an existing virtual array. The virtual array + * must have the same type as the newly created attribute. + * + * Note that this can be used to fill the new attribute with the default + */ +struct AttributeInitVArray : public AttributeInit { + const blender::fn::GVArray *varray; + + AttributeInitVArray(const blender::fn::GVArray *varray) + : AttributeInit(Type::VArray), varray(varray) + { + } +}; + +/** + * Create an attribute with a by passing ownership of a pre-allocated contiguous array of data. + * Sometimes data is created before a geometry component is available. In that case, it's + * preferable to move data directly to the created attribute to avoid a new allocation and a copy. + * + * Note that this will only have a benefit for attributes that are stored directly as contiguous + * arrays, so not for some built-in attributes. + * + * The array must be allocated with MEM_*, since `attribute_try_create` will free the array if it + * can't be used directly, and that is generally how Blender expects custom data to be allocated. + */ +struct AttributeInitMove : public AttributeInit { + void *data = nullptr; + + AttributeInitMove(void *data) : AttributeInit(Type::MoveArray), data(data) + { + } +}; + +/* Returns false when the iteration should be stopped. */ +using AttributeForeachCallback = blender::FunctionRef<bool(blender::StringRefNull attribute_name, + const AttributeMetaData &meta_data)>; namespace blender::bke { @@ -94,6 +173,7 @@ class OutputAttribute { SaveFn save_; std::optional<fn::GVMutableArray_GSpan> optional_span_varray_; bool ignore_old_values_ = false; + bool save_has_been_called_ = false; public: OutputAttribute() = default; @@ -109,6 +189,10 @@ class OutputAttribute { { } + OutputAttribute(OutputAttribute &&other) = default; + + ~OutputAttribute(); + operator bool() const { return varray_.get() != nullptr; @@ -226,4 +310,36 @@ template<typename T> class OutputAttribute_Typed { } }; +/** + * A basic container around DNA CustomData so that its users + * don't have to implement special copy and move constructors. + */ +class CustomDataAttributes { + /** + * #CustomData needs a size to be freed, and unfortunately it isn't stored in the struct + * itself, so keep track of the size here so this class can implement its own destructor. + * If the implementation of the attribute storage changes, this could be removed. + */ + int size_; + + public: + CustomData data; + + CustomDataAttributes(); + ~CustomDataAttributes(); + CustomDataAttributes(const CustomDataAttributes &other); + CustomDataAttributes(CustomDataAttributes &&other); + + void reallocate(const int size); + + std::optional<blender::fn::GSpan> get_for_read(const blender::StringRef name) const; + std::optional<blender::fn::GMutableSpan> get_for_write(const blender::StringRef name); + bool create(const blender::StringRef name, const CustomDataType data_type); + bool create_by_move(const blender::StringRef name, const CustomDataType data_type, void *buffer); + bool remove(const blender::StringRef name); + + bool foreach_attribute(const AttributeForeachCallback callback, + const AttributeDomain domain) const; +}; + } // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_attribute_math.hh b/source/blender/blenkernel/BKE_attribute_math.hh index 65ac5b5bfa8..ba683362e69 100644 --- a/source/blender/blenkernel/BKE_attribute_math.hh +++ b/source/blender/blenkernel/BKE_attribute_math.hh @@ -14,6 +14,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#pragma once + #include "BLI_array.hh" #include "BLI_color.hh" #include "BLI_float2.hh" @@ -50,7 +52,7 @@ inline void convert_to_static_type(const CustomDataType data_type, const Func &f func(bool()); break; case CD_PROP_COLOR: - func(Color4f()); + func(ColorGeometry4f()); break; default: BLI_assert_unreachable(); @@ -76,8 +78,8 @@ inline void convert_to_static_type(const fn::CPPType &cpp_type, const Func &func else if (cpp_type.is<bool>()) { func(bool()); } - else if (cpp_type.is<Color4f>()) { - func(Color4f()); + else if (cpp_type.is<ColorGeometry4f>()) { + func(ColorGeometry4f()); } else { BLI_assert_unreachable(); @@ -121,9 +123,12 @@ inline float3 mix3(const float3 &weights, const float3 &v0, const float3 &v1, co } template<> -inline Color4f mix3(const float3 &weights, const Color4f &v0, const Color4f &v1, const Color4f &v2) +inline ColorGeometry4f mix3(const float3 &weights, + const ColorGeometry4f &v0, + const ColorGeometry4f &v1, + const ColorGeometry4f &v2) { - Color4f result; + ColorGeometry4f result; interp_v4_v4v4v4(result, v0, v1, v2, weights); return result; } @@ -131,6 +136,49 @@ inline Color4f mix3(const float3 &weights, const Color4f &v0, const Color4f &v1, /** \} */ /* -------------------------------------------------------------------- */ +/** \name Mix two values of the same type. + * + * This is just basic linear interpolation. + * \{ */ + +template<typename T> T mix2(const float factor, const T &a, const T &b); + +template<> inline bool mix2(const float factor, const bool &a, const bool &b) +{ + return ((1.0f - factor) * a + factor * b) >= 0.5f; +} + +template<> inline int mix2(const float factor, const int &a, const int &b) +{ + return static_cast<int>((1.0f - factor) * a + factor * b); +} + +template<> inline float mix2(const float factor, const float &a, const float &b) +{ + return (1.0f - factor) * a + factor * b; +} + +template<> inline float2 mix2(const float factor, const float2 &a, const float2 &b) +{ + return float2::interpolate(a, b, factor); +} + +template<> inline float3 mix2(const float factor, const float3 &a, const float3 &b) +{ + return float3::interpolate(a, b, factor); +} + +template<> +inline ColorGeometry4f mix2(const float factor, const ColorGeometry4f &a, const ColorGeometry4f &b) +{ + ColorGeometry4f result; + interp_v4_v4v4(result, a, b, factor); + return result; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Mix a dynamic amount of values with weights for many elements. * * This section provides an abstraction for "mixers". The abstraction encapsulates details about @@ -183,8 +231,10 @@ template<typename T> class SimpleMixer { } }; -/** This mixer accumulates values in a type that is different from the one that is mixed. Some - * types cannot encode the floating point weights in their values (e.g. int and bool). */ +/** + * This mixer accumulates values in a type that is different from the one that is mixed. + * Some types cannot encode the floating point weights in their values (e.g. int and bool). + */ template<typename T, typename AccumulationT, T (*ConvertToT)(const AccumulationT &value)> class SimpleMixerWithAccumulationType { private: @@ -228,15 +278,16 @@ class SimpleMixerWithAccumulationType { } }; -class Color4fMixer { +class ColorGeometryMixer { private: - MutableSpan<Color4f> buffer_; - Color4f default_color_; + MutableSpan<ColorGeometry4f> buffer_; + ColorGeometry4f default_color_; Array<float> total_weights_; public: - Color4fMixer(MutableSpan<Color4f> buffer, Color4f default_color = {0, 0, 0, 1}); - void mix_in(const int64_t index, const Color4f &color, const float weight = 1.0f); + ColorGeometryMixer(MutableSpan<ColorGeometry4f> buffer, + ColorGeometry4f default_color = ColorGeometry4f(0.0f, 0.0f, 0.0f, 1.0f)); + void mix_in(const int64_t index, const ColorGeometry4f &color, const float weight = 1.0f); void finalize(); }; @@ -253,10 +304,10 @@ template<> struct DefaultMixerStruct<float2> { template<> struct DefaultMixerStruct<float3> { using type = SimpleMixer<float3>; }; -template<> struct DefaultMixerStruct<Color4f> { - /* Use a special mixer for colors. Color4f can't be added/multiplied, because this is not +template<> struct DefaultMixerStruct<ColorGeometry4f> { + /* Use a special mixer for colors. ColorGeometry4f can't be added/multiplied, because this is not * something one should usually do with colors. */ - using type = Color4fMixer; + using type = ColorGeometryMixer; }; template<> struct DefaultMixerStruct<int> { static int double_to_int(const double &value) diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 61489aa7494..0bab980cfcd 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -39,7 +39,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 0 +#define BLENDER_FILE_SUBVERSION 1 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and show a warning if the file diff --git a/source/blender/blenkernel/BKE_callbacks.h b/source/blender/blenkernel/BKE_callbacks.h index fadba5644de..f04b5e45720 100644 --- a/source/blender/blenkernel/BKE_callbacks.h +++ b/source/blender/blenkernel/BKE_callbacks.h @@ -59,6 +59,7 @@ typedef enum { BKE_CB_EVT_VERSION_UPDATE, BKE_CB_EVT_LOAD_FACTORY_USERDEF_POST, BKE_CB_EVT_LOAD_FACTORY_STARTUP_POST, + BKE_CB_EVT_XR_SESSION_START_PRE, BKE_CB_EVT_TOT, } eCbEvent; diff --git a/source/blender/blenkernel/BKE_collection.h b/source/blender/blenkernel/BKE_collection.h index 3412be92a3a..7963d54126e 100644 --- a/source/blender/blenkernel/BKE_collection.h +++ b/source/blender/blenkernel/BKE_collection.h @@ -112,7 +112,9 @@ bool BKE_scene_collections_object_remove(struct Main *bmain, struct Object *object, const bool free_us); void BKE_collections_object_remove_nulls(struct Main *bmain); -void BKE_collections_child_remove_nulls(struct Main *bmain, struct Collection *old_collection); +void BKE_collections_child_remove_nulls(struct Main *bmain, + struct Collection *parent_collection, + struct Collection *child_collection); /* Dependencies. */ diff --git a/source/blender/blenkernel/BKE_geometry_set.h b/source/blender/blenkernel/BKE_geometry_set.h index c00310408af..5f6a9ec7b91 100644 --- a/source/blender/blenkernel/BKE_geometry_set.h +++ b/source/blender/blenkernel/BKE_geometry_set.h @@ -36,25 +36,13 @@ 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); bool BKE_geometry_set_has_instances(const struct GeometrySet *geometry_set); -typedef enum InstancedDataType { - INSTANCE_DATA_TYPE_OBJECT = 0, - INSTANCE_DATA_TYPE_COLLECTION = 1, -} InstancedDataType; - -typedef struct InstancedData { - InstancedDataType type; - union { - struct Object *object; - struct Collection *collection; - } data; -} InstancedData; - #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index 027338a5d5c..3b3856f11ab 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -25,11 +25,11 @@ #include "BLI_float3.hh" #include "BLI_float4x4.hh" -#include "BLI_function_ref.hh" #include "BLI_hash.hh" #include "BLI_map.hh" #include "BLI_set.hh" #include "BLI_user_counter.hh" +#include "BLI_vector_set.hh" #include "BKE_attribute_access.hh" #include "BKE_geometry_set.h" @@ -39,6 +39,7 @@ struct Mesh; struct Object; struct PointCloud; struct Volume; +class CurveEval; enum class GeometryOwnershipType { /* The geometry is owned. This implies that it can be changed. */ @@ -56,79 +57,6 @@ class ComponentAttributeProviders; class GeometryComponent; /** - * Contains information about an attribute in a geometry component. - * More information can be added in the future. E.g. whether the attribute is builtin and how it is - * stored (uv map, vertex group, ...). - */ -struct AttributeMetaData { - AttributeDomain domain; - CustomDataType data_type; -}; - -/* Returns false when the iteration should be stopped. */ -using AttributeForeachCallback = blender::FunctionRef<bool(blender::StringRefNull attribute_name, - const AttributeMetaData &meta_data)>; - -/** - * Base class for the attribute intializer types described below. - */ -struct AttributeInit { - enum class Type { - Default, - VArray, - MoveArray, - }; - Type type; - AttributeInit(const Type type) : type(type) - { - } -}; - -/** - * Create an attribute using the default value for the data type. - * The default values may depend on the attribute provider implementation. - */ -struct AttributeInitDefault : public AttributeInit { - AttributeInitDefault() : AttributeInit(Type::Default) - { - } -}; - -/** - * Create an attribute by copying data from an existing virtual array. The virtual array - * must have the same type as the newly created attribute. - * - * Note that this can be used to fill the new attribute with the default - */ -struct AttributeInitVArray : public AttributeInit { - const blender::fn::GVArray *varray; - - AttributeInitVArray(const blender::fn::GVArray *varray) - : AttributeInit(Type::VArray), varray(varray) - { - } -}; - -/** - * Create an attribute with a by passing ownership of a pre-allocated contiguous array of data. - * Sometimes data is created before a geometry component is available. In that case, it's - * preferable to move data directly to the created attribute to avoid a new allocation and a copy. - * - * Note that this will only have a benefit for attributes that are stored directly as contigious - * arrays, so not for some built-in attributes. - * - * The array must be allocated with MEM_*, since `attribute_try_create` will free the array if it - * can't be used directly, and that is generally how Blender expects custom data to be allocated. - */ -struct AttributeInitMove : public AttributeInit { - void *data = nullptr; - - AttributeInitMove(void *data) : AttributeInit(Type::MoveArray), data(data) - { - } -}; - -/** * This is the base class for specialized geometry component types. */ class GeometryComponent { @@ -363,23 +291,34 @@ struct GeometrySet { Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); static GeometrySet create_with_pointcloud( PointCloud *pointcloud, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + static GeometrySet create_with_curve( + CurveEval *curve, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); /* Utility methods for access. */ bool has_mesh() const; bool has_pointcloud() const; bool has_instances() const; bool has_volume() const; + bool has_curve() const; + const Mesh *get_mesh_for_read() const; const PointCloud *get_pointcloud_for_read() const; const Volume *get_volume_for_read() const; + const CurveEval *get_curve_for_read() const; + Mesh *get_mesh_for_write(); PointCloud *get_pointcloud_for_write(); Volume *get_volume_for_write(); + CurveEval *get_curve_for_write(); /* Utility methods for replacement. */ void replace_mesh(Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); void replace_pointcloud(PointCloud *pointcloud, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + void replace_volume(Volume *volume, + GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + void replace_curve(CurveEval *curve, + GeometryOwnershipType ownership = GeometryOwnershipType::Owned); }; /** A geometry component that can store a mesh. */ @@ -461,12 +400,113 @@ class PointCloudComponent : public GeometryComponent { const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final; }; +/** A geometry component that stores curve data, in other words, a group of splines. */ +class CurveComponent : public GeometryComponent { + private: + CurveEval *curve_ = nullptr; + GeometryOwnershipType ownership_ = GeometryOwnershipType::Owned; + + public: + CurveComponent(); + ~CurveComponent(); + GeometryComponent *copy() const override; + + void clear(); + bool has_curve() const; + void replace(CurveEval *curve, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + CurveEval *release(); + + const CurveEval *get_for_read() const; + CurveEval *get_for_write(); + + int attribute_domain_size(const AttributeDomain domain) const final; + + bool is_empty() const final; + + bool owns_direct_data() const override; + void ensure_owns_direct_data() override; + + static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_CURVE; + + private: + const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final; +}; + +class InstanceReference { + public: + enum class Type { + /** + * An empty instance. This allows an `InstanceReference` to be default constructed without + * being in an invalid state. There might also be other use cases that we haven't explored much + * yet (such as changing the instance later on, and "disabling" some instances). + */ + None, + Object, + Collection, + }; + + private: + Type type_ = Type::None; + /** Depending on the type this is either null, an Object or Collection pointer. */ + void *data_ = nullptr; + + public: + InstanceReference() = default; + + InstanceReference(Object &object) : type_(Type::Object), data_(&object) + { + } + + InstanceReference(Collection &collection) : type_(Type::Collection), data_(&collection) + { + } + + Type type() const + { + return type_; + } + + Object &object() const + { + BLI_assert(type_ == Type::Object); + return *(Object *)data_; + } + + Collection &collection() const + { + BLI_assert(type_ == Type::Collection); + return *(Collection *)data_; + } + + uint64_t hash() const + { + return blender::get_default_hash(data_); + } + + friend bool operator==(const InstanceReference &a, const InstanceReference &b) + { + return a.data_ == b.data_; + } +}; + /** A geometry component that stores instances. */ class InstancesComponent : public GeometryComponent { private: - blender::Vector<blender::float4x4> transforms_; - blender::Vector<int> ids_; - blender::Vector<InstancedData> instanced_data_; + /** + * Indexed set containing information about the data that is instanced. + * Actual instances store an index ("handle") into this set. + */ + blender::VectorSet<InstanceReference> references_; + + /** Index into `references_`. Determines what data is instanced. */ + blender::Vector<int> instance_reference_handles_; + /** Transformation of the instances. */ + blender::Vector<blender::float4x4> instance_transforms_; + /** + * IDs of the instances. They are used for consistency over multiple frames for things like + * motion blur. + */ + blender::Vector<int> instance_ids_; /* These almost unique ids are generated based on `ids_`, which might not contain unique ids at * all. They are *almost* unique, because under certain very unlikely circumstances, they are not @@ -481,14 +521,22 @@ class InstancesComponent : public GeometryComponent { GeometryComponent *copy() const override; void clear(); - void add_instance(Object *object, blender::float4x4 transform, const int id = -1); - void add_instance(Collection *collection, blender::float4x4 transform, const int id = -1); - void add_instance(InstancedData data, blender::float4x4 transform, const int id = -1); - - blender::Span<InstancedData> instanced_data() const; - blender::Span<blender::float4x4> transforms() const; - blender::Span<int> ids() const; - blender::MutableSpan<blender::float4x4> transforms(); + + void reserve(int min_capacity); + void resize(int capacity); + + int add_reference(InstanceReference reference); + void add_instance(int instance_handle, const blender::float4x4 &transform, const int id = -1); + + blender::Span<InstanceReference> references() const; + + blender::Span<int> instance_reference_handles() const; + blender::MutableSpan<int> instance_reference_handles(); + blender::MutableSpan<blender::float4x4> instance_transforms(); + blender::Span<blender::float4x4> instance_transforms() const; + blender::MutableSpan<int> instance_ids(); + blender::Span<int> instance_ids() const; + int instances_amount() const; blender::Span<int> almost_unique_ids() const; diff --git a/source/blender/blenkernel/BKE_gpencil.h b/source/blender/blenkernel/BKE_gpencil.h index 4b4886e8bf3..bb145580928 100644 --- a/source/blender/blenkernel/BKE_gpencil.h +++ b/source/blender/blenkernel/BKE_gpencil.h @@ -108,7 +108,10 @@ void BKE_gpencil_stroke_select_index_reset(struct bGPDstroke *gps); struct bGPDframe *BKE_gpencil_frame_addnew(struct bGPDlayer *gpl, int cframe); struct bGPDframe *BKE_gpencil_frame_addcopy(struct bGPDlayer *gpl, int cframe); -struct bGPDlayer *BKE_gpencil_layer_addnew(struct bGPdata *gpd, const char *name, bool setactive); +struct bGPDlayer *BKE_gpencil_layer_addnew(struct bGPdata *gpd, + const char *name, + const bool setactive, + const bool add_to_header); struct bGPdata *BKE_gpencil_data_addnew(struct Main *bmain, const char name[]); struct bGPDframe *BKE_gpencil_frame_duplicate(const struct bGPDframe *gpf_src, diff --git a/source/blender/blenkernel/BKE_gpencil_geom.h b/source/blender/blenkernel/BKE_gpencil_geom.h index a9bd0a524c4..8fc3ce133a0 100644 --- a/source/blender/blenkernel/BKE_gpencil_geom.h +++ b/source/blender/blenkernel/BKE_gpencil_geom.h @@ -111,7 +111,10 @@ void BKE_gpencil_dissolve_points(struct bGPdata *gpd, struct bGPDstroke *gps, const short tag); -bool BKE_gpencil_stroke_stretch(struct bGPDstroke *gps, const float dist, const float tip_length); +bool BKE_gpencil_stroke_stretch(struct bGPDstroke *gps, + const float dist, + const float overshoot_fac, + const short mode); bool BKE_gpencil_stroke_trim_points(struct bGPDstroke *gps, const int index_from, const int index_to); @@ -135,7 +138,7 @@ bool BKE_gpencil_stroke_split(struct bGPdata *gpd, struct bGPDstroke *gps, const int before_index, struct bGPDstroke **remaining_gps); -bool BKE_gpencil_stroke_shrink(struct bGPDstroke *gps, const float dist); +bool BKE_gpencil_stroke_shrink(struct bGPDstroke *gps, const float dist, const short mode); float BKE_gpencil_stroke_length(const struct bGPDstroke *gps, bool use_3d); float BKE_gpencil_stroke_segment_length(const struct bGPDstroke *gps, diff --git a/source/blender/blenkernel/BKE_image.h b/source/blender/blenkernel/BKE_image.h index c51a5f7e5e1..d298e5dcf6d 100644 --- a/source/blender/blenkernel/BKE_image.h +++ b/source/blender/blenkernel/BKE_image.h @@ -325,6 +325,7 @@ int BKE_image_get_tile_from_pos(struct Image *ima, const float uv[2], float r_uv[2], float r_ofs[2]); +int BKE_image_find_nearest_tile(const struct Image *image, const float co[2]); void BKE_image_get_size(struct Image *image, struct ImageUser *iuser, int *r_width, int *r_height); void BKE_image_get_size_fl(struct Image *image, struct ImageUser *iuser, float r_size[2]); diff --git a/source/blender/blenkernel/BKE_lib_id.h b/source/blender/blenkernel/BKE_lib_id.h index adac92105ee..6b706f3bcd0 100644 --- a/source/blender/blenkernel/BKE_lib_id.h +++ b/source/blender/blenkernel/BKE_lib_id.h @@ -104,6 +104,10 @@ enum { * specific code in some copy cases (mostly for node trees). */ LIB_ID_CREATE_LOCAL = 1 << 9, + /** Create for the depsgraph, when set #LIB_TAG_COPIED_ON_WRITE must be set. + * Internally this is used to share some pointers instead of duplicating them. */ + LIB_ID_COPY_SET_COPIED_ON_WRITE = 1 << 10, + /* *** Specific options to some ID types or usages. *** */ /* *** May be ignored by unrelated ID copying functions. *** */ /** Object only, needed by make_local code. */ diff --git a/source/blender/blenkernel/BKE_lib_override.h b/source/blender/blenkernel/BKE_lib_override.h index b9a478f8227..f1ed5a453ba 100644 --- a/source/blender/blenkernel/BKE_lib_override.h +++ b/source/blender/blenkernel/BKE_lib_override.h @@ -85,10 +85,12 @@ bool BKE_lib_override_library_resync(struct Main *bmain, struct ID *id_root, struct Collection *override_resync_residual_storage, const bool do_hierarchy_enforce, - const bool do_post_process); + const bool do_post_process, + struct ReportList *reports); void BKE_lib_override_library_main_resync(struct Main *bmain, struct Scene *scene, - struct ViewLayer *view_layer); + struct ViewLayer *view_layer, + struct ReportList *reports); void BKE_lib_override_library_delete(struct Main *bmain, struct ID *id_root); diff --git a/source/blender/blenkernel/BKE_lib_query.h b/source/blender/blenkernel/BKE_lib_query.h index 4e781aea9d3..9c49514e7b8 100644 --- a/source/blender/blenkernel/BKE_lib_query.h +++ b/source/blender/blenkernel/BKE_lib_query.h @@ -70,12 +70,15 @@ enum { /** That ID is used as library override's reference by its owner. */ IDWALK_CB_OVERRIDE_LIBRARY_REFERENCE = (1 << 5), + /** That ID pointer is not overridable. */ + IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE = (1 << 6), + /** * Indicates that this is an internal runtime ID pointer, like e.g. `ID.newid` or `ID.original`. * \note Those should be ignored in most cases, and won't be processed/generated anyway unless * `IDWALK_DO_INTERNAL_RUNTIME_POINTERS` option is enabled. */ - IDWALK_CB_INTERNAL = (1 << 6), + IDWALK_CB_INTERNAL = (1 << 7), /** * This ID usage is fully refcounted. diff --git a/source/blender/blenkernel/BKE_material.h b/source/blender/blenkernel/BKE_material.h index 14ea50f808a..dc471fcb62f 100644 --- a/source/blender/blenkernel/BKE_material.h +++ b/source/blender/blenkernel/BKE_material.h @@ -51,6 +51,9 @@ void BKE_object_material_remap(struct Object *ob, const unsigned int *remap); void BKE_object_material_remap_calc(struct Object *ob_dst, struct Object *ob_src, short *remap_src_to_dst); +void BKE_object_material_from_eval_data(struct Main *bmain, + struct Object *ob_orig, + struct ID *data_eval); struct Material *BKE_material_add(struct Main *bmain, const char *name); struct Material *BKE_gpencil_material_add(struct Main *bmain, const char *name); void BKE_gpencil_material_attr_init(struct Material *ma); @@ -105,6 +108,12 @@ struct Material *BKE_id_material_pop(struct Main *bmain, /* index is an int because of RNA. */ int index); void BKE_id_material_clear(struct Main *bmain, struct ID *id); + +/* eval api */ +struct Material *BKE_object_material_get_eval(struct Object *ob, short act); +int BKE_object_material_count_eval(struct Object *ob); +void BKE_id_material_eval_assign(struct ID *id, int slot, struct Material *material); + /* rendering */ void ramp_blend(int type, float r_col[3], const float fac, const float col[3]); diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h index e39caac7c36..62837c4f2a7 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -215,7 +215,8 @@ void BKE_mesh_split_faces(struct Mesh *mesh, bool free_loop_normals); * ignored otherwise. */ struct Mesh *BKE_mesh_new_from_object(struct Depsgraph *depsgraph, struct Object *object, - bool preserve_all_data_layers); + const bool preserve_all_data_layers, + const bool preserve_origindex); /* This is a version of BKE_mesh_new_from_object() which stores mesh in the given main database. * However, that function enforces object type to be a geometry one, and ensures a mesh is always @@ -577,9 +578,9 @@ void BKE_mesh_polygons_flip(struct MPoly *mpoly, struct CustomData *ldata, int totpoly); -/* merge verts */ -/* Enum for merge_mode of CDDM_merge_verts. - * Refer to mesh.c for details. */ +/* Merge verts. */ +/* Enum for merge_mode of #BKE_mesh_merge_verts. + * Refer to mesh_merge.c for details. */ enum { MESH_MERGE_VERTS_DUMP_IF_MAPPED, MESH_MERGE_VERTS_DUMP_IF_EQUAL, diff --git a/source/blender/blenkernel/BKE_modifier.h b/source/blender/blenkernel/BKE_modifier.h index ab2ecbe2507..48b4540e3d9 100644 --- a/source/blender/blenkernel/BKE_modifier.h +++ b/source/blender/blenkernel/BKE_modifier.h @@ -258,10 +258,6 @@ typedef struct ModifierTypeInfo { const struct ModifierEvalContext *ctx, struct GeometrySet *geometry_set); - struct Volume *(*modifyVolume)(struct ModifierData *md, - const struct ModifierEvalContext *ctx, - struct Volume *volume); - /********************* Optional functions *********************/ /** @@ -450,8 +446,8 @@ bool BKE_modifier_is_preview(struct ModifierData *md); void BKE_modifiers_foreach_ID_link(struct Object *ob, IDWalkFunc walk, void *userData); void BKE_modifiers_foreach_tex_link(struct Object *ob, TexWalkFunc walk, void *userData); -struct ModifierData *BKE_modifiers_findby_type(struct Object *ob, ModifierType type); -struct ModifierData *BKE_modifiers_findby_name(struct Object *ob, const char *name); +struct ModifierData *BKE_modifiers_findby_type(const struct Object *ob, ModifierType type); +struct ModifierData *BKE_modifiers_findby_name(const struct Object *ob, const char *name); void BKE_modifiers_clear_errors(struct Object *ob); int BKE_modifiers_get_cage_index(const struct Scene *scene, struct Object *ob, diff --git a/source/blender/blenkernel/BKE_nla.h b/source/blender/blenkernel/BKE_nla.h index 16d48024d07..af238fda659 100644 --- a/source/blender/blenkernel/BKE_nla.h +++ b/source/blender/blenkernel/BKE_nla.h @@ -58,7 +58,14 @@ struct NlaTrack *BKE_nlatrack_copy(struct Main *bmain, struct NlaTrack *nlt, const bool use_same_actions, const int flag); -void BKE_nla_tracks_copy(struct Main *bmain, ListBase *dst, ListBase *src, const int flag); +void BKE_nla_tracks_copy(struct Main *bmain, ListBase *dst, const ListBase *src, const int flag); + +/* Copy NLA tracks from #adt_source to #adt_dest, and update the active track/strip pointers to + * point at those copies. */ +void BKE_nla_tracks_copy_from_adt(struct Main *bmain, + struct AnimData *adt_dest, + const struct AnimData *adt_source, + int flag); struct NlaTrack *BKE_nlatrack_add(struct AnimData *adt, struct NlaTrack *prev, diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 2a33c4819e3..448f4ae48ad 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -325,6 +325,7 @@ typedef struct bNodeType { /* Execute a geometry node. */ NodeGeometryExecFunction geometry_node_execute; + bool geometry_node_execute_supports_laziness; /* RNA integration */ ExtensionRNA rna_ext; @@ -410,6 +411,9 @@ typedef struct bNodeTreeType { void (*node_add_init)(struct bNodeTree *ntree, struct bNode *bnode); + /* Check if the socket type is valid for this tree type. */ + bool (*valid_socket_type)(enum eNodeSocketDatatype socket_type, struct bNodeTreeType *ntreetype); + /* RNA integration */ ExtensionRNA rna_ext; } bNodeTreeType; @@ -1414,6 +1418,14 @@ 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_ATTRIBUTE_TRANSFER 1044 +#define GEO_NODE_CURVE_TO_MESH 1045 +#define GEO_NODE_ATTRIBUTE_CURVE_MAP 1046 +#define GEO_NODE_CURVE_RESAMPLE 1047 +#define GEO_NODE_ATTRIBUTE_VECTOR_ROTATE 1048 +#define GEO_NODE_MATERIAL_ASSIGN 1049 +#define GEO_NODE_INPUT_MATERIAL 1050 +#define GEO_NODE_MATERIAL_REPLACE 1051 /** \} */ diff --git a/source/blender/blenkernel/BKE_node_ui_storage.hh b/source/blender/blenkernel/BKE_node_ui_storage.hh index 5f9c039ef9e..8bf89cd8f58 100644 --- a/source/blender/blenkernel/BKE_node_ui_storage.hh +++ b/source/blender/blenkernel/BKE_node_ui_storage.hh @@ -95,8 +95,16 @@ struct AvailableAttributeInfo { }; struct NodeUIStorage { + std::mutex mutex; blender::Vector<NodeWarning> warnings; blender::Set<AvailableAttributeInfo> attribute_hints; + + NodeUIStorage() = default; + /* Needed because the mutex can't be moved or copied. */ + NodeUIStorage(NodeUIStorage &&other) + : warnings(std::move(other.warnings)), attribute_hints(std::move(other.attribute_hints)) + { + } }; struct NodeTreeUIStorage { diff --git a/source/blender/blenkernel/BKE_object.h b/source/blender/blenkernel/BKE_object.h index 9fe286df36d..f3a5c794de8 100644 --- a/source/blender/blenkernel/BKE_object.h +++ b/source/blender/blenkernel/BKE_object.h @@ -374,6 +374,7 @@ struct MovieClip *BKE_object_movieclip_get(struct Scene *scene, void BKE_object_runtime_reset(struct Object *object); void BKE_object_runtime_reset_on_copy(struct Object *object, const int flag); +void BKE_object_runtime_free_data(struct Object *object); void BKE_object_batch_cache_dirty_tag(struct Object *ob); diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h index 228b52123f3..73413b61456 100644 --- a/source/blender/blenkernel/BKE_paint.h +++ b/source/blender/blenkernel/BKE_paint.h @@ -486,7 +486,11 @@ typedef struct SculptSession { /* Total number of polys of the base mesh. */ int totfaces; /* Face sets store its visibility in the sign of the integer, using the absolute value as the - * Face Set ID. Positive IDs are visible, negative IDs are hidden. */ + * Face Set ID. Positive IDs are visible, negative IDs are hidden. + * The 0 ID is not used by the tools or the visibility system, it is just used when creating new + * geometry (the trim tool, for example) to detect which geometry was just added, so it can be + * assigned a valid Face Set after creation. Tools are not intended to run with Face Sets IDs set + * to 0. */ int *face_sets; /* BMesh for dynamic topology sculpting */ diff --git a/source/blender/blenkernel/BKE_persistent_data_handle.hh b/source/blender/blenkernel/BKE_persistent_data_handle.hh deleted file mode 100644 index bbee09c7bf4..00000000000 --- a/source/blender/blenkernel/BKE_persistent_data_handle.hh +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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 - * - * A PersistentDataHandle is a weak reference to some data in a Blender file. The handle itself is - * just a number. A PersistentDataHandleMap is used to convert between handles and the actual data. - */ - -#include "BLI_map.hh" - -#include "DNA_ID.h" - -struct Collection; -struct Object; - -namespace blender::bke { - -class PersistentDataHandleMap; - -class PersistentDataHandle { - private: - /* Negative values indicate that the handle is "empty". */ - int32_t handle_; - - friend PersistentDataHandleMap; - - protected: - PersistentDataHandle(int handle) : handle_(handle) - { - } - - public: - PersistentDataHandle() : handle_(-1) - { - } - - friend bool operator==(const PersistentDataHandle &a, const PersistentDataHandle &b) - { - return a.handle_ == b.handle_; - } - - friend bool operator!=(const PersistentDataHandle &a, const PersistentDataHandle &b) - { - return !(a == b); - } - - friend std::ostream &operator<<(std::ostream &stream, const PersistentDataHandle &a) - { - stream << a.handle_; - return stream; - } - - uint64_t hash() const - { - return static_cast<uint64_t>(handle_); - } -}; - -class PersistentIDHandle : public PersistentDataHandle { - friend PersistentDataHandleMap; - using PersistentDataHandle::PersistentDataHandle; -}; - -class PersistentObjectHandle : public PersistentIDHandle { - friend PersistentDataHandleMap; - using PersistentIDHandle::PersistentIDHandle; -}; - -class PersistentCollectionHandle : public PersistentIDHandle { - friend PersistentDataHandleMap; - using PersistentIDHandle::PersistentIDHandle; -}; - -class PersistentDataHandleMap { - private: - Map<int32_t, ID *> id_by_handle_; - Map<ID *, int32_t> handle_by_id_; - - public: - void add(int32_t handle, ID &id) - { - BLI_assert(handle >= 0); - handle_by_id_.add(&id, handle); - id_by_handle_.add(handle, &id); - } - - PersistentIDHandle lookup(ID *id) const - { - const int handle = handle_by_id_.lookup_default(id, -1); - return PersistentIDHandle(handle); - } - - PersistentObjectHandle lookup(Object *object) const - { - const int handle = handle_by_id_.lookup_default((ID *)object, -1); - return PersistentObjectHandle(handle); - } - - PersistentCollectionHandle lookup(Collection *collection) const - { - const int handle = handle_by_id_.lookup_default((ID *)collection, -1); - return PersistentCollectionHandle(handle); - } - - ID *lookup(const PersistentIDHandle &handle) const - { - ID *id = id_by_handle_.lookup_default(handle.handle_, nullptr); - return id; - } - - Object *lookup(const PersistentObjectHandle &handle) const - { - ID *id = this->lookup((const PersistentIDHandle &)handle); - if (id == nullptr) { - return nullptr; - } - if (GS(id->name) != ID_OB) { - return nullptr; - } - return (Object *)id; - } - - Collection *lookup(const PersistentCollectionHandle &handle) const - { - ID *id = this->lookup((const PersistentIDHandle &)handle); - if (id == nullptr) { - return nullptr; - } - if (GS(id->name) != ID_GR) { - return nullptr; - } - return (Collection *)id; - } -}; - -} // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_softbody.h b/source/blender/blenkernel/BKE_softbody.h index 4f8b21141b6..58dc90f62dc 100644 --- a/source/blender/blenkernel/BKE_softbody.h +++ b/source/blender/blenkernel/BKE_softbody.h @@ -47,7 +47,7 @@ typedef struct BodyPoint { } BodyPoint; /* allocates and initializes general main data */ -extern struct SoftBody *sbNew(struct Scene *scene); +extern struct SoftBody *sbNew(void); /* frees internal data and soft-body itself */ extern void sbFree(struct Object *ob); diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh new file mode 100644 index 00000000000..9e5552082af --- /dev/null +++ b/source/blender/blenkernel/BKE_spline.hh @@ -0,0 +1,517 @@ +/* + * 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_access.hh" +#include "BKE_attribute_math.hh" + +struct Curve; + +class Spline; +using SplinePtr = std::unique_ptr<Spline>; + +/** + * A spline is an abstraction of a single branch-less curve section, its evaluation methods, + * and data. The spline data itself is just control points and a set of attributes by the set + * of "evaluated" data is often used instead. + * + * Any derived class of Spline has to manage two things: + * 1. Interpolating arbitrary attribute data from the control points to evaluated points. + * 2. Evaluating the positions based on the stored control point data. + * + * Beyond that, everything is the base class's responsibility, with minor exceptions. Further + * evaluation happens in a layer on top of the evaluated points generated by the derived types. + * + * There are a few methods to evaluate a spline: + * 1. #evaluated_positions and #interpolate_to_evaluated_points give data for the initial + * evaluated points, depending on the resolution. + * 2. #lookup_evaluated_factor and #lookup_evaluated_factor are meant for one-off lookups + * along the length of a curve. + * 3. #sample_uniform_index_factors returns an array that stores uniform-length samples + * along the spline which can be used to interpolate data from method 1. + * + * Commonly used evaluated data is stored in caches on the spline itself so that operations on + * splines don't need to worry about taking ownership of evaluated data when they don't need to. + */ +class Spline { + public: + enum class Type { + Bezier, + NURBS, + Poly, + }; + + enum NormalCalculationMode { + ZUp, + Minimum, + Tangent, + }; + /* Only #Zup is supported at the moment. */ + NormalCalculationMode normal_mode; + + blender::bke::CustomDataAttributes attributes; + + protected: + Type type_; + bool is_cyclic_ = false; + + /** Direction of the spline at each evaluated point. */ + mutable blender::Vector<blender::float3> evaluated_tangents_cache_; + mutable std::mutex tangent_cache_mutex_; + mutable bool tangent_cache_dirty_ = true; + + /** Normal direction vectors for each evaluated point. */ + mutable blender::Vector<blender::float3> evaluated_normals_cache_; + mutable std::mutex normal_cache_mutex_; + mutable bool normal_cache_dirty_ = true; + + /** Accumulated lengths along the evaluated points. */ + mutable blender::Vector<float> evaluated_lengths_cache_; + mutable std::mutex length_cache_mutex_; + mutable bool length_cache_dirty_ = true; + + public: + virtual ~Spline() = default; + Spline(const Type type) : type_(type) + { + } + Spline(Spline &other) + : normal_mode(other.normal_mode), + attributes(other.attributes), + type_(other.type_), + is_cyclic_(other.is_cyclic_) + { + } + + virtual SplinePtr copy() const = 0; + + Spline::Type type() const; + + /** Return the number of control points. */ + virtual int size() const = 0; + int segments_size() const; + bool is_cyclic() const; + void set_cyclic(const bool value); + + virtual void resize(const int size) = 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; + + virtual void translate(const blender::float3 &translation); + virtual void transform(const blender::float4x4 &matrix); + + /** + * Mark all caches for re-computation. This must be called after any operation that would + * change the generated positions, tangents, normals, mapping, etc. of the evaluated points. + */ + virtual void mark_cache_invalid() = 0; + virtual int evaluated_points_size() const = 0; + int evaluated_edges_size() const; + + float length() const; + + virtual blender::Span<blender::float3> evaluated_positions() const = 0; + + blender::Span<float> evaluated_lengths() const; + blender::Span<blender::float3> evaluated_tangents() const; + blender::Span<blender::float3> evaluated_normals() const; + + void bounds_min_max(blender::float3 &min, blender::float3 &max, const bool use_evaluated) const; + + struct LookupResult { + /** + * The index of the evaluated point before the result location. In other words, the index of + * the edge that the result lies on. If the sampled factor/length is the very end of the + * spline, this will be the second to last index, if it's the very beginning, this will be 0. + */ + int evaluated_index; + /** + * The index of the evaluated point after the result location, accounting for wrapping when + * the spline is cyclic. If the sampled factor/length is the very end of the spline, this will + * be the last index (#evaluated_points_size - 1). + */ + int next_evaluated_index; + /** + * The portion of the way from the evaluated point at #evaluated_index to the next point. + * If the sampled factor/length is the very end of the spline, this will be the 1.0f + */ + float factor; + }; + LookupResult lookup_evaluated_factor(const float factor) const; + LookupResult lookup_evaluated_length(const float length) const; + + blender::Array<float> sample_uniform_index_factors(const int samples_size) const; + LookupResult lookup_data_from_index_factor(const float index_factor) const; + + /** + * Interpolate a virtual array of data with the size of the number of control points to the + * evaluated points. For poly splines, the lifetime of the returned virtual array must not + * exceed the lifetime of the input data. + */ + virtual blender::fn::GVArrayPtr interpolate_to_evaluated_points( + const blender::fn::GVArray &source_data) const = 0; + + protected: + virtual void correct_end_tangents() const = 0; +}; + +/** + * A Bézier spline is made up of a many curve segments, possibly achieving continuity of curvature + * by constraining the alignment of curve handles. Evaluation stores the positions and a map of + * factors and indices in a list of floats, which is then used to interpolate any other data. + */ +class BezierSpline final : public Spline { + public: + enum class HandleType { + /** The handle can be moved anywhere, and doesn't influence the point's other handle. */ + Free, + /** The location is automatically calculated to be smooth. */ + Auto, + /** The location is calculated to point to the next/previous control point. */ + Vector, + /** The location is constrained to point in the opposite direction as the other handle. */ + Align, + }; + + private: + blender::Vector<blender::float3> positions_; + blender::Vector<float> radii_; + blender::Vector<float> tilts_; + int resolution_; + + blender::Vector<HandleType> handle_types_left_; + blender::Vector<HandleType> handle_types_right_; + + /* These are mutable to allow lazy recalculation of #Auto and #Vector handle positions. */ + mutable blender::Vector<blender::float3> handle_positions_left_; + mutable blender::Vector<blender::float3> handle_positions_right_; + + mutable std::mutex auto_handle_mutex_; + mutable bool auto_handles_dirty_ = true; + + /** Start index in evaluated points array for every control point. */ + mutable blender::Vector<int> offset_cache_; + mutable std::mutex offset_cache_mutex_; + mutable bool offset_cache_dirty_ = true; + + /** Cache of evaluated positions. */ + mutable blender::Vector<blender::float3> evaluated_position_cache_; + mutable std::mutex position_cache_mutex_; + mutable bool position_cache_dirty_ = true; + + /** Cache of "index factors" based calculated from the evaluated positions. */ + mutable blender::Vector<float> evaluated_mapping_cache_; + mutable std::mutex mapping_cache_mutex_; + mutable bool mapping_cache_dirty_ = true; + + public: + virtual SplinePtr copy() const final; + BezierSpline() : Spline(Type::Bezier) + { + } + BezierSpline(const BezierSpline &other) + : Spline((Spline &)other), + positions_(other.positions_), + radii_(other.radii_), + tilts_(other.tilts_), + resolution_(other.resolution_), + handle_types_left_(other.handle_types_left_), + handle_types_right_(other.handle_types_right_), + handle_positions_left_(other.handle_positions_left_), + handle_positions_right_(other.handle_positions_right_) + { + } + + int size() const final; + int resolution() const; + void set_resolution(const int value); + + void add_point(const blender::float3 position, + const HandleType handle_type_start, + const blender::float3 handle_position_start, + const HandleType handle_type_end, + const blender::float3 handle_position_end, + const float radius, + const float tilt); + + void resize(const int size) 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_left() const; + blender::MutableSpan<HandleType> handle_types_left(); + blender::Span<blender::float3> handle_positions_left() const; + blender::MutableSpan<blender::float3> handle_positions_left(); + blender::Span<HandleType> handle_types_right() const; + blender::MutableSpan<HandleType> handle_types_right(); + blender::Span<blender::float3> handle_positions_right() const; + blender::MutableSpan<blender::float3> handle_positions_right(); + + void translate(const blender::float3 &translation) override; + void transform(const blender::float4x4 &matrix) override; + + bool point_is_sharp(const int index) const; + + void mark_cache_invalid() final; + int evaluated_points_size() const final; + + blender::Span<int> control_point_offsets() const; + blender::Span<float> evaluated_mappings() const; + blender::Span<blender::float3> evaluated_positions() const final; + struct InterpolationData { + int control_point_index; + int next_control_point_index; + /** + * Linear interpolation weight between the two indices, from 0 to 1. + * Higher means closer to next control point. + */ + float factor; + }; + InterpolationData interpolation_data_from_index_factor(const float index_factor) const; + + virtual blender::fn::GVArrayPtr interpolate_to_evaluated_points( + const blender::fn::GVArray &source_data) const override; + + private: + void ensure_auto_handles() const; + 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; +}; + +/** + * Data for Non-Uniform Rational B-Splines. The mapping from control points to evaluated points is + * influenced by a vector of knots, weights for each point, and the order of the spline. Every + * mapping of data to evaluated points is handled the same way, but the positions are cached in + * the spline. + */ +class NURBSpline final : public Spline { + public: + enum class KnotsMode { + Normal, + EndPoint, + Bezier, + }; + + /** Method used to recalculate the knots vector when points are added or removed. */ + KnotsMode knots_mode; + + struct BasisCache { + /** The influence at each control point `i + #start_index`. */ + blender::Vector<float> weights; + /** + * An offset for the start of #weights: the first control point index with a non-zero weight. + */ + int start_index; + }; + + private: + blender::Vector<blender::float3> positions_; + blender::Vector<float> radii_; + blender::Vector<float> tilts_; + blender::Vector<float> weights_; + int resolution_; + /** + * Defines the number of nearby control points that influence a given evaluated point. Higher + * orders give smoother results. The number of control points must be greater than or equal to + * this value. + */ + uint8_t order_; + + /** + * Determines where and how the control points affect the evaluated points. The length should + * always be the value returned by #knots_size(), and each value should be greater than or equal + * to the previous. Only invalidated when a point is added or removed. + */ + mutable blender::Vector<float> knots_; + mutable std::mutex knots_mutex_; + mutable bool knots_dirty_ = true; + + /** Cache of control point influences on each evaluated point. */ + mutable blender::Vector<BasisCache> basis_cache_; + mutable std::mutex basis_cache_mutex_; + mutable bool basis_cache_dirty_ = true; + + /** + * Cache of position data calculated from the basis cache. Though it is interpolated + * in the same way as any other attribute, it is stored to save unnecessary recalculation. + */ + mutable blender::Vector<blender::float3> evaluated_position_cache_; + mutable std::mutex position_cache_mutex_; + mutable bool position_cache_dirty_ = true; + + public: + SplinePtr copy() const final; + NURBSpline() : Spline(Type::NURBS) + { + } + NURBSpline(const NURBSpline &other) + : Spline((Spline &)other), + knots_mode(other.knots_mode), + positions_(other.positions_), + radii_(other.radii_), + tilts_(other.tilts_), + weights_(other.weights_), + resolution_(other.resolution_), + order_(other.order_) + { + } + + int size() const final; + int resolution() const; + void set_resolution(const int value); + uint8_t order() const; + void set_order(const uint8_t value); + + void add_point(const blender::float3 position, + const float radius, + const float tilt, + const float weight); + + bool check_valid_size_and_order() const; + int knots_size() const; + + void resize(const int size) 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<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 { + blender::Vector<blender::float3> positions_; + blender::Vector<float> radii_; + blender::Vector<float> tilts_; + + public: + SplinePtr copy() const final; + PolySpline() : Spline(Type::Poly) + { + } + PolySpline(const PolySpline &other) + : Spline((Spline &)other), + positions_(other.positions_), + radii_(other.radii_), + tilts_(other.tilts_) + { + } + + int size() const final; + + void add_point(const blender::float3 position, const float radius, const float tilt); + + void resize(const int size) 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 #CurveEval corresponds to the #Curve object data. The name is different for clarity, since + * more of the data is stored in the splines, but also just to be different than the name in DNA. + */ +class CurveEval { + private: + blender::Vector<SplinePtr> splines_; + + public: + blender::bke::CustomDataAttributes attributes; + + CurveEval() = default; + CurveEval(const CurveEval &other) : attributes(other.attributes) + { + for (const SplinePtr &spline : other.splines()) { + this->add_spline(spline->copy()); + } + } + + blender::Span<SplinePtr> splines() const; + blender::MutableSpan<SplinePtr> splines(); + + void add_spline(SplinePtr spline); + void remove_splines(blender::IndexMask mask); + + 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; + + blender::Array<int> control_point_offsets() const; + blender::Array<int> evaluated_point_offsets() const; + + void assert_valid_point_attributes() const; +}; + +std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &curve); diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index adf321da8f0..021d7e15814 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -112,12 +112,13 @@ set(SRC intern/curve_convert.c intern/curve_decimate.c intern/curve_deform.c + intern/curve_eval.cc intern/curveprofile.c intern/customdata.c intern/customdata_file.c intern/data_transfer.c intern/deform.c - intern/displist.c + intern/displist.cc intern/displist_tangent.c intern/dynamicpaint.c intern/editlattice.c @@ -133,6 +134,7 @@ set(SRC intern/fmodifier.c intern/font.c intern/freestyle.c + intern/geometry_component_curve.cc intern/geometry_component_instances.cc intern/geometry_component_mesh.cc intern/geometry_component_pointcloud.cc @@ -241,6 +243,10 @@ set(SRC intern/softbody.c intern/sound.c intern/speaker.c + intern/spline_base.cc + intern/spline_bezier.cc + intern/spline_nurbs.cc + intern/spline_poly.cc intern/studiolight.c intern/subdiv.c intern/subdiv_ccg.c @@ -322,6 +328,7 @@ set(SRC BKE_customdata_file.h BKE_data_transfer.h BKE_deform.h + BKE_spline.hh BKE_displist.h BKE_displist_tangent.h BKE_duplilist.h @@ -399,7 +406,6 @@ set(SRC BKE_paint.h BKE_particle.h BKE_pbvh.h - BKE_persistent_data_handle.hh BKE_pointcache.h BKE_pointcloud.h BKE_preferences.h @@ -585,10 +591,6 @@ if(WITH_CODEC_FFMPEG) ${FFMPEG_LIBRARIES} ) add_definitions(-DWITH_FFMPEG) - - remove_strict_c_flags_file( - intern/writeffmpeg.c - ) endif() if(WITH_PYTHON) @@ -767,6 +769,7 @@ if(WITH_GTESTS) intern/fcurve_test.cc intern/lattice_deform_test.cc intern/layer_test.cc + intern/lib_id_test.cc intern/tracking_test.cc ) set(TEST_INC diff --git a/source/blender/blenkernel/intern/DerivedMesh.cc b/source/blender/blenkernel/intern/DerivedMesh.cc index 9f51ef5292f..d4dd7e248d5 100644 --- a/source/blender/blenkernel/intern/DerivedMesh.cc +++ b/source/blender/blenkernel/intern/DerivedMesh.cc @@ -712,11 +712,13 @@ static float (*get_orco_coords(Object *ob, BMEditMesh *em, int layer, int *free) if (!em) { ClothModifierData *clmd = (ClothModifierData *)BKE_modifiers_findby_type( ob, eModifierType_Cloth); - KeyBlock *kb = BKE_keyblock_from_key(BKE_key_from_object(ob), - clmd->sim_parms->shapekey_rest); + if (clmd) { + KeyBlock *kb = BKE_keyblock_from_key(BKE_key_from_object(ob), + clmd->sim_parms->shapekey_rest); - if (kb && kb->data) { - return (float(*)[3])kb->data; + if (kb && kb->data) { + return (float(*)[3])kb->data; + } } } @@ -1603,6 +1605,12 @@ static void editbmesh_calc_modifiers(struct Depsgraph *depsgraph, /* This geometry set contains the non-mesh data that might be generated by modifiers. */ GeometrySet geometry_set_final; + /* Add the initial mesh component, with a copy of the vertex group names from the object, + * since they need to be stored in the geometry set for evaluation. */ + MeshComponent &initial_mesh_component = + geometry_set_final.get_component_for_write<MeshComponent>(); + initial_mesh_component.copy_vertex_group_names_from_object(*ob); + /* Deformed vertex locations array. Deform only modifier need this type of * float array rather than MVert*. Tracked along with mesh_final as an * optimization to avoid copying coordinates back and forth if there are @@ -1853,9 +1861,11 @@ static void editbmesh_calc_modifiers(struct Depsgraph *depsgraph, BKE_id_free(nullptr, mesh_orco); } - /* Ensure normals calculation below is correct. */ - BLI_assert((mesh_input->flag & ME_AUTOSMOOTH) == (mesh_final->flag & ME_AUTOSMOOTH)); - BLI_assert(mesh_input->smoothresh == mesh_final->smoothresh); + /* Ensure normals calculation below is correct (normal settings have transferred properly). + * However, nodes modifiers might create meshes from scratch or transfer meshes from other + * objects with different settings, and in general it doesn't make sense to guarantee that + * the settings are the same as the original mesh. If necessary, this could become a modifier + * type flag. */ BLI_assert(mesh_input->smoothresh == mesh_cage->smoothresh); /* Compute normals. */ diff --git a/source/blender/blenkernel/intern/action.c b/source/blender/blenkernel/intern/action.c index 0b38e2d9f75..a7e36b09516 100644 --- a/source/blender/blenkernel/intern/action.c +++ b/source/blender/blenkernel/intern/action.c @@ -635,7 +635,7 @@ bPoseChannel *BKE_pose_channel_find_name(const bPose *pose, const char *name) * \note Use with care, not on Armature poses but for temporal ones. * \note (currently used for action constraints and in rebuild_pose). */ -bPoseChannel *BKE_pose_channel_verify(bPose *pose, const char *name) +bPoseChannel *BKE_pose_channel_ensure(bPose *pose, const char *name) { bPoseChannel *chan; @@ -656,7 +656,9 @@ bPoseChannel *BKE_pose_channel_verify(bPose *pose, const char *name) BLI_strncpy(chan->name, name, sizeof(chan->name)); - chan->custom_scale = 1.0f; + copy_v3_fl(chan->custom_scale_xyz, 1.0f); + zero_v3(chan->custom_translation); + zero_v3(chan->custom_rotation_euler); /* init vars to prevent math errors */ unit_qt(chan->quat); @@ -815,7 +817,7 @@ void BKE_pose_copy_data_ex(bPose **dst, */ if (outPose->chanbase.first != outPose->chanbase.last) { outPose->chanhash = NULL; - BKE_pose_channels_hash_make(outPose); + BKE_pose_channels_hash_ensure(outPose); } outPose->iksolver = src->iksolver; @@ -945,7 +947,7 @@ bool BKE_pose_channel_in_IK_chain(Object *ob, bPoseChannel *pchan) * Removes the hash for quick lookup of channels, must * be done when adding/removing channels. */ -void BKE_pose_channels_hash_make(bPose *pose) +void BKE_pose_channels_hash_ensure(bPose *pose) { if (!pose->chanhash) { bPoseChannel *pchan; @@ -1191,7 +1193,7 @@ void BKE_pose_free(bPose *pose) * and ID-Props, used when duplicating bones in editmode. * (unlike copy_pose_channel_data which only does posing-related stuff). * - * \note use when copying bones in editmode (on returned value from #BKE_pose_channel_verify) + * \note use when copying bones in editmode (on returned value from #BKE_pose_channel_ensure) */ void BKE_pose_channel_copy_data(bPoseChannel *pchan, const bPoseChannel *pchan_from) { @@ -1235,8 +1237,10 @@ void BKE_pose_channel_copy_data(bPoseChannel *pchan, const bPoseChannel *pchan_f if (pchan->custom) { id_us_plus(&pchan->custom->id); } + copy_v3_v3(pchan->custom_scale_xyz, pchan_from->custom_scale_xyz); + copy_v3_v3(pchan->custom_translation, pchan_from->custom_translation); + copy_v3_v3(pchan->custom_rotation_euler, pchan_from->custom_rotation_euler); - pchan->custom_scale = pchan_from->custom_scale; pchan->drawflag = pchan_from->drawflag; } @@ -1774,7 +1778,7 @@ void what_does_obaction(Object *ob, * allocation and also will make lookup slower. */ if (pose->chanbase.first != pose->chanbase.last) { - BKE_pose_channels_hash_make(pose); + BKE_pose_channels_hash_ensure(pose); } if (pose->flag & POSE_CONSTRAINTS_NEED_UPDATE_FLAGS) { BKE_pose_update_constraint_flags(pose); diff --git a/source/blender/blenkernel/intern/anim_data.c b/source/blender/blenkernel/intern/anim_data.c index 447ed8fbe14..44b760aefc8 100644 --- a/source/blender/blenkernel/intern/anim_data.c +++ b/source/blender/blenkernel/intern/anim_data.c @@ -354,7 +354,7 @@ AnimData *BKE_animdata_copy(Main *bmain, AnimData *adt, const int flag) } /* duplicate NLA data */ - BKE_nla_tracks_copy(bmain, &dadt->nla_tracks, &adt->nla_tracks, flag); + BKE_nla_tracks_copy_from_adt(bmain, dadt, adt, flag); /* duplicate drivers (F-Curves) */ BKE_fcurves_copy(&dadt->drivers, &adt->drivers); @@ -947,7 +947,7 @@ static bool nlastrips_path_rename_fix(ID *owner_id, owner_id, prefix, oldName, newName, oldKey, newKey, &strip->act->curves, verify_paths); } /* Ignore own F-Curves, since those are local. */ - /* Check sub-strips (if metas) */ + /* Check sub-strips (if meta-strips). */ is_changed |= nlastrips_path_rename_fix( owner_id, prefix, oldName, newName, oldKey, newKey, &strip->strips, verify_paths); } @@ -1177,7 +1177,7 @@ static bool nlastrips_path_remove_fix(const char *prefix, ListBase *strips) any_removed |= fcurves_path_remove_fix(prefix, &strip->act->curves); } - /* check sub-strips (if metas) */ + /* Check sub-strips (if meta-strips). */ any_removed |= nlastrips_path_remove_fix(prefix, &strip->strips); } return any_removed; @@ -1245,7 +1245,7 @@ static void nlastrips_apply_all_curves_cb(ID *id, ListBase *strips, AllFCurvesCb fcurves_apply_cb(id, &strip->act->curves, wrapper->func, wrapper->user_data); } - /* check sub-strips (if metas) */ + /* Check sub-strips (if meta-strips). */ nlastrips_apply_all_curves_cb(id, &strip->strips, wrapper); } } diff --git a/source/blender/blenkernel/intern/anim_sys.c b/source/blender/blenkernel/intern/anim_sys.c index 6f4af6f655d..e347306e0ae 100644 --- a/source/blender/blenkernel/intern/anim_sys.c +++ b/source/blender/blenkernel/intern/anim_sys.c @@ -1040,6 +1040,7 @@ static NlaEvalChannelSnapshot *nlaevalchan_snapshot_new(NlaEvalChannel *nec) nec_snapshot->channel = nec; nec_snapshot->length = length; nlavalidmask_init(&nec_snapshot->blend_domain, length); + nlavalidmask_init(&nec_snapshot->remap_domain, length); return nec_snapshot; } @@ -1050,6 +1051,7 @@ static void nlaevalchan_snapshot_free(NlaEvalChannelSnapshot *nec_snapshot) BLI_assert(!nec_snapshot->is_base); nlavalidmask_free(&nec_snapshot->blend_domain); + nlavalidmask_free(&nec_snapshot->remap_domain); MEM_freeN(nec_snapshot); } @@ -1650,6 +1652,363 @@ static bool nla_combine_quaternion_get_inverted_strip_values(const float lower_v } /* ---------------------- */ + +/* Assert necs and necs->channel is nonNull. */ +static void nlaevalchan_assert_nonNull(NlaEvalChannelSnapshot *necs) +{ + UNUSED_VARS_NDEBUG(necs); + BLI_assert(necs != NULL && necs->channel != NULL); +} + +/* Assert that the channels given can be blended or combined together. */ +static void nlaevalchan_assert_blendOrcombine_compatible(NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *upper_necs, + NlaEvalChannelSnapshot *blended_necs) +{ + UNUSED_VARS_NDEBUG(lower_necs, upper_necs, blended_necs); + BLI_assert(!ELEM(NULL, lower_necs, blended_necs)); + BLI_assert(upper_necs == NULL || lower_necs->length == upper_necs->length); + BLI_assert(lower_necs->length == blended_necs->length); +} + +/* Assert that the channels given can be blended or combined together as a quaternion. */ +static void nlaevalchan_assert_blendOrcombine_compatible_quaternion( + NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *upper_necs, + NlaEvalChannelSnapshot *blended_necs) +{ + nlaevalchan_assert_blendOrcombine_compatible(lower_necs, upper_necs, blended_necs); + BLI_assert(lower_necs->length == 4); +} + +static void nlaevalchan_copy_values(NlaEvalChannelSnapshot *dst, NlaEvalChannelSnapshot *src) +{ + memcpy(dst->values, src->values, src->length * sizeof(float)); +} + +/** + * Copies lower necs to blended necs if upper necs is NULL or has zero influence. + * \return true if copied. + */ +static bool nlaevalchan_blendOrcombine_try_copy_lower(NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *upper_necs, + const float upper_influence, + NlaEvalChannelSnapshot *r_blended_necs) +{ + const bool has_influence = !IS_EQF(upper_influence, 0.0f); + if (upper_necs != NULL && has_influence) { + return false; + } + + nlaevalchan_copy_values(r_blended_necs, lower_necs); + return true; +} + +/** + * Based on blend-mode, blend lower necs with upper necs into blended necs. + * + * Each upper value's blend domain determines whether to blend or to copy directly from lower. + */ +static void nlaevalchan_blend_value(NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *upper_necs, + const int upper_blendmode, + const float upper_influence, + NlaEvalChannelSnapshot *r_blended_necs) +{ + nlaevalchan_assert_blendOrcombine_compatible(lower_necs, upper_necs, r_blended_necs); + if (nlaevalchan_blendOrcombine_try_copy_lower( + lower_necs, upper_necs, upper_influence, r_blended_necs)) { + return; + } + + const int length = lower_necs->length; + for (int j = 0; j < length; j++) { + if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, j)) { + r_blended_necs->values[j] = lower_necs->values[j]; + continue; + } + + r_blended_necs->values[j] = nla_blend_value( + upper_blendmode, lower_necs->values[j], upper_necs->values[j], upper_influence); + } +} + +/** + * Based on mix-mode, provided by one the necs, + * combines lower necs with upper necs into blended necs. + * + * Each upper value's blend domain determines whether to blend or to copy directly from lower. + */ +static void nlaevalchan_combine_value(NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *upper_necs, + const float upper_influence, + NlaEvalChannelSnapshot *r_blended_necs) +{ + nlaevalchan_assert_blendOrcombine_compatible(lower_necs, upper_necs, r_blended_necs); + if (nlaevalchan_blendOrcombine_try_copy_lower( + lower_necs, upper_necs, upper_influence, r_blended_necs)) { + return; + } + + /* Assumes every base is the same. */ + float *base_values = lower_necs->channel->base_snapshot.values; + const int length = lower_necs->length; + const char mix_mode = lower_necs->channel->mix_mode; + + for (int j = 0; j < length; j++) { + if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, j)) { + r_blended_necs->values[j] = lower_necs->values[j]; + continue; + } + + r_blended_necs->values[j] = nla_combine_value( + mix_mode, base_values[j], lower_necs->values[j], upper_necs->values[j], upper_influence); + } +} + +/** + * Quaternion combines lower necs with upper necs into blended necs. + * + * Each upper value's blend domain determines whether to blend or to copy directly + * from lower. + */ +static void nlaevalchan_combine_quaternion(NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *upper_necs, + const float upper_influence, + NlaEvalChannelSnapshot *r_blended_necs) +{ + nlaevalchan_assert_blendOrcombine_compatible_quaternion(lower_necs, upper_necs, r_blended_necs); + if (nlaevalchan_blendOrcombine_try_copy_lower( + lower_necs, upper_necs, upper_influence, r_blended_necs)) { + return; + } + + /** No need to check per index. We limit to all or nothing combining for quaternions. */ + if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, 0)) { + nlaevalchan_copy_values(r_blended_necs, lower_necs); + return; + } + + nla_combine_quaternion( + lower_necs->values, upper_necs->values, upper_influence, r_blended_necs->values); +} + +/** + * Based on blend-mode and mix-mode, blend lower necs with upper necs into blended necs. + * + * Each upper value's blend domain determines whether to blend or to copy directly + * from lower. + * + * \param lower_necs: Never NULL. + * \param upper_necs: Can be NULL. + * \param upper_blendmode: Enum value in eNlaStrip_Blend_Mode. + * \param upper_influence: Value in range [0, 1]. + * \param upper_necs: Never NULL. + * + */ +static void nlaevalchan_blendOrcombine(NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *upper_necs, + const int upper_blendmode, + const float upper_influence, + NlaEvalChannelSnapshot *r_blended_necs) +{ + nlaevalchan_assert_nonNull(r_blended_necs); + + switch (upper_blendmode) { + case NLASTRIP_MODE_COMBINE: { + switch (r_blended_necs->channel->mix_mode) { + case NEC_MIX_QUATERNION: { + nlaevalchan_combine_quaternion(lower_necs, upper_necs, upper_influence, r_blended_necs); + return; + } + case NEC_MIX_ADD: + case NEC_MIX_AXIS_ANGLE: + case NEC_MIX_MULTIPLY: { + nlaevalchan_combine_value(lower_necs, upper_necs, upper_influence, r_blended_necs); + return; + } + default: + BLI_assert("Mix mode should've been handled"); + } + return; + } + case NLASTRIP_MODE_ADD: + case NLASTRIP_MODE_SUBTRACT: + case NLASTRIP_MODE_MULTIPLY: + case NLASTRIP_MODE_REPLACE: { + nlaevalchan_blend_value( + lower_necs, upper_necs, upper_blendmode, upper_influence, r_blended_necs); + return; + } + default: + BLI_assert("Blend mode should've been handled"); + } +} + +/** + * Based on blend-mode, solve for the upper values such that when lower blended with upper then we + * get blended values as a result. + * + * Only processes blended values in the remap domain. Successfully remapped upper values are placed + * in the remap domain so caller knows which values are usable. + */ +static void nlaevalchan_blend_value_get_inverted_upper_evalchan( + NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *blended_necs, + const int upper_blendmode, + const float upper_influence, + NlaEvalChannelSnapshot *r_upper_necs) +{ + nlaevalchan_assert_nonNull(r_upper_necs); + nlaevalchan_assert_blendOrcombine_compatible(lower_necs, r_upper_necs, blended_necs); + + const int length = lower_necs->length; + for (int j = 0; j < length; j++) { + if (!BLI_BITMAP_TEST_BOOL(blended_necs->remap_domain.ptr, j)) { + BLI_BITMAP_DISABLE(r_upper_necs->remap_domain.ptr, j); + continue; + } + + const bool success = nla_blend_get_inverted_strip_value(upper_blendmode, + lower_necs->values[j], + blended_necs->values[j], + upper_influence, + &r_upper_necs->values[j]); + BLI_BITMAP_SET(r_upper_necs->remap_domain.ptr, j, success); + } +} + +/** + * Based on mix-mode, solve for the upper values such that when lower combined with upper then we + * get blended values as a result. + * + * Only processes blended values in the remap domain. Successfully remapped upper values are placed + * in the remap domain so caller knows which values are usable. + */ +static void nlaevalchan_combine_value_get_inverted_upper_evalchan( + NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *blended_necs, + const float upper_influence, + NlaEvalChannelSnapshot *r_upper_necs) +{ + nlaevalchan_assert_nonNull(r_upper_necs); + nlaevalchan_assert_blendOrcombine_compatible(lower_necs, r_upper_necs, blended_necs); + + /* Assumes every channel's base is the same. */ + float *base_values = lower_necs->channel->base_snapshot.values; + const int length = lower_necs->length; + const char mix_mode = lower_necs->channel->mix_mode; + + for (int j = 0; j < length; j++) { + if (!BLI_BITMAP_TEST_BOOL(blended_necs->remap_domain.ptr, j)) { + BLI_BITMAP_DISABLE(r_upper_necs->remap_domain.ptr, j); + continue; + } + + const bool success = nla_combine_get_inverted_strip_value(mix_mode, + base_values[j], + lower_necs->values[j], + blended_necs->values[j], + upper_influence, + &r_upper_necs->values[j]); + + BLI_BITMAP_SET(r_upper_necs->remap_domain.ptr, j, success); + } +} + +/** + * Solve for the upper values such that when lower quaternion combined with upper then we get + * blended values as a result. + * + * All blended values must be in the remap domain. If successfully remapped, then all upper values + * are placed in the remap domain so caller knows the result is usable. + */ +static void nlaevalchan_combine_quaternion_get_inverted_upper_evalchan( + NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *blended_necs, + const float upper_influence, + NlaEvalChannelSnapshot *r_upper_necs) +{ + nlaevalchan_assert_nonNull(r_upper_necs); + nlaevalchan_assert_blendOrcombine_compatible_quaternion(lower_necs, r_upper_necs, blended_necs); + + /* Must check each domain index individually in case animator had a non-combine NLA strip with a + * subset of quaternion channels and remapping through any of them failed and thus potentially + * has undefined values. */ + for (int j = 0; j < 4; j++) { + if (!BLI_BITMAP_TEST_BOOL(blended_necs->remap_domain.ptr, j)) { + BLI_bitmap_set_all(r_upper_necs->remap_domain.ptr, false, 4); + return; + } + } + + const bool success = nla_combine_quaternion_get_inverted_strip_values( + lower_necs->values, blended_necs->values, upper_influence, r_upper_necs->values); + + BLI_bitmap_set_all(r_upper_necs->remap_domain.ptr, success, 4); +} + +/** + * Based on blend-mode and mix mode, solve for the upper values such that when lower blended or + * combined with upper then we get blended values as a result. + * + * Only processes blended values in the remap domain. Successfully remapped upper values are placed + * in the remap domain so caller knows which values are usable. + * + * \param lower_necs: Never NULL. + * \param blended_necs: Never NULL. + * \param upper_blendmode: Enum value in eNlaStrip_Blend_Mode. + * \param upper_influence: Value in range [0, 1]. + * \param r_upper_necs: Never NULL. + */ +static void nlaevalchan_blendOrcombine_get_inverted_upper_evalchan( + NlaEvalChannelSnapshot *lower_necs, + NlaEvalChannelSnapshot *blended_necs, + const int upper_blendmode, + const float upper_influence, + NlaEvalChannelSnapshot *r_upper_necs) +{ + nlaevalchan_assert_nonNull(r_upper_necs); + + if (IS_EQF(upper_influence, 0.0f)) { + BLI_bitmap_set_all(r_upper_necs->remap_domain.ptr, false, r_upper_necs->length); + return; + } + + switch (upper_blendmode) { + case NLASTRIP_MODE_COMBINE: { + switch (r_upper_necs->channel->mix_mode) { + case NEC_MIX_QUATERNION: { + nlaevalchan_combine_quaternion_get_inverted_upper_evalchan( + lower_necs, blended_necs, upper_influence, r_upper_necs); + return; + } + case NEC_MIX_ADD: + case NEC_MIX_AXIS_ANGLE: + case NEC_MIX_MULTIPLY: { + nlaevalchan_combine_value_get_inverted_upper_evalchan( + lower_necs, blended_necs, upper_influence, r_upper_necs); + return; + } + default: + BLI_assert("Mix mode should've been handled"); + } + return; + } + case NLASTRIP_MODE_ADD: + case NLASTRIP_MODE_SUBTRACT: + case NLASTRIP_MODE_MULTIPLY: + case NLASTRIP_MODE_REPLACE: { + nlaevalchan_blend_value_get_inverted_upper_evalchan( + lower_necs, blended_necs, upper_blendmode, upper_influence, r_upper_necs); + return; + } + default: + BLI_assert("Blend mode should've been handled"); + } +} + +/* ---------------------- */ /* F-Modifier stack joining/separation utilities - * should we generalize these for BLI_listbase.h interface? */ @@ -2048,12 +2407,12 @@ static void nla_eval_domain_strips(PointerRNA *ptr, GSet *touched_actions) { LISTBASE_FOREACH (NlaStrip *, strip, strips) { - /* check strip's action */ + /* Check strip's action. */ if (strip->act) { nla_eval_domain_action(ptr, channels, strip->act, touched_actions); } - /* check sub-strips (if metas) */ + /* Check sub-strips (if meta-strips). */ nla_eval_domain_strips(ptr, channels, &strip->strips, touched_actions); } } @@ -2500,9 +2859,9 @@ void nlasnapshot_ensure_channels(NlaEvalData *eval_data, NlaEvalSnapshot *snapsh * Blends the \a lower_snapshot with the \a upper_snapshot into \a r_blended_snapshot according * to the given \a upper_blendmode and \a upper_influence. * - * For \a upper_snapshot, blending limited to values in the \a blend_domain. For Replace blendmode, - * this allows the upper snapshot to have a location XYZ channel where only a subset of values are - * blended. + * For \a upper_snapshot, blending limited to values in the \a blend_domain. + * For Replace blend-mode, this allows the upper snapshot to have a location XYZ channel + * where only a subset of values are blended. */ void nlasnapshot_blend(NlaEvalData *eval_data, NlaEvalSnapshot *lower_snapshot, @@ -2513,11 +2872,7 @@ void nlasnapshot_blend(NlaEvalData *eval_data, { nlaeval_snapshot_ensure_size(r_blended_snapshot, eval_data->num_channels); - const bool zero_upper_influence = IS_EQF(upper_influence, 0.0f); - LISTBASE_FOREACH (NlaEvalChannel *, nec, &eval_data->channels) { - const int length = nec->base_snapshot.length; - NlaEvalChannelSnapshot *upper_necs = nlaeval_snapshot_get(upper_snapshot, nec->index); NlaEvalChannelSnapshot *lower_necs = nlaeval_snapshot_get(lower_snapshot, nec->index); if (upper_necs == NULL && lower_necs == NULL) { @@ -2530,49 +2885,44 @@ void nlasnapshot_blend(NlaEvalData *eval_data, } NlaEvalChannelSnapshot *result_necs = nlaeval_snapshot_ensure_channel(r_blended_snapshot, nec); + nlaevalchan_blendOrcombine( + lower_necs, upper_necs, upper_blendmode, upper_influence, result_necs); + } +} - /** Always copy \a lower_snapshot to result, irrelevant of whether \a upper_snapshot has a - * corresponding channel. This only matters when \a lower_snapshot not the same as - * \a r_blended_snapshot. */ - memcpy(result_necs->values, lower_necs->values, length * sizeof(float)); - if (upper_necs == NULL || zero_upper_influence) { +/** + * Using \a blended_snapshot and \a lower_snapshot, we can solve for the \a r_upper_snapshot. + * + * Only channels that exist within \a blended_snapshot are inverted. + * + * For \a r_upper_snapshot, disables \a NlaEvalChannelSnapshot->remap_domain for failed inversions. + * Only values within the \a remap_domain are processed. + */ +void nlasnapshot_blend_get_inverted_upper_snapshot(NlaEvalData *eval_data, + NlaEvalSnapshot *lower_snapshot, + NlaEvalSnapshot *blended_snapshot, + const short upper_blendmode, + const float upper_influence, + NlaEvalSnapshot *r_upper_snapshot) +{ + nlaeval_snapshot_ensure_size(r_upper_snapshot, eval_data->num_channels); + + LISTBASE_FOREACH (NlaEvalChannel *, nec, &eval_data->channels) { + NlaEvalChannelSnapshot *blended_necs = nlaeval_snapshot_get(blended_snapshot, nec->index); + if (blended_necs == NULL) { + /** We assume the caller only wants a subset of channels to be inverted, those that exist + * within \a blended_snapshot. */ continue; } - if (upper_blendmode == NLASTRIP_MODE_COMBINE) { - const int mix_mode = nec->mix_mode; - if (mix_mode == NEC_MIX_QUATERNION) { - if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, 0)) { - continue; - } - - nla_combine_quaternion( - lower_necs->values, upper_necs->values, upper_influence, result_necs->values); - } - else { - for (int j = 0; j < length; j++) { - if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, j)) { - continue; - } - - result_necs->values[j] = nla_combine_value(mix_mode, - nec->base_snapshot.values[j], - lower_necs->values[j], - upper_necs->values[j], - upper_influence); - } - } + NlaEvalChannelSnapshot *lower_necs = nlaeval_snapshot_get(lower_snapshot, nec->index); + if (lower_necs == NULL) { + lower_necs = nlaeval_snapshot_find_channel(lower_snapshot->base, nec); } - else { - for (int j = 0; j < length; j++) { - if (!BLI_BITMAP_TEST_BOOL(upper_necs->blend_domain.ptr, j)) { - continue; - } - result_necs->values[j] = nla_blend_value( - upper_blendmode, lower_necs->values[j], upper_necs->values[j], upper_influence); - } - } + NlaEvalChannelSnapshot *result_necs = nlaeval_snapshot_ensure_channel(r_upper_snapshot, nec); + nlaevalchan_blendOrcombine_get_inverted_upper_evalchan( + lower_necs, blended_necs, upper_blendmode, upper_influence, result_necs); } } @@ -2670,74 +3020,64 @@ bool BKE_animsys_nla_remap_keyframe_values(struct NlaKeyframingContext *context, return false; } - /* Find the evaluation channel for the NLA stack below current strip. */ + /** Create \a blended_snapshot and fill with input \a values. */ + NlaEvalData *eval_data = &context->lower_eval_data; + NlaEvalSnapshot blended_snapshot; + nlaeval_snapshot_init(&blended_snapshot, eval_data, NULL); + NlaEvalChannelKey key = { .ptr = *prop_ptr, .prop = prop, }; - /** - * Remove lower NLA stack effects. - * - * Using the tweak strip's blended result and the lower snapshot value, we can solve for the - * tweak strip value it must evaluate to. - */ - NlaEvalData *const lower_eval_data = &context->lower_eval_data; - NlaEvalChannel *const lower_nec = nlaevalchan_verify_key(lower_eval_data, NULL, &key); - if ((lower_nec->base_snapshot.length != count)) { + NlaEvalChannel *nec = nlaevalchan_verify_key(eval_data, NULL, &key); + BLI_assert(nec); + if (nec->base_snapshot.length != count) { BLI_assert(!"invalid value count"); + nlaeval_snapshot_free_data(&blended_snapshot); return false; } - /* Invert the blending operation to compute the desired strip values. */ - NlaEvalChannelSnapshot *const lower_nec_snapshot = nlaeval_snapshot_find_channel( - &lower_eval_data->eval_snapshot, lower_nec); + NlaEvalChannelSnapshot *blended_necs = nlaeval_snapshot_ensure_channel(&blended_snapshot, nec); + memcpy(blended_necs->values, values, sizeof(float) * count); + BLI_bitmap_set_all(blended_necs->remap_domain.ptr, true, count); - float *lower_values = lower_nec_snapshot->values; + /** Remove lower NLA stack effects. */ + nlasnapshot_blend_get_inverted_upper_snapshot(eval_data, + &context->lower_eval_data.eval_snapshot, + &blended_snapshot, + blend_mode, + influence, + &blended_snapshot); - if (blend_mode == NLASTRIP_MODE_COMBINE) { - /* Quaternion combine handles all sub-channels as a unit. */ - if (lower_nec->mix_mode == NEC_MIX_QUATERNION) { - if (r_force_all == NULL) { - return false; - } + /** Write results into \a values. */ + bool successful_remap = true; + if (blended_necs->channel->mix_mode == NEC_MIX_QUATERNION && + blend_mode == NLASTRIP_MODE_COMBINE) { + if (r_force_all != NULL) { *r_force_all = true; - - if (!nla_combine_quaternion_get_inverted_strip_values( - lower_values, values, influence, values)) { - return false; - } + index = -1; } else { - float *base_values = lower_nec->base_snapshot.values; - - for (int i = 0; i < count; i++) { - if (ELEM(index, i, -1)) { - if (!nla_combine_get_inverted_strip_value(lower_nec->mix_mode, - base_values[i], - lower_values[i], - values[i], - influence, - &values[i])) { - return false; - } - } - } + successful_remap = false; } } - else { - for (int i = 0; i < count; i++) { - if (ELEM(index, i, -1)) { - if (!nla_blend_get_inverted_strip_value( - blend_mode, lower_values[i], values[i], influence, &values[i])) { - return false; - } - } + + for (int i = 0; i < count; i++) { + if (!ELEM(index, i, -1)) { + continue; + } + if (!BLI_BITMAP_TEST_BOOL(blended_necs->remap_domain.ptr, i)) { + successful_remap = false; } + + values[i] = blended_necs->values[i]; } - return true; + nlaeval_snapshot_free_data(&blended_snapshot); + + return successful_remap; } /** diff --git a/source/blender/blenkernel/intern/armature.c b/source/blender/blenkernel/intern/armature.c index a1ebec1d756..4ea71922df5 100644 --- a/source/blender/blenkernel/intern/armature.c +++ b/source/blender/blenkernel/intern/armature.c @@ -2444,7 +2444,7 @@ static void pose_proxy_sync(Object *ob, Object *from, int layer_protected) static int rebuild_pose_bone( bPose *pose, Bone *bone, bPoseChannel *parchan, int counter, Bone **r_last_visited_bone_p) { - bPoseChannel *pchan = BKE_pose_channel_verify(pose, bone->name); /* verify checks and/or adds */ + bPoseChannel *pchan = BKE_pose_channel_ensure(pose, bone->name); /* verify checks and/or adds */ pchan->bone = bone; pchan->parent = parchan; @@ -2515,6 +2515,17 @@ void BKE_pchan_rebuild_bbone_handles(bPose *pose, bPoseChannel *pchan) pchan->bbone_next = pose_channel_find_bone(pose, pchan->bone->bbone_next); } +void BKE_pose_channels_clear_with_null_bone(bPose *pose, const bool do_id_user) +{ + LISTBASE_FOREACH_MUTABLE (bPoseChannel *, pchan, &pose->chanbase) { + if (pchan->bone == NULL) { + BKE_pose_channel_free_ex(pchan, do_id_user); + BKE_pose_channels_hash_free(pose); + BLI_freelinkN(&pose->chanbase, pchan); + } + } +} + /** * Only after leave editmode, duplicating, validating older files, library syncing. * @@ -2526,7 +2537,7 @@ void BKE_pose_rebuild(Main *bmain, Object *ob, bArmature *arm, const bool do_id_ { Bone *bone; bPose *pose; - bPoseChannel *pchan, *next; + bPoseChannel *pchan; int counter = 0; /* only done here */ @@ -2549,16 +2560,9 @@ void BKE_pose_rebuild(Main *bmain, Object *ob, bArmature *arm, const bool do_id_ } /* and a check for garbage */ - for (pchan = pose->chanbase.first; pchan; pchan = next) { - next = pchan->next; - if (pchan->bone == NULL) { - BKE_pose_channel_free_ex(pchan, do_id_user); - BKE_pose_channels_hash_free(pose); - BLI_freelinkN(&pose->chanbase, pchan); - } - } + BKE_pose_channels_clear_with_null_bone(pose, do_id_user); - BKE_pose_channels_hash_make(pose); + BKE_pose_channels_hash_ensure(pose); for (pchan = pose->chanbase.first; pchan; pchan = pchan->next) { /* Find the custom B-Bone handles. */ @@ -2877,7 +2881,8 @@ bool BKE_pose_minmax(Object *ob, float r_min[3], float r_max[3], bool use_hidden NULL; if (bb_custom) { float mat[4][4], smat[4][4]; - scale_m4_fl(smat, PCHAN_CUSTOM_DRAW_SIZE(pchan)); + scale_m4_fl(smat, PCHAN_CUSTOM_BONE_LENGTH(pchan)); + rescale_m4(smat, pchan->custom_scale_xyz); mul_m4_series(mat, ob->obmat, pchan_tx->pose_mat, smat); BKE_boundbox_minmax(bb_custom, mat, r_min, r_max); } diff --git a/source/blender/blenkernel/intern/armature_update.c b/source/blender/blenkernel/intern/armature_update.c index 94c2755d4c6..4504f10967c 100644 --- a/source/blender/blenkernel/intern/armature_update.c +++ b/source/blender/blenkernel/intern/armature_update.c @@ -340,13 +340,22 @@ static int position_tail_on_spline(bSplineIKConstraint *ik_data, float isect_1[3], isect_2[3]; /* Calculate the intersection point. */ - isect_line_sphere_v3(prev_bp->vec, bp->vec, head_pos, sphere_radius, isect_1, isect_2); + int ret = isect_line_sphere_v3(prev_bp->vec, bp->vec, head_pos, sphere_radius, isect_1, isect_2); - /* Because of how `isect_line_sphere_v3` works, we know that `isect_1` contains the - * intersection point we want. And it will always intersect as we go from inside to outside - * of the sphere. - */ - copy_v3_v3(r_tail_pos, isect_1); + if (ret > 0) { + /* Because of how `isect_line_sphere_v3` works, we know that `isect_1` contains the + * intersection point we want. And it will always intersect as we go from inside to outside + * of the sphere. + */ + copy_v3_v3(r_tail_pos, isect_1); + } + else { + /* Couldn't find an intersection point. This means that the floating point + * values are too small and thus the intersection check fails. + * So assume that the distance is so small that tail_pos == head_pos. + */ + copy_v3_v3(r_tail_pos, head_pos); + } cur_seg_idx = bp_idx - 2; float prev_seg_len = 0; @@ -360,7 +369,7 @@ static int position_tail_on_spline(bSplineIKConstraint *ik_data, } /* Convert the point back into the 0-1 interpolation range. */ - const float isect_seg_len = len_v3v3(prev_bp->vec, isect_1); + const float isect_seg_len = len_v3v3(prev_bp->vec, r_tail_pos); const float frac = isect_seg_len / len_v3v3(prev_bp->vec, bp->vec); *r_new_curve_pos = (prev_seg_len + isect_seg_len) / spline_len; @@ -380,7 +389,7 @@ static void splineik_evaluate_bone( { bSplineIKConstraint *ik_data = tree->ik_data; - if (pchan->bone->length == 0.0f) { + if (pchan->bone->length < FLT_EPSILON) { /* Only move the bone position with zero length bones. */ float bone_pos[4], dir[3], rad; BKE_where_on_path(ik_data->tar, state->curve_position, bone_pos, dir, NULL, &rad, NULL); @@ -516,6 +525,25 @@ static void splineik_evaluate_bone( */ cross_v3_v3v3(raxis, rmat[1], spline_vec); + /* Check if the old and new bone direction is parallel to each other. + * If they are, then 'raxis' should be near zero and we will have to get the rotation axis in + * some other way. + */ + float norm = normalize_v3(raxis); + + if (norm < FLT_EPSILON) { + /* Can't use cross product! */ + int order[3] = {0, 1, 2}; + float tmp_axis[3]; + zero_v3(tmp_axis); + + axis_sort_v3(spline_vec, order); + + /* Use the second largest axis as the basis for the rotation axis. */ + tmp_axis[order[1]] = 1.0f; + cross_v3_v3v3(raxis, tmp_axis, spline_vec); + } + rangle = dot_v3v3(rmat[1], spline_vec); CLAMP(rangle, -1.0f, 1.0f); rangle = acosf(rangle); diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index 6c37d34dc9b..d36e9ed3e86 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -45,6 +45,7 @@ using blender::Set; using blender::StringRef; using blender::StringRefNull; using blender::fn::GMutableSpan; +using blender::fn::GSpan; namespace blender::bke { @@ -60,7 +61,7 @@ const blender::fn::CPPType *custom_data_type_to_cpp_type(const CustomDataType ty case CD_PROP_INT32: return &CPPType::get<int>(); case CD_PROP_COLOR: - return &CPPType::get<Color4f>(); + return &CPPType::get<ColorGeometry4f>(); case CD_PROP_BOOL: return &CPPType::get<bool>(); default: @@ -83,7 +84,7 @@ CustomDataType cpp_type_to_custom_data_type(const blender::fn::CPPType &type) if (type.is<int>()) { return CD_PROP_INT32; } - if (type.is<Color4f>()) { + if (type.is<ColorGeometry4f>()) { return CD_PROP_COLOR; } if (type.is<bool>()) { @@ -143,10 +144,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: @@ -184,6 +183,7 @@ AttributeDomain attribute_domain_highest_priority(Span<AttributeDomain> domains) void OutputAttribute::save() { + save_has_been_called_ = true; if (optional_span_varray_.has_value()) { optional_span_varray_->save(); } @@ -192,6 +192,15 @@ void OutputAttribute::save() } } +OutputAttribute::~OutputAttribute() +{ + if (!save_has_been_called_) { + if (varray_) { + std::cout << "Warning: Call `save()` to make sure that changes persist in all cases.\n"; + } + } +} + GVArrayPtr BuiltinCustomDataLayerProvider::try_get_for_read( const GeometryComponent &component) const { @@ -346,7 +355,7 @@ ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read( case CD_PROP_INT32: return this->layer_to_read_attribute<int>(layer, domain_size); case CD_PROP_COLOR: - return this->layer_to_read_attribute<Color4f>(layer, domain_size); + return this->layer_to_read_attribute<ColorGeometry4f>(layer, domain_size); case CD_PROP_BOOL: return this->layer_to_read_attribute<bool>(layer, domain_size); default: @@ -380,7 +389,7 @@ WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write( case CD_PROP_INT32: return this->layer_to_write_attribute<int>(layer, domain_size); case CD_PROP_COLOR: - return this->layer_to_write_attribute<Color4f>(layer, domain_size); + return this->layer_to_write_attribute<ColorGeometry4f>(layer, domain_size); case CD_PROP_BOOL: return this->layer_to_write_attribute<bool>(layer, domain_size); default: @@ -582,6 +591,105 @@ void NamedLegacyCustomDataProvider::foreach_domain( callback(domain_); } +CustomDataAttributes::CustomDataAttributes() +{ + CustomData_reset(&data); + size_ = 0; +} + +CustomDataAttributes::~CustomDataAttributes() +{ + CustomData_free(&data, size_); +} + +CustomDataAttributes::CustomDataAttributes(const CustomDataAttributes &other) +{ + size_ = other.size_; + CustomData_copy(&other.data, &data, CD_MASK_ALL, CD_DUPLICATE, size_); +} + +CustomDataAttributes::CustomDataAttributes(CustomDataAttributes &&other) +{ + size_ = other.size_; + data = other.data; + CustomData_reset(&other.data); +} + +std::optional<GSpan> CustomDataAttributes::get_for_read(const StringRef name) const +{ + BLI_assert(size_ != 0); + for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) { + if (layer.name == name) { + const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type); + BLI_assert(cpp_type != nullptr); + return GSpan(*cpp_type, layer.data, size_); + } + } + return {}; +} + +std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const StringRef name) +{ + BLI_assert(size_ != 0); + for (CustomDataLayer &layer : MutableSpan(data.layers, data.totlayer)) { + if (layer.name == name) { + const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type); + BLI_assert(cpp_type != nullptr); + return GMutableSpan(*cpp_type, layer.data, size_); + } + } + return {}; +} + +bool CustomDataAttributes::create(const StringRef name, const CustomDataType data_type) +{ + char name_c[MAX_NAME]; + name.copy(name_c); + void *result = CustomData_add_layer_named(&data, data_type, CD_DEFAULT, nullptr, size_, name_c); + return result != nullptr; +} + +bool CustomDataAttributes::create_by_move(const blender::StringRef name, + const CustomDataType data_type, + void *buffer) +{ + char name_c[MAX_NAME]; + name.copy(name_c); + void *result = CustomData_add_layer_named(&data, data_type, CD_ASSIGN, buffer, size_, name_c); + return result != nullptr; +} + +bool CustomDataAttributes::remove(const blender::StringRef name) +{ + bool result = false; + for (const int i : IndexRange(data.totlayer)) { + const CustomDataLayer &layer = data.layers[i]; + if (layer.name == name) { + CustomData_free_layer(&data, layer.type, size_, i); + result = true; + } + } + return result; +} + +void CustomDataAttributes::reallocate(const int size) +{ + size_ = size; + CustomData_realloc(&data, size); +} + +bool CustomDataAttributes::foreach_attribute(const AttributeForeachCallback callback, + const AttributeDomain domain) const +{ + for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) { + AttributeMetaData meta_data{domain, (CustomDataType)layer.type}; + if (!callback(layer.name, meta_data)) { + return false; + } + } + return true; +} + } // namespace blender::bke /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/intern/attribute_math.cc b/source/blender/blenkernel/intern/attribute_math.cc index 4ff3a6ceff5..5cdf329effb 100644 --- a/source/blender/blenkernel/intern/attribute_math.cc +++ b/source/blender/blenkernel/intern/attribute_math.cc @@ -18,18 +18,21 @@ namespace blender::attribute_math { -Color4fMixer::Color4fMixer(MutableSpan<Color4f> output_buffer, Color4f default_color) +ColorGeometryMixer::ColorGeometryMixer(MutableSpan<ColorGeometry4f> output_buffer, + ColorGeometry4f default_color) : buffer_(output_buffer), default_color_(default_color), total_weights_(output_buffer.size(), 0.0f) { - buffer_.fill(Color4f(0, 0, 0, 0)); + buffer_.fill(ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f)); } -void Color4fMixer::mix_in(const int64_t index, const Color4f &color, const float weight) +void ColorGeometryMixer::mix_in(const int64_t index, + const ColorGeometry4f &color, + const float weight) { BLI_assert(weight >= 0.0f); - Color4f &output_color = buffer_[index]; + ColorGeometry4f &output_color = buffer_[index]; output_color.r += color.r * weight; output_color.g += color.g * weight; output_color.b += color.b * weight; @@ -37,11 +40,11 @@ void Color4fMixer::mix_in(const int64_t index, const Color4f &color, const float total_weights_[index] += weight; } -void Color4fMixer::finalize() +void ColorGeometryMixer::finalize() { for (const int64_t i : buffer_.index_range()) { const float weight = total_weights_[i]; - Color4f &output_color = buffer_[i]; + ColorGeometry4f &output_color = buffer_[i]; if (weight > 0.0f) { const float weight_inv = 1.0f / weight; output_color.r *= weight_inv; diff --git a/source/blender/blenkernel/intern/blendfile.c b/source/blender/blenkernel/intern/blendfile.c index a61e7a2d1d8..54fd3f55c31 100644 --- a/source/blender/blenkernel/intern/blendfile.c +++ b/source/blender/blenkernel/intern/blendfile.c @@ -399,7 +399,8 @@ static void setup_app_data(bContext *C, BKE_lib_override_library_main_resync( bmain, curscene, - bfd->cur_view_layer ? bfd->cur_view_layer : BKE_view_layer_default_view(curscene)); + bfd->cur_view_layer ? bfd->cur_view_layer : BKE_view_layer_default_view(curscene), + reports); /* We need to rebuild some of the deleted override rules (for UI feedback purpose). */ BKE_lib_override_library_main_operations_create(bmain, true); } diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c index ef567044282..20c5af0efb6 100644 --- a/source/blender/blenkernel/intern/brush.c +++ b/source/blender/blenkernel/intern/brush.c @@ -989,6 +989,7 @@ void BKE_gpencil_brush_preset_set(Main *bmain, Brush *brush, const short type) brush->gpencil_settings->draw_smoothfac = 0.1f; brush->gpencil_settings->draw_smoothlvl = 1; brush->gpencil_settings->draw_subdivide = 1; + brush->gpencil_settings->dilate_pixels = 1; brush->gpencil_settings->flag |= GP_BRUSH_FILL_SHOW_EXTENDLINES; diff --git a/source/blender/blenkernel/intern/collection.c b/source/blender/blenkernel/intern/collection.c index 89bb7b36406..3170c3aa65c 100644 --- a/source/blender/blenkernel/intern/collection.c +++ b/source/blender/blenkernel/intern/collection.c @@ -1150,6 +1150,8 @@ bool BKE_collection_object_add(Main *bmain, Collection *collection, Object *ob) BKE_main_collection_sync(bmain); } + DEG_id_tag_update(&collection->id, ID_RECALC_GEOMETRY); + return true; } @@ -1201,6 +1203,8 @@ bool BKE_collection_object_remove(Main *bmain, BKE_main_collection_sync(bmain); } + DEG_id_tag_update(&collection->id, ID_RECALC_GEOMETRY); + return true; } @@ -1302,41 +1306,50 @@ static void collection_missing_parents_remove(Collection *collection) * * \note caller must ensure #BKE_main_collection_sync_remap() is called afterwards! * - * \param collection: may be \a NULL, + * \param parent_collection: The collection owning the pointers that were remapped. May be \a NULL, + * in which case whole \a bmain database of collections is checked. + * \param child_collection: The collection that was remapped to another pointer. May be \a NULL, * in which case whole \a bmain database of collections is checked. */ -void BKE_collections_child_remove_nulls(Main *bmain, Collection *collection) -{ - if (collection == NULL) { - /* We need to do the checks in two steps when more than one collection may be involved, - * otherwise we can miss some cases... - * Also, master collections are not in bmain, so we also need to loop over scenes. - */ - for (collection = bmain->collections.first; collection != NULL; - collection = collection->id.next) { - collection_null_children_remove(collection); - } - for (Scene *scene = bmain->scenes.first; scene != NULL; scene = scene->id.next) { - collection_null_children_remove(scene->master_collection); +void BKE_collections_child_remove_nulls(Main *bmain, + Collection *parent_collection, + Collection *child_collection) +{ + if (child_collection == NULL) { + if (parent_collection != NULL) { + collection_null_children_remove(parent_collection); + } + else { + /* We need to do the checks in two steps when more than one collection may be involved, + * otherwise we can miss some cases... + * Also, master collections are not in bmain, so we also need to loop over scenes. + */ + for (child_collection = bmain->collections.first; child_collection != NULL; + child_collection = child_collection->id.next) { + collection_null_children_remove(child_collection); + } + for (Scene *scene = bmain->scenes.first; scene != NULL; scene = scene->id.next) { + collection_null_children_remove(scene->master_collection); + } } - for (collection = bmain->collections.first; collection != NULL; - collection = collection->id.next) { - collection_missing_parents_remove(collection); + for (child_collection = bmain->collections.first; child_collection != NULL; + child_collection = child_collection->id.next) { + collection_missing_parents_remove(child_collection); } for (Scene *scene = bmain->scenes.first; scene != NULL; scene = scene->id.next) { collection_missing_parents_remove(scene->master_collection); } } else { - for (CollectionParent *parent = collection->parents.first, *parent_next; parent; + for (CollectionParent *parent = child_collection->parents.first, *parent_next; parent; parent = parent_next) { parent_next = parent->next; collection_null_children_remove(parent->collection); - if (!collection_find_child(parent->collection, collection)) { - BLI_freelinkN(&collection->parents, parent); + if (!collection_find_child(parent->collection, child_collection)) { + BLI_freelinkN(&child_collection->parents, parent); } } } diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c index 2ee030ca83f..9293a2b449a 100644 --- a/source/blender/blenkernel/intern/constraint.c +++ b/source/blender/blenkernel/intern/constraint.c @@ -1473,8 +1473,12 @@ static void followpath_get_tarmat(struct Depsgraph *UNUSED(depsgraph), * that's animated, but this will only work if it actually is animated... * * we divide the curvetime calculated in the previous step by the length of the path, - * to get a time factor, which then gets clamped to lie within 0.0 - 1.0 range. */ + * to get a time factor. */ curvetime /= cu->pathlen; + + if (cu->flag & CU_PATH_CLAMP) { + CLAMP(curvetime, 0.0f, 1.0f); + } } else { /* fixed position along curve */ @@ -2836,7 +2840,7 @@ static void actcon_get_tarmat(struct Depsgraph *depsgraph, * including rotation order, otherwise this fails. */ pchan = cob->pchan; - tchan = BKE_pose_channel_verify(&pose, pchan->name); + tchan = BKE_pose_channel_ensure(&pose, pchan->name); tchan->rotmode = pchan->rotmode; /* evaluate action using workob (it will only set the PoseChannel in question) */ diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc new file mode 100644 index 00000000000..9cafe1124b1 --- /dev/null +++ b/source/blender/blenkernel/intern/curve_eval.cc @@ -0,0 +1,272 @@ +/* + * 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_map.hh" +#include "BLI_span.hh" +#include "BLI_string_ref.hh" + +#include "DNA_curve_types.h" + +#include "BKE_curve.h" +#include "BKE_spline.hh" + +using blender::Array; +using blender::float3; +using blender::float4x4; +using blender::Map; +using blender::Span; +using blender::StringRefNull; + +blender::Span<SplinePtr> CurveEval::splines() const +{ + return splines_; +} + +blender::MutableSpan<SplinePtr> CurveEval::splines() +{ + return splines_; +} + +/** + * \warning Call #reallocate on the spline's attributes after adding all splines. + */ +void CurveEval::add_spline(SplinePtr spline) +{ + splines_.append(std::move(spline)); +} + +void CurveEval::remove_splines(blender::IndexMask mask) +{ + for (int i = mask.size() - 1; i >= 0; i--) { + splines_.remove_and_reorder(mask.indices()[i]); + } +} + +void CurveEval::translate(const float3 &translation) +{ + for (SplinePtr &spline : this->splines()) { + spline->translate(translation); + spline->mark_cache_invalid(); + } +} + +void CurveEval::transform(const float4x4 &matrix) +{ + for (SplinePtr &spline : this->splines()) { + spline->transform(matrix); + } +} + +void CurveEval::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated) const +{ + for (const SplinePtr &spline : this->splines()) { + spline->bounds_min_max(min, max, use_evaluated); + } +} + +/** + * Return the start indices for each of the curve spline's evaluated points, as if they were part + * of a flattened array. This can be used to facilitate parallelism by avoiding the need to + * accumulate an offset while doing more complex calculations. + * + * \note The result array is one longer than the spline count; the last element is the total size. + */ +blender::Array<int> CurveEval::control_point_offsets() const +{ + Array<int> offsets(splines_.size() + 1); + int offset = 0; + for (const int i : splines_.index_range()) { + offsets[i] = offset; + offset += splines_[i]->size(); + } + offsets.last() = offset; + return offsets; +} + +/** + * Exactly like #control_point_offsets, but uses the number of evaluated points instead. + */ +blender::Array<int> CurveEval::evaluated_point_offsets() const +{ + Array<int> offsets(splines_.size() + 1); + int offset = 0; + for (const int i : splines_.index_range()) { + offsets[i] = offset; + offset += splines_[i]->evaluated_points_size(); + } + offsets.last() = offset; + return offsets; +} + +static BezierSpline::HandleType handle_type_from_dna_bezt(const eBezTriple_Handle dna_handle_type) +{ + switch (dna_handle_type) { + case HD_FREE: + return BezierSpline::HandleType::Free; + case HD_AUTO: + return BezierSpline::HandleType::Auto; + case HD_VECT: + return BezierSpline::HandleType::Vector; + case HD_ALIGN: + return BezierSpline::HandleType::Align; + case HD_AUTO_ANIM: + return BezierSpline::HandleType::Auto; + case HD_ALIGN_DOUBLESIDE: + return BezierSpline::HandleType::Align; + } + BLI_assert_unreachable(); + return BezierSpline::HandleType::Auto; +} + +static Spline::NormalCalculationMode normal_mode_from_dna_curve(const int twist_mode) +{ + switch (twist_mode) { + case CU_TWIST_Z_UP: + return Spline::NormalCalculationMode::ZUp; + case CU_TWIST_MINIMUM: + return Spline::NormalCalculationMode::Minimum; + case CU_TWIST_TANGENT: + return Spline::NormalCalculationMode::Tangent; + } + BLI_assert_unreachable(); + return Spline::NormalCalculationMode::Minimum; +} + +static NURBSpline::KnotsMode knots_mode_from_dna_nurb(const short flag) +{ + switch (flag & (CU_NURB_ENDPOINT | CU_NURB_BEZIER)) { + case CU_NURB_ENDPOINT: + return NURBSpline::KnotsMode::EndPoint; + case CU_NURB_BEZIER: + return NURBSpline::KnotsMode::Bezier; + default: + return NURBSpline::KnotsMode::Normal; + } + + BLI_assert_unreachable(); + return NURBSpline::KnotsMode::Normal; +} + +std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve) +{ + std::unique_ptr<CurveEval> curve = std::make_unique<CurveEval>(); + + const ListBase *nurbs = BKE_curve_nurbs_get(&const_cast<Curve &>(dna_curve)); + + /* TODO: Optimize by reserving the correct points size. */ + LISTBASE_FOREACH (const Nurb *, nurb, nurbs) { + switch (nurb->type) { + case CU_BEZIER: { + std::unique_ptr<BezierSpline> spline = std::make_unique<BezierSpline>(); + spline->set_resolution(nurb->resolu); + spline->set_cyclic(nurb->flagu & CU_NURB_CYCLIC); + + for (const BezTriple &bezt : Span(nurb->bezt, nurb->pntsu)) { + spline->add_point(bezt.vec[1], + handle_type_from_dna_bezt((eBezTriple_Handle)bezt.h1), + bezt.vec[0], + handle_type_from_dna_bezt((eBezTriple_Handle)bezt.h2), + bezt.vec[2], + bezt.radius, + bezt.tilt); + } + spline->attributes.reallocate(spline->size()); + curve->add_spline(std::move(spline)); + break; + } + case CU_NURBS: { + std::unique_ptr<NURBSpline> spline = std::make_unique<NURBSpline>(); + spline->set_resolution(nurb->resolu); + spline->set_cyclic(nurb->flagu & CU_NURB_CYCLIC); + spline->set_order(nurb->orderu); + spline->knots_mode = knots_mode_from_dna_nurb(nurb->flagu); + + for (const BPoint &bp : Span(nurb->bp, nurb->pntsu)) { + spline->add_point(bp.vec, bp.radius, bp.tilt, bp.vec[3]); + } + spline->attributes.reallocate(spline->size()); + curve->add_spline(std::move(spline)); + break; + } + case CU_POLY: { + std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); + spline->set_cyclic(nurb->flagu & CU_NURB_CYCLIC); + + for (const BPoint &bp : Span(nurb->bp, nurb->pntsu)) { + spline->add_point(bp.vec, bp.radius, bp.tilt); + } + spline->attributes.reallocate(spline->size()); + curve->add_spline(std::move(spline)); + break; + } + default: { + BLI_assert_unreachable(); + break; + } + } + } + + /* Though the curve has no attributes, this is necessary to properly set the custom data size. */ + curve->attributes.reallocate(curve->splines().size()); + + /* 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; +} + +/** + * Check the invariants that curve control point attributes should always uphold, necessary + * because attributes are stored on splines rather than in a flat array on the curve: + * - The same set of attributes exists on every spline. + * - Attributes with the same name have the same type on every spline. + */ +void CurveEval::assert_valid_point_attributes() const +{ +#ifdef DEBUG + if (splines_.size() == 0) { + return; + } + const int layer_len = splines_.first()->attributes.data.totlayer; + Map<StringRefNull, AttributeMetaData> map; + for (const SplinePtr &spline : splines_) { + BLI_assert(spline->attributes.data.totlayer == layer_len); + spline->attributes.foreach_attribute( + [&](StringRefNull name, const AttributeMetaData &meta_data) { + map.add_or_modify( + name, + [&](AttributeMetaData *map_data) { + /* All unique attribute names should be added on the first spline. */ + BLI_assert(spline == splines_.first()); + *map_data = meta_data; + }, + [&](AttributeMetaData *map_data) { + /* Attributes on different splines should all have the same type. */ + BLI_assert(meta_data == *map_data); + }); + return true; + }, + ATTR_DOMAIN_POINT); + } +#endif +}
\ No newline at end of file diff --git a/source/blender/blenkernel/intern/curveprofile.c b/source/blender/blenkernel/intern/curveprofile.c index 752e0d4dfcf..00cdc7b3031 100644 --- a/source/blender/blenkernel/intern/curveprofile.c +++ b/source/blender/blenkernel/intern/curveprofile.c @@ -1015,7 +1015,6 @@ void BKE_curveprofile_create_samples_even_spacing(CurveProfile *profile, { const float total_length = BKE_curveprofile_total_length(profile); const float segment_length = total_length / n_segments; - float length_travelled = 0.0f; float distance_to_next_table_point = curveprofile_distance_to_next_table_point(profile, 0); float distance_to_previous_table_point = 0.0f; int i_table = 0; @@ -1029,7 +1028,6 @@ void BKE_curveprofile_create_samples_even_spacing(CurveProfile *profile, for (int i = 1; i < n_segments; i++) { /* Travel over all of the points that fit inside this segment. */ while (distance_to_next_table_point < segment_left) { - length_travelled += distance_to_next_table_point; segment_left -= distance_to_next_table_point; i_table++; distance_to_next_table_point = curveprofile_distance_to_next_table_point(profile, i_table); @@ -1057,7 +1055,6 @@ void BKE_curveprofile_create_samples_even_spacing(CurveProfile *profile, /* We sampled in between this table point and the next, so the next travel step is smaller. */ distance_to_next_table_point -= segment_left; distance_to_previous_table_point += segment_left; - length_travelled += segment_left; segment_left = segment_length; } } diff --git a/source/blender/blenkernel/intern/displist.c b/source/blender/blenkernel/intern/displist.cc index ad8939fa5d1..20534ef933b 100644 --- a/source/blender/blenkernel/intern/displist.c +++ b/source/blender/blenkernel/intern/displist.cc @@ -21,9 +21,9 @@ * \ingroup bke */ -#include <math.h> -#include <stdio.h> -#include <string.h> +#include <cmath> +#include <cstdio> +#include <cstring> #include "MEM_guardedalloc.h" @@ -82,7 +82,7 @@ void BKE_displist_free(ListBase *lb) { DispList *dl; - while ((dl = BLI_pophead(lb))) { + while ((dl = (DispList *)BLI_pophead(lb))) { BKE_displist_elem_free(dl); } } @@ -95,7 +95,7 @@ DispList *BKE_displist_find_or_create(ListBase *lb, int type) } } - DispList *dl = MEM_callocN(sizeof(DispList), "find_disp"); + DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), "find_disp"); dl->type = type; BLI_addtail(lb, dl); @@ -110,7 +110,7 @@ DispList *BKE_displist_find(ListBase *lb, int type) } } - return NULL; + return nullptr; } bool BKE_displist_has_faces(const ListBase *lb) @@ -129,11 +129,11 @@ void BKE_displist_copy(ListBase *lbn, const ListBase *lb) BKE_displist_free(lbn); LISTBASE_FOREACH (const DispList *, dl, lb) { - DispList *dln = MEM_dupallocN(dl); + DispList *dln = (DispList *)MEM_dupallocN(dl); BLI_addtail(lbn, dln); - dln->verts = MEM_dupallocN(dl->verts); - dln->nors = MEM_dupallocN(dl->nors); - dln->index = MEM_dupallocN(dl->index); + dln->verts = (float *)MEM_dupallocN(dl->verts); + dln->nors = (float *)MEM_dupallocN(dl->nors); + dln->index = (int *)MEM_dupallocN(dl->index); } } @@ -146,8 +146,8 @@ void BKE_displist_normals_add(ListBase *lb) LISTBASE_FOREACH (DispList *, dl, lb) { if (dl->type == DL_INDEX3) { - if (dl->nors == NULL) { - dl->nors = MEM_callocN(sizeof(float[3]), "dlnors"); + if (dl->nors == nullptr) { + dl->nors = (float *)MEM_callocN(sizeof(float[3]), "dlnors"); if (dl->flag & DL_BACK_CURVE) { dl->nors[2] = -1.0f; @@ -158,8 +158,8 @@ void BKE_displist_normals_add(ListBase *lb) } } else if (dl->type == DL_SURF) { - if (dl->nors == NULL) { - dl->nors = MEM_callocN(sizeof(float[3]) * dl->nr * dl->parts, "dlnors"); + if (dl->nors == nullptr) { + dl->nors = (float *)MEM_callocN(sizeof(float[3]) * dl->nr * dl->parts, "dlnors"); vdata = dl->verts; ndata = dl->nors; @@ -338,9 +338,9 @@ static void curve_to_displist(const Curve *cu, * and resolution > 1. */ const bool use_cyclic_sample = is_cyclic && (samples_len != 2); - DispList *dl = MEM_callocN(sizeof(DispList), __func__); + DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), __func__); /* Add one to the length because of 'BKE_curve_forward_diff_bezier'. */ - dl->verts = MEM_mallocN(sizeof(float[3]) * (samples_len + 1), "dlverts"); + dl->verts = (float *)MEM_mallocN(sizeof(float[3]) * (samples_len + 1), "dlverts"); BLI_addtail(r_dispbase, dl); dl->parts = 1; dl->nr = samples_len; @@ -393,8 +393,8 @@ static void curve_to_displist(const Curve *cu, } else if (nu->type == CU_NURBS) { const int len = (resolution * SEGMENTSU(nu)); - DispList *dl = MEM_callocN(sizeof(DispList), __func__); - dl->verts = MEM_mallocN(len * sizeof(float[3]), "dlverts"); + DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), __func__); + dl->verts = (float *)MEM_mallocN(len * sizeof(float[3]), "dlverts"); BLI_addtail(r_dispbase, dl); dl->parts = 1; dl->nr = len; @@ -402,12 +402,12 @@ static void curve_to_displist(const Curve *cu, dl->charidx = nu->charidx; dl->type = is_cyclic ? DL_POLY : DL_SEGM; - BKE_nurb_makeCurve(nu, dl->verts, NULL, NULL, NULL, resolution, sizeof(float[3])); + BKE_nurb_makeCurve(nu, dl->verts, nullptr, nullptr, nullptr, resolution, sizeof(float[3])); } else if (nu->type == CU_POLY) { const int len = nu->pntsu; - DispList *dl = MEM_callocN(sizeof(DispList), __func__); - dl->verts = MEM_mallocN(len * sizeof(float[3]), "dlverts"); + DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), __func__); + dl->verts = (float *)MEM_mallocN(len * sizeof(float[3]), "dlverts"); BLI_addtail(r_dispbase, dl); dl->parts = 1; dl->nr = len; @@ -435,7 +435,7 @@ void BKE_displist_fill(const ListBase *dispbase, const float normal_proj[3], const bool flip_normal) { - if (dispbase == NULL) { + if (dispbase == nullptr) { return; } if (BLI_listbase_is_empty(dispbase)) { @@ -471,14 +471,14 @@ void BKE_displist_fill(const ListBase *dispbase, sf_ctx.poly_nr++; /* Make verts and edges. */ - ScanFillVert *sf_vert = NULL; - ScanFillVert *sf_vert_last = NULL; - ScanFillVert *sf_vert_new = NULL; + ScanFillVert *sf_vert = nullptr; + ScanFillVert *sf_vert_last = nullptr; + ScanFillVert *sf_vert_new = nullptr; for (int i = 0; i < dl->nr; i++) { sf_vert_last = sf_vert; sf_vert = BLI_scanfill_vert_add(&sf_ctx, &dl->verts[3 * i]); totvert++; - if (sf_vert_last == NULL) { + if (sf_vert_last == nullptr) { sf_vert_new = sf_vert; } else { @@ -486,7 +486,7 @@ void BKE_displist_fill(const ListBase *dispbase, } } - if (sf_vert != NULL && sf_vert_new != NULL) { + if (sf_vert != nullptr && sf_vert_new != nullptr) { BLI_scanfill_edge_add(&sf_ctx, sf_vert, sf_vert_new); } } @@ -503,7 +503,7 @@ void BKE_displist_fill(const ListBase *dispbase, const int triangles_len = BLI_scanfill_calc_ex(&sf_ctx, scanfill_flag, normal_proj); if (totvert != 0 && triangles_len != 0) { - DispList *dlnew = MEM_callocN(sizeof(DispList), "filldisplist"); + DispList *dlnew = (DispList *)MEM_callocN(sizeof(DispList), "filldisplist"); dlnew->type = DL_INDEX3; dlnew->flag = (dl_flag_accum & (DL_BACK_CURVE | DL_FRONT_CURVE)); dlnew->rt = (dl_rt_accum & CU_SMOOTH); @@ -511,8 +511,8 @@ void BKE_displist_fill(const ListBase *dispbase, dlnew->nr = totvert; dlnew->parts = triangles_len; - dlnew->index = MEM_mallocN(sizeof(int[3]) * triangles_len, "dlindex"); - dlnew->verts = MEM_mallocN(sizeof(float[3]) * totvert, "dlverts"); + dlnew->index = (int *)MEM_mallocN(sizeof(int[3]) * triangles_len, "dlindex"); + dlnew->verts = (float *)MEM_mallocN(sizeof(float[3]) * totvert, "dlverts"); /* vert data */ int i; @@ -551,16 +551,16 @@ void BKE_displist_fill(const ListBase *dispbase, static void bevels_to_filledpoly(const Curve *cu, ListBase *dispbase) { - ListBase front = {NULL, NULL}; - ListBase back = {NULL, NULL}; + ListBase front = {nullptr, nullptr}; + ListBase back = {nullptr, nullptr}; LISTBASE_FOREACH (const DispList *, dl, dispbase) { if (dl->type == DL_SURF) { if ((dl->flag & DL_CYCL_V) && (dl->flag & DL_CYCL_U) == 0) { if ((cu->flag & CU_BACK) && (dl->flag & DL_BACK_CURVE)) { - DispList *dlnew = MEM_callocN(sizeof(DispList), __func__); + DispList *dlnew = (DispList *)MEM_callocN(sizeof(DispList), __func__); BLI_addtail(&front, dlnew); - dlnew->verts = MEM_mallocN(sizeof(float[3]) * dl->parts, __func__); + dlnew->verts = (float *)MEM_mallocN(sizeof(float[3]) * dl->parts, __func__); dlnew->nr = dl->parts; dlnew->parts = 1; dlnew->type = DL_POLY; @@ -577,9 +577,9 @@ static void bevels_to_filledpoly(const Curve *cu, ListBase *dispbase) } } if ((cu->flag & CU_FRONT) && (dl->flag & DL_FRONT_CURVE)) { - DispList *dlnew = MEM_callocN(sizeof(DispList), __func__); + DispList *dlnew = (DispList *)MEM_callocN(sizeof(DispList), __func__); BLI_addtail(&back, dlnew); - dlnew->verts = MEM_mallocN(sizeof(float[3]) * dl->parts, __func__); + dlnew->verts = (float *)MEM_mallocN(sizeof(float[3]) * dl->parts, __func__); dlnew->nr = dl->parts; dlnew->parts = 1; dlnew->type = DL_POLY; @@ -634,16 +634,16 @@ static float displist_calc_taper(Depsgraph *depsgraph, Object *taperobj, float fac) { - DispList *dl; - - if (taperobj == NULL || taperobj->type != OB_CURVE) { + if (taperobj == nullptr || taperobj->type != OB_CURVE) { return 1.0; } - dl = taperobj->runtime.curve_cache ? taperobj->runtime.curve_cache->disp.first : NULL; - if (dl == NULL) { + DispList *dl = taperobj->runtime.curve_cache ? + (DispList *)taperobj->runtime.curve_cache->disp.first : + nullptr; + if (dl == nullptr) { BKE_displist_make_curveTypes(depsgraph, scene, taperobj, false, false); - dl = taperobj->runtime.curve_cache->disp.first; + dl = (DispList *)taperobj->runtime.curve_cache->disp.first; } if (dl) { float minx, dx, *fp; @@ -693,7 +693,8 @@ void BKE_displist_make_mball(Depsgraph *depsgraph, Scene *scene, Object *ob) BKE_displist_free(&(ob->runtime.curve_cache->disp)); } else { - ob->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for MBall"); + ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), + "CurveCache for MBall"); } BKE_mball_polygonize(depsgraph, scene, ob, &ob->runtime.curve_cache->disp); @@ -738,9 +739,9 @@ static ModifierData *curve_get_tessellate_point(const Scene *scene, required_mode |= eModifierMode_Editmode; } - pretessellatePoint = NULL; + pretessellatePoint = nullptr; for (; md; md = md->next) { - const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); + const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type); if (!BKE_modifier_is_enabled(scene, md, required_mode)) { continue; @@ -777,22 +778,22 @@ bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph, VirtualModifierData virtualModifierData; ModifierData *md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData); ModifierData *pretessellatePoint; - Curve *cu = ob->data; + Curve *cu = (Curve *)ob->data; int numElems = 0, numVerts = 0; const bool editmode = (!for_render && (cu->editnurb || cu->editfont)); - ModifierApplyFlag apply_flag = 0; - float(*deformedVerts)[3] = NULL; - float *keyVerts = NULL; + ModifierApplyFlag apply_flag = (ModifierApplyFlag)0; + float(*deformedVerts)[3] = nullptr; + float *keyVerts = nullptr; int required_mode; bool modified = false; BKE_modifiers_clear_errors(ob); if (editmode) { - apply_flag |= MOD_APPLY_USECACHE; + apply_flag = MOD_APPLY_USECACHE; } if (for_render) { - apply_flag |= MOD_APPLY_RENDER; + apply_flag = MOD_APPLY_RENDER; required_mode = eModifierMode_Render; } else { @@ -823,7 +824,7 @@ bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph, if (pretessellatePoint) { for (; md; md = md->next) { - const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); + const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type); if (!BKE_modifier_is_enabled(scene, md, required_mode)) { continue; @@ -836,7 +837,7 @@ bool BKE_curve_calc_modifiers_pre(Depsgraph *depsgraph, deformedVerts = BKE_curve_nurbs_vert_coords_alloc(source_nurb, &numVerts); } - mti->deformVerts(md, &mectx, NULL, deformedVerts, numVerts); + mti->deformVerts(md, &mectx, nullptr, deformedVerts, numVerts); modified = true; if (md == pretessellatePoint) { @@ -869,7 +870,7 @@ static float (*displist_vert_coords_alloc(ListBase *dispbase, int *r_vert_len))[ *r_vert_len += (dl->type == DL_INDEX3) ? dl->nr : dl->parts * dl->nr; } - allverts = MEM_mallocN(sizeof(float[3]) * (*r_vert_len), "displist_vert_coords_alloc allverts"); + allverts = (float(*)[3])MEM_mallocN(sizeof(float[3]) * (*r_vert_len), __func__); fp = (float *)allverts; LISTBASE_FOREACH (DispList *, dl, dispbase) { int ofs = 3 * ((dl->type == DL_INDEX3) ? dl->nr : dl->parts * dl->nr); @@ -903,16 +904,16 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph, VirtualModifierData virtualModifierData; ModifierData *md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData); ModifierData *pretessellatePoint; - const Curve *cu = ob->data; + const Curve *cu = (const Curve *)ob->data; int required_mode = 0, totvert = 0; const bool editmode = (!for_render && (cu->editnurb || cu->editfont)); - Mesh *modified = NULL, *mesh_applied; - float(*vertCos)[3] = NULL; + Mesh *modified = nullptr, *mesh_applied; + float(*vertCos)[3] = nullptr; int useCache = !for_render; - ModifierApplyFlag apply_flag = 0; + ModifierApplyFlag apply_flag = (ModifierApplyFlag)0; if (for_render) { - apply_flag |= MOD_APPLY_RENDER; + apply_flag = MOD_APPLY_RENDER; required_mode = eModifierMode_Render; } else { @@ -920,9 +921,9 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph, } const ModifierEvalContext mectx_deform = { - depsgraph, ob, editmode ? apply_flag | MOD_APPLY_USECACHE : apply_flag}; + depsgraph, ob, editmode ? (ModifierApplyFlag)(apply_flag | MOD_APPLY_USECACHE) : apply_flag}; const ModifierEvalContext mectx_apply = { - depsgraph, ob, useCache ? apply_flag | MOD_APPLY_USECACHE : apply_flag}; + depsgraph, ob, useCache ? (ModifierApplyFlag)(apply_flag | MOD_APPLY_USECACHE) : apply_flag}; pretessellatePoint = curve_get_tessellate_point(scene, ob, for_render, editmode); @@ -935,22 +936,22 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph, } if (r_final && *r_final) { - BKE_id_free(NULL, *r_final); + BKE_id_free(nullptr, *r_final); } for (; md; md = md->next) { - const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); + const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type); if (!BKE_modifier_is_enabled(scene, md, required_mode)) { continue; } /* If we need normals, no choice, have to convert to mesh now. */ - bool need_normal = mti->dependsOnNormals != NULL && mti->dependsOnNormals(md); + bool need_normal = mti->dependsOnNormals != nullptr && mti->dependsOnNormals(md); /* XXX 2.8 : now that batch cache is stored inside the ob->data * we need to create a Mesh for each curve that uses modifiers. */ - if (modified == NULL /* && need_normal */) { - if (vertCos != NULL) { + if (modified == nullptr /* && need_normal */) { + if (vertCos != nullptr) { displist_vert_coords_apply(dispbase, vertCos); } @@ -976,7 +977,7 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph, if (!vertCos) { vertCos = displist_vert_coords_alloc(dispbase, &totvert); } - mti->deformVerts(md, &mectx_deform, NULL, vertCos, totvert); + mti->deformVerts(md, &mectx_deform, nullptr, vertCos, totvert); } } else { @@ -991,8 +992,8 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph, if (modified) { if (vertCos) { Mesh *temp_mesh = (Mesh *)BKE_id_copy_ex( - NULL, &modified->id, NULL, LIB_ID_COPY_LOCALIZE); - BKE_id_free(NULL, modified); + nullptr, &modified->id, nullptr, LIB_ID_COPY_LOCALIZE); + BKE_id_free(nullptr, modified); modified = temp_mesh; BKE_mesh_vert_coords_apply(modified, vertCos); @@ -1013,7 +1014,7 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph, if (vertCos) { /* Vertex coordinates were applied to necessary data, could free it */ MEM_freeN(vertCos); - vertCos = NULL; + vertCos = nullptr; } if (need_normal) { @@ -1025,7 +1026,7 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph, /* Modifier returned a new derived mesh */ if (modified && modified != mesh_applied) { /* Modifier */ - BKE_id_free(NULL, modified); + BKE_id_free(nullptr, modified); } modified = mesh_applied; } @@ -1034,8 +1035,9 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph, if (vertCos) { if (modified) { - Mesh *temp_mesh = (Mesh *)BKE_id_copy_ex(NULL, &modified->id, NULL, LIB_ID_COPY_LOCALIZE); - BKE_id_free(NULL, modified); + Mesh *temp_mesh = (Mesh *)BKE_id_copy_ex( + nullptr, &modified->id, nullptr, LIB_ID_COPY_LOCALIZE); + BKE_id_free(nullptr, modified); modified = temp_mesh; BKE_mesh_vert_coords_apply(modified, vertCos); @@ -1046,7 +1048,7 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph, else { displist_vert_coords_apply(dispbase, vertCos); MEM_freeN(vertCos); - vertCos = NULL; + vertCos = nullptr; } } @@ -1081,18 +1083,18 @@ static void curve_calc_modifiers_post(Depsgraph *depsgraph, MEM_SAFE_FREE(modified->mat); /* Set flag which makes it easier to see what's going on in a debugger. */ modified->id.tag |= LIB_TAG_COPIED_ON_WRITE_EVAL_RESULT; - modified->mat = MEM_dupallocN(cu->mat); + modified->mat = (Material **)MEM_dupallocN(cu->mat); modified->totcol = cu->totcol; (*r_final) = modified; } else { - (*r_final) = NULL; + (*r_final) = nullptr; } } - else if (modified != NULL) { + else if (modified != nullptr) { /* Pretty stupid to generate that whole mesh if it's unused, yet we have to free it. */ - BKE_id_free(NULL, modified); + BKE_id_free(nullptr, modified); } } @@ -1103,8 +1105,8 @@ static void displist_surf_indices(DispList *dl) dl->totindex = 0; - index = dl->index = MEM_mallocN(sizeof(int[4]) * (dl->parts + 1) * (dl->nr + 1), - "index array nurbs"); + index = dl->index = (int *)MEM_mallocN(sizeof(int[4]) * (dl->parts + 1) * (dl->nr + 1), + "index array nurbs"); for (a = 0; a < dl->parts; a++) { @@ -1136,8 +1138,8 @@ void BKE_displist_make_surf(Depsgraph *depsgraph, const bool for_render, const bool for_orco) { - ListBase nubase = {NULL, NULL}; - Curve *cu = ob->data; + ListBase nubase = {nullptr, nullptr}; + Curve *cu = (Curve *)ob->data; DispList *dl; float *data; int len; @@ -1174,8 +1176,8 @@ void BKE_displist_make_surf(Depsgraph *depsgraph, if (nu->pntsv == 1) { len = SEGMENTSU(nu) * resolu; - dl = MEM_callocN(sizeof(DispList), "makeDispListsurf"); - dl->verts = MEM_mallocN(len * sizeof(float[3]), "dlverts"); + dl = (DispList *)MEM_callocN(sizeof(DispList), "makeDispListsurf"); + dl->verts = (float *)MEM_mallocN(len * sizeof(float[3]), "dlverts"); BLI_addtail(dispbase, dl); dl->parts = 1; @@ -1195,13 +1197,13 @@ void BKE_displist_make_surf(Depsgraph *depsgraph, dl->type = DL_SEGM; } - BKE_nurb_makeCurve(nu, data, NULL, NULL, NULL, resolu, sizeof(float[3])); + BKE_nurb_makeCurve(nu, data, nullptr, nullptr, nullptr, resolu, sizeof(float[3])); } else { len = (nu->pntsu * resolu) * (nu->pntsv * resolv); - dl = MEM_callocN(sizeof(DispList), "makeDispListsurf"); - dl->verts = MEM_mallocN(len * sizeof(float[3]), "dlverts"); + dl = (DispList *)MEM_callocN(sizeof(DispList), "makeDispListsurf"); + dl->verts = (float *)MEM_mallocN(len * sizeof(float[3]), "dlverts"); BLI_addtail(dispbase, dl); dl->col = nu->mat_nr; @@ -1258,7 +1260,7 @@ static void rotateBevelPiece(const Curve *cu, vec[1] = fp[2]; vec[2] = 0.0; - if (nbevp == NULL) { + if (nbevp == nullptr) { copy_v3_v3(data, bevp->vec); copy_qt_qt(quat, bevp->quat); } @@ -1276,7 +1278,7 @@ static void rotateBevelPiece(const Curve *cu, else { float sina, cosa; - if (nbevp == NULL) { + if (nbevp == nullptr) { copy_v3_v3(data, bevp->vec); sina = bevp->sina; cosa = bevp->cosa; @@ -1307,8 +1309,8 @@ static void fillBevelCap(const Nurb *nu, { DispList *dl; - dl = MEM_callocN(sizeof(DispList), "makeDispListbev2"); - dl->verts = MEM_mallocN(sizeof(float[3]) * dlb->nr, "dlverts"); + dl = (DispList *)MEM_callocN(sizeof(DispList), "makeDispListbev2"); + dl->verts = (float *)MEM_mallocN(sizeof(float[3]) * dlb->nr, "dlverts"); memcpy(dl->verts, prev_fp, sizeof(float[3]) * dlb->nr); dl->type = DL_POLY; @@ -1469,7 +1471,7 @@ static void do_makeDispListCurveTypes(Depsgraph *depsgraph, const bool for_orco, Mesh **r_final) { - Curve *cu = ob->data; + Curve *cu = (Curve *)ob->data; /* we do allow duplis... this is only displist on curve level */ if (!ELEM(ob->type, OB_SURF, OB_CURVE, OB_FONT)) { @@ -1481,7 +1483,7 @@ static void do_makeDispListCurveTypes(Depsgraph *depsgraph, } else if (ELEM(ob->type, OB_CURVE, OB_FONT)) { ListBase dlbev; - ListBase nubase = {NULL, NULL}; + ListBase nubase = {nullptr, nullptr}; bool force_mesh_conversion = false; BKE_curve_bevelList_free(&ob->runtime.curve_cache->bev); @@ -1494,7 +1496,7 @@ static void do_makeDispListCurveTypes(Depsgraph *depsgraph, if (ob->runtime.curve_cache->anim_path_accum_length) { MEM_freeN((void *)ob->runtime.curve_cache->anim_path_accum_length); } - ob->runtime.curve_cache->anim_path_accum_length = NULL; + ob->runtime.curve_cache->anim_path_accum_length = nullptr; } if (ob->type == OB_FONT) { @@ -1520,8 +1522,8 @@ static void do_makeDispListCurveTypes(Depsgraph *depsgraph, } else { const float widfac = cu->width - 1.0f; - BevList *bl = ob->runtime.curve_cache->bev.first; - Nurb *nu = nubase.first; + BevList *bl = (BevList *)ob->runtime.curve_cache->bev.first; + Nurb *nu = (Nurb *)nubase.first; for (; bl && nu; bl = bl->next, nu = nu->next) { float *data; @@ -1532,8 +1534,8 @@ static void do_makeDispListCurveTypes(Depsgraph *depsgraph, /* exception handling; curve without bevel or extrude, with width correction */ if (BLI_listbase_is_empty(&dlbev)) { - DispList *dl = MEM_callocN(sizeof(DispList), "makeDispListbev"); - dl->verts = MEM_mallocN(sizeof(float[3]) * bl->nr, "dlverts"); + DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), "makeDispListbev"); + dl->verts = (float *)MEM_mallocN(sizeof(float[3]) * bl->nr, "dlverts"); BLI_addtail(dispbase, dl); if (bl->poly != -1) { @@ -1565,8 +1567,8 @@ static void do_makeDispListCurveTypes(Depsgraph *depsgraph, } } else { - ListBase bottom_capbase = {NULL, NULL}; - ListBase top_capbase = {NULL, NULL}; + ListBase bottom_capbase = {nullptr, nullptr}; + ListBase top_capbase = {nullptr, nullptr}; float bottom_no[3] = {0.0f}; float top_no[3] = {0.0f}; float first_blend = 0.0f, last_blend = 0.0f; @@ -1585,8 +1587,8 @@ static void do_makeDispListCurveTypes(Depsgraph *depsgraph, LISTBASE_FOREACH (DispList *, dlb, &dlbev) { /* for each part of the bevel use a separate displblock */ - DispList *dl = MEM_callocN(sizeof(DispList), "makeDispListbev1"); - dl->verts = data = MEM_mallocN(sizeof(float[3]) * dlb->nr * steps, "dlverts"); + DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), "makeDispListbev1"); + dl->verts = data = (float *)MEM_mallocN(sizeof(float[3]) * dlb->nr * steps, "dlverts"); BLI_addtail(dispbase, dl); dl->type = DL_SURF; @@ -1616,7 +1618,7 @@ static void do_makeDispListCurveTypes(Depsgraph *depsgraph, float radius_factor = 1.0; float *cur_data = data; - if (cu->taperobj == NULL) { + if (cu->taperobj == nullptr) { radius_factor = bevp->radius; } else { @@ -1666,7 +1668,7 @@ static void do_makeDispListCurveTypes(Depsgraph *depsgraph, cu, bevp, bevp - 1, dlb, 1.0f - last_blend, widfac, radius_factor, &data); } else { - rotateBevelPiece(cu, bevp, NULL, dlb, 0.0f, widfac, radius_factor, &data); + rotateBevelPiece(cu, bevp, nullptr, dlb, 0.0f, widfac, radius_factor, &data); } if ((cu->flag & CU_FILL_CAPS) && !(nu->flagu & CU_NURB_CYCLIC)) { @@ -1737,15 +1739,16 @@ void BKE_displist_make_curveTypes(Depsgraph *depsgraph, BKE_object_free_derived_caches(ob); if (!ob->runtime.curve_cache) { - ob->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for curve types"); + ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), + "CurveCache for curve types"); } dispbase = &(ob->runtime.curve_cache->disp); - Mesh *mesh_eval = NULL; + Mesh *mesh_eval = nullptr; do_makeDispListCurveTypes(depsgraph, scene, ob, dispbase, for_render, for_orco, &mesh_eval); - if (mesh_eval != NULL) { + if (mesh_eval != nullptr) { BKE_object_eval_assign_data(ob, &mesh_eval->id, true); } @@ -1759,8 +1762,9 @@ void BKE_displist_make_curveTypes_forRender(Depsgraph *depsgraph, Mesh **r_final, const bool for_orco) { - if (ob->runtime.curve_cache == NULL) { - ob->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for Curve"); + if (ob->runtime.curve_cache == nullptr) { + ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), + "CurveCache for Curve"); } do_makeDispListCurveTypes(depsgraph, scene, ob, dispbase, true, for_orco, r_final); @@ -1797,8 +1801,8 @@ static void boundbox_displist_object(Object *ob) */ /* object's BB is calculated from final displist */ - if (ob->runtime.bb == NULL) { - ob->runtime.bb = MEM_callocN(sizeof(BoundBox), "boundbox"); + if (ob->runtime.bb == nullptr) { + ob->runtime.bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), "boundbox"); } Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); diff --git a/source/blender/blenkernel/intern/dynamicpaint.c b/source/blender/blenkernel/intern/dynamicpaint.c index 4a25b0e9d98..42af3a391ed 100644 --- a/source/blender/blenkernel/intern/dynamicpaint.c +++ b/source/blender/blenkernel/intern/dynamicpaint.c @@ -1702,7 +1702,7 @@ static void dynamicPaint_setInitialColor(const Scene *scene, DynamicPaintSurface } for (int i = 0; i < totloop; i++) { - rgba_uchar_to_float(pPoint[mloop[i].v].color, (const unsigned char *)&col[mloop[i].v].r); + rgba_uchar_to_float(pPoint[mloop[i].v].color, (const unsigned char *)&col[i].r); } } else if (surface->format == MOD_DPAINT_SURFACE_F_IMAGESEQ) { diff --git a/source/blender/blenkernel/intern/fluid.c b/source/blender/blenkernel/intern/fluid.c index 851d8aae378..493a267c2f0 100644 --- a/source/blender/blenkernel/intern/fluid.c +++ b/source/blender/blenkernel/intern/fluid.c @@ -623,7 +623,8 @@ static void clamp_bounds_in_domain(FluidDomainSettings *fds, static bool is_static_object(Object *ob) { /* Check if the object has modifiers that might make the object "dynamic". */ - ModifierData *md = ob->modifiers.first; + VirtualModifierData virtualModifierData; + ModifierData *md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData); for (; md; md = md->next) { if (ELEM(md->type, eModifierType_Cloth, @@ -631,7 +632,8 @@ static bool is_static_object(Object *ob) eModifierType_Explode, eModifierType_Ocean, eModifierType_ShapeKey, - eModifierType_Softbody)) { + eModifierType_Softbody, + eModifierType_Nodes)) { return false; } } diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc new file mode 100644 index 00000000000..d08681da6ec --- /dev/null +++ b/source/blender/blenkernel/intern/geometry_component_curve.cc @@ -0,0 +1,1019 @@ +/* + * 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" + +using blender::fn::GMutableSpan; +using blender::fn::GSpan; +using blender::fn::GVArray_For_GSpan; +using blender::fn::GVArray_GSpan; +using blender::fn::GVMutableArray_For_GMutableSpan; + +/* -------------------------------------------------------------------- */ +/** \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_ = new CurveEval(*curve_); + new_component->ownership_ = GeometryOwnershipType::Owned; + } + return new_component; +} + +void CurveComponent::clear() +{ + BLI_assert(this->is_mutable()); + if (curve_ != nullptr) { + if (ownership_ == GeometryOwnershipType::Owned) { + delete curve_; + } + curve_ = nullptr; + } +} + +bool CurveComponent::has_curve() const +{ + return curve_ != nullptr; +} + +/* Clear the component and replace it with the new curve. */ +void CurveComponent::replace(CurveEval *curve, GeometryOwnershipType ownership) +{ + BLI_assert(this->is_mutable()); + this->clear(); + curve_ = curve; + ownership_ = ownership; +} + +CurveEval *CurveComponent::release() +{ + BLI_assert(this->is_mutable()); + CurveEval *curve = curve_; + curve_ = nullptr; + return curve; +} + +const CurveEval *CurveComponent::get_for_read() const +{ + return curve_; +} + +CurveEval *CurveComponent::get_for_write() +{ + BLI_assert(this->is_mutable()); + if (ownership_ == GeometryOwnershipType::ReadOnly) { + curve_ = new CurveEval(*curve_); + 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_ = new CurveEval(*curve_); + ownership_ = GeometryOwnershipType::Owned; + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Attribute Access Helper Functions + * \{ */ + +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; +} + +static CurveEval *get_curve_from_component_for_write(GeometryComponent &component) +{ + BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE); + CurveComponent &curve_component = static_cast<CurveComponent &>(component); + return curve_component.get_for_write(); +} + +static const CurveEval *get_curve_from_component_for_read(const GeometryComponent &component) +{ + BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE); + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + return curve_component.get_for_read(); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Builtin Spline Attributes + * + * Attributes with a value for every spline, stored contiguously or in every spline separately. + * \{ */ + +namespace blender::bke { + +class BuiltinSplineAttributeProvider final : public BuiltinAttributeProvider { + using AsReadAttribute = GVArrayPtr (*)(const CurveEval &data); + using AsWriteAttribute = GVMutableArrayPtr (*)(CurveEval &data); + 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 CurveEval *curve = get_curve_from_component_for_read(component); + if (curve == nullptr) { + return {}; + } + return as_read_attribute_(*curve); + } + + GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const final + { + if (writable_ != Writable) { + return {}; + } + CurveEval *curve = get_curve_from_component_for_write(component); + if (curve == nullptr) { + return {}; + } + return as_write_attribute_(*curve); + } + + bool try_delete(GeometryComponent &UNUSED(component)) const final + { + return false; + } + + bool try_create(GeometryComponent &UNUSED(component), + const AttributeInit &UNUSED(initializer)) const final + { + return false; + } + + bool exists(const GeometryComponent &component) const final + { + return component.attribute_domain_size(ATTR_DOMAIN_CURVE) != 0; + } +}; + +static int get_spline_resolution(const SplinePtr &spline) +{ + if (const BezierSpline *bezier_spline = dynamic_cast<const BezierSpline *>(spline.get())) { + return bezier_spline->resolution(); + } + if (const NURBSpline *nurb_spline = dynamic_cast<const NURBSpline *>(spline.get())) { + return nurb_spline->resolution(); + } + return 1; +} + +static void set_spline_resolution(SplinePtr &spline, const int resolution) +{ + if (BezierSpline *bezier_spline = dynamic_cast<BezierSpline *>(spline.get())) { + bezier_spline->set_resolution(std::max(resolution, 1)); + } + if (NURBSpline *nurb_spline = dynamic_cast<NURBSpline *>(spline.get())) { + nurb_spline->set_resolution(std::max(resolution, 1)); + } +} + +static GVArrayPtr make_resolution_read_attribute(const CurveEval &curve) +{ + return std::make_unique<fn::GVArray_For_DerivedSpan<SplinePtr, int, get_spline_resolution>>( + curve.splines()); +} + +static GVMutableArrayPtr make_resolution_write_attribute(CurveEval &curve) +{ + return std::make_unique<fn::GVMutableArray_For_DerivedSpan<SplinePtr, + int, + get_spline_resolution, + set_spline_resolution>>( + curve.splines()); +} + +static bool get_cyclic_value(const SplinePtr &spline) +{ + return spline->is_cyclic(); +} + +static void set_cyclic_value(SplinePtr &spline, const bool value) +{ + if (spline->is_cyclic() != value) { + spline->set_cyclic(value); + spline->mark_cache_invalid(); + } +} + +static GVArrayPtr make_cyclic_read_attribute(const CurveEval &curve) +{ + return std::make_unique<fn::GVArray_For_DerivedSpan<SplinePtr, bool, get_cyclic_value>>( + curve.splines()); +} + +static GVMutableArrayPtr make_cyclic_write_attribute(CurveEval &curve) +{ + return std::make_unique< + fn::GVMutableArray_For_DerivedSpan<SplinePtr, bool, get_cyclic_value, set_cyclic_value>>( + curve.splines()); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Builtin Control Point Attributes + * + * Attributes with a value for every control point. Most of the complexity here is due to the fact + * that we must provide access to the attribute data as if it was a contiguous array when it is + * really stored separately on each spline. That will be inherently rather slow, but these virtual + * array implementations try to make it workable in common situations. + * \{ */ + +namespace { +struct PointIndices { + int spline_index; + int point_index; +}; +} // namespace +static PointIndices lookup_point_indices(Span<int> offsets, const int index) +{ + const int spline_index = std::upper_bound(offsets.begin(), offsets.end(), index) - + offsets.begin() - 1; + const int index_in_spline = index - offsets[spline_index]; + return {spline_index, index_in_spline}; +} + +template<typename T> +static void point_attribute_materialize(Span<Span<T>> data, + Span<int> offsets, + const IndexMask mask, + MutableSpan<T> r_span) +{ + const int total_size = offsets.last(); + if (mask.is_range() && mask.as_range() == IndexRange(total_size)) { + for (const int spline_index : data.index_range()) { + const int offset = offsets[spline_index]; + const int next_offset = offsets[spline_index + 1]; + initialized_copy_n(data[spline_index].data(), next_offset - offset, r_span.data() + offset); + } + } + else { + int spline_index = 0; + for (const int i : r_span.index_range()) { + const int dst_index = mask[i]; + + while (offsets[spline_index] < dst_index) { + spline_index++; + } + + const int index_in_spline = dst_index - offsets[spline_index]; + r_span[dst_index] = data[spline_index][index_in_spline]; + } + } +} + +template<typename T> +static void point_attribute_materialize_to_uninitialized(Span<Span<T>> data, + Span<int> offsets, + const IndexMask mask, + MutableSpan<T> r_span) +{ + T *dst = r_span.data(); + const int total_size = offsets.last(); + if (mask.is_range() && mask.as_range() == IndexRange(total_size)) { + for (const int spline_index : data.index_range()) { + const int offset = offsets[spline_index]; + const int next_offset = offsets[spline_index + 1]; + uninitialized_copy_n(data[spline_index].data(), next_offset - offset, dst + offset); + } + } + else { + int spline_index = 0; + for (const int i : r_span.index_range()) { + const int dst_index = mask[i]; + + while (offsets[spline_index] < dst_index) { + spline_index++; + } + + const int index_in_spline = dst_index - offsets[spline_index]; + new (dst + dst_index) T(data[spline_index][index_in_spline]); + } + } +} + +/** + * Virtual array for any control point data accessed with spans and an offset array. + */ +template<typename T> class VArray_For_SplinePoints : public VArray<T> { + private: + const Array<Span<T>> data_; + Array<int> offsets_; + + public: + VArray_For_SplinePoints(Array<Span<T>> data, Array<int> offsets) + : VArray<T>(offsets.last()), data_(std::move(data)), offsets_(std::move(offsets)) + { + } + + T get_impl(const int64_t index) const final + { + const PointIndices indices = lookup_point_indices(offsets_, index); + return data_[indices.spline_index][indices.point_index]; + } + + void materialize_impl(const IndexMask mask, MutableSpan<T> r_span) const final + { + point_attribute_materialize(data_.as_span(), offsets_, mask, r_span); + } + + void materialize_to_uninitialized_impl(const IndexMask mask, MutableSpan<T> r_span) const final + { + point_attribute_materialize_to_uninitialized(data_.as_span(), offsets_, mask, r_span); + } +}; + +/** + * Mutable virtual array for any control point data accessed with spans and an offset array. + */ +template<typename T> class VMutableArray_For_SplinePoints final : public VMutableArray<T> { + private: + Array<MutableSpan<T>> data_; + Array<int> offsets_; + + public: + VMutableArray_For_SplinePoints(Array<MutableSpan<T>> data, Array<int> offsets) + : VMutableArray<T>(offsets.last()), data_(std::move(data)), offsets_(std::move(offsets)) + { + } + + T get_impl(const int64_t index) const final + { + const PointIndices indices = lookup_point_indices(offsets_, index); + return data_[indices.spline_index][indices.point_index]; + } + + void set_impl(const int64_t index, T value) final + { + const PointIndices indices = lookup_point_indices(offsets_, index); + data_[indices.spline_index][indices.point_index] = value; + } + + void set_all_impl(Span<T> src) final + { + for (const int spline_index : data_.index_range()) { + const int offset = offsets_[spline_index]; + const int next_offsets = offsets_[spline_index + 1]; + data_[spline_index].copy_from(src.slice(offset, next_offsets - offset)); + } + } + + void materialize_impl(const IndexMask mask, MutableSpan<T> r_span) const final + { + point_attribute_materialize({(Span<T> *)data_.data(), data_.size()}, offsets_, mask, r_span); + } + + void materialize_to_uninitialized_impl(const IndexMask mask, MutableSpan<T> r_span) const final + { + point_attribute_materialize_to_uninitialized( + {(Span<T> *)data_.data(), data_.size()}, offsets_, mask, r_span); + } +}; + +template<typename T> GVArrayPtr point_data_gvarray(Array<Span<T>> spans, Array<int> offsets) +{ + return std::make_unique<fn::GVArray_For_EmbeddedVArray<T, VArray_For_SplinePoints<T>>>( + offsets.last(), std::move(spans), std::move(offsets)); +} + +template<typename T> +GVMutableArrayPtr point_data_gvarray(Array<MutableSpan<T>> spans, Array<int> offsets) +{ + return std::make_unique< + fn::GVMutableArray_For_EmbeddedVMutableArray<T, VMutableArray_For_SplinePoints<T>>>( + offsets.last(), std::move(spans), std::move(offsets)); +} + +/** + * Virtual array implementation specifically for control point positions. This is only needed for + * Bezier splines, where adjusting the position also requires adjusting handle positions depending + * on handle types. We pay a small price for this when other spline types are mixed with Bezier. + * + * \note There is no need to check the handle type to avoid changing auto handles, since + * retrieving write access to the position data will mark them for recomputation anyway. + */ +class VMutableArray_For_SplinePosition final : public VMutableArray<float3> { + private: + MutableSpan<SplinePtr> splines_; + Array<int> offsets_; + + public: + VMutableArray_For_SplinePosition(MutableSpan<SplinePtr> splines, Array<int> offsets) + : VMutableArray<float3>(offsets.last()), splines_(splines), offsets_(std::move(offsets)) + { + } + + float3 get_impl(const int64_t index) const final + { + const PointIndices indices = lookup_point_indices(offsets_, index); + return splines_[indices.spline_index]->positions()[indices.point_index]; + } + + void set_impl(const int64_t index, float3 value) final + { + const PointIndices indices = lookup_point_indices(offsets_, index); + Spline &spline = *splines_[indices.spline_index]; + if (BezierSpline *bezier_spline = dynamic_cast<BezierSpline *>(&spline)) { + const float3 delta = value - bezier_spline->positions()[indices.point_index]; + bezier_spline->handle_positions_left()[indices.point_index] += delta; + bezier_spline->handle_positions_right()[indices.point_index] += delta; + bezier_spline->positions()[indices.point_index] = value; + } + else { + spline.positions()[indices.point_index] = value; + } + } + + void set_all_impl(Span<float3> src) final + { + for (const int spline_index : splines_.index_range()) { + Spline &spline = *splines_[spline_index]; + const int offset = offsets_[spline_index]; + const int next_offset = offsets_[spline_index + 1]; + if (BezierSpline *bezier_spline = dynamic_cast<BezierSpline *>(&spline)) { + MutableSpan<float3> positions = bezier_spline->positions(); + MutableSpan<float3> handle_positions_left = bezier_spline->handle_positions_left(); + MutableSpan<float3> handle_positions_right = bezier_spline->handle_positions_right(); + for (const int i : IndexRange(next_offset - offset)) { + const float3 delta = src[offset + i] - positions[i]; + handle_positions_left[i] += delta; + handle_positions_right[i] += delta; + positions[i] = src[offset + i]; + } + } + else { + spline.positions().copy_from(src.slice(offset, next_offset - offset)); + } + } + } + + /** Utility so we can pass positions to the materialize functions above. */ + Array<Span<float3>> get_position_spans() const + { + Array<Span<float3>> spans(splines_.size()); + for (const int i : spans.index_range()) { + spans[i] = splines_[i]->positions(); + } + return spans; + } + + void materialize_impl(const IndexMask mask, MutableSpan<float3> r_span) const final + { + Array<Span<float3>> spans = this->get_position_spans(); + point_attribute_materialize(spans.as_span(), offsets_, mask, r_span); + } + + void materialize_to_uninitialized_impl(const IndexMask mask, + MutableSpan<float3> r_span) const final + { + Array<Span<float3>> spans = this->get_position_spans(); + point_attribute_materialize_to_uninitialized(spans.as_span(), offsets_, mask, r_span); + } +}; + +/** + * Provider for any builtin control point attribute that doesn't need + * special handling like access to other arrays in the spline. + */ +template<typename T> class BuiltinPointAttributeProvider : public BuiltinAttributeProvider { + protected: + using GetSpan = Span<T> (*)(const Spline &spline); + using GetMutableSpan = MutableSpan<T> (*)(Spline &spline); + using UpdateOnWrite = void (*)(Spline &spline); + const GetSpan get_span_; + const GetMutableSpan get_mutable_span_; + const UpdateOnWrite update_on_write_; + + public: + BuiltinPointAttributeProvider(std::string attribute_name, + const WritableEnum writable, + const GetSpan get_span, + const GetMutableSpan get_mutable_span, + const UpdateOnWrite update_on_write) + : BuiltinAttributeProvider(std::move(attribute_name), + ATTR_DOMAIN_POINT, + bke::cpp_type_to_custom_data_type(CPPType::get<T>()), + BuiltinAttributeProvider::NonCreatable, + writable, + BuiltinAttributeProvider::NonDeletable), + get_span_(get_span), + get_mutable_span_(get_mutable_span), + update_on_write_(update_on_write) + { + } + + GVArrayPtr try_get_for_read(const GeometryComponent &component) const override + { + const CurveEval *curve = get_curve_from_component_for_read(component); + if (curve == nullptr) { + return {}; + } + + Span<SplinePtr> splines = curve->splines(); + if (splines.size() == 1) { + return std::make_unique<fn::GVArray_For_GSpan>(get_span_(*splines.first())); + } + + Array<int> offsets = curve->control_point_offsets(); + Array<Span<T>> spans(splines.size()); + for (const int i : splines.index_range()) { + spans[i] = get_span_(*splines[i]); + } + + return point_data_gvarray(spans, offsets); + } + + GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const override + { + CurveEval *curve = get_curve_from_component_for_write(component); + if (curve == nullptr) { + return {}; + } + + MutableSpan<SplinePtr> splines = curve->splines(); + if (splines.size() == 1) { + return std::make_unique<fn::GVMutableArray_For_GMutableSpan>( + get_mutable_span_(*splines.first())); + } + + Array<int> offsets = curve->control_point_offsets(); + Array<MutableSpan<T>> spans(splines.size()); + for (const int i : splines.index_range()) { + spans[i] = get_mutable_span_(*splines[i]); + if (update_on_write_) { + update_on_write_(*splines[i]); + } + } + + return point_data_gvarray(spans, offsets); + } + + 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; + } +}; + +/** + * Special attribute provider for the position attribute. Keeping this separate means we don't + * need to make #BuiltinPointAttributeProvider overly generic, and the special handling for the + * positions is more clear. + */ +class PositionAttributeProvider final : public BuiltinPointAttributeProvider<float3> { + public: + PositionAttributeProvider() + : BuiltinPointAttributeProvider( + "position", + BuiltinAttributeProvider::Writable, + [](const Spline &spline) { return spline.positions(); }, + [](Spline &spline) { return spline.positions(); }, + [](Spline &spline) { spline.mark_cache_invalid(); }) + { + } + + GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const final + { + CurveEval *curve = get_curve_from_component_for_write(component); + if (curve == nullptr) { + return {}; + } + + bool curve_has_bezier_spline = false; + for (SplinePtr &spline : curve->splines()) { + if (spline->type() == Spline::Type::Bezier) { + curve_has_bezier_spline = true; + break; + } + } + + /* Use the regular position virtual array when there aren't any Bezier splines + * to avoid the overhead of checking the spline type for every point. */ + if (!curve_has_bezier_spline) { + return BuiltinPointAttributeProvider<float3>::try_get_for_write(component); + } + + /* Changing the positions requires recalculation of cached evaluated data in many cases. + * This could set more specific flags in the future to avoid unnecessary recomputation. */ + for (SplinePtr &spline : curve->splines()) { + spline->mark_cache_invalid(); + } + + Array<int> offsets = curve->control_point_offsets(); + return std::make_unique< + fn::GVMutableArray_For_EmbeddedVMutableArray<float3, VMutableArray_For_SplinePosition>>( + offsets.last(), curve->splines(), std::move(offsets)); + } +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Dynamic Control Point Attributes + * + * The dynamic control point attribute implementation is very similar to the builtin attribute + * implementation-- it uses the same virtual array types. In order to work, this code depends on + * the fact that all a curve's splines will have the same attributes and they all have the same + * type. + * \{ */ + +class DynamicPointAttributeProvider final : public DynamicAttributesProvider { + private: + static constexpr uint64_t supported_types_mask = CD_MASK_PROP_FLOAT | CD_MASK_PROP_FLOAT2 | + CD_MASK_PROP_FLOAT3 | CD_MASK_PROP_INT32 | + CD_MASK_PROP_COLOR | CD_MASK_PROP_BOOL; + + public: + ReadAttributeLookup try_get_for_read(const GeometryComponent &component, + const StringRef attribute_name) const final + { + const CurveEval *curve = get_curve_from_component_for_read(component); + if (curve == nullptr || curve->splines().size() == 0) { + return {}; + } + + Span<SplinePtr> splines = curve->splines(); + Vector<GSpan> spans; /* GSpan has no default constructor. */ + spans.reserve(splines.size()); + std::optional<GSpan> first_span = splines[0]->attributes.get_for_read(attribute_name); + if (!first_span) { + return {}; + } + spans.append(*first_span); + for (const int i : IndexRange(1, splines.size() - 1)) { + std::optional<GSpan> span = splines[i]->attributes.get_for_read(attribute_name); + if (!span) { + /* All splines should have the same set of data layers. It would be possible to recover + * here and return partial data instead, but that would add a lot of complexity for a + * situation we don't even expect to encounter. */ + BLI_assert_unreachable(); + return {}; + } + if (span->type() != spans.last().type()) { + /* Data layer types on separate splines do not match. */ + BLI_assert_unreachable(); + return {}; + } + spans.append(*span); + } + + /* First check for the simpler situation when we can return a simpler span virtual array. */ + if (spans.size() == 1) { + return {std::make_unique<GVArray_For_GSpan>(spans.first()), ATTR_DOMAIN_POINT}; + } + + ReadAttributeLookup attribute = {}; + Array<int> offsets = curve->control_point_offsets(); + attribute_math::convert_to_static_type(spans[0].type(), [&](auto dummy) { + using T = decltype(dummy); + Array<Span<T>> data(splines.size()); + for (const int i : splines.index_range()) { + data[i] = spans[i].typed<T>(); + BLI_assert(data[i].data() != nullptr); + } + attribute = {point_data_gvarray(data, offsets), ATTR_DOMAIN_POINT}; + }); + return attribute; + } + + /* This function is almost the same as #try_get_for_read, but without const. */ + WriteAttributeLookup try_get_for_write(GeometryComponent &component, + const StringRef attribute_name) const final + { + CurveEval *curve = get_curve_from_component_for_write(component); + if (curve == nullptr || curve->splines().size() == 0) { + return {}; + } + + MutableSpan<SplinePtr> splines = curve->splines(); + Vector<GMutableSpan> spans; /* GMutableSpan has no default constructor. */ + spans.reserve(splines.size()); + std::optional<GMutableSpan> first_span = splines[0]->attributes.get_for_write(attribute_name); + if (!first_span) { + return {}; + } + spans.append(*first_span); + for (const int i : IndexRange(1, splines.size() - 1)) { + std::optional<GMutableSpan> span = splines[i]->attributes.get_for_write(attribute_name); + if (!span) { + /* All splines should have the same set of data layers. It would be possible to recover + * here and return partial data instead, but that would add a lot of complexity for a + * situation we don't even expect to encounter. */ + BLI_assert_unreachable(); + return {}; + } + if (span->type() != spans.last().type()) { + /* Data layer types on separate splines do not match. */ + BLI_assert_unreachable(); + return {}; + } + spans.append(*span); + } + + /* First check for the simpler situation when we can return a simpler span virtual array. */ + if (spans.size() == 1) { + return {std::make_unique<GVMutableArray_For_GMutableSpan>(spans.first()), ATTR_DOMAIN_POINT}; + } + + WriteAttributeLookup attribute = {}; + Array<int> offsets = curve->control_point_offsets(); + attribute_math::convert_to_static_type(spans[0].type(), [&](auto dummy) { + using T = decltype(dummy); + Array<MutableSpan<T>> data(splines.size()); + for (const int i : splines.index_range()) { + data[i] = spans[i].typed<T>(); + BLI_assert(data[i].data() != nullptr); + } + attribute = {point_data_gvarray(data, offsets), ATTR_DOMAIN_POINT}; + }); + return attribute; + } + + bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final + { + CurveEval *curve = get_curve_from_component_for_write(component); + if (curve == nullptr) { + return false; + } + + bool layer_freed = false; + for (SplinePtr &spline : curve->splines()) { + spline->attributes.remove(attribute_name); + } + return layer_freed; + } + + static GVArrayPtr varray_from_initializer(const AttributeInit &initializer, + const CustomDataType data_type, + const int total_size) + { + switch (initializer.type) { + case AttributeInit::Type::Default: + /* This function shouldn't be called in this case, since there + * is no need to copy anything to the new custom data array. */ + BLI_assert_unreachable(); + return {}; + case AttributeInit::Type::VArray: + return static_cast<const AttributeInitVArray &>(initializer).varray->shallow_copy(); + case AttributeInit::Type::MoveArray: + return std::make_unique<fn::GVArray_For_GSpan>( + GSpan(*bke::custom_data_type_to_cpp_type(data_type), + static_cast<const AttributeInitMove &>(initializer).data, + total_size)); + } + BLI_assert_unreachable(); + return {}; + } + + bool try_create(GeometryComponent &component, + const StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type, + const AttributeInit &initializer) const final + { + BLI_assert(this->type_is_supported(data_type)); + if (domain != ATTR_DOMAIN_POINT) { + return false; + } + CurveEval *curve = get_curve_from_component_for_write(component); + if (curve == nullptr || curve->splines().size() == 0) { + return false; + } + + MutableSpan<SplinePtr> splines = curve->splines(); + + /* First check the one case that allows us to avoid copying the input data. */ + if (splines.size() == 1 && initializer.type == AttributeInit::Type::MoveArray) { + void *source_data = static_cast<const AttributeInitMove &>(initializer).data; + if (!splines[0]->attributes.create_by_move(attribute_name, data_type, source_data)) { + MEM_freeN(source_data); + return false; + } + return true; + } + + /* Otherwise just create a custom data layer on each of the splines. */ + for (const int i : splines.index_range()) { + if (!splines[i]->attributes.create(attribute_name, data_type)) { + /* If attribute creation fails on one of the splines, we cannot leave the custom data + * layers in the previous splines around, so delete them before returning. However, + * this is not an expected case. */ + BLI_assert_unreachable(); + return false; + } + } + + /* With a default initializer type, we can keep the values at their initial values. */ + if (initializer.type == AttributeInit::Type::Default) { + return true; + } + + WriteAttributeLookup write_attribute = this->try_get_for_write(component, attribute_name); + /* We just created the attribute, it should exist. */ + BLI_assert(write_attribute); + + const int total_size = curve->control_point_offsets().last(); + GVArrayPtr source_varray = varray_from_initializer(initializer, data_type, total_size); + /* TODO: When we can call a variant of #set_all with a virtual array argument, + * this theoretically unnecessary materialize step could be removed. */ + GVArray_GSpan source_varray_span{*source_varray}; + write_attribute.varray->set_all(source_varray_span.data()); + + if (initializer.type == AttributeInit::Type::MoveArray) { + MEM_freeN(static_cast<const AttributeInitMove &>(initializer).data); + } + + return true; + } + + bool foreach_attribute(const GeometryComponent &component, + const AttributeForeachCallback callback) const final + { + const CurveEval *curve = get_curve_from_component_for_read(component); + if (curve == nullptr || curve->splines().size() == 0) { + return false; + } + + Span<SplinePtr> splines = curve->splines(); + + /* In a debug build, check that all corresponding custom data layers have the same type. */ + curve->assert_valid_point_attributes(); + + /* Use the first spline as a representative for all the others. */ + splines.first()->attributes.foreach_attribute(callback, ATTR_DOMAIN_POINT); + + return true; + } + + void foreach_domain(const FunctionRef<void(AttributeDomain)> callback) const final + { + callback(ATTR_DOMAIN_POINT); + } + + bool type_is_supported(CustomDataType data_type) const + { + return ((1ULL << data_type) & supported_types_mask) != 0; + } +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Attribute Provider Declaration + * \{ */ + +/** + * In this function all the attribute providers for a curve component are created. + * Most data in this function is statically allocated, because it does not change over time. + */ +static ComponentAttributeProviders create_attribute_providers_for_curve() +{ + static BuiltinSplineAttributeProvider resolution("resolution", + CD_PROP_INT32, + BuiltinAttributeProvider::Writable, + make_resolution_read_attribute, + make_resolution_write_attribute); + + static BuiltinSplineAttributeProvider cyclic("cyclic", + CD_PROP_BOOL, + BuiltinAttributeProvider::Writable, + make_cyclic_read_attribute, + make_cyclic_write_attribute); + + static CustomDataAccessInfo spline_custom_data_access = { + [](GeometryComponent &component) -> CustomData * { + CurveEval *curve = get_curve_from_component_for_write(component); + return curve ? &curve->attributes.data : nullptr; + }, + [](const GeometryComponent &component) -> const CustomData * { + const CurveEval *curve = get_curve_from_component_for_read(component); + return curve ? &curve->attributes.data : nullptr; + }, + nullptr}; + + static CustomDataAttributeProvider spline_custom_data(ATTR_DOMAIN_CURVE, + spline_custom_data_access); + + static PositionAttributeProvider position; + + static BuiltinPointAttributeProvider<float> radius( + "radius", + BuiltinAttributeProvider::Writable, + [](const Spline &spline) { return spline.radii(); }, + [](Spline &spline) { return spline.radii(); }, + nullptr); + + static BuiltinPointAttributeProvider<float> tilt( + "tilt", + BuiltinAttributeProvider::Writable, + [](const Spline &spline) { return spline.tilts(); }, + [](Spline &spline) { return spline.tilts(); }, + [](Spline &spline) { spline.mark_cache_invalid(); }); + + static DynamicPointAttributeProvider point_custom_data; + + return ComponentAttributeProviders({&position, &radius, &tilt, &resolution, &cyclic}, + {&spline_custom_data, &point_custom_data}); +} + +} // namespace blender::bke + +const blender::bke::ComponentAttributeProviders *CurveComponent::get_attribute_providers() const +{ + static blender::bke::ComponentAttributeProviders providers = + blender::bke::create_attribute_providers_for_curve(); + return &providers; +} + +/** \} */ diff --git a/source/blender/blenkernel/intern/geometry_component_instances.cc b/source/blender/blenkernel/intern/geometry_component_instances.cc index 11526eda762..3b1b7456162 100644 --- a/source/blender/blenkernel/intern/geometry_component_instances.cc +++ b/source/blender/blenkernel/intern/geometry_component_instances.cc @@ -42,70 +42,104 @@ InstancesComponent::InstancesComponent() : GeometryComponent(GEO_COMPONENT_TYPE_ GeometryComponent *InstancesComponent::copy() const { InstancesComponent *new_component = new InstancesComponent(); - new_component->transforms_ = transforms_; - new_component->instanced_data_ = instanced_data_; + new_component->instance_reference_handles_ = instance_reference_handles_; + new_component->instance_transforms_ = instance_transforms_; + new_component->instance_ids_ = instance_ids_; + new_component->references_ = references_; return new_component; } +void InstancesComponent::reserve(int min_capacity) +{ + instance_reference_handles_.reserve(min_capacity); + instance_transforms_.reserve(min_capacity); + instance_ids_.reserve(min_capacity); +} + +/** + * Resize the transform, handles, and ID vectors to the specified capacity. + * + * \note This function should be used carefully, only when it's guaranteed + * that the data will be filled. + */ +void InstancesComponent::resize(int capacity) +{ + instance_reference_handles_.resize(capacity); + instance_transforms_.resize(capacity); + instance_ids_.resize(capacity); +} + void InstancesComponent::clear() { - instanced_data_.clear(); - transforms_.clear(); + instance_reference_handles_.clear(); + instance_transforms_.clear(); + instance_ids_.clear(); + + references_.clear(); } -void InstancesComponent::add_instance(Object *object, float4x4 transform, const int id) +void InstancesComponent::add_instance(const int instance_handle, + const float4x4 &transform, + const int id) { - InstancedData data; - data.type = INSTANCE_DATA_TYPE_OBJECT; - data.data.object = object; - this->add_instance(data, transform, id); + BLI_assert(instance_handle >= 0); + BLI_assert(instance_handle < references_.size()); + instance_reference_handles_.append(instance_handle); + instance_transforms_.append(transform); + instance_ids_.append(id); } -void InstancesComponent::add_instance(Collection *collection, float4x4 transform, const int id) +blender::Span<int> InstancesComponent::instance_reference_handles() const { - InstancedData data; - data.type = INSTANCE_DATA_TYPE_COLLECTION; - data.data.collection = collection; - this->add_instance(data, transform, id); + return instance_reference_handles_; } -void InstancesComponent::add_instance(InstancedData data, float4x4 transform, const int id) +blender::MutableSpan<int> InstancesComponent::instance_reference_handles() { - instanced_data_.append(data); - transforms_.append(transform); - ids_.append(id); + return instance_reference_handles_; } -Span<InstancedData> InstancesComponent::instanced_data() const +blender::MutableSpan<blender::float4x4> InstancesComponent::instance_transforms() +{ + return instance_transforms_; +} +blender::Span<blender::float4x4> InstancesComponent::instance_transforms() const { - return instanced_data_; + return instance_transforms_; } -Span<float4x4> InstancesComponent::transforms() const +blender::MutableSpan<int> InstancesComponent::instance_ids() { - return transforms_; + return instance_ids_; +} +blender::Span<int> InstancesComponent::instance_ids() const +{ + return instance_ids_; } -Span<int> InstancesComponent::ids() const +/** + * Returns a handle for the given reference. + * If the reference exists already, the handle of the existing reference is returned. + * Otherwise a new handle is added. + */ +int InstancesComponent::add_reference(InstanceReference reference) { - return ids_; + return references_.index_of_or_add_as(reference); } -MutableSpan<float4x4> InstancesComponent::transforms() +blender::Span<InstanceReference> InstancesComponent::references() const { - return transforms_; + return references_; } int InstancesComponent::instances_amount() const { - const int size = instanced_data_.size(); - BLI_assert(transforms_.size() == size); - return size; + return instance_transforms_.size(); } bool InstancesComponent::is_empty() const { - return transforms_.size() == 0; + return this->instance_reference_handles_.size() == 0; } bool InstancesComponent::owns_direct_data() const @@ -176,8 +210,8 @@ static blender::Array<int> generate_unique_instance_ids(Span<int> original_ids) blender::Span<int> InstancesComponent::almost_unique_ids() const { std::lock_guard lock(almost_unique_ids_mutex_); - if (almost_unique_ids_.size() != ids_.size()) { - almost_unique_ids_ = generate_unique_instance_ids(ids_); + if (almost_unique_ids_.size() != instance_ids_.size()) { + almost_unique_ids_ = generate_unique_instance_ids(instance_ids_); } return almost_unique_ids_; } diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc index e54c3716660..42f3a854aec 100644 --- a/source/blender/blenkernel/intern/geometry_component_mesh.cc +++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc @@ -219,8 +219,7 @@ static void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh, static GVArrayPtr adapt_mesh_domain_corner_to_point(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; - const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { /* We compute all interpolated values at once, because for this interpolation, one has to @@ -249,8 +248,7 @@ static void adapt_mesh_domain_point_to_corner_impl(const Mesh &mesh, static GVArrayPtr adapt_mesh_domain_point_to_corner(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; - const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); /* It is not strictly necessary to compute the value for all corners here. Instead one could * lazily lookup the mesh topology when a specific index accessed. This can be more efficient @@ -290,8 +288,7 @@ static void adapt_mesh_domain_corner_to_face_impl(const Mesh &mesh, static GVArrayPtr adapt_mesh_domain_corner_to_face(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; - const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totpoly); @@ -329,8 +326,7 @@ static void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh, static GVArrayPtr adapt_mesh_domain_corner_to_edge(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; - const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totedge); @@ -365,8 +361,7 @@ void adapt_mesh_domain_face_to_point_impl(const Mesh &mesh, static GVArrayPtr adapt_mesh_domain_face_to_point(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; - const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totvert); @@ -394,8 +389,7 @@ void adapt_mesh_domain_face_to_corner_impl(const Mesh &mesh, static GVArrayPtr adapt_mesh_domain_face_to_corner(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; - const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totloop); @@ -428,8 +422,7 @@ void adapt_mesh_domain_face_to_edge_impl(const Mesh &mesh, static GVArrayPtr adapt_mesh_domain_face_to_edge(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; - const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totedge); @@ -467,8 +460,7 @@ static void adapt_mesh_domain_point_to_face_impl(const Mesh &mesh, static GVArrayPtr adapt_mesh_domain_point_to_face(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; - const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totpoly); @@ -504,8 +496,7 @@ static void adapt_mesh_domain_point_to_edge_impl(const Mesh &mesh, static GVArrayPtr adapt_mesh_domain_point_to_edge(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; - const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totedge); @@ -529,7 +520,7 @@ void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh, /* For every corner, mix the values from the adjacent edges on the face. */ for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { - const int loop_index_prev = (loop_index - 1) % poly.totloop; + const int loop_index_prev = loop_index - 1 + (loop_index == poly.loopstart) * poly.totloop; const MLoop &loop = mesh.mloop[loop_index]; const MLoop &loop_prev = mesh.mloop[loop_index_prev]; mixer.mix_in(loop_index, old_values[loop.e]); @@ -543,8 +534,7 @@ void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh, static GVArrayPtr adapt_mesh_domain_edge_to_corner(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; - const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totloop); @@ -576,8 +566,7 @@ static void adapt_mesh_domain_edge_to_point_impl(const Mesh &mesh, static GVArrayPtr adapt_mesh_domain_edge_to_point(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; - const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totvert); @@ -615,8 +604,7 @@ static void adapt_mesh_domain_edge_to_face_impl(const Mesh &mesh, static GVArrayPtr adapt_mesh_domain_edge_to_face(const Mesh &mesh, GVArrayPtr varray) { GVArrayPtr new_varray; - const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type()); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totpoly); @@ -785,18 +773,20 @@ static void set_loop_uv(MLoopUV &uv, float2 co) copy_v2_v2(uv.uv, co); } -static Color4f get_loop_color(const MLoopCol &col) +static ColorGeometry4f get_loop_color(const MLoopCol &col) { - Color4f srgb_color; - rgba_uchar_to_float(srgb_color, &col.r); - Color4f linear_color; - srgb_to_linearrgb_v4(linear_color, srgb_color); + ColorGeometry4b encoded_color = ColorGeometry4b(col.r, col.g, col.b, col.a); + ColorGeometry4f linear_color = encoded_color.decode(); return linear_color; } -static void set_loop_color(MLoopCol &col, Color4f linear_color) +static void set_loop_color(MLoopCol &col, ColorGeometry4f linear_color) { - linearrgb_to_srgb_uchar4(&col.r, linear_color); + ColorGeometry4b encoded_color = linear_color.encode(); + col.r = encoded_color.r; + col.g = encoded_color.g; + col.b = encoded_color.b; + col.a = encoded_color.a; } static float get_crease(const MEdge &edge) @@ -1133,8 +1123,8 @@ static ComponentAttributeProviders create_attribute_providers_for_mesh() CD_PROP_COLOR, CD_MLOOPCOL, corner_access, - make_derived_read_attribute<MLoopCol, Color4f, get_loop_color>, - make_derived_write_attribute<MLoopCol, Color4f, get_loop_color, set_loop_color>); + make_derived_read_attribute<MLoopCol, ColorGeometry4f, get_loop_color>, + make_derived_write_attribute<MLoopCol, ColorGeometry4f, get_loop_color, set_loop_color>); static VertexGroupsAttributeProvider vertex_groups; static CustomDataAttributeProvider corner_custom_data(ATTR_DOMAIN_CORNER, corner_access); diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc index 3e457e48076..3d85118deee 100644 --- a/source/blender/blenkernel/intern/geometry_set.cc +++ b/source/blender/blenkernel/intern/geometry_set.cc @@ -24,6 +24,7 @@ #include "BKE_mesh_wrapper.h" #include "BKE_modifier.h" #include "BKE_pointcloud.h" +#include "BKE_spline.hh" #include "BKE_volume.h" #include "DNA_collection_types.h" @@ -60,6 +61,8 @@ GeometryComponent *GeometryComponent::create(GeometryComponentType component_typ return new InstancesComponent(); case GEO_COMPONENT_TYPE_VOLUME: return new VolumeComponent(); + case GEO_COMPONENT_TYPE_CURVE: + return new CurveComponent(); } BLI_assert_unreachable(); return nullptr; @@ -182,6 +185,11 @@ void GeometrySet::compute_boundbox_without_instances(float3 *r_min, float3 *r_ma if (volume != nullptr) { BKE_volume_min_max(volume, *r_min, *r_max); } + const CurveEval *curve = this->get_curve_for_read(); + if (curve != nullptr) { + /* Using the evaluated positions is somewhat arbitrary, but it is probably expected. */ + curve->bounds_min_max(*r_min, *r_max, true); + } } std::ostream &operator<<(std::ostream &stream, const GeometrySet &geometry_set) @@ -252,6 +260,13 @@ const Volume *GeometrySet::get_volume_for_read() const return (component == nullptr) ? nullptr : component->get_for_read(); } +/* Returns a read-only curve or null. */ +const CurveEval *GeometrySet::get_curve_for_read() const +{ + const CurveComponent *component = this->get_component_for_read<CurveComponent>(); + return (component == nullptr) ? nullptr : component->get_for_read(); +} + /* Returns true when the geometry set has a point cloud component that has a point cloud. */ bool GeometrySet::has_pointcloud() const { @@ -273,6 +288,13 @@ bool GeometrySet::has_volume() const return component != nullptr && component->has_volume(); } +/* Returns true when the geometry set has a curve component that has a curve. */ +bool GeometrySet::has_curve() const +{ + const CurveComponent *component = this->get_component_for_read<CurveComponent>(); + return component != nullptr && component->has_curve(); +} + /* Create a new geometry set that only contains the given mesh. */ GeometrySet GeometrySet::create_with_mesh(Mesh *mesh, GeometryOwnershipType ownership) { @@ -292,6 +314,15 @@ GeometrySet GeometrySet::create_with_pointcloud(PointCloud *pointcloud, return geometry_set; } +/* Create a new geometry set that only contains the given curve. */ +GeometrySet GeometrySet::create_with_curve(CurveEval *curve, GeometryOwnershipType ownership) +{ + GeometrySet geometry_set; + CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>(); + component.replace(curve, ownership); + return geometry_set; +} + /* Clear the existing mesh and replace it with the given one. */ void GeometrySet::replace_mesh(Mesh *mesh, GeometryOwnershipType ownership) { @@ -299,6 +330,13 @@ void GeometrySet::replace_mesh(Mesh *mesh, GeometryOwnershipType ownership) component.replace(mesh, ownership); } +/* Clear the existing curve and replace it with the given one. */ +void GeometrySet::replace_curve(CurveEval *curve, GeometryOwnershipType ownership) +{ + CurveComponent &component = this->get_component_for_write<CurveComponent>(); + component.replace(curve, ownership); +} + /* Clear the existing point cloud and replace with the given one. */ void GeometrySet::replace_pointcloud(PointCloud *pointcloud, GeometryOwnershipType ownership) { @@ -306,6 +344,13 @@ void GeometrySet::replace_pointcloud(PointCloud *pointcloud, GeometryOwnershipTy pointcloud_component.replace(pointcloud, ownership); } +/* Clear the existing volume and replace with the given one. */ +void GeometrySet::replace_volume(Volume *volume, GeometryOwnershipType ownership) +{ + VolumeComponent &volume_component = this->get_component_for_write<VolumeComponent>(); + volume_component.replace(volume, ownership); +} + /* Returns a mutable mesh or null. No ownership is transferred. */ Mesh *GeometrySet::get_mesh_for_write() { @@ -327,6 +372,13 @@ Volume *GeometrySet::get_volume_for_write() return component.get_for_write(); } +/* Returns a mutable curve or null. No ownership is transferred. */ +CurveEval *GeometrySet::get_curve_for_write() +{ + CurveComponent &component = this->get_component_for_write<CurveComponent>(); + return component.get_for_write(); +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/intern/geometry_set_instances.cc b/source/blender/blenkernel/intern/geometry_set_instances.cc index 47db5d1f901..9abd00c2b4f 100644 --- a/source/blender/blenkernel/intern/geometry_set_instances.cc +++ b/source/blender/blenkernel/intern/geometry_set_instances.cc @@ -15,10 +15,12 @@ */ #include "BKE_geometry_set_instances.hh" +#include "BKE_material.h" #include "BKE_mesh.h" #include "BKE_mesh_wrapper.h" #include "BKE_modifier.h" #include "BKE_pointcloud.h" +#include "BKE_spline.hh" #include "DNA_collection_types.h" #include "DNA_mesh_types.h" @@ -50,6 +52,16 @@ static void add_final_mesh_as_geometry_component(const Object &object, GeometryS } } +static void add_curve_data_as_geometry_component(const Object &object, GeometrySet &geometry_set) +{ + BLI_assert(object.type == OB_CURVE); + if (object.data != nullptr) { + std::unique_ptr<CurveEval> curve = curve_eval_from_dna_curve(*(Curve *)object.data); + CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); + curve_component.replace(curve.release(), GeometryOwnershipType::Owned); + } +} + /** * \note This doesn't extract instances from the "dupli" system for non-geometry-nodes instances. */ @@ -73,6 +85,9 @@ static GeometrySet object_get_geometry_set_for_read(const Object &object) if (object.type == OB_MESH) { add_final_mesh_as_geometry_component(object, geometry_set); } + else if (object.type == OB_CURVE) { + add_curve_data_as_geometry_component(object, geometry_set); + } /* TODO: Cover the case of point-clouds without modifiers-- they may not be covered by the * #geometry_set_eval case above. */ @@ -135,21 +150,28 @@ static void geometry_set_collect_recursive(const GeometrySet &geometry_set, const InstancesComponent &instances_component = *geometry_set.get_component_for_read<InstancesComponent>(); - Span<float4x4> transforms = instances_component.transforms(); - Span<InstancedData> instances = instances_component.instanced_data(); - for (const int i : instances.index_range()) { - const InstancedData &data = instances[i]; + Span<float4x4> transforms = instances_component.instance_transforms(); + Span<int> handles = instances_component.instance_reference_handles(); + Span<InstanceReference> references = instances_component.references(); + for (const int i : transforms.index_range()) { + const InstanceReference &reference = references[handles[i]]; const float4x4 instance_transform = transform * transforms[i]; - if (data.type == INSTANCE_DATA_TYPE_OBJECT) { - BLI_assert(data.data.object != nullptr); - const Object &object = *data.data.object; - geometry_set_collect_recursive_object(object, instance_transform, r_sets); - } - else if (data.type == INSTANCE_DATA_TYPE_COLLECTION) { - BLI_assert(data.data.collection != nullptr); - const Collection &collection = *data.data.collection; - geometry_set_collect_recursive_collection_instance(collection, instance_transform, r_sets); + switch (reference.type()) { + case InstanceReference::Type::Object: { + Object &object = reference.object(); + geometry_set_collect_recursive_object(object, instance_transform, r_sets); + break; + } + case InstanceReference::Type::Collection: { + Collection &collection = reference.collection(); + geometry_set_collect_recursive_collection_instance( + collection, instance_transform, r_sets); + break; + } + case InstanceReference::Type::None: { + break; + } } } } @@ -253,19 +275,24 @@ static bool instances_attribute_foreach_recursive(const GeometrySet &geometry_se return true; } - for (const InstancedData &data : instances_component->instanced_data()) { - if (data.type == INSTANCE_DATA_TYPE_OBJECT) { - BLI_assert(data.data.object != nullptr); - const Object &object = *data.data.object; - if (!object_instance_attribute_foreach(object, callback, limit, count)) { - return false; + for (const InstanceReference &reference : instances_component->references()) { + switch (reference.type()) { + case InstanceReference::Type::Object: { + const Object &object = reference.object(); + if (!object_instance_attribute_foreach(object, callback, limit, count)) { + return false; + } + break; } - } - else if (data.type == INSTANCE_DATA_TYPE_COLLECTION) { - BLI_assert(data.data.collection != nullptr); - const Collection &collection = *data.data.collection; - if (!collection_instance_attribute_foreach(collection, callback, limit, count)) { - return false; + case InstanceReference::Type::Collection: { + const Collection &collection = reference.collection(); + if (!collection_instance_attribute_foreach(collection, callback, limit, count)) { + return false; + } + break; + } + case InstanceReference::Type::None: { + break; } } } @@ -335,6 +362,8 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou int64_t cd_dirty_poly = 0; int64_t cd_dirty_edge = 0; int64_t cd_dirty_loop = 0; + VectorSet<Material *> materials; + for (const GeometryInstanceGroup &set_group : set_groups) { const GeometrySet &set = set_group.geometry_set; const int tot_transforms = set_group.transforms.size(); @@ -348,6 +377,10 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou cd_dirty_poly |= mesh.runtime.cd_dirty_poly; cd_dirty_edge |= mesh.runtime.cd_dirty_edge; cd_dirty_loop |= mesh.runtime.cd_dirty_loop; + for (const int slot_index : IndexRange(mesh.totcol)) { + Material *material = mesh.mat[slot_index]; + materials.add(material); + } } if (convert_points_to_vertices && set.has_pointcloud()) { const PointCloud &pointcloud = *set.get_pointcloud_for_read(); @@ -370,6 +403,10 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou break; } } + for (const int i : IndexRange(materials.size())) { + Material *material = materials[i]; + BKE_id_material_eval_assign(&new_mesh->id, i + 1, material); + } new_mesh->runtime.cd_dirty_vert = cd_dirty_vert; new_mesh->runtime.cd_dirty_poly = cd_dirty_poly; new_mesh->runtime.cd_dirty_edge = cd_dirty_edge; @@ -383,6 +420,14 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou const GeometrySet &set = set_group.geometry_set; if (set.has_mesh()) { const Mesh &mesh = *set.get_mesh_for_read(); + + Array<int> material_index_map(mesh.totcol); + for (const int i : IndexRange(mesh.totcol)) { + Material *material = mesh.mat[i]; + const int new_material_index = materials.index_of(material); + material_index_map[i] = new_material_index; + } + for (const float4x4 &transform : set_group.transforms) { for (const int i : IndexRange(mesh.totvert)) { const MVert &old_vert = mesh.mvert[i]; @@ -412,6 +457,13 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou MPoly &new_poly = new_mesh->mpoly[poly_offset + i]; new_poly = old_poly; new_poly.loopstart += loop_offset; + if (old_poly.mat_nr >= 0 && old_poly.mat_nr < mesh.totcol) { + new_poly.mat_nr = material_index_map[new_poly.mat_nr]; + } + else { + /* The material index was invalid before. */ + new_poly.mat_nr = 0; + } } vert_offset += mesh.totvert; @@ -492,6 +544,39 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups, } } +static void join_curve_splines(Span<GeometryInstanceGroup> set_groups, CurveComponent &result) +{ + CurveEval *new_curve = new CurveEval(); + for (const GeometryInstanceGroup &set_group : set_groups) { + const GeometrySet &set = set_group.geometry_set; + if (!set.has_curve()) { + continue; + } + + const CurveEval &source_curve = *set.get_curve_for_read(); + for (const SplinePtr &source_spline : source_curve.splines()) { + for (const float4x4 &transform : set_group.transforms) { + SplinePtr new_spline = source_spline->copy(); + new_spline->transform(transform); + new_curve->add_spline(std::move(new_spline)); + } + } + } + + for (SplinePtr &spline : new_curve->splines()) { + /* Spline instances should have no custom attributes, since they always come + * from original objects which currently do not support custom attributes. + * + * This is only true as long as a #GeometrySet cannot be instanced directly. */ + BLI_assert(spline->attributes.data.totlayer == 0); + UNUSED_VARS_NDEBUG(spline); + } + + new_curve->attributes.reallocate(new_curve->splines().size()); + + result.replace(new_curve); +} + static void join_instance_groups_mesh(Span<GeometryInstanceGroup> set_groups, bool convert_points_to_vertices, GeometrySet &result) @@ -558,6 +643,12 @@ static void join_instance_groups_volume(Span<GeometryInstanceGroup> set_groups, UNUSED_VARS(set_groups, dst_component); } +static void join_instance_groups_curve(Span<GeometryInstanceGroup> set_groups, GeometrySet &result) +{ + CurveComponent &dst_component = result.get_component_for_write<CurveComponent>(); + join_curve_splines(set_groups, dst_component); +} + GeometrySet geometry_set_realize_mesh_for_modifier(const GeometrySet &geometry_set) { if (!geometry_set.has_instances() && !geometry_set.has_pointcloud()) { @@ -589,6 +680,7 @@ GeometrySet geometry_set_realize_instances(const GeometrySet &geometry_set) join_instance_groups_mesh(set_groups, false, new_geometry_set); join_instance_groups_pointcloud(set_groups, new_geometry_set); join_instance_groups_volume(set_groups, new_geometry_set); + join_instance_groups_curve(set_groups, new_geometry_set); return new_geometry_set; } diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index 4c30b1aa10c..7e56755ee17 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -653,9 +653,13 @@ bGPDframe *BKE_gpencil_frame_addcopy(bGPDlayer *gpl, int cframe) * \param gpd: Grease pencil data-block * \param name: Name of the layer * \param setactive: Set as active + * \param add_to_header: Used to force the layer added at header * \return Pointer to new layer */ -bGPDlayer *BKE_gpencil_layer_addnew(bGPdata *gpd, const char *name, bool setactive) +bGPDlayer *BKE_gpencil_layer_addnew(bGPdata *gpd, + const char *name, + const bool setactive, + const bool add_to_header) { bGPDlayer *gpl = NULL; bGPDlayer *gpl_active = NULL; @@ -671,14 +675,18 @@ bGPDlayer *BKE_gpencil_layer_addnew(bGPdata *gpd, const char *name, bool setacti gpl_active = BKE_gpencil_layer_active_get(gpd); /* Add to data-block. */ - if (gpl_active == NULL) { - BLI_addtail(&gpd->layers, gpl); + if (add_to_header) { + BLI_addhead(&gpd->layers, gpl); } else { - /* if active layer, add after that layer */ - BLI_insertlinkafter(&gpd->layers, gpl_active, gpl); + if (gpl_active == NULL) { + BLI_addtail(&gpd->layers, gpl); + } + else { + /* if active layer, add after that layer */ + BLI_insertlinkafter(&gpd->layers, gpl_active, gpl); + } } - /* annotation vs GP Object behavior is slightly different */ if (gpd->flag & GP_DATA_ANNOTATIONS) { /* set default color of new strokes for this layer */ diff --git a/source/blender/blenkernel/intern/gpencil_curve.c b/source/blender/blenkernel/intern/gpencil_curve.c index 88d3e917a7a..906d0fb0792 100644 --- a/source/blender/blenkernel/intern/gpencil_curve.c +++ b/source/blender/blenkernel/intern/gpencil_curve.c @@ -515,7 +515,7 @@ void BKE_gpencil_convert_curve(Main *bmain, if (collection != NULL) { gpl = BKE_gpencil_layer_named_get(gpd, collection->id.name + 2); if (gpl == NULL) { - gpl = BKE_gpencil_layer_addnew(gpd, collection->id.name + 2, true); + gpl = BKE_gpencil_layer_addnew(gpd, collection->id.name + 2, true, false); } } } @@ -523,7 +523,7 @@ void BKE_gpencil_convert_curve(Main *bmain, if (gpl == NULL) { gpl = BKE_gpencil_layer_active_get(gpd); if (gpl == NULL) { - gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true); + gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true, false); } } diff --git a/source/blender/blenkernel/intern/gpencil_geom.c b/source/blender/blenkernel/intern/gpencil_geom.c index 27c2d9e146b..7f839650f33 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.c +++ b/source/blender/blenkernel/intern/gpencil_geom.c @@ -530,14 +530,23 @@ bool BKE_gpencil_stroke_sample(bGPdata *gpd, bGPDstroke *gps, const float dist, /** * Backbone stretch similar to Freestyle. - * \param gps: Stroke to sample - * \param dist: Distance of one segment - * \param tip_length: Ignore tip jittering, set zero to use default value. + * \param gps: Stroke to sample. + * \param dist: Distance of one segment. + * \param overshoot_fac: How exact is the follow curve algorithm. + * \param mode: Affect to Start, End or Both extremes (0->Both, 1->Start, 2->End) */ -bool BKE_gpencil_stroke_stretch(bGPDstroke *gps, const float dist, const float tip_length) +bool BKE_gpencil_stroke_stretch(bGPDstroke *gps, + const float dist, + const float overshoot_fac, + const short mode) { +#define BOTH 0 +#define START 1 +#define END 2 + bGPDspoint *pt = gps->points, *last_pt, *second_last, *next_pt; - float threshold = (tip_length == 0 ? 0.001f : tip_length); + int i; + float threshold = (overshoot_fac == 0 ? 0.001f : overshoot_fac); if (gps->totpoints < 2 || dist < FLT_EPSILON) { return false; @@ -547,33 +556,36 @@ bool BKE_gpencil_stroke_stretch(bGPDstroke *gps, const float dist, const float t second_last = &pt[gps->totpoints - 2]; next_pt = &pt[1]; - float len1 = 0.0f; - float len2 = 0.0f; - - int i = 1; - while (len1 < threshold && gps->totpoints > i) { - next_pt = &pt[i]; - len1 = len_v3v3(&next_pt->x, &pt->x); - i++; - } + if (mode == BOTH || mode == START) { + float len1 = 0.0f; + i = 1; + while (len1 < threshold && gps->totpoints > i) { + next_pt = &pt[i]; + len1 = len_v3v3(&next_pt->x, &pt->x); + i++; + } + float extend1 = (len1 + dist) / len1; + float result1[3]; - i = 2; - while (len2 < threshold && gps->totpoints >= i) { - second_last = &pt[gps->totpoints - i]; - len2 = len_v3v3(&last_pt->x, &second_last->x); - i++; + interp_v3_v3v3(result1, &next_pt->x, &pt->x, extend1); + copy_v3_v3(&pt->x, result1); } - float extend1 = (len1 + dist) / len1; - float extend2 = (len2 + dist) / len2; - - float result1[3], result2[3]; + if (mode == BOTH || mode == END) { + float len2 = 0.0f; + i = 2; + while (len2 < threshold && gps->totpoints >= i) { + second_last = &pt[gps->totpoints - i]; + len2 = len_v3v3(&last_pt->x, &second_last->x); + i++; + } - interp_v3_v3v3(result1, &next_pt->x, &pt->x, extend1); - interp_v3_v3v3(result2, &second_last->x, &last_pt->x, extend2); + float extend2 = (len2 + dist) / len2; + float result2[3]; + interp_v3_v3v3(result2, &second_last->x, &last_pt->x, extend2); - copy_v3_v3(&pt->x, result1); - copy_v3_v3(&last_pt->x, result2); + copy_v3_v3(&last_pt->x, result2); + } return true; } @@ -702,48 +714,64 @@ bool BKE_gpencil_stroke_split(bGPdata *gpd, * Shrink the stroke by length. * \param gps: Stroke to shrink * \param dist: delta length + * \param mode: 1->Start, 2->End */ -bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist) +bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mode) { +#define START 1 +#define END 2 + bGPDspoint *pt = gps->points, *second_last; int i; - if (gps->totpoints < 2 || dist < FLT_EPSILON) { + if (gps->totpoints < 2) { + if (gps->totpoints == 1) { + second_last = &pt[1]; + if (len_v3v3(&second_last->x, &pt->x) < dist) { + BKE_gpencil_stroke_trim_points(gps, 0, 0); + return true; + } + } + return false; } second_last = &pt[gps->totpoints - 2]; - float len1, this_len1, cut_len1; - float len2, this_len2, cut_len2; - int index_start, index_end; - - len1 = len2 = this_len1 = this_len2 = cut_len1 = cut_len2 = 0.0f; - - i = 1; - while (len1 < dist && gps->totpoints > i - 1) { - this_len1 = len_v3v3(&pt[i].x, &pt[i + 1].x); - len1 += this_len1; - cut_len1 = len1 - dist; - i++; + float len1, cut_len1; + float len2, cut_len2; + len1 = len2 = cut_len1 = cut_len2 = 0.0f; + + int index_start = 0; + int index_end = 0; + if (mode == START) { + i = 0; + index_end = gps->totpoints - 1; + while (len1 < dist && gps->totpoints > i + 1) { + len1 += len_v3v3(&pt[i].x, &pt[i + 1].x); + cut_len1 = len1 - dist; + i++; + } + index_start = i - 1; } - index_start = i - 2; - i = 2; - while (len2 < dist && gps->totpoints >= i) { - second_last = &pt[gps->totpoints - i]; - this_len2 = len_v3v3(&second_last[1].x, &second_last->x); - len2 += this_len2; - cut_len2 = len2 - dist; - i++; + if (mode == END) { + index_start = 0; + i = 2; + while (len2 < dist && gps->totpoints >= i) { + second_last = &pt[gps->totpoints - i]; + len2 += len_v3v3(&second_last[1].x, &second_last->x); + cut_len2 = len2 - dist; + i++; + } + index_end = gps->totpoints - i + 2; } - index_end = gps->totpoints - i + 2; - if (len1 < dist || len2 < dist || index_end <= index_start) { + if (index_end <= index_start) { index_start = index_end = 0; /* empty stroke */ } - if ((index_end == index_start + 1) && (cut_len1 + cut_len2 > 1.0f)) { + if ((index_end == index_start + 1) && (cut_len1 + cut_len2 < dist)) { index_start = index_end = 0; /* no length left to cut */ } @@ -753,22 +781,8 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist) return false; } - pt = gps->points; - - float cut1 = cut_len1 / this_len1; - float cut2 = cut_len2 / this_len2; - - float result1[3], result2[3]; - - interp_v3_v3v3(result1, &pt[1].x, &pt[0].x, cut1); - interp_v3_v3v3(result2, &pt[gps->totpoints - 2].x, &pt[gps->totpoints - 1].x, cut2); - - copy_v3_v3(&pt[0].x, result1); - copy_v3_v3(&pt[gps->totpoints - 1].x, result2); - return true; } - /** * Apply smooth position to stroke point. * \param gps: Stroke to smooth @@ -1050,8 +1064,21 @@ void BKE_gpencil_stroke_2d_flat(const bGPDspoint *points, normalize_v3(locx); normalize_v3(locy); + /* Calculate last point first. */ + const bGPDspoint *pt_last = &points[totpoints - 1]; + float tmp[3]; + sub_v3_v3v3(tmp, &pt_last->x, &pt0->x); + + points2d[totpoints - 1][0] = dot_v3v3(tmp, locx); + points2d[totpoints - 1][1] = dot_v3v3(tmp, locy); + + /* Calculate the scalar cross product of the 2d points. */ + float cross = 0.0f; + float *co_curr; + float *co_prev = (float *)&points2d[totpoints - 1]; + /* Get all points in local space */ - for (int i = 0; i < totpoints; i++) { + for (int i = 0; i < totpoints - 1; i++) { const bGPDspoint *pt = &points[i]; float loc[3]; @@ -1060,10 +1087,15 @@ void BKE_gpencil_stroke_2d_flat(const bGPDspoint *points, points2d[i][0] = dot_v3v3(loc, locx); points2d[i][1] = dot_v3v3(loc, locy); + + /* Calculate cross product. */ + co_curr = (float *)&points2d[i][0]; + cross += (co_curr[0] - co_prev[0]) * (co_curr[1] + co_prev[1]); + co_prev = (float *)&points2d[i][0]; } - /* Concave (-1), Convex (1), or Auto-detect (0)? */ - *r_direction = (int)locy[2]; + /* Concave (-1), Convex (1) */ + *r_direction = (cross >= 0.0f) ? 1 : -1; } /** @@ -2421,7 +2453,7 @@ bool BKE_gpencil_convert_mesh(Main *bmain, /* Create Layer and Frame. */ bGPDlayer *gpl_fill = BKE_gpencil_layer_named_get(gpd, element_name); if (gpl_fill == NULL) { - gpl_fill = BKE_gpencil_layer_addnew(gpd, element_name, true); + gpl_fill = BKE_gpencil_layer_addnew(gpd, element_name, true, false); } bGPDframe *gpf_fill = BKE_gpencil_layer_frame_get( gpl_fill, CFRA + frame_offset, GP_GETFRAME_ADD_NEW); @@ -2433,7 +2465,7 @@ bool BKE_gpencil_convert_mesh(Main *bmain, Material *ma = BKE_object_material_get(ob_mesh, mp->mat_nr + 1); make_element_name( ob_mesh->id.name + 2, (ma != NULL) ? ma->id.name + 2 : "Fill", 64, element_name); - mat_idx = gpencil_material_find_index_by_name(ob_gp, element_name); + mat_idx = BKE_gpencil_material_find_index_by_name_prefix(ob_gp, element_name); if (mat_idx == -1) { float color[4]; if (ma != NULL) { @@ -2474,7 +2506,7 @@ bool BKE_gpencil_convert_mesh(Main *bmain, /* Create Layer and Frame. */ bGPDlayer *gpl_stroke = BKE_gpencil_layer_named_get(gpd, element_name); if (gpl_stroke == NULL) { - gpl_stroke = BKE_gpencil_layer_addnew(gpd, element_name, true); + gpl_stroke = BKE_gpencil_layer_addnew(gpd, element_name, true, false); } bGPDframe *gpf_stroke = BKE_gpencil_layer_frame_get( gpl_stroke, CFRA + frame_offset, GP_GETFRAME_ADD_NEW); diff --git a/source/blender/blenkernel/intern/gpencil_modifier.c b/source/blender/blenkernel/intern/gpencil_modifier.c index 1f13f008464..16386cac029 100644 --- a/source/blender/blenkernel/intern/gpencil_modifier.c +++ b/source/blender/blenkernel/intern/gpencil_modifier.c @@ -757,9 +757,9 @@ void BKE_gpencil_modifiers_calc(Depsgraph *depsgraph, Scene *scene, Object *ob) { bGPdata *gpd = (bGPdata *)ob->data; const bool is_edit = GPENCIL_ANY_EDIT_MODE(gpd); - const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); - const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); const bool is_render = (bool)(DEG_get_mode(depsgraph) == DAG_EVAL_RENDER); + const bool is_curve_edit = (bool)(GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd) && !is_render); + const bool is_multiedit = (bool)(GPENCIL_MULTIEDIT_SESSIONS_ON(gpd) && !is_render); const bool do_modifiers = (bool)((!is_multiedit) && (!is_curve_edit) && (ob->greasepencil_modifiers.first != NULL) && (!GPENCIL_SIMPLIFY_MODIF(scene))); diff --git a/source/blender/blenkernel/intern/idprop.c b/source/blender/blenkernel/intern/idprop.c index 6b164e6bc50..58715ac2e05 100644 --- a/source/blender/blenkernel/intern/idprop.c +++ b/source/blender/blenkernel/intern/idprop.c @@ -503,7 +503,7 @@ void IDP_SyncGroupValues(IDProperty *dest, const IDProperty *src) void IDP_SyncGroupTypes(IDProperty *dest, const IDProperty *src, const bool do_arraylen) { - LISTBASE_FOREACH_MUTABLE (IDProperty *, prop_dst, &src->data.group) { + LISTBASE_FOREACH_MUTABLE (IDProperty *, prop_dst, &dest->data.group) { const IDProperty *prop_src = IDP_GetPropertyFromGroup((IDProperty *)src, prop_dst->name); if (prop_src != NULL) { /* check of we should replace? */ diff --git a/source/blender/blenkernel/intern/image.c b/source/blender/blenkernel/intern/image.c index 368b1c2e66b..2f7e2b41a73 100644 --- a/source/blender/blenkernel/intern/image.c +++ b/source/blender/blenkernel/intern/image.c @@ -735,6 +735,37 @@ int BKE_image_get_tile_from_pos(struct Image *ima, return tile_number; } +/** + * Return the tile_number for the closest UDIM tile. + */ +int BKE_image_find_nearest_tile(const Image *image, const float co[2]) +{ + const float co_floor[2] = {floorf(co[0]), floorf(co[1])}; + /* Distance to the closest UDIM tile. */ + float dist_best_sq = FLT_MAX; + int tile_number_best = -1; + + LISTBASE_FOREACH (const ImageTile *, tile, &image->tiles) { + const int tile_index = tile->tile_number - 1001; + /* Coordinates of the current tile. */ + const float tile_index_co[2] = {tile_index % 10, tile_index / 10}; + + if (equals_v2v2(co_floor, tile_index_co)) { + return tile->tile_number; + } + + /* Distance between co[2] and UDIM tile. */ + const float dist_sq = len_squared_v2v2(tile_index_co, co); + + if (dist_sq < dist_best_sq) { + dist_best_sq = dist_sq; + tile_number_best = tile->tile_number; + } + } + + return tile_number_best; +} + static void image_init_color_management(Image *ima) { ImBuf *ibuf; diff --git a/source/blender/blenkernel/intern/image_gpu.c b/source/blender/blenkernel/intern/image_gpu.c index 8847b88d6f2..bb7495437bb 100644 --- a/source/blender/blenkernel/intern/image_gpu.c +++ b/source/blender/blenkernel/intern/image_gpu.c @@ -408,17 +408,19 @@ static GPUTexture *image_get_gpu_texture(Image *ima, store_premultiplied, limit_gl_texture_size); - GPU_texture_wrap_mode(*tex, true, false); + if (*tex) { + GPU_texture_wrap_mode(*tex, true, false); - if (GPU_mipmap_enabled()) { - GPU_texture_generate_mipmap(*tex); - if (ima) { - ima->gpuflag |= IMA_GPU_MIPMAP_COMPLETE; + if (GPU_mipmap_enabled()) { + GPU_texture_generate_mipmap(*tex); + if (ima) { + ima->gpuflag |= IMA_GPU_MIPMAP_COMPLETE; + } + GPU_texture_mipmap_mode(*tex, true, true); + } + else { + GPU_texture_mipmap_mode(*tex, false, true); } - GPU_texture_mipmap_mode(*tex, true, true); - } - else { - GPU_texture_mipmap_mode(*tex, false, true); } } @@ -427,7 +429,9 @@ static GPUTexture *image_get_gpu_texture(Image *ima, BKE_image_release_ibuf(ima, ibuf_intern, NULL); } - GPU_texture_orig_size_set(*tex, ibuf_intern->x, ibuf_intern->y); + if (*tex) { + GPU_texture_orig_size_set(*tex, ibuf_intern->x, ibuf_intern->y); + } return *tex; } diff --git a/source/blender/blenkernel/intern/lib_id.c b/source/blender/blenkernel/intern/lib_id.c index 795a5ad9468..c2e5006cbc1 100644 --- a/source/blender/blenkernel/intern/lib_id.c +++ b/source/blender/blenkernel/intern/lib_id.c @@ -525,7 +525,13 @@ static int id_copy_libmanagement_cb(LibraryIDLinkCallbackData *cb_data) /* Increase used IDs refcount if needed and required. */ if ((data->flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0 && (cb_flag & IDWALK_CB_USER)) { - id_us_plus(id); + if ((data->flag & LIB_ID_CREATE_NO_MAIN) != 0) { + BLI_assert(cb_data->id_self->tag & LIB_TAG_NO_MAIN); + id_us_plus_no_lib(id); + } + else { + id_us_plus(id); + } } return IDWALK_RET_NOP; @@ -578,7 +584,7 @@ ID *BKE_id_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int flag) } } - /* Early output is source is NULL. */ + /* Early output if source is NULL. */ if (id == NULL) { return NULL; } @@ -1245,6 +1251,13 @@ void BKE_libblock_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int ori } BLI_assert(new_id != NULL); + if ((flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) != 0) { + new_id->tag |= LIB_TAG_COPIED_ON_WRITE; + } + else { + new_id->tag &= ~LIB_TAG_COPIED_ON_WRITE; + } + const size_t id_len = BKE_libblock_get_alloc_info(GS(new_id->name), NULL); const size_t id_offset = sizeof(ID); if ((int)id_len - (int)id_offset > 0) { /* signed to allow neg result */ /* XXX ????? */ @@ -1348,12 +1361,12 @@ void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint) BLI_remlink(lb, id); /* Check if we can actually insert id before or after id_sorting_hint, if given. */ - if (!ELEM(id_sorting_hint, NULL, id)) { + if (!ELEM(id_sorting_hint, NULL, id) && id_sorting_hint->lib == id->lib) { BLI_assert(BLI_findindex(lb, id_sorting_hint) >= 0); ID *id_sorting_hint_next = id_sorting_hint->next; if (BLI_strcasecmp(id_sorting_hint->name, id->name) < 0 && - (id_sorting_hint_next == NULL || + (id_sorting_hint_next == NULL || id_sorting_hint_next->lib != id->lib || BLI_strcasecmp(id_sorting_hint_next->name, id->name) > 0)) { BLI_insertlinkafter(lb, id_sorting_hint, id); return; @@ -1361,7 +1374,7 @@ void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint) ID *id_sorting_hint_prev = id_sorting_hint->prev; if (BLI_strcasecmp(id_sorting_hint->name, id->name) > 0 && - (id_sorting_hint_prev == NULL || + (id_sorting_hint_prev == NULL || id_sorting_hint_prev->lib != id->lib || BLI_strcasecmp(id_sorting_hint_prev->name, id->name) < 0)) { BLI_insertlinkbefore(lb, id_sorting_hint, id); return; @@ -1376,16 +1389,33 @@ void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint) /* Note: We start from the end, because in typical 'heavy' case (insertion of lots of IDs at * once using the same base name), newly inserted items will generally be towards the end * (higher extension numbers). */ - for (idtest = lb->last, item_array_index = ID_SORT_STEP_SIZE - 1; idtest != NULL; - idtest = idtest->prev, item_array_index--) { + bool is_in_library = false; + item_array_index = ID_SORT_STEP_SIZE - 1; + for (idtest = lb->last; idtest != NULL; idtest = idtest->prev) { + if (is_in_library) { + if (idtest->lib != id->lib) { + /* We got out of expected library 'range' in the list, so we are done here and can move on + * to the next step. */ + break; + } + } + else if (idtest->lib == id->lib) { + /* We are entering the expected library 'range' of IDs in the list. */ + is_in_library = true; + } + + if (!is_in_library) { + continue; + } + item_array[item_array_index] = idtest; if (item_array_index == 0) { - if ((idtest->lib == NULL && id->lib != NULL) || - BLI_strcasecmp(idtest->name, id->name) <= 0) { + if (BLI_strcasecmp(idtest->name, id->name) <= 0) { break; } item_array_index = ID_SORT_STEP_SIZE; } + item_array_index--; } /* Step two: we go forward in the selected chunk of items and check all of them, as we know @@ -1397,7 +1427,7 @@ void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint) * So we can increment that index in any case. */ for (item_array_index++; item_array_index < ID_SORT_STEP_SIZE; item_array_index++) { idtest = item_array[item_array_index]; - if ((idtest->lib != NULL && id->lib == NULL) || BLI_strcasecmp(idtest->name, id->name) > 0) { + if (BLI_strcasecmp(idtest->name, id->name) > 0) { BLI_insertlinkbefore(lb, idtest, id); break; } @@ -1405,12 +1435,18 @@ void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint) if (item_array_index == ID_SORT_STEP_SIZE) { if (idtest == NULL) { /* If idtest is NULL here, it means that in the first loop, the last comparison was - * performed exactly on the first item of the list, and that it also failed. In other - * words, all items in the list are greater than inserted one, so we can put it at the - * start of the list. */ - /* Note that BLI_insertlinkafter() would have same behavior in that case, but better be - * explicit here. */ - BLI_addhead(lb, id); + * performed exactly on the first item of the list, and that it also failed. And that the + * second loop was not walked at all. + * + * In other words, if `id` is local, all the items in the list are greater than the inserted + * one, so we can put it at the start of the list. Or, if `id` is linked, it is the first one + * of its library, and we can put it at the very end of the list. */ + if (ID_IS_LINKED(id)) { + BLI_addtail(lb, id); + } + else { + BLI_addhead(lb, id); + } } else { BLI_insertlinkafter(lb, idtest, id); @@ -1678,12 +1714,14 @@ static bool check_for_dupid(ListBase *lb, ID *id, char *name, ID **r_id_sorting_ */ bool BKE_id_new_name_validate(ListBase *lb, ID *id, const char *tname) { - bool result; + bool result = false; char name[MAX_ID_NAME - 2]; - /* if library, don't rename */ + /* If library, don't rename, but do ensure proper sorting. */ if (ID_IS_LINKED(id)) { - return false; + id_sort_by_name(lb, id, NULL); + + return result; } /* if no name given, use name of current ID diff --git a/source/blender/blenkernel/intern/lib_id_test.cc b/source/blender/blenkernel/intern/lib_id_test.cc new file mode 100644 index 00000000000..fbe4a15da1c --- /dev/null +++ b/source/blender/blenkernel/intern/lib_id_test.cc @@ -0,0 +1,113 @@ +/* + * 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. + * + * The Original Code is Copyright (C) 2020 by Blender Foundation. + */ +#include "testing/testing.h" + +#include "MEM_guardedalloc.h" + +#include "BLI_listbase.h" + +#include "BKE_idtype.h" +#include "BKE_lib_id.h" +#include "BKE_main.h" + +#include "DNA_ID.h" +#include "DNA_mesh_types.h" +#include "DNA_object_types.h" + +namespace blender::bke::tests { + +struct LibIDMainSortTestContext { + Main *bmain; +}; + +static void test_lib_id_main_sort_init(LibIDMainSortTestContext *ctx) +{ + BKE_idtype_init(); + ctx->bmain = BKE_main_new(); +} + +static void test_lib_id_main_sort_free(LibIDMainSortTestContext *ctx) +{ + BKE_main_free(ctx->bmain); +} + +static void test_lib_id_main_sort_check_order(std::initializer_list<ID *> list) +{ + ID *prev_id = nullptr; + for (ID *id : list) { + EXPECT_EQ(id->prev, prev_id); + if (prev_id != nullptr) { + EXPECT_EQ(prev_id->next, id); + } + prev_id = id; + } + EXPECT_EQ(prev_id->next, nullptr); +} + +TEST(lib_id_main_sort, local_ids_1) +{ + LibIDMainSortTestContext ctx = {nullptr}; + test_lib_id_main_sort_init(&ctx); + EXPECT_TRUE(BLI_listbase_is_empty(&ctx.bmain->libraries)); + + ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_C")); + ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_A")); + ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_B")); + EXPECT_TRUE(ctx.bmain->objects.first == id_a); + EXPECT_TRUE(ctx.bmain->objects.last == id_c); + test_lib_id_main_sort_check_order({id_a, id_b, id_c}); + + test_lib_id_main_sort_free(&ctx); +} + +TEST(lib_id_main_sort, linked_ids_1) +{ + LibIDMainSortTestContext ctx = {nullptr}; + test_lib_id_main_sort_init(&ctx); + EXPECT_TRUE(BLI_listbase_is_empty(&ctx.bmain->libraries)); + + Library *lib_a = static_cast<Library *>(BKE_id_new(ctx.bmain, ID_LI, "LI_A")); + Library *lib_b = static_cast<Library *>(BKE_id_new(ctx.bmain, ID_LI, "LI_B")); + ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_C")); + ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_A")); + ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_B")); + + id_a->lib = lib_a; + id_sort_by_name(&ctx.bmain->objects, id_a, nullptr); + id_b->lib = lib_a; + id_sort_by_name(&ctx.bmain->objects, id_b, nullptr); + EXPECT_TRUE(ctx.bmain->objects.first == id_c); + EXPECT_TRUE(ctx.bmain->objects.last == id_b); + test_lib_id_main_sort_check_order({id_c, id_a, id_b}); + + id_a->lib = lib_b; + id_sort_by_name(&ctx.bmain->objects, id_a, nullptr); + EXPECT_TRUE(ctx.bmain->objects.first == id_c); + EXPECT_TRUE(ctx.bmain->objects.last == id_a); + test_lib_id_main_sort_check_order({id_c, id_b, id_a}); + + id_b->lib = lib_b; + id_sort_by_name(&ctx.bmain->objects, id_b, nullptr); + EXPECT_TRUE(ctx.bmain->objects.first == id_c); + EXPECT_TRUE(ctx.bmain->objects.last == id_b); + test_lib_id_main_sort_check_order({id_c, id_a, id_b}); + + test_lib_id_main_sort_free(&ctx); +} + +} // namespace blender::bke::tests diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c index 40b7681614d..8341c5b6e78 100644 --- a/source/blender/blenkernel/intern/lib_override.c +++ b/source/blender/blenkernel/intern/lib_override.c @@ -435,8 +435,8 @@ static bool lib_override_hierarchy_dependencies_recursive_tag(LibOverrideGroupTa for (MainIDRelationsEntryItem *to_id_entry = entry->to_ids; to_id_entry != NULL; to_id_entry = to_id_entry->next) { - if ((to_id_entry->usage_flag & IDWALK_CB_LOOPBACK) != 0) { - /* Never consider 'loop back' relationships ('from', 'parents', 'owner' etc. pointers) as + if ((to_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) { + /* Never consider non-overridable relationships ('from', 'parents', 'owner' etc. pointers) as * actual dependencies. */ continue; } @@ -480,10 +480,8 @@ static void lib_override_linked_group_tag_recursive(LibOverrideGroupTagData *dat for (MainIDRelationsEntryItem *to_id_entry = entry->to_ids; to_id_entry != NULL; to_id_entry = to_id_entry->next) { - if ((to_id_entry->usage_flag & - (IDWALK_CB_EMBEDDED | IDWALK_CB_LOOPBACK | IDWALK_CB_OVERRIDE_LIBRARY_REFERENCE)) != 0) { - /* Never consider 'loop back' relationships ('from', 'parents', 'owner' etc. pointers), nor - * override references or embedded ID pointers, as actual dependencies. */ + if ((to_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) { + /* Never consider non-overridable relationships as actual dependencies. */ continue; } @@ -578,10 +576,8 @@ static void lib_override_local_group_tag_recursive(LibOverrideGroupTagData *data for (MainIDRelationsEntryItem *to_id_entry = entry->to_ids; to_id_entry != NULL; to_id_entry = to_id_entry->next) { - if ((to_id_entry->usage_flag & - (IDWALK_CB_EMBEDDED | IDWALK_CB_LOOPBACK | IDWALK_CB_OVERRIDE_LIBRARY_REFERENCE)) != 0) { - /* Never consider 'loop back' relationships ('from', 'parents', 'owner' etc. pointers), nor - * override references or embedded ID pointers, as actual dependencies. */ + if ((to_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) { + /* Never consider non-overridable relationships as actual dependencies. */ continue; } @@ -723,8 +719,19 @@ static void lib_override_library_create_post_process(Main *bmain, if (BLI_gset_lookup(all_objects_in_scene, ob_new) == NULL) { if (id_root != NULL && default_instantiating_collection == NULL) { - switch (GS(id_root->name)) { + ID *id_ref = id_root->newid != NULL ? id_root->newid : id_root; + switch (GS(id_ref->name)) { case ID_GR: { + /* Adding the object to a specific collection outside of the root overridden one is a + * fairly bad idea (it breaks the override hierarchy concept). But there is no other + * way to do this currently (we cannot add new collections to overridden root one, + * this is not currently supported). + * Since that will be fairly annoying and noisy, only do that in case the override + * object is not part of any existing collection (i.e. its user count is 0). In + * practice this should never happen I think. */ + if (ID_REAL_USERS(ob_new) != 0) { + continue; + } default_instantiating_collection = BKE_collection_add( bmain, (Collection *)id_root, "OVERRIDE_HIDDEN"); /* Hide the collection from viewport and render. */ @@ -735,9 +742,9 @@ static void lib_override_library_create_post_process(Main *bmain, case ID_OB: { /* Add the other objects to one of the collections instantiating the * root object, or scene's master collection if none found. */ - Object *ob_root = (Object *)id_root; + Object *ob_ref = (Object *)id_ref; LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { - if (BKE_collection_has_object(collection, ob_root) && + if (BKE_collection_has_object(collection, ob_ref) && BKE_view_layer_has_collection(view_layer, collection) && !ID_IS_LINKED(collection) && !ID_IS_OVERRIDE_LIBRARY(collection)) { default_instantiating_collection = collection; @@ -871,7 +878,8 @@ bool BKE_lib_override_library_resync(Main *bmain, ID *id_root, Collection *override_resync_residual_storage, const bool do_hierarchy_enforce, - const bool do_post_process) + const bool do_post_process, + ReportList *reports) { BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root)); BLI_assert(!ID_IS_LINKED(id_root)); @@ -895,6 +903,19 @@ bool BKE_lib_override_library_resync(Main *bmain, BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__); ID *id; FOREACH_MAIN_ID_BEGIN (bmain, id) { + /* IDs that get fully removed from linked data remain as local overrides (using place-holder + * linked IDs as reference), but they are often not reachable from any current valid local + * override hierarchy anymore. This will ensure they get properly deleted at the end of this + * function. */ + if (!ID_IS_LINKED(id) && ID_IS_OVERRIDE_LIBRARY_REAL(id) && + (id->override_library->reference->tag & LIB_TAG_MISSING) != 0 && + /* Unfortunately deleting obdata means deleting their objects too. Since there is no + * guarantee that a valid override object using an obsolete override obdata gets properly + * updated, we ignore those here for now. In practice this should not be a big issue. */ + !OB_DATA_SUPPORT_ID(GS(id->name))) { + id->tag |= LIB_TAG_MISSING; + } + if (id->tag & LIB_TAG_DOIT && !ID_IS_LINKED(id) && ID_IS_OVERRIDE_LIBRARY_REAL(id)) { /* While this should not happen in typical cases (and won't be properly supported here), user * is free to do all kind of very bad things, including having different local overrides of a @@ -1037,6 +1058,7 @@ bool BKE_lib_override_library_resync(Main *bmain, /* Delete old override IDs. * Note that we have to use tagged group deletion here, since ID deletion also uses LIB_TAG_DOIT. * This improves performances anyway, so everything is fine. */ + int user_edited_overrides_deletion_count = 0; FOREACH_MAIN_ID_BEGIN (bmain, id) { if (id->tag & LIB_TAG_DOIT) { /* Note that this works because linked IDs are always after local ones (including overrides), @@ -1061,6 +1083,7 @@ bool BKE_lib_override_library_resync(Main *bmain, id->tag &= ~LIB_TAG_MISSING; CLOG_INFO(&LOG, 2, "Old override %s is being deleted", id->name); } +#if 0 else { /* Otherwise, keep them, user needs to decide whether what to do with them. */ BLI_assert((id->tag & LIB_TAG_DOIT) == 0); @@ -1068,6 +1091,17 @@ bool BKE_lib_override_library_resync(Main *bmain, id->flag |= LIB_LIB_OVERRIDE_RESYNC_LEFTOVER; CLOG_INFO(&LOG, 2, "Old override %s is being kept around as it was user-edited", id->name); } +#else + else { + /* Delete them nevertheless, with fat warning, user needs to decide whether they want to + * save that version of the file (and accept the loss), or not. */ + id->tag |= LIB_TAG_DOIT; + id->tag &= ~LIB_TAG_MISSING; + CLOG_WARN( + &LOG, "Old override %s is being deleted even though it was user-edited", id->name); + user_edited_overrides_deletion_count++; + } +#endif } } FOREACH_MAIN_ID_END; @@ -1078,6 +1112,15 @@ bool BKE_lib_override_library_resync(Main *bmain, */ id_root = id_root_reference->newid; + if (user_edited_overrides_deletion_count > 0) { + BKE_reportf(reports, + RPT_WARNING, + "During resync of data-block %s, %d obsolete overrides were deleted, that had " + "local changes defined by user", + id_root->name + 2, + user_edited_overrides_deletion_count); + } + if (do_post_process) { /* Essentially ensures that potentially new overrides of new objects will be instantiated. */ /* Note: Here 'reference' collection and 'newly added' collection are the same, which is fine @@ -1116,7 +1159,10 @@ bool BKE_lib_override_library_resync(Main *bmain, * Then it will handle the resync of necessary IDs (through calls to * #BKE_lib_override_library_resync). */ -void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer *view_layer) +void BKE_lib_override_library_main_resync(Main *bmain, + Scene *scene, + ViewLayer *view_layer, + ReportList *reports) { /* We use a specific collection to gather/store all 'orphaned' override collections and objects * generated by re-sync-process. This avoids putting them in scene's master collection. */ @@ -1182,8 +1228,7 @@ void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer * for (MainIDRelationsEntryItem *entry_item = entry->to_ids; entry_item != NULL; entry_item = entry_item->next) { - if (entry_item->usage_flag & - (IDWALK_CB_EMBEDDED | IDWALK_CB_LOOPBACK | IDWALK_CB_OVERRIDE_LIBRARY_REFERENCE)) { + if (entry_item->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) { continue; } ID *id_to = *entry_item->id_pointer.to; @@ -1224,9 +1269,16 @@ void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer * } do_continue = true; + /* In complex non-supported cases, with several different override hierarchies sharing + * relations between each-other, we may end up not actually updating/replacing the given + * root id (see e.g. pro/shots/110_rextoria/110_0150_A/110_0150_A.anim.blend of sprites + * project repository, r2687). + * This can lead to infinite loop here, at least avoid this. */ + id->tag &= ~LIB_TAG_LIB_OVERRIDE_NEED_RESYNC; + CLOG_INFO(&LOG, 2, "Resyncing %s...", id->name); const bool success = BKE_lib_override_library_resync( - bmain, scene, view_layer, id, override_resync_residual_storage, false, false); + bmain, scene, view_layer, id, override_resync_residual_storage, false, false, reports); CLOG_INFO(&LOG, 2, "\tSuccess: %d", success); break; } @@ -1617,7 +1669,7 @@ bool BKE_lib_override_library_property_operation_operands_validate( return true; } -/** Check against potential \a bmain. */ +/** Check against potential \a bmain. */ void BKE_lib_override_library_validate(Main *UNUSED(bmain), ID *id, ReportList *reports) { if (id->override_library == NULL) { @@ -1651,7 +1703,7 @@ void BKE_lib_override_library_validate(Main *UNUSED(bmain), ID *id, ReportList * } } -/** Check against potential \a bmain. */ +/** Check against potential \a bmain. */ void BKE_lib_override_library_main_validate(Main *bmain, ReportList *reports) { ID *id; @@ -2052,8 +2104,8 @@ static void lib_override_library_id_hierarchy_recursive_reset(Main *bmain, ID *i for (MainIDRelationsEntryItem *to_id_entry = entry->to_ids; to_id_entry != NULL; to_id_entry = to_id_entry->next) { - if ((to_id_entry->usage_flag & IDWALK_CB_LOOPBACK) != 0) { - /* Never consider 'loop back' relationships ('from', 'parents', 'owner' etc. pointers) as + if ((to_id_entry->usage_flag & IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE) != 0) { + /* Never consider non-overridable relationships ('from', 'parents', 'owner' etc. pointers) as * actual dependencies. */ continue; } diff --git a/source/blender/blenkernel/intern/lib_query.c b/source/blender/blenkernel/intern/lib_query.c index e33743eb36b..3281783d81a 100644 --- a/source/blender/blenkernel/intern/lib_query.c +++ b/source/blender/blenkernel/intern/lib_query.c @@ -56,11 +56,19 @@ typedef struct LibraryForeachIDData { */ ID *self_id; + /** Flags controlling the behavior of the 'foreach id' looping code. */ int flag; + /** Generic flags to be passed to all callback calls for current processed data. */ int cb_flag; + /** Callback flags that are forbidden for all callback calls for current processed data. */ int cb_flag_clear; + + /* Function to call for every ID pointers of current processed data, and its opaque user data + * pointer. */ LibraryIDLinkCallback callback; void *user_data; + /** Store the returned value from the callback, to decide how to continue the processing of ID + * pointers for current data. */ int status; /* To handle recursion. */ @@ -73,13 +81,25 @@ bool BKE_lib_query_foreachid_process(LibraryForeachIDData *data, ID **id_pp, int if (!(data->status & IDWALK_STOP)) { const int flag = data->flag; ID *old_id = *id_pp; - const int callback_return = data->callback(&(struct LibraryIDLinkCallbackData){ - .user_data = data->user_data, - .bmain = data->bmain, - .id_owner = data->owner_id, - .id_self = data->self_id, - .id_pointer = id_pp, - .cb_flag = ((cb_flag | data->cb_flag) & ~data->cb_flag_clear)}); + + /* Update the callback flags with the ones defined (or forbidden) in `data` by the generic + * caller code. */ + cb_flag = ((cb_flag | data->cb_flag) & ~data->cb_flag_clear); + + /* Update the callback flags with some extra information regarding overrides: all 'loopback', + * 'internal', 'embedded' etc. ID pointers are never overridable. */ + if (cb_flag & (IDWALK_CB_INTERNAL | IDWALK_CB_EMBEDDED | IDWALK_CB_LOOPBACK | + IDWALK_CB_OVERRIDE_LIBRARY_REFERENCE)) { + cb_flag |= IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE; + } + + const int callback_return = data->callback( + &(struct LibraryIDLinkCallbackData){.user_data = data->user_data, + .bmain = data->bmain, + .id_owner = data->owner_id, + .id_self = data->self_id, + .id_pointer = id_pp, + .cb_flag = cb_flag}); if (flag & IDWALK_READONLY) { BLI_assert(*(id_pp) == old_id); } @@ -132,7 +152,10 @@ void BKE_lib_query_idpropertiesForeachIDLink_callback(IDProperty *id_prop, void BLI_assert(id_prop->type == IDP_ID); LibraryForeachIDData *data = (LibraryForeachIDData *)user_data; - BKE_LIB_FOREACHID_PROCESS_ID(data, id_prop->data.pointer, IDWALK_CB_USER); + const int cb_flag = IDWALK_CB_USER | ((id_prop->flag & IDP_FLAG_OVERRIDABLE_LIBRARY) ? + 0 : + IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE); + BKE_LIB_FOREACHID_PROCESS_ID(data, id_prop->data.pointer, cb_flag); } bool BKE_library_foreach_ID_embedded(LibraryForeachIDData *data, ID **id_pp) diff --git a/source/blender/blenkernel/intern/lib_remap.c b/source/blender/blenkernel/intern/lib_remap.c index 1f597bbb9a6..b32b97dc250 100644 --- a/source/blender/blenkernel/intern/lib_remap.c +++ b/source/blender/blenkernel/intern/lib_remap.c @@ -303,6 +303,7 @@ static void libblock_remap_data_postprocess_object_update(Main *bmain, /* Can be called with both old_collection and new_collection being NULL, * this means we have to check whole Main database then. */ static void libblock_remap_data_postprocess_collection_update(Main *bmain, + Collection *owner_collection, Collection *UNUSED(old_collection), Collection *new_collection) { @@ -311,7 +312,7 @@ static void libblock_remap_data_postprocess_collection_update(Main *bmain, * and BKE_main_collection_sync_remap() does not tolerate any of those, so for now always check * whole existing collections for NULL pointers. * I'd consider optimizing that whole collection remapping process a TODO for later. */ - BKE_collections_child_remove_nulls(bmain, NULL /*old_collection*/); + BKE_collections_child_remove_nulls(bmain, owner_collection, NULL /*old_collection*/); } else { /* Temp safe fix, but a "tad" brute force... We should probably be able to use parents from @@ -523,7 +524,7 @@ void BKE_libblock_remap_locked(Main *bmain, void *old_idv, void *new_idv, const break; case ID_GR: libblock_remap_data_postprocess_collection_update( - bmain, (Collection *)old_id, (Collection *)new_id); + bmain, NULL, (Collection *)old_id, (Collection *)new_id); break; case ID_ME: case ID_CU: @@ -628,6 +629,12 @@ void BKE_libblock_relink_ex( switch (GS(id->name)) { case ID_SCE: case ID_GR: { + /* Note: here we know which collection we have affected, so at lest for NULL children + * detection we can only process that one. + * This is also a required fix in case `id` would not be in Main anymore, which can happen + * e.g. when called from `id_delete`. */ + Collection *owner_collection = (GS(id->name) == ID_GR) ? (Collection *)id : + ((Scene *)id)->master_collection; if (old_id) { switch (GS(old_id->name)) { case ID_OB: @@ -636,7 +643,7 @@ void BKE_libblock_relink_ex( break; case ID_GR: libblock_remap_data_postprocess_collection_update( - bmain, (Collection *)old_id, (Collection *)new_id); + bmain, owner_collection, (Collection *)old_id, (Collection *)new_id); break; default: break; @@ -644,7 +651,7 @@ void BKE_libblock_relink_ex( } else { /* No choice but to check whole objects/collections. */ - libblock_remap_data_postprocess_collection_update(bmain, NULL, NULL); + libblock_remap_data_postprocess_collection_update(bmain, owner_collection, NULL, NULL); libblock_remap_data_postprocess_object_update(bmain, NULL, NULL); } break; diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c index 37d47a984cc..73b64e6efb3 100644 --- a/source/blender/blenkernel/intern/material.c +++ b/source/blender/blenkernel/intern/material.c @@ -77,6 +77,7 @@ #include "DEG_depsgraph.h" #include "DEG_depsgraph_build.h" +#include "DEG_depsgraph_query.h" #include "GPU_material.h" @@ -700,6 +701,98 @@ Material *BKE_object_material_get(Object *ob, short act) return ma_p ? *ma_p : NULL; } +static ID *get_evaluated_object_data_with_materials(Object *ob) +{ + ID *data = ob->data; + /* Meshes in edit mode need special handling. */ + if (ob->type == OB_MESH && ob->mode == OB_MODE_EDIT) { + Mesh *mesh = ob->data; + if (mesh->edit_mesh && mesh->edit_mesh->mesh_eval_final) { + data = &mesh->edit_mesh->mesh_eval_final->id; + } + } + return data; +} + +/** + * On evaluated objects the number of materials on an object and its data might go out of sync. + * This is because during evaluation materials can be added/removed on the object data. + * + * For rendering or exporting we generally use the materials on the object data. However, some + * material indices might be overwritten by the object. + */ +Material *BKE_object_material_get_eval(Object *ob, short act) +{ + BLI_assert(DEG_is_evaluated_object(ob)); + const int slot_index = act - 1; + + if (slot_index < 0) { + return NULL; + } + ID *data = get_evaluated_object_data_with_materials(ob); + const short *tot_slots_data_ptr = BKE_id_material_len_p(data); + const int tot_slots_data = tot_slots_data_ptr ? *tot_slots_data_ptr : 0; + if (slot_index >= tot_slots_data) { + return NULL; + } + const int tot_slots_object = ob->totcol; + + Material ***materials_data_ptr = BKE_id_material_array_p(data); + Material **materials_data = materials_data_ptr ? *materials_data_ptr : NULL; + Material **materials_object = ob->mat; + + /* Check if slot is overwritten by object. */ + if (slot_index < tot_slots_object) { + if (ob->matbits) { + if (ob->matbits[slot_index]) { + Material *material = materials_object[slot_index]; + if (material != NULL) { + return material; + } + } + } + } + /* Otherwise use data from object-data. */ + if (slot_index < tot_slots_data) { + Material *material = materials_data[slot_index]; + return material; + } + return NULL; +} + +int BKE_object_material_count_eval(Object *ob) +{ + BLI_assert(DEG_is_evaluated_object(ob)); + ID *id = get_evaluated_object_data_with_materials(ob); + const short *len_p = BKE_id_material_len_p(id); + return len_p ? *len_p : 0; +} + +void BKE_id_material_eval_assign(ID *id, int slot, Material *material) +{ + Material ***materials_ptr = BKE_id_material_array_p(id); + short *len_ptr = BKE_id_material_len_p(id); + if (ELEM(NULL, materials_ptr, len_ptr)) { + BLI_assert_unreachable(); + return; + } + + const int slot_index = slot - 1; + const int old_length = *len_ptr; + + if (slot_index >= old_length) { + /* Need to grow slots array. */ + const int new_length = slot_index + 1; + *materials_ptr = MEM_reallocN(*materials_ptr, sizeof(void *) * new_length); + *len_ptr = new_length; + for (int i = old_length; i < new_length; i++) { + (*materials_ptr)[i] = NULL; + } + } + + (*materials_ptr)[slot_index] = material; +} + Material *BKE_gpencil_material(Object *ob, short act) { Material *ma = BKE_object_material_get(ob, act); @@ -1028,6 +1121,43 @@ void BKE_object_material_remap_calc(Object *ob_dst, Object *ob_src, short *remap BLI_ghash_free(gh_mat_map, NULL, NULL); } +/** + * Copy materials from evaluated geometry to the original geometry of an object. + */ +void BKE_object_material_from_eval_data(Main *bmain, Object *ob_orig, ID *data_eval) +{ + ID *data_orig = ob_orig->data; + + short *orig_totcol = BKE_id_material_len_p(data_orig); + Material ***orig_mat = BKE_id_material_array_p(data_orig); + + short *eval_totcol = BKE_id_material_len_p(data_eval); + Material ***eval_mat = BKE_id_material_array_p(data_eval); + + if (ELEM(NULL, orig_totcol, orig_mat, eval_totcol, eval_mat)) { + return; + } + + /* Remove old materials from original geometry. */ + for (int i = 0; i < *orig_totcol; i++) { + id_us_min(&(*orig_mat)[i]->id); + } + MEM_SAFE_FREE(*orig_mat); + + /* Create new material slots based on materials on evaluated geometry. */ + *orig_totcol = *eval_totcol; + *orig_mat = MEM_callocN(sizeof(void *) * (*eval_totcol), __func__); + for (int i = 0; i < *eval_totcol; i++) { + Material *material_eval = (*eval_mat)[i]; + if (material_eval != NULL) { + Material *material_orig = (Material *)DEG_get_original_id(&material_eval->id); + (*orig_mat)[i] = material_orig; + id_us_plus(&material_orig->id); + } + } + BKE_object_materials_test(bmain, ob_orig, data_orig); +} + /* XXX - this calls many more update calls per object then are needed, could be optimized */ void BKE_object_material_array_assign(Main *bmain, struct Object *ob, diff --git a/source/blender/blenkernel/intern/mball_tessellate.c b/source/blender/blenkernel/intern/mball_tessellate.c index 1550401cc9c..bb46c7b16c0 100644 --- a/source/blender/blenkernel/intern/mball_tessellate.c +++ b/source/blender/blenkernel/intern/mball_tessellate.c @@ -162,7 +162,7 @@ static void make_box_from_metaelem(Box *r, const MetaElem *ml) } /** - * Partitions part of mainb array [start, end) along axis s. Returns i, + * Partitions part of #process.mainb array [start, end) along axis s. Returns i, * where centroids of elements in the [start, i) segment lie "on the right side" of div, * and elements in the [i, end) segment lie "on the left" */ @@ -1170,8 +1170,9 @@ static void polygonize(PROCESS *process) /** * Iterates over ALL objects in the scene and all of its sets, including - * making all duplis(not only metas). Copies metas to mainb array. - * Computes bounding boxes for building BVH. */ + * making all duplis (not only meta-elements). Copies meta-elements to #process.mainb array. + * Computes bounding boxes for building BVH. + */ static void init_meta(Depsgraph *depsgraph, PROCESS *process, Scene *scene, Object *ob) { Scene *sce_iter = scene; @@ -1435,7 +1436,7 @@ void BKE_mball_polygonize(Depsgraph *depsgraph, Scene *scene, Object *ob, ListBa if (process.totelem > 0) { build_bvh_spatial(&process, &process.metaball_bvh, 0, process.totelem, &process.allbb); - /* Don't polygonize meta-balls with too high resolution (base mball to small) + /* Don't polygonize meta-balls with too high resolution (base mball too small) * note: Eps was 0.0001f but this was giving problems for blood animation for * the open movie "Sintel", using 0.00001f. */ if (ob->scale[0] > 0.00001f * (process.allbb.max[0] - process.allbb.min[0]) || diff --git a/source/blender/blenkernel/intern/mesh_convert.c b/source/blender/blenkernel/intern/mesh_convert.c index 1ec3b6a1cbf..e893a3983bd 100644 --- a/source/blender/blenkernel/intern/mesh_convert.c +++ b/source/blender/blenkernel/intern/mesh_convert.c @@ -1027,11 +1027,15 @@ static Object *object_for_curve_to_mesh_create(Object *object) * * Note that there are extra fields in there like bevel and path, but those are not needed during * conversion, so they are not copied to save unnecessary allocations. */ - if (object->runtime.curve_cache != NULL) { + if (temp_object->runtime.curve_cache == NULL) { temp_object->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for curve types"); + } + + if (object->runtime.curve_cache != NULL) { BKE_displist_copy(&temp_object->runtime.curve_cache->disp, &object->runtime.curve_cache->disp); } + /* Constructive modifiers will use mesh to store result. */ if (object->runtime.data_eval != NULL) { BKE_id_copy_ex( @@ -1057,17 +1061,25 @@ static Object *object_for_curve_to_mesh_create(Object *object) return temp_object; } +/** + * Populate `object->runtime.curve_cache` which is then used to create the mesh. + */ static void curve_to_mesh_eval_ensure(Object *object) { - if (object->runtime.curve_cache == NULL) { - object->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for Curve"); - } Curve *curve = (Curve *)object->data; Curve remapped_curve = *curve; Object remapped_object = *object; - remapped_object.runtime.bb = NULL; + BKE_object_runtime_reset(&remapped_object); + remapped_object.data = &remapped_curve; + if (object->runtime.curve_cache == NULL) { + object->runtime.curve_cache = MEM_callocN(sizeof(CurveCache), "CurveCache for Curve"); + } + + /* Temporarily share the curve-cache with the temporary object, owned by `object`. */ + remapped_object.runtime.curve_cache = object->runtime.curve_cache; + /* Clear all modifiers for the bevel object. * * This is because they can not be reliably evaluated for an original object (at least because @@ -1078,8 +1090,8 @@ static void curve_to_mesh_eval_ensure(Object *object) Object bevel_object = {{NULL}}; if (remapped_curve.bevobj != NULL) { bevel_object = *remapped_curve.bevobj; - bevel_object.runtime.bb = NULL; BLI_listbase_clear(&bevel_object.modifiers); + BKE_object_runtime_reset(&bevel_object); remapped_curve.bevobj = &bevel_object; } @@ -1087,8 +1099,8 @@ static void curve_to_mesh_eval_ensure(Object *object) Object taper_object = {{NULL}}; if (remapped_curve.taperobj != NULL) { taper_object = *remapped_curve.taperobj; - taper_object.runtime.bb = NULL; BLI_listbase_clear(&taper_object.modifiers); + BKE_object_runtime_reset(&taper_object); remapped_curve.taperobj = &taper_object; } @@ -1110,12 +1122,12 @@ static void curve_to_mesh_eval_ensure(Object *object) BKE_object_eval_assign_data(&remapped_object, &mesh_eval->id, true); } - MEM_SAFE_FREE(remapped_object.runtime.bb); - MEM_SAFE_FREE(taper_object.runtime.bb); - MEM_SAFE_FREE(bevel_object.runtime.bb); + /* Owned by `object` & needed by the caller to create the mesh. */ + remapped_object.runtime.curve_cache = NULL; - BKE_object_free_curve_cache(&bevel_object); - BKE_object_free_curve_cache(&taper_object); + BKE_object_runtime_free_data(&remapped_object); + BKE_object_runtime_free_data(&taper_object); + BKE_object_runtime_free_data(&taper_object); } static Mesh *mesh_new_from_curve_type_object(Object *object) @@ -1199,7 +1211,9 @@ static Mesh *mesh_new_from_mesh(Object *object, Mesh *mesh) return mesh_result; } -static Mesh *mesh_new_from_mesh_object_with_layers(Depsgraph *depsgraph, Object *object) +static Mesh *mesh_new_from_mesh_object_with_layers(Depsgraph *depsgraph, + Object *object, + const bool preserve_origindex) { if (DEG_is_original_id(&object->id)) { return mesh_new_from_mesh(object, (Mesh *)object->data); @@ -1216,16 +1230,23 @@ static Mesh *mesh_new_from_mesh_object_with_layers(Depsgraph *depsgraph, Object Scene *scene = DEG_get_evaluated_scene(depsgraph); CustomData_MeshMasks mask = CD_MASK_MESH; + if (preserve_origindex) { + mask.vmask |= CD_MASK_ORIGINDEX; + mask.emask |= CD_MASK_ORIGINDEX; + mask.lmask |= CD_MASK_ORIGINDEX; + mask.pmask |= CD_MASK_ORIGINDEX; + } Mesh *result = mesh_create_eval_final(depsgraph, scene, &object_for_eval, &mask); return result; } static Mesh *mesh_new_from_mesh_object(Depsgraph *depsgraph, Object *object, - bool preserve_all_data_layers) + const bool preserve_all_data_layers, + const bool preserve_origindex) { - if (preserve_all_data_layers) { - return mesh_new_from_mesh_object_with_layers(depsgraph, object); + if (preserve_all_data_layers || preserve_origindex) { + return mesh_new_from_mesh_object_with_layers(depsgraph, object, preserve_origindex); } Mesh *mesh_input = object->data; /* If we are in edit mode, use evaluated mesh from edit structure, matching to what @@ -1236,7 +1257,10 @@ static Mesh *mesh_new_from_mesh_object(Depsgraph *depsgraph, return mesh_new_from_mesh(object, mesh_input); } -Mesh *BKE_mesh_new_from_object(Depsgraph *depsgraph, Object *object, bool preserve_all_data_layers) +Mesh *BKE_mesh_new_from_object(Depsgraph *depsgraph, + Object *object, + const bool preserve_all_data_layers, + const bool preserve_origindex) { Mesh *new_mesh = NULL; switch (object->type) { @@ -1249,7 +1273,8 @@ Mesh *BKE_mesh_new_from_object(Depsgraph *depsgraph, Object *object, bool preser new_mesh = mesh_new_from_mball_object(object); break; case OB_MESH: - new_mesh = mesh_new_from_mesh_object(depsgraph, object, preserve_all_data_layers); + new_mesh = mesh_new_from_mesh_object( + depsgraph, object, preserve_all_data_layers, preserve_origindex); break; default: /* Object does not have geometry data. */ @@ -1314,7 +1339,7 @@ Mesh *BKE_mesh_new_from_object_to_bmain(Main *bmain, { BLI_assert(ELEM(object->type, OB_FONT, OB_CURVE, OB_SURF, OB_MBALL, OB_MESH)); - Mesh *mesh = BKE_mesh_new_from_object(depsgraph, object, preserve_all_data_layers); + Mesh *mesh = BKE_mesh_new_from_object(depsgraph, object, preserve_all_data_layers, false); if (mesh == NULL) { /* Unable to convert the object to a mesh, return an empty one. */ Mesh *mesh_in_bmain = BKE_mesh_add(bmain, ((ID *)object->data)->name + 2); diff --git a/source/blender/blenkernel/intern/mesh_mirror.c b/source/blender/blenkernel/intern/mesh_mirror.c index 93a2e9058fa..3d30c218fba 100644 --- a/source/blender/blenkernel/intern/mesh_mirror.c +++ b/source/blender/blenkernel/intern/mesh_mirror.c @@ -183,6 +183,19 @@ Mesh *BKE_mesh_mirror_apply_mirror_on_axis_for_modifier(MirrorModifierData *mmd, if (do_bisect) { copy_v3_v3(plane_co, itmp[3]); copy_v3_v3(plane_no, itmp[axis]); + + /* Account for non-uniform scale in `ob`, see: T87592. */ + float ob_scale[3] = { + len_squared_v3(ob->obmat[0]), + len_squared_v3(ob->obmat[1]), + len_squared_v3(ob->obmat[2]), + }; + /* Scale to avoid precision loss with extreme values. */ + const float ob_scale_max = max_fff(UNPACK3(ob_scale)); + if (LIKELY(ob_scale_max != 0.0f)) { + mul_v3_fl(ob_scale, 1.0f / ob_scale_max); + mul_v3_v3(plane_no, ob_scale); + } } } else if (do_bisect) { diff --git a/source/blender/blenkernel/intern/modifier.c b/source/blender/blenkernel/intern/modifier.c index 34b7c4234ec..3b67237f5eb 100644 --- a/source/blender/blenkernel/intern/modifier.c +++ b/source/blender/blenkernel/intern/modifier.c @@ -281,7 +281,7 @@ bool BKE_modifier_is_preview(ModifierData *md) return false; } -ModifierData *BKE_modifiers_findby_type(Object *ob, ModifierType type) +ModifierData *BKE_modifiers_findby_type(const Object *ob, ModifierType type) { LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) { if (md->type == type) { @@ -291,7 +291,7 @@ ModifierData *BKE_modifiers_findby_type(Object *ob, ModifierType type) return NULL; } -ModifierData *BKE_modifiers_findby_name(Object *ob, const char *name) +ModifierData *BKE_modifiers_findby_name(const Object *ob, const char *name) { return BLI_findstring(&(ob->modifiers), name, offsetof(ModifierData, name)); } diff --git a/source/blender/blenkernel/intern/movieclip.c b/source/blender/blenkernel/intern/movieclip.c index 9c2cd03dbc2..0f2f56f4f2b 100644 --- a/source/blender/blenkernel/intern/movieclip.c +++ b/source/blender/blenkernel/intern/movieclip.c @@ -950,7 +950,7 @@ static void movieclip_load_get_size(MovieClip *clip) int width, height; MovieClipUser user = {0}; - user.framenr = 1; + user.framenr = BKE_movieclip_remap_clip_to_scene_frame(clip, 1); BKE_movieclip_get_size(clip, &user, &width, &height); if (width && height) { diff --git a/source/blender/blenkernel/intern/nla.c b/source/blender/blenkernel/intern/nla.c index 97aa6b07ab0..92e21183acb 100644 --- a/source/blender/blenkernel/intern/nla.c +++ b/source/blender/blenkernel/intern/nla.c @@ -254,7 +254,7 @@ NlaTrack *BKE_nlatrack_copy(Main *bmain, * \param flag: Control ID pointers management, see LIB_ID_CREATE_.../LIB_ID_COPY_... * flags in BKE_lib_id.h */ -void BKE_nla_tracks_copy(Main *bmain, ListBase *dst, ListBase *src, const int flag) +void BKE_nla_tracks_copy(Main *bmain, ListBase *dst, const ListBase *src, const int flag) { NlaTrack *nlt, *nlt_d; @@ -275,6 +275,54 @@ void BKE_nla_tracks_copy(Main *bmain, ListBase *dst, ListBase *src, const int fl } } +/* Set adt_dest->actstrip to the strip with the same index as adt_source->actstrip. */ +static void update_active_strip(AnimData *adt_dest, + NlaTrack *track_dest, + const AnimData *adt_source, + NlaTrack *track_source) +{ + BLI_assert(BLI_listbase_count(&track_source->strips) == BLI_listbase_count(&track_dest->strips)); + + NlaStrip *strip_dest = track_dest->strips.first; + LISTBASE_FOREACH (NlaStrip *, strip_source, &track_source->strips) { + if (strip_source == adt_source->actstrip) { + adt_dest->actstrip = strip_dest; + } + + strip_dest = strip_dest->next; + } +} + +/* Set adt_dest->act_track to the track with the same index as adt_source->act_track. */ +static void update_active_track(AnimData *adt_dest, const AnimData *adt_source) +{ + BLI_assert(BLI_listbase_count(&adt_source->nla_tracks) == + BLI_listbase_count(&adt_dest->nla_tracks)); + + NlaTrack *track_dest = adt_dest->nla_tracks.first; + LISTBASE_FOREACH (NlaTrack *, track_source, &adt_source->nla_tracks) { + if (track_source == adt_source->act_track) { + adt_dest->act_track = track_dest; + /* Assumption: the active strip is on the active track. */ + update_active_strip(adt_dest, track_dest, adt_source, track_source); + } + + track_dest = track_dest->next; + } +} + +void BKE_nla_tracks_copy_from_adt(Main *bmain, + AnimData *adt_dest, + const AnimData *adt_source, + const int flag) +{ + adt_dest->act_track = NULL; + adt_dest->actstrip = NULL; + + BKE_nla_tracks_copy(bmain, &adt_dest->nla_tracks, &adt_source->nla_tracks, flag); + update_active_track(adt_dest, adt_source); +} + /* Adding ------------------------------------------- */ /* Add a NLA Track to the given AnimData diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 473be34d69a..3377f5c69dc 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -303,6 +303,16 @@ static void library_foreach_node_socket(LibraryForeachIDData *data, bNodeSocket BKE_LIB_FOREACHID_PROCESS(data, default_value->value, IDWALK_CB_USER); break; } + case SOCK_TEXTURE: { + bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value; + BKE_LIB_FOREACHID_PROCESS(data, default_value->value, IDWALK_CB_USER); + break; + } + case SOCK_MATERIAL: { + bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value; + BKE_LIB_FOREACHID_PROCESS(data, default_value->value, IDWALK_CB_USER); + break; + } case SOCK_FLOAT: case SOCK_VECTOR: case SOCK_RGBA: @@ -434,6 +444,12 @@ static void write_node_socket_default_value(BlendWriter *writer, bNodeSocket *so case SOCK_COLLECTION: BLO_write_struct(writer, bNodeSocketValueCollection, sock->default_value); break; + case SOCK_TEXTURE: + BLO_write_struct(writer, bNodeSocketValueTexture, sock->default_value); + break; + case SOCK_MATERIAL: + BLO_write_struct(writer, bNodeSocketValueMaterial, sock->default_value); + break; case __SOCK_MESH: case SOCK_CUSTOM: case SOCK_SHADER: @@ -497,10 +513,16 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) if (node->storage) { /* could be handlerized at some point, now only 1 exception still */ - if ((ntree->type == NTREE_SHADER) && + if ((ELEM(ntree->type, NTREE_SHADER, NTREE_GEOMETRY)) && ELEM(node->type, SH_NODE_CURVE_VEC, SH_NODE_CURVE_RGB)) { BKE_curvemapping_blend_write(writer, (const CurveMapping *)node->storage); } + else if ((ntree->type == NTREE_GEOMETRY) && (node->type == GEO_NODE_ATTRIBUTE_CURVE_MAP)) { + BLO_write_struct_by_name(writer, node->typeinfo->storagename, node->storage); + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; + BKE_curvemapping_blend_write(writer, (const CurveMapping *)data->curve_vec); + BKE_curvemapping_blend_write(writer, (const CurveMapping *)data->curve_rgb); + } else if (ntree->type == NTREE_SHADER && (node->type == SH_NODE_SCRIPT)) { NodeShaderScript *nss = (NodeShaderScript *)node->storage; if (nss->bytecode) { @@ -676,6 +698,18 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) BKE_curvemapping_blend_read(reader, (CurveMapping *)node->storage); break; } + case GEO_NODE_ATTRIBUTE_CURVE_MAP: { + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; + BLO_read_data_address(reader, &data->curve_vec); + if (data->curve_vec) { + BKE_curvemapping_blend_read(reader, data->curve_vec); + } + BLO_read_data_address(reader, &data->curve_rgb); + if (data->curve_rgb) { + BKE_curvemapping_blend_read(reader, data->curve_rgb); + } + break; + } case SH_NODE_SCRIPT: { NodeShaderScript *nss = (NodeShaderScript *)node->storage; BLO_read_data_address(reader, &nss->bytecode); @@ -802,6 +836,16 @@ static void lib_link_node_socket(BlendLibReader *reader, Library *lib, bNodeSock BLO_read_id_address(reader, lib, &default_value->value); break; } + case SOCK_TEXTURE: { + bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value; + BLO_read_id_address(reader, lib, &default_value->value); + break; + } + case SOCK_MATERIAL: { + bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value; + BLO_read_id_address(reader, lib, &default_value->value); + break; + } case SOCK_FLOAT: case SOCK_VECTOR: case SOCK_RGBA: @@ -887,6 +931,16 @@ static void expand_node_socket(BlendExpander *expander, bNodeSocket *sock) BLO_expand(expander, default_value->value); break; } + case SOCK_TEXTURE: { + bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value; + BLO_expand(expander, default_value->value); + break; + } + case SOCK_MATERIAL: { + bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value; + BLO_expand(expander, default_value->value); + break; + } case SOCK_FLOAT: case SOCK_VECTOR: case SOCK_RGBA: @@ -1454,6 +1508,16 @@ static void socket_id_user_increment(bNodeSocket *sock) id_us_plus((ID *)default_value->value); break; } + case SOCK_TEXTURE: { + bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value; + id_us_plus((ID *)default_value->value); + break; + } + case SOCK_MATERIAL: { + bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value; + id_us_plus((ID *)default_value->value); + break; + } case SOCK_FLOAT: case SOCK_VECTOR: case SOCK_RGBA: @@ -1493,6 +1557,20 @@ static void socket_id_user_decrement(bNodeSocket *sock) } break; } + case SOCK_TEXTURE: { + bNodeSocketValueTexture *default_value = (bNodeSocketValueTexture *)sock->default_value; + if (default_value->value != nullptr) { + id_us_min(&default_value->value->id); + } + break; + } + case SOCK_MATERIAL: { + bNodeSocketValueMaterial *default_value = (bNodeSocketValueMaterial *)sock->default_value; + if (default_value->value != nullptr) { + id_us_min(&default_value->value->id); + } + break; + } case SOCK_FLOAT: case SOCK_VECTOR: case SOCK_RGBA: @@ -1636,6 +1714,10 @@ const char *nodeStaticSocketType(int type, int subtype) return "NodeSocketGeometry"; case SOCK_COLLECTION: return "NodeSocketCollection"; + case SOCK_TEXTURE: + return "NodeSocketTexture"; + case SOCK_MATERIAL: + return "NodeSocketMaterial"; } return nullptr; } @@ -1707,6 +1789,10 @@ const char *nodeStaticSocketInterfaceType(int type, int subtype) return "NodeSocketInterfaceGeometry"; case SOCK_COLLECTION: return "NodeSocketInterfaceCollection"; + case SOCK_TEXTURE: + return "NodeSocketInterfaceTexture"; + case SOCK_MATERIAL: + return "NodeSocketInterfaceMaterial"; } return nullptr; } @@ -2172,6 +2258,17 @@ bNodeTree *ntreeCopyTree_ex_new_pointers(const bNodeTree *ntree, return new_ntree; } +static int node_count_links(const bNodeTree *ntree, const bNodeSocket *socket) +{ + int count = 0; + LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { + if (ELEM(socket, link->fromsock, link->tosock)) { + count++; + } + } + return count; +} + /* also used via rna api, so we check for proper input output direction */ bNodeLink *nodeAddLink( bNodeTree *ntree, bNode *fromnode, bNodeSocket *fromsock, bNode *tonode, bNodeSocket *tosock) @@ -2208,6 +2305,10 @@ bNodeLink *nodeAddLink( ntree->update |= NTREE_UPDATE_LINKS; } + if (link->tosock->flag & SOCK_MULTI_INPUT) { + link->multi_input_socket_index = node_count_links(ntree, link->tosock) - 1; + } + return link; } @@ -4226,7 +4327,7 @@ void ntreeUpdateAllUsers(Main *main, ID *id) if (GS(id->name) == ID_NT) { bNodeTree *ngroup = (bNodeTree *)id; - if (ngroup->type == NTREE_GEOMETRY) { + if (ngroup->type == NTREE_GEOMETRY && (ngroup->update & NTREE_UPDATE_GROUP)) { LISTBASE_FOREACH (Object *, object, &main->objects) { LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { if (md->type == eModifierType_Nodes) { @@ -4934,6 +5035,7 @@ static void registerGeometryNodes() register_node_type_geo_attribute_combine_xyz(); register_node_type_geo_attribute_compare(); register_node_type_geo_attribute_convert(); + register_node_type_geo_attribute_curve_map(); register_node_type_geo_attribute_fill(); register_node_type_geo_attribute_map_range(); register_node_type_geo_attribute_math(); @@ -4941,14 +5043,21 @@ static void registerGeometryNodes() register_node_type_geo_attribute_proximity(); register_node_type_geo_attribute_randomize(); register_node_type_geo_attribute_separate_xyz(); + register_node_type_geo_attribute_transfer(); register_node_type_geo_attribute_vector_math(); + register_node_type_geo_attribute_vector_rotate(); register_node_type_geo_attribute_remove(); register_node_type_geo_boolean(); register_node_type_geo_bounding_box(); register_node_type_geo_collection_info(); + register_node_type_geo_curve_to_mesh(); + register_node_type_geo_curve_resample(); register_node_type_geo_edge_split(); + register_node_type_geo_input_material(); register_node_type_geo_is_viewport(); register_node_type_geo_join_geometry(); + register_node_type_geo_material_assign(); + register_node_type_geo_material_replace(); register_node_type_geo_mesh_primitive_circle(); register_node_type_geo_mesh_primitive_cone(); register_node_type_geo_mesh_primitive_cube(); diff --git a/source/blender/blenkernel/intern/node_ui_storage.cc b/source/blender/blenkernel/intern/node_ui_storage.cc index cc910bab6ac..7a28fd295fb 100644 --- a/source/blender/blenkernel/intern/node_ui_storage.cc +++ b/source/blender/blenkernel/intern/node_ui_storage.cc @@ -152,6 +152,7 @@ void BKE_nodetree_error_message_add(bNodeTree &ntree, node_error_message_log(ntree, node, message, type); NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ntree, context, node); + std::lock_guard lock{node_ui_storage.mutex}; node_ui_storage.warnings.append({type, std::move(message)}); } @@ -163,6 +164,7 @@ void BKE_nodetree_attribute_hint_add(bNodeTree &ntree, const CustomDataType data_type) { NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ntree, context, node); + std::lock_guard lock{node_ui_storage.mutex}; node_ui_storage.attribute_hints.add_as( AvailableAttributeInfo{attribute_name, domain, data_type}); } diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.c index 522b4549f57..e4cfe64df11 100644 --- a/source/blender/blenkernel/intern/object.c +++ b/source/blender/blenkernel/intern/object.c @@ -1332,12 +1332,9 @@ bool BKE_object_support_modifier_type_check(const Object *ob, int modifier_type) if (ob->type == OB_HAIR) { return (mti->modifyHair != NULL) || (mti->flags & eModifierTypeFlag_AcceptsVertexCosOnly); } - if (ob->type == OB_POINTCLOUD) { + if (ELEM(ob->type, OB_POINTCLOUD, OB_VOLUME)) { return (mti->modifyGeometrySet != NULL); } - if (ob->type == OB_VOLUME) { - return (mti->modifyVolume != NULL) || (mti->modifyGeometrySet != NULL); - } if (ELEM(ob->type, OB_MESH, OB_CURVE, OB_SURF, OB_FONT, OB_LATTICE)) { if (ob->type == OB_LATTICE && (mti->flags & eModifierTypeFlag_AcceptsVertexCosOnly) == 0) { return false; @@ -2292,7 +2289,7 @@ Object *BKE_object_add_for_data( void BKE_object_copy_softbody(Object *ob_dst, const Object *ob_src, const int flag) { SoftBody *sb = ob_src->soft; - bool tagged_no_main = ob_dst->id.tag & LIB_TAG_NO_MAIN; + const bool is_orig = (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) == 0; ob_dst->softflag = ob_src->softflag; if (sb == NULL) { @@ -2333,7 +2330,7 @@ void BKE_object_copy_softbody(Object *ob_dst, const Object *ob_src, const int fl sbn->scratch = NULL; - if (!tagged_no_main) { + if (is_orig) { sbn->shared = MEM_dupallocN(sb->shared); sbn->shared->pointcache = BKE_ptcache_copy_list( &sbn->shared->ptcaches, &sb->shared->ptcaches, flag); @@ -2372,7 +2369,7 @@ ParticleSystem *BKE_object_copy_particlesystem(ParticleSystem *psys, const int f BLI_listbase_clear(&psysn->pathcachebufs); BLI_listbase_clear(&psysn->childcachebufs); - if (flag & LIB_ID_CREATE_NO_MAIN) { + if (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) { /* XXX Disabled, fails when evaluating depsgraph after copying ID with no main for preview * creation. */ // BLI_assert((psys->flag & PSYS_SHARED_CACHES) == 0); @@ -3288,6 +3285,10 @@ static bool ob_parcurve(Object *ob, Object *par, float r_mat[4][4]) ctime = cu->ctime; } + if (cu->flag & CU_PATH_CLAMP) { + CLAMP(ctime, 0.0f, 1.0f); + } + unit_m4(r_mat); /* vec: 4 items! */ @@ -4346,7 +4347,7 @@ void BKE_object_handle_update_ex(Depsgraph *depsgraph, } /* Speed optimization for animation lookups. */ if (ob->pose != NULL) { - BKE_pose_channels_hash_make(ob->pose); + BKE_pose_channels_hash_ensure(ob->pose); if (ob->pose->flag & POSE_CONSTRAINTS_NEED_UPDATE_FLAGS) { BKE_pose_update_constraint_flags(ob->pose); } @@ -5117,6 +5118,20 @@ void BKE_object_runtime_reset_on_copy(Object *object, const int UNUSED(flag)) } /** + * The function frees memory used by the runtime data, but not the runtime field itself. + * + * All runtime data is cleared to ensure it's not used again, + * in keeping with other `_free_data(..)` functions. + */ +void BKE_object_runtime_free_data(Object *object) +{ + /* Currently this is all that's needed. */ + BKE_object_free_derived_caches(object); + + BKE_object_runtime_reset(object); +} + +/** * Find an associated armature object. */ static Object *obrel_armature_find(Object *ob) @@ -5657,7 +5672,7 @@ Mesh *BKE_object_to_mesh(Depsgraph *depsgraph, Object *object, bool preserve_all { BKE_object_to_mesh_clear(object); - Mesh *mesh = BKE_mesh_new_from_object(depsgraph, object, preserve_all_data_layers); + Mesh *mesh = BKE_mesh_new_from_object(depsgraph, object, preserve_all_data_layers, false); object->runtime.object_as_temp_mesh = mesh; return mesh; } diff --git a/source/blender/blenkernel/intern/object_dupli.cc b/source/blender/blenkernel/intern/object_dupli.cc index ddf7826969b..768fa9373c1 100644 --- a/source/blender/blenkernel/intern/object_dupli.cc +++ b/source/blender/blenkernel/intern/object_dupli.cc @@ -36,6 +36,7 @@ #include "BLI_math.h" #include "BLI_rand.h" #include "BLI_span.hh" +#include "BLI_vector.hh" #include "DNA_anim_types.h" #include "DNA_collection_types.h" @@ -73,6 +74,7 @@ using blender::Array; using blender::float3; using blender::float4x4; using blender::Span; +using blender::Vector; /* -------------------------------------------------------------------- */ /** \name Internal Duplicate Context @@ -90,6 +92,13 @@ struct DupliContext { Object *object; float space_mat[4][4]; + /** + * A stack that contains all the "parent" objects of a particular instance when recursive + * instancing is used. This is used to prevent objects from instancing themselves accidentally. + * Use a vector instead of a stack because we want to use the #contains method. + */ + Vector<Object *> *instance_stack; + int persistent_id[MAX_DUPLI_RECUR]; int level; @@ -113,7 +122,8 @@ static void init_context(DupliContext *r_ctx, Depsgraph *depsgraph, Scene *scene, Object *ob, - const float space_mat[4][4]) + const float space_mat[4][4], + Vector<Object *> &instance_stack) { r_ctx->depsgraph = depsgraph; r_ctx->scene = scene; @@ -122,6 +132,7 @@ static void init_context(DupliContext *r_ctx, r_ctx->object = ob; r_ctx->obedit = OBEDIT_FROM_OBACT(ob); + r_ctx->instance_stack = &instance_stack; if (space_mat) { copy_m4_m4(r_ctx->space_mat, space_mat); } @@ -150,6 +161,7 @@ static void copy_dupli_context( } r_ctx->object = ob; + r_ctx->instance_stack = ctx->instance_stack; if (mat) { mul_m4_m4m4(r_ctx->space_mat, (float(*)[4])ctx->space_mat, mat); } @@ -235,12 +247,19 @@ static void make_recursive_duplis(const DupliContext *ctx, const float space_mat[4][4], int index) { + if (ctx->instance_stack->contains(ob)) { + /* Avoid recursive instances. */ + printf("Warning: '%s' object is trying to instance itself.\n", ob->id.name + 2); + return; + } /* Simple preventing of too deep nested collections with #MAX_DUPLI_RECUR. */ if (ctx->level < MAX_DUPLI_RECUR) { DupliContext rctx; copy_dupli_context(&rctx, ctx, ob, space_mat, index); if (rctx.gen) { + ctx->instance_stack->append(ob); rctx.gen->make_duplis(&rctx); + ctx->instance_stack->remove_last(); } } } @@ -823,38 +842,38 @@ static void make_duplis_instances_component(const DupliContext *ctx) return; } - Span<float4x4> instance_offset_matrices = component->transforms(); + Span<float4x4> instance_offset_matrices = component->instance_transforms(); + Span<int> instance_reference_handles = component->instance_reference_handles(); Span<int> almost_unique_ids = component->almost_unique_ids(); - Span<InstancedData> instanced_data = component->instanced_data(); + Span<InstanceReference> references = component->references(); - for (int i = 0; i < component->instances_amount(); i++) { - const InstancedData &data = instanced_data[i]; + for (int64_t i : instance_offset_matrices.index_range()) { + const InstanceReference &reference = references[instance_reference_handles[i]]; const int id = almost_unique_ids[i]; - if (data.type == INSTANCE_DATA_TYPE_OBJECT) { - Object *object = data.data.object; - if (object != nullptr) { + switch (reference.type()) { + case InstanceReference::Type::Object: { + Object &object = reference.object(); float matrix[4][4]; mul_m4_m4m4(matrix, ctx->object->obmat, instance_offset_matrices[i].values); - make_dupli(ctx, object, matrix, id); + make_dupli(ctx, &object, matrix, id); float space_matrix[4][4]; - mul_m4_m4m4(space_matrix, instance_offset_matrices[i].values, object->imat); + mul_m4_m4m4(space_matrix, instance_offset_matrices[i].values, object.imat); mul_m4_m4_pre(space_matrix, ctx->object->obmat); - make_recursive_duplis(ctx, object, space_matrix, id); + make_recursive_duplis(ctx, &object, space_matrix, id); + break; } - } - else if (data.type == INSTANCE_DATA_TYPE_COLLECTION) { - Collection *collection = data.data.collection; - if (collection != nullptr) { + case InstanceReference::Type::Collection: { + Collection &collection = reference.collection(); float collection_matrix[4][4]; unit_m4(collection_matrix); - sub_v3_v3(collection_matrix[3], collection->instance_offset); + sub_v3_v3(collection_matrix[3], collection.instance_offset); mul_m4_m4_pre(collection_matrix, instance_offset_matrices[i].values); mul_m4_m4_pre(collection_matrix, ctx->object->obmat); eEvaluationMode mode = DEG_get_mode(ctx->depsgraph); - FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN (collection, object, mode) { + FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN (&collection, object, mode) { if (object == ctx->object) { continue; } @@ -866,6 +885,10 @@ static void make_duplis_instances_component(const DupliContext *ctx) make_recursive_duplis(ctx, object, collection_matrix, id); } FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_END; + break; + } + case InstanceReference::Type::None: { + break; } } } @@ -1588,7 +1611,9 @@ ListBase *object_duplilist(Depsgraph *depsgraph, Scene *sce, Object *ob) { ListBase *duplilist = (ListBase *)MEM_callocN(sizeof(ListBase), "duplilist"); DupliContext ctx; - init_context(&ctx, depsgraph, sce, ob, nullptr); + Vector<Object *> instance_stack; + instance_stack.append(ob); + init_context(&ctx, depsgraph, sce, ob, nullptr, instance_stack); if (ctx.gen) { ctx.duplilist = duplilist; ctx.gen->make_duplis(&ctx); diff --git a/source/blender/blenkernel/intern/ocean.c b/source/blender/blenkernel/intern/ocean.c index d2f4d0702ed..9d53dad8d03 100644 --- a/source/blender/blenkernel/intern/ocean.c +++ b/source/blender/blenkernel/intern/ocean.c @@ -911,8 +911,12 @@ void BKE_ocean_init(struct Ocean *o, for (i = 0; i < o->_M; i++) { for (j = 0; j < o->_N; j++) { /* This ensures we get a value tied to the surface location, avoiding dramatic surface - * change with changing resolution. */ - int new_seed = seed + BLI_hash_int_2d(o->_kx[i] * 360.0f, o->_kz[j] * 360.0f); + * change with changing resolution. + * Explicitly cast to signed int first to ensure consistent behavior on all processors, + * since behavior of float to unsigned int cast is undefined in C. */ + const int hash_x = o->_kx[i] * 360.0f; + const int hash_z = o->_kz[j] * 360.0f; + int new_seed = seed + BLI_hash_int_2d(hash_x, hash_z); BLI_rng_seed(rng, new_seed); float r1 = gaussRand(rng); diff --git a/source/blender/blenkernel/intern/particle.c b/source/blender/blenkernel/intern/particle.c index ae685357151..a873ecec6f1 100644 --- a/source/blender/blenkernel/intern/particle.c +++ b/source/blender/blenkernel/intern/particle.c @@ -101,6 +101,8 @@ static void particle_settings_init(ID *id) MEMCPY_STRUCT_AFTER(particle_settings, DNA_struct_default_get(ParticleSettings), id); particle_settings->effector_weights = BKE_effector_add_weights(NULL); + particle_settings->pd = BKE_partdeflect_new(PFIELD_NULL); + particle_settings->pd2 = BKE_partdeflect_new(PFIELD_NULL); } static void particle_settings_copy_data(Main *UNUSED(bmain), diff --git a/source/blender/blenkernel/intern/pbvh_intern.h b/source/blender/blenkernel/intern/pbvh_intern.h index 63bc8753fc7..948b57578dc 100644 --- a/source/blender/blenkernel/intern/pbvh_intern.h +++ b/source/blender/blenkernel/intern/pbvh_intern.h @@ -187,31 +187,31 @@ int BB_widest_axis(const BB *bb); void pbvh_grow_nodes(PBVH *bvh, int totnode); bool ray_face_intersection_quad(const float ray_start[3], struct IsectRayPrecalc *isect_precalc, - const float *t0, - const float *t1, - const float *t2, - const float *t3, + const float t0[3], + const float t1[3], + const float t2[3], + const float t3[3], float *depth); bool ray_face_intersection_tri(const float ray_start[3], struct IsectRayPrecalc *isect_precalc, - const float *t0, - const float *t1, - const float *t2, + const float t0[3], + const float t1[3], + const float t2[3], float *depth); bool ray_face_nearest_quad(const float ray_start[3], const float ray_normal[3], - const float *t0, - const float *t1, - const float *t2, - const float *t3, + const float t0[3], + const float t1[3], + const float t2[3], + const float t3[3], float *r_depth, float *r_dist_sq); bool ray_face_nearest_tri(const float ray_start[3], const float ray_normal[3], - const float *t0, - const float *t1, - const float *t2, + const float t0[3], + const float t1[3], + const float t2[3], float *r_depth, float *r_dist_sq); diff --git a/source/blender/blenkernel/intern/pointcache.c b/source/blender/blenkernel/intern/pointcache.c index 17434ee8023..be206f8a642 100644 --- a/source/blender/blenkernel/intern/pointcache.c +++ b/source/blender/blenkernel/intern/pointcache.c @@ -1805,7 +1805,7 @@ int BKE_ptcache_mem_pointers_seek(int point_index, PTCacheMem *pm, void *cur[BPH } for (i = 0; i < BPHYS_TOT_DATA; i++) { - cur[i] = data_types & (1 << i) ? (char *)pm->data[i] + index * ptcache_data_size[i] : NULL; + cur[i] = (data_types & (1 << i)) ? (char *)pm->data[i] + index * ptcache_data_size[i] : NULL; } return 1; diff --git a/source/blender/blenkernel/intern/rigidbody.c b/source/blender/blenkernel/intern/rigidbody.c index 19078446009..2539b990210 100644 --- a/source/blender/blenkernel/intern/rigidbody.c +++ b/source/blender/blenkernel/intern/rigidbody.c @@ -260,10 +260,12 @@ static RigidBodyOb *rigidbody_copy_object(const Object *ob, const int flag) RigidBodyOb *rboN = NULL; if (ob->rigidbody_object) { + const bool is_orig = (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) == 0; + /* just duplicate the whole struct first (to catch all the settings) */ rboN = MEM_dupallocN(ob->rigidbody_object); - if ((flag & LIB_ID_CREATE_NO_MAIN) == 0) { + if (is_orig) { /* This is a regular copy, and not a CoW copy for depsgraph evaluation */ rboN->shared = MEM_callocN(sizeof(*rboN->shared), "RigidBodyOb_Shared"); } diff --git a/source/blender/blenkernel/intern/scene.c b/source/blender/blenkernel/intern/scene.c index e1f013eb589..a4ab64a8a02 100644 --- a/source/blender/blenkernel/intern/scene.c +++ b/source/blender/blenkernel/intern/scene.c @@ -742,7 +742,8 @@ static void scene_foreach_id(ID *id, LibraryForeachIDData *data) BKE_LIB_FOREACHID_PROCESS(data, view_layer->mat_override, IDWALK_CB_USER); LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { - BKE_LIB_FOREACHID_PROCESS(data, base->object, IDWALK_CB_NOP); + BKE_LIB_FOREACHID_PROCESS( + data, base->object, IDWALK_CB_NOP | IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE); } scene_foreach_layer_collection(data, &view_layer->layer_collections); diff --git a/source/blender/blenkernel/intern/softbody.c b/source/blender/blenkernel/intern/softbody.c index d52e4443ac1..fcc1afbc59b 100644 --- a/source/blender/blenkernel/intern/softbody.c +++ b/source/blender/blenkernel/intern/softbody.c @@ -499,7 +499,6 @@ static void ccd_mesh_free(ccd_Mesh *ccdm) } MEM_freeN(ccdm->mima); MEM_freeN(ccdm); - ccdm = NULL; } } @@ -830,13 +829,13 @@ static void calculate_collision_balls(Object *ob) } /* creates new softbody if didn't exist yet, makes new points and springs arrays */ -static void renew_softbody(Scene *scene, Object *ob, int totpoint, int totspring) +static void renew_softbody(Object *ob, int totpoint, int totspring) { SoftBody *sb; int i; short softflag; if (ob->soft == NULL) { - ob->soft = sbNew(scene); + ob->soft = sbNew(); } else { free_softbody_intern(ob->soft); @@ -2680,7 +2679,7 @@ static void springs_from_mesh(Object *ob) } /* makes totally fresh start situation */ -static void mesh_to_softbody(Scene *scene, Object *ob) +static void mesh_to_softbody(Object *ob) { SoftBody *sb; Mesh *me = ob->data; @@ -2698,7 +2697,7 @@ static void mesh_to_softbody(Scene *scene, Object *ob) } /* renew ends with ob->soft with points and edges, also checks & makes ob->soft */ - renew_softbody(scene, ob, me->totvert, totedge); + renew_softbody(ob, me->totvert, totedge); /* we always make body points */ sb = ob->soft; @@ -2910,7 +2909,7 @@ static void makelatticesprings(Lattice *lt, BodySpring *bs, int dostiff, Object } /* makes totally fresh start situation */ -static void lattice_to_softbody(Scene *scene, Object *ob) +static void lattice_to_softbody(Object *ob) { Lattice *lt = ob->data; SoftBody *sb; @@ -2930,7 +2929,7 @@ static void lattice_to_softbody(Scene *scene, Object *ob) } /* renew ends with ob->soft with points and edges, also checks & makes ob->soft */ - renew_softbody(scene, ob, totvert, totspring); + renew_softbody(ob, totvert, totspring); sb = ob->soft; /* can be created in renew_softbody() */ bp = sb->bpoint; @@ -2973,7 +2972,7 @@ static void lattice_to_softbody(Scene *scene, Object *ob) } /* makes totally fresh start situation */ -static void curve_surf_to_softbody(Scene *scene, Object *ob) +static void curve_surf_to_softbody(Object *ob) { Curve *cu = ob->data; SoftBody *sb; @@ -2994,7 +2993,7 @@ static void curve_surf_to_softbody(Scene *scene, Object *ob) } /* renew ends with ob->soft with points and edges, also checks & makes ob->soft */ - renew_softbody(scene, ob, totvert, totspring); + renew_softbody(ob, totvert, totspring); sb = ob->soft; /* can be created in renew_softbody() */ /* set vars now */ @@ -3118,7 +3117,7 @@ static void sb_new_scratch(SoftBody *sb) /* ************ Object level, exported functions *************** */ /* allocates and initializes general main data */ -SoftBody *sbNew(Scene *scene) +SoftBody *sbNew(void) { SoftBody *sb; @@ -3141,12 +3140,6 @@ SoftBody *sbNew(Scene *scene) /*todo backward file compat should copy inspring to inpush while reading old files*/ sb->inpush = 0.5f; - sb->interval = 10; - if (scene != NULL) { - sb->sfra = scene->r.sfra; - sb->efra = scene->r.efra; - } - sb->colball = 0.49f; sb->balldamp = 0.50f; sb->ballstiff = 1.0f; @@ -3578,17 +3571,17 @@ void sbObjectStep(struct Depsgraph *depsgraph, switch (ob->type) { case OB_MESH: - mesh_to_softbody(scene, ob); + mesh_to_softbody(ob); break; case OB_LATTICE: - lattice_to_softbody(scene, ob); + lattice_to_softbody(ob); break; case OB_CURVE: case OB_SURF: - curve_surf_to_softbody(scene, ob); + curve_surf_to_softbody(ob); break; default: - renew_softbody(scene, ob, numVerts, 0); + renew_softbody(ob, numVerts, 0); break; } diff --git a/source/blender/blenkernel/intern/spline_base.cc b/source/blender/blenkernel/intern/spline_base.cc new file mode 100644 index 00000000000..11620a30948 --- /dev/null +++ b/source/blender/blenkernel/intern/spline_base.cc @@ -0,0 +1,343 @@ +/* + * 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_timeit.hh" + +#include "BKE_spline.hh" + +#include "FN_generic_virtual_array.hh" + +using blender::Array; +using blender::float3; +using blender::IndexRange; +using blender::MutableSpan; +using blender::Span; + +Spline::Type Spline::type() const +{ + return type_; +} + +void Spline::translate(const blender::float3 &translation) +{ + for (float3 &position : this->positions()) { + position += translation; + } + this->mark_cache_invalid(); +} + +void Spline::transform(const blender::float4x4 &matrix) +{ + for (float3 &position : this->positions()) { + position = matrix * position; + } + this->mark_cache_invalid(); +} + +int Spline::evaluated_edges_size() const +{ + const int eval_size = this->evaluated_points_size(); + if (eval_size == 1) { + return 0; + } + + return this->is_cyclic_ ? eval_size : eval_size - 1; +} + +float Spline::length() const +{ + Span<float> lengths = this->evaluated_lengths(); + return (lengths.size() == 0) ? 0 : this->evaluated_lengths().last(); +} + +int Spline::segments_size() const +{ + const int points_len = this->size(); + + return is_cyclic_ ? points_len : points_len - 1; +} + +bool Spline::is_cyclic() const +{ + return is_cyclic_; +} + +void Spline::set_cyclic(const bool value) +{ + is_cyclic_ = value; +} + +static void accumulate_lengths(Span<float3> positions, + const bool is_cyclic, + MutableSpan<float> lengths) +{ + float length = 0.0f; + for (const int i : IndexRange(positions.size() - 1)) { + length += float3::distance(positions[i], positions[i + 1]); + lengths[i] = length; + } + if (is_cyclic) { + lengths.last() = length + float3::distance(positions.last(), positions.first()); + } +} + +/** + * Return non-owning access to the cache of accumulated lengths along the spline. Each item is the + * length of the subsequent segment, i.e. the first value is the length of the first segment rather + * than 0. This calculation is rather trivial, and only depends on the evaluated positions. + * However, the results are used often, so it makes sense to cache it. + */ +Span<float> Spline::evaluated_lengths() const +{ + if (!length_cache_dirty_) { + return evaluated_lengths_cache_; + } + + std::lock_guard lock{length_cache_mutex_}; + if (!length_cache_dirty_) { + return evaluated_lengths_cache_; + } + + const int total = evaluated_edges_size(); + evaluated_lengths_cache_.resize(total); + + Span<float3> positions = this->evaluated_positions(); + accumulate_lengths(positions, is_cyclic_, evaluated_lengths_cache_); + + length_cache_dirty_ = false; + return evaluated_lengths_cache_; +} + +static float3 direction_bisect(const float3 &prev, const float3 &middle, const float3 &next) +{ + const float3 dir_prev = (middle - prev).normalized(); + const float3 dir_next = (next - middle).normalized(); + + return (dir_prev + dir_next).normalized(); +} + +static void calculate_tangents(Span<float3> positions, + const bool is_cyclic, + MutableSpan<float3> tangents) +{ + if (positions.size() == 1) { + return; + } + + for (const int i : IndexRange(1, positions.size() - 2)) { + tangents[i] = direction_bisect(positions[i - 1], positions[i], positions[i + 1]); + } + + if (is_cyclic) { + const float3 &second_to_last = positions[positions.size() - 2]; + const float3 &last = positions.last(); + const float3 &first = positions.first(); + const float3 &second = positions[1]; + tangents.first() = direction_bisect(last, first, second); + tangents.last() = direction_bisect(second_to_last, last, first); + } + else { + tangents.first() = (positions[1] - positions[0]).normalized(); + tangents.last() = (positions.last() - positions[positions.size() - 2]).normalized(); + } +} + +/** + * Return non-owning access to the direction of the curve at each evaluated point. + */ +Span<float3> Spline::evaluated_tangents() const +{ + if (!tangent_cache_dirty_) { + return evaluated_tangents_cache_; + } + + std::lock_guard lock{tangent_cache_mutex_}; + if (!tangent_cache_dirty_) { + return evaluated_tangents_cache_; + } + + const int eval_size = this->evaluated_points_size(); + evaluated_tangents_cache_.resize(eval_size); + + Span<float3> positions = this->evaluated_positions(); + + if (eval_size == 1) { + evaluated_tangents_cache_.first() = float3(1.0f, 0.0f, 0.0f); + } + else { + calculate_tangents(positions, is_cyclic_, evaluated_tangents_cache_); + this->correct_end_tangents(); + } + + tangent_cache_dirty_ = false; + return evaluated_tangents_cache_; +} + +static float3 rotate_direction_around_axis(const float3 &direction, + const float3 &axis, + const float angle) +{ + BLI_ASSERT_UNIT_V3(direction); + BLI_ASSERT_UNIT_V3(axis); + + const float3 axis_scaled = axis * float3::dot(direction, axis); + const float3 diff = direction - axis_scaled; + const float3 cross = float3::cross(axis, diff); + + return axis_scaled + diff * std::cos(angle) + cross * std::sin(angle); +} + +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 (!normal_cache_dirty_) { + return evaluated_normals_cache_; + } + + std::lock_guard lock{normal_cache_mutex_}; + if (!normal_cache_dirty_) { + return evaluated_normals_cache_; + } + + const int eval_size = this->evaluated_points_size(); + evaluated_normals_cache_.resize(eval_size); + + Span<float3> tangents = evaluated_tangents(); + MutableSpan<float3> normals = evaluated_normals_cache_; + + /* Only Z up normals are supported at the moment. */ + calculate_normals_z_up(tangents, normals); + + /* Rotate the generated normals with the interpolated tilt data. */ + blender::fn::GVArray_Typed<float> tilts{ + this->interpolate_to_evaluated_points(blender::fn::GVArray_For_Span(this->tilts()))}; + for (const int i : normals.index_range()) { + normals[i] = rotate_direction_around_axis(normals[i], tangents[i], tilts[i]); + } + + 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}; +} + +/** + * Return an array of evenly spaced samples along the length of the spline. The samples are indices + * and factors to the next index encoded in floats. The logic for converting from the float values + * to interpolation data is in #lookup_data_from_index_factor. + */ +Array<float> Spline::sample_uniform_index_factors(const int samples_size) const +{ + const Span<float> lengths = this->evaluated_lengths(); + + BLI_assert(samples_size > 0); + Array<float> samples(samples_size); + + samples[0] = 0.0f; + if (samples_size == 1) { + return samples; + } + + const float total_length = this->length(); + const float sample_length = total_length / (samples_size - (is_cyclic_ ? 0 : 1)); + + /* Store the length at the previous evaluated point in a variable so it can + * start out at zero (the lengths array doesn't contain 0 for the first point). */ + float prev_length = 0.0f; + int i_sample = 1; + for (const int i_evaluated : IndexRange(this->evaluated_edges_size())) { + const float length = lengths[i_evaluated]; + + /* Add every sample that fits in this evaluated edge. */ + while ((sample_length * i_sample) < length && i_sample < samples_size) { + const float factor = (sample_length * i_sample - prev_length) / (length - prev_length); + samples[i_sample] = i_evaluated + factor; + i_sample++; + } + + prev_length = length; + } + + if (!is_cyclic_) { + /* In rare cases this can prevent overflow of the stored index. */ + samples.last() = lengths.size(); + } + + return samples; +} + +Spline::LookupResult Spline::lookup_data_from_index_factor(const float index_factor) const +{ + const int points_len = this->evaluated_points_size(); + + if (is_cyclic_) { + if (index_factor < points_len) { + const int index = std::floor(index_factor); + const int next_index = (index < points_len - 1) ? index + 1 : 0; + return LookupResult{index, next_index, index_factor - index}; + } + return LookupResult{points_len - 1, 0, 1.0f}; + } + + if (index_factor < points_len - 1) { + const int index = std::floor(index_factor); + const int next_index = index + 1; + return LookupResult{index, next_index, index_factor - index}; + } + return LookupResult{points_len - 2, points_len - 1, 1.0f}; +} + +void Spline::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated) const +{ + Span<float3> positions = use_evaluated ? this->evaluated_positions() : this->positions(); + for (const float3 &position : positions) { + minmax_v3v3_v3(min, max, position); + } +} diff --git a/source/blender/blenkernel/intern/spline_bezier.cc b/source/blender/blenkernel/intern/spline_bezier.cc new file mode 100644 index 00000000000..4be3ba8576e --- /dev/null +++ b/source/blender/blenkernel/intern/spline_bezier.cc @@ -0,0 +1,584 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_array.hh" +#include "BLI_span.hh" +#include "BLI_task.hh" + +#include "BKE_spline.hh" + +using blender::Array; +using blender::float3; +using blender::IndexRange; +using blender::MutableSpan; +using blender::Span; + +SplinePtr BezierSpline::copy() const +{ + return std::make_unique<BezierSpline>(*this); +} + +int BezierSpline::size() const +{ + const int size = positions_.size(); + BLI_assert(size == handle_types_left_.size()); + BLI_assert(size == handle_positions_left_.size()); + BLI_assert(size == handle_types_right_.size()); + BLI_assert(size == handle_positions_right_.size()); + BLI_assert(size == radii_.size()); + BLI_assert(size == tilts_.size()); + return size; +} + +int BezierSpline::resolution() const +{ + return resolution_; +} + +void BezierSpline::set_resolution(const int value) +{ + BLI_assert(value > 0); + resolution_ = value; + this->mark_cache_invalid(); +} + +/** + * \warning Call #reallocate on the spline's attributes after adding all points. + */ +void BezierSpline::add_point(const float3 position, + const HandleType handle_type_start, + const float3 handle_position_start, + const HandleType handle_type_end, + const float3 handle_position_end, + const float radius, + const float tilt) +{ + handle_types_left_.append(handle_type_start); + handle_positions_left_.append(handle_position_start); + positions_.append(position); + handle_types_right_.append(handle_type_end); + handle_positions_right_.append(handle_position_end); + radii_.append(radius); + tilts_.append(tilt); + this->mark_cache_invalid(); +} + +void BezierSpline::resize(const int size) +{ + handle_types_left_.resize(size); + handle_positions_left_.resize(size); + positions_.resize(size); + handle_types_right_.resize(size); + handle_positions_right_.resize(size); + radii_.resize(size); + tilts_.resize(size); + this->mark_cache_invalid(); + attributes.reallocate(size); +} + +MutableSpan<float3> BezierSpline::positions() +{ + return positions_; +} +Span<float3> BezierSpline::positions() const +{ + return positions_; +} +MutableSpan<float> BezierSpline::radii() +{ + return radii_; +} +Span<float> BezierSpline::radii() const +{ + return radii_; +} +MutableSpan<float> BezierSpline::tilts() +{ + return tilts_; +} +Span<float> BezierSpline::tilts() const +{ + return tilts_; +} +Span<BezierSpline::HandleType> BezierSpline::handle_types_left() const +{ + return handle_types_left_; +} +MutableSpan<BezierSpline::HandleType> BezierSpline::handle_types_left() +{ + return handle_types_left_; +} +Span<float3> BezierSpline::handle_positions_left() const +{ + this->ensure_auto_handles(); + return handle_positions_left_; +} +MutableSpan<float3> BezierSpline::handle_positions_left() +{ + this->ensure_auto_handles(); + return handle_positions_left_; +} +Span<BezierSpline::HandleType> BezierSpline::handle_types_right() const +{ + return handle_types_right_; +} +MutableSpan<BezierSpline::HandleType> BezierSpline::handle_types_right() +{ + return handle_types_right_; +} +Span<float3> BezierSpline::handle_positions_right() const +{ + this->ensure_auto_handles(); + return handle_positions_right_; +} +MutableSpan<float3> BezierSpline::handle_positions_right() +{ + this->ensure_auto_handles(); + return handle_positions_right_; +} + +static float3 previous_position(Span<float3> positions, const bool cyclic, const int i) +{ + if (i == 0) { + if (cyclic) { + return positions[positions.size() - 1]; + } + return 2.0f * positions[i] - positions[i + 1]; + } + return positions[i - 1]; +} + +static float3 next_position(Span<float3> positions, const bool cyclic, const int i) +{ + if (i == positions.size() - 1) { + if (cyclic) { + return positions[0]; + } + return 2.0f * positions[i] - positions[i - 1]; + } + return positions[i + 1]; +} + +/** + * Recalculate all #Auto and #Vector handles with positions automatically + * derived from the neighboring control points. + */ +void BezierSpline::ensure_auto_handles() const +{ + if (!auto_handles_dirty_) { + return; + } + + std::lock_guard lock{auto_handle_mutex_}; + if (!auto_handles_dirty_) { + return; + } + + for (const int i : IndexRange(this->size())) { + if (ELEM(HandleType::Auto, handle_types_left_[i], handle_types_right_[i])) { + const float3 prev_diff = positions_[i] - previous_position(positions_, is_cyclic_, i); + const float3 next_diff = next_position(positions_, is_cyclic_, i) - positions_[i]; + float prev_len = prev_diff.length(); + float next_len = next_diff.length(); + if (prev_len == 0.0f) { + prev_len = 1.0f; + } + if (next_len == 0.0f) { + next_len = 1.0f; + } + const float3 dir = next_diff / next_len + prev_diff / prev_len; + + /* This magic number is unfortunate, but comes from elsewhere in Blender. */ + const float len = dir.length() * 2.5614f; + if (len != 0.0f) { + if (handle_types_left_[i] == HandleType::Auto) { + const float prev_len_clamped = std::min(prev_len, next_len * 5.0f); + handle_positions_left_[i] = positions_[i] + dir * -(prev_len_clamped / len); + } + if (handle_types_right_[i] == HandleType::Auto) { + const float next_len_clamped = std::min(next_len, prev_len * 5.0f); + handle_positions_right_[i] = positions_[i] + dir * (next_len_clamped / len); + } + } + } + + if (handle_types_left_[i] == HandleType::Vector) { + const float3 prev = previous_position(positions_, is_cyclic_, i); + handle_positions_left_[i] = float3::interpolate(positions_[i], prev, 1.0f / 3.0f); + } + + if (handle_types_right_[i] == HandleType::Vector) { + const float3 next = next_position(positions_, is_cyclic_, i); + handle_positions_right_[i] = float3::interpolate(positions_[i], next, 1.0f / 3.0f); + } + } + + auto_handles_dirty_ = false; +} + +void BezierSpline::translate(const blender::float3 &translation) +{ + for (float3 &position : this->positions()) { + position += translation; + } + for (float3 &handle_position : this->handle_positions_left()) { + handle_position += translation; + } + for (float3 &handle_position : this->handle_positions_right()) { + handle_position += translation; + } + this->mark_cache_invalid(); +} + +void BezierSpline::transform(const blender::float4x4 &matrix) +{ + for (float3 &position : this->positions()) { + position = matrix * position; + } + for (float3 &handle_position : this->handle_positions_left()) { + handle_position = matrix * handle_position; + } + for (float3 &handle_position : this->handle_positions_right()) { + handle_position = matrix * handle_position; + } + this->mark_cache_invalid(); +} + +bool BezierSpline::point_is_sharp(const int index) const +{ + return ELEM(handle_types_left_[index], HandleType::Vector, HandleType::Free) || + ELEM(handle_types_right_[index], HandleType::Vector, HandleType::Free); +} + +bool BezierSpline::segment_is_vector(const int index) const +{ + if (index == this->size() - 1) { + if (is_cyclic_) { + return handle_types_right_.last() == HandleType::Vector && + handle_types_left_.first() == HandleType::Vector; + } + /* There is actually no segment in this case, but it's nice to avoid + * having a special case for the last segment in calling code. */ + return true; + } + return handle_types_right_[index] == HandleType::Vector && + handle_types_left_[index + 1] == HandleType::Vector; +} + +void BezierSpline::mark_cache_invalid() +{ + offset_cache_dirty_ = true; + position_cache_dirty_ = true; + mapping_cache_dirty_ = true; + tangent_cache_dirty_ = true; + normal_cache_dirty_ = true; + length_cache_dirty_ = true; + auto_handles_dirty_ = true; +} + +int BezierSpline::evaluated_points_size() const +{ + BLI_assert(this->size() > 0); + return this->control_point_offsets().last(); +} + +/** + * If the spline is not cyclic, the direction for the first and last points is just the + * direction formed by the corresponding handles and control points. In the unlikely situation + * that the handles define a zero direction, fallback to using the direction defined by the + * first and last evaluated segments already calculated in #Spline::evaluated_tangents(). + */ +void BezierSpline::correct_end_tangents() const +{ + if (is_cyclic_) { + return; + } + + MutableSpan<float3> tangents(evaluated_tangents_cache_); + + if (handle_positions_left_.first() != positions_.first()) { + tangents.first() = (positions_.first() - handle_positions_left_.first()).normalized(); + } + if (handle_positions_right_.last() != positions_.last()) { + tangents.last() = (handle_positions_right_.last() - positions_.last()).normalized(); + } +} + +static void bezier_forward_difference_3d(const float3 &point_0, + const float3 &point_1, + const float3 &point_2, + const float3 &point_3, + MutableSpan<float3> result) +{ + BLI_assert(result.size() > 0); + const float inv_len = 1.0f / static_cast<float>(result.size()); + const float inv_len_squared = inv_len * inv_len; + const float inv_len_cubed = inv_len_squared * inv_len; + + const float3 rt1 = 3.0f * (point_1 - point_0) * inv_len; + const float3 rt2 = 3.0f * (point_0 - 2.0f * point_1 + point_2) * inv_len_squared; + const float3 rt3 = (point_3 - point_0 + 3.0f * (point_1 - point_2)) * inv_len_cubed; + + float3 q0 = point_0; + float3 q1 = rt1 + rt2 + rt3; + float3 q2 = 2.0f * rt2 + 6.0f * rt3; + float3 q3 = 6.0f * rt3; + for (const int i : result.index_range()) { + result[i] = q0; + q0 += q1; + q1 += q2; + q2 += q3; + } +} + +void BezierSpline::evaluate_bezier_segment(const int index, + const int next_index, + MutableSpan<float3> positions) const +{ + if (this->segment_is_vector(index)) { + BLI_assert(positions.size() == 1); + positions.first() = positions_[index]; + } + else { + bezier_forward_difference_3d(positions_[index], + handle_positions_right_[index], + handle_positions_left_[next_index], + positions_[next_index], + positions); + } +} + +/** + * Returns access to a cache of offsets into the evaluated point array for each control point. + * While most control point edges generate the number of edges specified by the resolution, vector + * segments only generate one edge. + * + * \note The length of the result is one greater than the number of points, so that the last item + * is the total number of evaluated points. This is useful to avoid recalculating the size of the + * last segment everywhere. + */ +Span<int> BezierSpline::control_point_offsets() const +{ + if (!offset_cache_dirty_) { + return offset_cache_; + } + + std::lock_guard lock{offset_cache_mutex_}; + if (!offset_cache_dirty_) { + return offset_cache_; + } + + const int points_len = this->size(); + offset_cache_.resize(points_len + 1); + + MutableSpan<int> offsets = offset_cache_; + + int offset = 0; + for (const int i : IndexRange(points_len)) { + offsets[i] = offset; + offset += this->segment_is_vector(i) ? 1 : resolution_; + } + offsets.last() = offset; + + offset_cache_dirty_ = false; + return offsets; +} + +static void calculate_mappings_linear_resolution(Span<int> offsets, + const int size, + const int resolution, + const bool is_cyclic, + MutableSpan<float> r_mappings) +{ + const float first_segment_len_inv = 1.0f / offsets[1]; + for (const int i : IndexRange(0, offsets[1])) { + r_mappings[i] = i * first_segment_len_inv; + } + + const int grain_size = std::max(2048 / resolution, 1); + parallel_for(IndexRange(1, size - 2), grain_size, [&](IndexRange range) { + for (const int i_control_point : range) { + const int segment_len = offsets[i_control_point + 1] - offsets[i_control_point]; + const float segment_len_inv = 1.0f / segment_len; + for (const int i : IndexRange(segment_len)) { + r_mappings[offsets[i_control_point] + i] = i_control_point + i * segment_len_inv; + } + } + }); + + if (is_cyclic) { + const int last_segment_len = offsets[size] - offsets[size - 1]; + const float last_segment_len_inv = 1.0f / last_segment_len; + for (const int i : IndexRange(last_segment_len)) { + r_mappings[offsets[size - 1] + i] = size - 1 + i * last_segment_len_inv; + } + } + else { + r_mappings.last() = size - 1; + } +} + +/** + * Returns non-owning access to an array of values containing the information necessary to + * interpolate values from the original control points to evaluated points. The control point + * index is the integer part of each value, and the factor used for interpolating to the next + * control point is the remaining factional part. + */ +Span<float> BezierSpline::evaluated_mappings() const +{ + if (!mapping_cache_dirty_) { + return evaluated_mapping_cache_; + } + + std::lock_guard lock{mapping_cache_mutex_}; + if (!mapping_cache_dirty_) { + return evaluated_mapping_cache_; + } + + const int size = this->size(); + const int eval_size = this->evaluated_points_size(); + evaluated_mapping_cache_.resize(eval_size); + MutableSpan<float> mappings = evaluated_mapping_cache_; + + if (eval_size == 1) { + mappings.first() = 0.0f; + mapping_cache_dirty_ = false; + return mappings; + } + + Span<int> offsets = this->control_point_offsets(); + + calculate_mappings_linear_resolution(offsets, size, resolution_, is_cyclic_, mappings); + + mapping_cache_dirty_ = false; + return mappings; +} + +Span<float3> BezierSpline::evaluated_positions() const +{ + if (!position_cache_dirty_) { + return evaluated_position_cache_; + } + + std::lock_guard lock{position_cache_mutex_}; + if (!position_cache_dirty_) { + return evaluated_position_cache_; + } + + this->ensure_auto_handles(); + + const int size = this->size(); + const int eval_size = this->evaluated_points_size(); + evaluated_position_cache_.resize(eval_size); + + MutableSpan<float3> positions = evaluated_position_cache_; + + Span<int> offsets = this->control_point_offsets(); + + const int grain_size = std::max(512 / resolution_, 1); + parallel_for(IndexRange(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])); + } + }); + if (is_cyclic_) { + this->evaluate_bezier_segment( + size - 1, 0, positions.slice(offsets[size - 1], offsets[size] - offsets[size - 1])); + } + else { + /* Since evaluating the bezier segment doesn't add the final point, + * it must be added manually in the non-cyclic case. */ + positions.last() = positions_.last(); + } + + position_cache_dirty_ = false; + return positions; +} + +/** + * Convert the data encoded in #evaulated_mappings into its parts-- the information necessary + * to interpolate data from control points to evaluated points between them. The next control + * point index result will not overflow the size of the control point vectors. + */ +BezierSpline::InterpolationData BezierSpline::interpolation_data_from_index_factor( + const float index_factor) const +{ + const int points_len = this->size(); + + if (is_cyclic_) { + if (index_factor < points_len) { + const int index = std::floor(index_factor); + const int next_index = (index < points_len - 1) ? index + 1 : 0; + return InterpolationData{index, next_index, index_factor - index}; + } + return InterpolationData{points_len - 1, 0, 1.0f}; + } + + if (index_factor < points_len - 1) { + const int index = std::floor(index_factor); + const int next_index = index + 1; + return InterpolationData{index, next_index, index_factor - index}; + } + return InterpolationData{points_len - 2, points_len - 1, 1.0f}; +} + +/* Use a spline argument to avoid adding this to the header. */ +template<typename T> +static void interpolate_to_evaluated_points_impl(const BezierSpline &spline, + const blender::VArray<T> &source_data, + MutableSpan<T> result_data) +{ + Span<float> mappings = spline.evaluated_mappings(); + + for (const int i : result_data.index_range()) { + BezierSpline::InterpolationData interp = spline.interpolation_data_from_index_factor( + mappings[i]); + + const T &value = source_data[interp.control_point_index]; + const T &next_value = source_data[interp.next_control_point_index]; + + result_data[i] = blender::attribute_math::mix2(interp.factor, value, next_value); + } +} + +blender::fn::GVArrayPtr BezierSpline::interpolate_to_evaluated_points( + const blender::fn::GVArray &source_data) const +{ + BLI_assert(source_data.size() == this->size()); + + if (source_data.is_single()) { + return source_data.shallow_copy(); + } + + const int eval_size = this->evaluated_points_size(); + if (eval_size == 1) { + return source_data.shallow_copy(); + } + + blender::fn::GVArrayPtr new_varray; + blender::attribute_math::convert_to_static_type(source_data.type(), [&](auto dummy) { + using T = decltype(dummy); + if constexpr (!std::is_void_v<blender::attribute_math::DefaultMixer<T>>) { + Array<T> values(eval_size); + interpolate_to_evaluated_points_impl<T>(*this, source_data.typed<T>(), values); + new_varray = std::make_unique<blender::fn::GVArray_For_ArrayContainer<Array<T>>>( + std::move(values)); + } + }); + + return new_varray; +} diff --git a/source/blender/blenkernel/intern/spline_nurbs.cc b/source/blender/blenkernel/intern/spline_nurbs.cc new file mode 100644 index 00000000000..ae691d26cdb --- /dev/null +++ b/source/blender/blenkernel/intern/spline_nurbs.cc @@ -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. + */ + +#include "BLI_array.hh" +#include "BLI_span.hh" +#include "BLI_virtual_array.hh" + +#include "BKE_attribute_math.hh" +#include "BKE_spline.hh" + +using blender::Array; +using blender::float3; +using blender::IndexRange; +using blender::MutableSpan; +using blender::Span; + +SplinePtr NURBSpline::copy() const +{ + return std::make_unique<NURBSpline>(*this); +} + +int NURBSpline::size() const +{ + const int size = positions_.size(); + BLI_assert(size == radii_.size()); + BLI_assert(size == tilts_.size()); + BLI_assert(size == weights_.size()); + return size; +} + +int NURBSpline::resolution() const +{ + return resolution_; +} + +void NURBSpline::set_resolution(const int value) +{ + BLI_assert(value > 0); + resolution_ = value; + this->mark_cache_invalid(); +} + +uint8_t NURBSpline::order() const +{ + return order_; +} + +void NURBSpline::set_order(const uint8_t value) +{ + BLI_assert(value >= 2 && value <= 6); + order_ = value; + this->mark_cache_invalid(); +} + +/** + * \warning Call #reallocate on the spline's attributes after adding all points. + */ +void NURBSpline::add_point(const float3 position, + const float radius, + const float tilt, + const float weight) +{ + positions_.append(position); + radii_.append(radius); + tilts_.append(tilt); + weights_.append(weight); + knots_dirty_ = true; + this->mark_cache_invalid(); +} + +void NURBSpline::resize(const int size) +{ + positions_.resize(size); + radii_.resize(size); + tilts_.resize(size); + weights_.resize(size); + this->mark_cache_invalid(); + attributes.reallocate(size); +} + +MutableSpan<float3> NURBSpline::positions() +{ + return positions_; +} +Span<float3> NURBSpline::positions() const +{ + return positions_; +} +MutableSpan<float> NURBSpline::radii() +{ + return radii_; +} +Span<float> NURBSpline::radii() const +{ + return radii_; +} +MutableSpan<float> NURBSpline::tilts() +{ + return tilts_; +} +Span<float> NURBSpline::tilts() const +{ + return tilts_; +} +MutableSpan<float> NURBSpline::weights() +{ + return weights_; +} +Span<float> NURBSpline::weights() const +{ + return weights_; +} + +void NURBSpline::mark_cache_invalid() +{ + basis_cache_dirty_ = true; + position_cache_dirty_ = true; + tangent_cache_dirty_ = true; + normal_cache_dirty_ = true; + length_cache_dirty_ = true; +} + +int NURBSpline::evaluated_points_size() const +{ + if (!this->check_valid_size_and_order()) { + return 0; + } + return resolution_ * this->segments_size(); +} + +void NURBSpline::correct_end_tangents() const +{ +} + +bool NURBSpline::check_valid_size_and_order() const +{ + if (this->size() < order_) { + return false; + } + + if (!is_cyclic_ && this->knots_mode == KnotsMode::Bezier) { + if (order_ == 4) { + if (this->size() < 5) { + return false; + } + } + else if (order_ != 3) { + return false; + } + } + + return true; +} + +int NURBSpline::knots_size() const +{ + const int size = this->size() + order_; + return is_cyclic_ ? size + order_ - 1 : size; +} + +void NURBSpline::calculate_knots() const +{ + const KnotsMode mode = this->knots_mode; + const int length = this->size(); + const int order = order_; + + knots_.resize(this->knots_size()); + + MutableSpan<float> knots = knots_; + + if (mode == NURBSpline::KnotsMode::Normal || is_cyclic_) { + for (const int i : knots.index_range()) { + knots[i] = static_cast<float>(i); + } + } + else if (mode == NURBSpline::KnotsMode::EndPoint) { + float k = 0.0f; + for (const int i : IndexRange(1, knots.size())) { + knots[i - 1] = k; + if (i >= order && i <= length) { + k += 1.0f; + } + } + } + else if (mode == NURBSpline::KnotsMode::Bezier) { + BLI_assert(ELEM(order, 3, 4)); + if (order == 3) { + float k = 0.6f; + for (const int i : knots.index_range()) { + if (i >= order && i <= length) { + k += 0.5f; + } + knots[i] = std::floor(k); + } + } + else { + float k = 0.34f; + for (const int i : knots.index_range()) { + knots[i] = std::floor(k); + k += 1.0f / 3.0f; + } + } + } + + if (is_cyclic_) { + const int b = length + order - 1; + if (order > 2) { + for (const int i : IndexRange(1, order - 2)) { + if (knots[b] != knots[b - i]) { + if (i == order - 1) { + knots[length + order - 2] += 1.0f; + break; + } + } + } + } + + int c = order; + for (int i = b; i < this->knots_size(); i++) { + knots[i] = knots[i - 1] + (knots[c] - knots[c - 1]); + c--; + } + } +} + +Span<float> NURBSpline::knots() const +{ + if (!knots_dirty_) { + BLI_assert(knots_.size() == this->size() + order_); + return knots_; + } + + std::lock_guard lock{knots_mutex_}; + if (!knots_dirty_) { + BLI_assert(knots_.size() == this->size() + order_); + return knots_; + } + + this->calculate_knots(); + + knots_dirty_ = false; + + return knots_; +} + +static void calculate_basis_for_point(const float parameter, + const int points_len, + const int order, + Span<float> knots, + MutableSpan<float> basis_buffer, + NURBSpline::BasisCache &basis_cache) +{ + /* Clamp parameter due to floating point inaccuracy. */ + const float t = std::clamp(parameter, knots[0], knots[points_len + order - 1]); + + int start = 0; + int end = 0; + for (const int i : IndexRange(points_len + order - 1)) { + const bool knots_equal = knots[i] == knots[i + 1]; + if (knots_equal || t < knots[i] || t > knots[i + 1]) { + basis_buffer[i] = 0.0f; + continue; + } + + basis_buffer[i] = 1.0f; + start = std::max(i - order - 1, 0); + end = i; + basis_buffer.slice(i + 1, points_len + order - 1 - i).fill(0.0f); + break; + } + basis_buffer[points_len + order - 1] = 0.0f; + + for (const int i_order : IndexRange(2, order - 1)) { + if (end + i_order >= points_len + order) { + end = points_len + order - 1 - i_order; + } + for (const int i : IndexRange(start, end - start + 1)) { + float new_basis = 0.0f; + if (basis_buffer[i] != 0.0f) { + new_basis += ((t - knots[i]) * basis_buffer[i]) / (knots[i + i_order - 1] - knots[i]); + } + + if (basis_buffer[i + 1] != 0.0f) { + new_basis += ((knots[i + i_order] - t) * basis_buffer[i + 1]) / + (knots[i + i_order] - knots[i + 1]); + } + + basis_buffer[i] = new_basis; + } + } + + /* Shrink the range of calculated values to avoid storing unnecessary zeros. */ + while (basis_buffer[start] == 0.0f && start < end) { + start++; + } + while (basis_buffer[end] == 0.0f && end > start) { + end--; + } + + basis_cache.weights.clear(); + basis_cache.weights.extend(basis_buffer.slice(start, end - start + 1)); + basis_cache.start_index = start; +} + +void NURBSpline::calculate_basis_cache() const +{ + if (!basis_cache_dirty_) { + return; + } + + std::lock_guard lock{basis_cache_mutex_}; + if (!basis_cache_dirty_) { + return; + } + + const int points_len = this->size(); + const int eval_size = this->evaluated_points_size(); + BLI_assert(this->evaluated_edges_size() > 0); + basis_cache_.resize(eval_size); + + const int order = this->order(); + Span<float> control_weights = this->weights(); + Span<float> knots = this->knots(); + + MutableSpan<BasisCache> basis_cache(basis_cache_); + + /* This buffer is reused by each basis calculation to store temporary values. + * Theoretically it could be optimized away in the future. */ + Array<float> basis_buffer(this->knots_size()); + + const float start = knots[order - 1]; + const float end = is_cyclic_ ? knots[points_len + order - 1] : knots[points_len]; + const float step = (end - start) / this->evaluated_edges_size(); + float parameter = start; + for (const int i : IndexRange(eval_size)) { + BasisCache &basis = basis_cache[i]; + calculate_basis_for_point( + parameter, points_len + (is_cyclic_ ? order - 1 : 0), order, knots, basis_buffer, basis); + BLI_assert(basis.weights.size() <= order); + + for (const int j : basis.weights.index_range()) { + const int point_index = (basis.start_index + j) % points_len; + basis.weights[j] *= control_weights[point_index]; + } + + parameter += step; + } + + basis_cache_dirty_ = false; +} + +template<typename T> +void interpolate_to_evaluated_points_impl(Span<NURBSpline::BasisCache> weights, + const blender::VArray<T> &source_data, + MutableSpan<T> result_data) +{ + const int points_len = source_data.size(); + BLI_assert(result_data.size() == weights.size()); + blender::attribute_math::DefaultMixer<T> mixer(result_data); + + for (const int i : result_data.index_range()) { + Span<float> point_weights = weights[i].weights; + const int start_index = weights[i].start_index; + + for (const int j : point_weights.index_range()) { + const int point_index = (start_index + j) % points_len; + mixer.mix_in(i, source_data[point_index], point_weights[j]); + } + } + + mixer.finalize(); +} + +blender::fn::GVArrayPtr NURBSpline::interpolate_to_evaluated_points( + const blender::fn::GVArray &source_data) const +{ + BLI_assert(source_data.size() == this->size()); + + if (source_data.is_single()) { + return source_data.shallow_copy(); + } + + this->calculate_basis_cache(); + Span<BasisCache> weights(basis_cache_); + + blender::fn::GVArrayPtr new_varray; + blender::attribute_math::convert_to_static_type(source_data.type(), [&](auto dummy) { + using T = decltype(dummy); + if constexpr (!std::is_void_v<blender::attribute_math::DefaultMixer<T>>) { + Array<T> values(this->evaluated_points_size()); + interpolate_to_evaluated_points_impl<T>(weights, source_data.typed<T>(), values); + new_varray = std::make_unique<blender::fn::GVArray_For_ArrayContainer<Array<T>>>( + std::move(values)); + } + }); + + return new_varray; +} + +Span<float3> NURBSpline::evaluated_positions() const +{ + if (!position_cache_dirty_) { + return evaluated_position_cache_; + } + + std::lock_guard lock{position_cache_mutex_}; + if (!position_cache_dirty_) { + return evaluated_position_cache_; + } + + const int eval_size = this->evaluated_points_size(); + evaluated_position_cache_.resize(eval_size); + + blender::fn::GVArray_Typed<float3> evaluated_positions{ + this->interpolate_to_evaluated_points(blender::fn::GVArray_For_Span<float3>(positions_))}; + + evaluated_positions->materialize(evaluated_position_cache_); + + position_cache_dirty_ = false; + return evaluated_position_cache_; +} diff --git a/source/blender/blenkernel/intern/spline_poly.cc b/source/blender/blenkernel/intern/spline_poly.cc new file mode 100644 index 00000000000..5c33b0052fc --- /dev/null +++ b/source/blender/blenkernel/intern/spline_poly.cc @@ -0,0 +1,117 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_span.hh" +#include "BLI_virtual_array.hh" + +#include "BKE_spline.hh" + +using blender::float3; +using blender::MutableSpan; +using blender::Span; + +SplinePtr PolySpline::copy() const +{ + return std::make_unique<PolySpline>(*this); +} + +int PolySpline::size() const +{ + const int size = positions_.size(); + BLI_assert(size == radii_.size()); + BLI_assert(size == tilts_.size()); + return size; +} + +/** + * \warning Call #reallocate on the spline's attributes after adding all points. + */ +void PolySpline::add_point(const float3 position, const float radius, const float tilt) +{ + positions_.append(position); + radii_.append(radius); + tilts_.append(tilt); + this->mark_cache_invalid(); +} + +void PolySpline::resize(const int size) +{ + positions_.resize(size); + radii_.resize(size); + tilts_.resize(size); + this->mark_cache_invalid(); + attributes.reallocate(size); +} + +MutableSpan<float3> PolySpline::positions() +{ + return positions_; +} +Span<float3> PolySpline::positions() const +{ + return positions_; +} +MutableSpan<float> PolySpline::radii() +{ + return radii_; +} +Span<float> PolySpline::radii() const +{ + return radii_; +} +MutableSpan<float> PolySpline::tilts() +{ + return tilts_; +} +Span<float> PolySpline::tilts() const +{ + return tilts_; +} + +void PolySpline::mark_cache_invalid() +{ + tangent_cache_dirty_ = true; + normal_cache_dirty_ = true; + length_cache_dirty_ = true; +} + +int PolySpline::evaluated_points_size() const +{ + return this->size(); +} + +void PolySpline::correct_end_tangents() const +{ +} + +Span<float3> PolySpline::evaluated_positions() const +{ + return this->positions(); +} + +/** + * Poly spline interpolation from control points to evaluated points is a special case, since + * the result data is the same as the input data. This function returns a GVArray that points to + * the original data. Therefore the lifetime of the returned virtual array must not be longer than + * the source data. + */ +blender::fn::GVArrayPtr PolySpline::interpolate_to_evaluated_points( + const blender::fn::GVArray &source_data) const +{ + BLI_assert(source_data.size() == this->size()); + + return source_data.shallow_copy(); +} diff --git a/source/blender/blenkernel/intern/text.c b/source/blender/blenkernel/intern/text.c index 944e01321ce..27f5593c2ca 100644 --- a/source/blender/blenkernel/intern/text.c +++ b/source/blender/blenkernel/intern/text.c @@ -171,6 +171,9 @@ static void text_free_data(ID *id) static void text_blend_write(BlendWriter *writer, ID *id, const void *id_address) { + if (id->us < 1 && !BLO_write_is_undo(writer)) { + return; + } Text *text = (Text *)id; /* Note: we are clearing local temp data here, *not* the flag in the actual 'real' ID. */ @@ -231,8 +234,6 @@ static void text_blend_read_data(BlendDataReader *reader, ID *id) } text->flags = (text->flags) & ~TXT_ISEXT; - - id_us_ensure_real(&text->id); } IDTypeInfo IDType_ID_TXT = { @@ -293,8 +294,10 @@ Text *BKE_text_add(Main *bmain, const char *name) Text *ta; ta = BKE_id_new(bmain, ID_TXT, name); - /* Texts always have 'real' user (see also read code). */ - id_us_ensure_real(&ta->id); + /* Texts have no users by default... Set the fake user flag to ensure that this text block + * doesn't get deleted by default when cleaning up data blocks. */ + id_us_min(&ta->id); + id_fake_user_set(&ta->id); return ta; } @@ -468,7 +471,7 @@ bool BKE_text_reload(Text *text) * \param is_internal: If \a true, this text data-block only exists in memory, * not as a file on disk. * - * \note text data-blocks have no user by default, only the 'real user' flag. + * \note text data-blocks have no real user but have 'fake user' enabled by default */ Text *BKE_text_load_ex(Main *bmain, const char *file, const char *relpath, const bool is_internal) { @@ -489,9 +492,8 @@ Text *BKE_text_load_ex(Main *bmain, const char *file, const char *relpath, const } ta = BKE_libblock_alloc(bmain, ID_TXT, BLI_path_basename(filepath_abs), 0); - /* Texts have no user by default... Only the 'real' user flag. */ - id_us_ensure_real(&ta->id); id_us_min(&ta->id); + id_fake_user_set(&ta->id); BLI_listbase_clear(&ta->lines); ta->curl = ta->sell = NULL; diff --git a/source/blender/blenkernel/intern/volume.cc b/source/blender/blenkernel/intern/volume.cc index 93d5e116131..c0ce57818d1 100644 --- a/source/blender/blenkernel/intern/volume.cc +++ b/source/blender/blenkernel/intern/volume.cc @@ -1007,7 +1007,6 @@ static void volume_update_simplify_level(Volume *volume, const Depsgraph *depsgr static void volume_evaluate_modifiers(struct Depsgraph *depsgraph, struct Scene *scene, Object *object, - Volume *volume_input, GeometrySet &geometry_set) { /* Modifier evaluation modes. */ @@ -1033,18 +1032,6 @@ static void volume_evaluate_modifiers(struct Depsgraph *depsgraph, if (mti->modifyGeometrySet) { mti->modifyGeometrySet(md, &mectx, &geometry_set); } - else if (mti->modifyVolume) { - VolumeComponent &volume_component = geometry_set.get_component_for_write<VolumeComponent>(); - Volume *volume_old = volume_component.get_for_write(); - if (volume_old == nullptr) { - volume_old = BKE_volume_new_for_eval(volume_input); - volume_component.replace(volume_old); - } - Volume *volume_new = mti->modifyVolume(md, &mectx, volume_old); - if (volume_new != volume_old) { - volume_component.replace(volume_new); - } - } } } @@ -1095,9 +1082,8 @@ void BKE_volume_data_update(struct Depsgraph *depsgraph, struct Scene *scene, Ob /* Evaluate modifiers. */ Volume *volume = (Volume *)object->data; GeometrySet geometry_set; - VolumeComponent &volume_component = geometry_set.get_component_for_write<VolumeComponent>(); - volume_component.replace(volume, GeometryOwnershipType::ReadOnly); - volume_evaluate_modifiers(depsgraph, scene, object, volume, geometry_set); + geometry_set.replace_volume(volume, GeometryOwnershipType::ReadOnly); + volume_evaluate_modifiers(depsgraph, scene, object, geometry_set); Volume *volume_eval = take_volume_ownership_from_geometry_set(geometry_set); diff --git a/source/blender/blenkernel/intern/writeffmpeg.c b/source/blender/blenkernel/intern/writeffmpeg.c index 7fc9e8cc0ef..39f65d76e3c 100644 --- a/source/blender/blenkernel/intern/writeffmpeg.c +++ b/source/blender/blenkernel/intern/writeffmpeg.c @@ -56,6 +56,7 @@ # include <libavcodec/avcodec.h> # include <libavformat/avformat.h> # include <libavutil/imgutils.h> +# include <libavutil/opt.h> # include <libavutil/rational.h> # include <libavutil/samplefmt.h> # include <libswscale/swscale.h> @@ -80,6 +81,8 @@ typedef struct FFMpegContext { int ffmpeg_preset; /* see eFFMpegPreset */ AVFormatContext *outfile; + AVCodecContext *video_codec; + AVCodecContext *audio_codec; AVStream *video_stream; AVStream *audio_stream; AVFrame *current_frame; /* Image frame in output pixel format. */ @@ -91,10 +94,6 @@ typedef struct FFMpegContext { uint8_t *audio_input_buffer; uint8_t *audio_deinterleave_buffer; int audio_input_samples; -# ifndef FFMPEG_HAVE_ENCODE_AUDIO2 - uint8_t *audio_output_buffer; - int audio_outbuf_size; -# endif double audio_time; bool audio_deinterleave; int audio_sample_size; @@ -141,32 +140,22 @@ static int request_float_audio_buffer(int codec_id) } # ifdef WITH_AUDASPACE + static int write_audio_frame(FFMpegContext *context) { - AVCodecContext *c = NULL; - AVPacket pkt; AVFrame *frame = NULL; - int got_output = 0; - - c = context->audio_stream->codec; - - av_init_packet(&pkt); - pkt.size = 0; - pkt.data = NULL; + AVCodecContext *c = context->audio_codec; AUD_Device_read( context->audio_mixdown_device, context->audio_input_buffer, context->audio_input_samples); context->audio_time += (double)context->audio_input_samples / (double)c->sample_rate; -# ifdef FFMPEG_HAVE_ENCODE_AUDIO2 frame = av_frame_alloc(); - av_frame_unref(frame); frame->pts = context->audio_time / av_q2d(c->time_base); frame->nb_samples = context->audio_input_samples; frame->format = c->sample_fmt; -# ifdef FFMPEG_HAVE_FRAME_CHANNEL_LAYOUT + frame->channels = c->channels; frame->channel_layout = c->channel_layout; -# endif if (context->audio_deinterleave) { int channel, i; @@ -194,61 +183,49 @@ static int write_audio_frame(FFMpegContext *context) context->audio_input_samples * c->channels * context->audio_sample_size, 1); - if (avcodec_encode_audio2(c, &pkt, frame, &got_output) < 0) { - // XXX error("Error writing audio packet"); - return -1; - } + int success = 0; - if (!got_output) { - av_frame_free(&frame); - return 0; + int ret = avcodec_send_frame(c, frame); + if (ret < 0) { + /* Can't send frame to encoder. This shouldn't happen. */ + fprintf(stderr, "Can't send audio frame: %s\n", av_err2str(ret)); + success = -1; } -# else - pkt.size = avcodec_encode_audio(c, - context->audio_output_buffer, - context->audio_outbuf_size, - (short *)context->audio_input_buffer); - if (pkt.size < 0) { - // XXX error("Error writing audio packet"); - return -1; - } + AVPacket *pkt = av_packet_alloc(); - pkt.data = context->audio_output_buffer; - got_output = 1; -# endif + while (ret >= 0) { - if (got_output) { - if (pkt.pts != AV_NOPTS_VALUE) { - pkt.pts = av_rescale_q(pkt.pts, c->time_base, context->audio_stream->time_base); + ret = avcodec_receive_packet(c, pkt); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; } - if (pkt.dts != AV_NOPTS_VALUE) { - pkt.dts = av_rescale_q(pkt.dts, c->time_base, context->audio_stream->time_base); + if (ret < 0) { + fprintf(stderr, "Error encoding audio frame: %s\n", av_err2str(ret)); + success = -1; } - if (pkt.duration > 0) { - pkt.duration = av_rescale_q(pkt.duration, c->time_base, context->audio_stream->time_base); + + av_packet_rescale_ts(pkt, c->time_base, context->audio_stream->time_base); + if (pkt->duration > 0) { + pkt->duration = av_rescale_q(pkt->duration, c->time_base, context->audio_stream->time_base); } - pkt.stream_index = context->audio_stream->index; + pkt->stream_index = context->audio_stream->index; - pkt.flags |= AV_PKT_FLAG_KEY; + pkt->flags |= AV_PKT_FLAG_KEY; - if (av_interleaved_write_frame(context->outfile, &pkt) != 0) { - fprintf(stderr, "Error writing audio packet!\n"); - if (frame) { - av_frame_free(&frame); - } - return -1; + int write_ret = av_interleaved_write_frame(context->outfile, pkt); + if (write_ret != 0) { + fprintf(stderr, "Error writing audio packet: %s\n", av_err2str(write_ret)); + success = -1; + break; } - - av_free_packet(&pkt); } - if (frame) { - av_frame_free(&frame); - } + av_packet_free(&pkt); + av_frame_free(&frame); - return 0; + return success; } # endif /* #ifdef WITH_AUDASPACE */ @@ -264,14 +241,15 @@ static AVFrame *alloc_picture(int pix_fmt, int width, int height) if (!f) { return NULL; } - size = avpicture_get_size(pix_fmt, width, height); + size = av_image_get_buffer_size(pix_fmt, width, height, 1); /* allocate the actual picture buffer */ buf = MEM_mallocN(size, "AVFrame buffer"); if (!buf) { free(f); return NULL; } - avpicture_fill((AVPicture *)f, buf, pix_fmt, width, height); + + av_image_fill_arrays(f->data, f->linesize, buf, pix_fmt, width, height, 1); f->format = pix_fmt; f->width = width; f->height = height; @@ -341,58 +319,57 @@ static const char **get_file_extensions(int format) } /* Write a frame to the output file */ -static int write_video_frame( - FFMpegContext *context, const RenderData *rd, int cfra, AVFrame *frame, ReportList *reports) +static int write_video_frame(FFMpegContext *context, int cfra, AVFrame *frame, ReportList *reports) { - int got_output; int ret, success = 1; - AVCodecContext *c = context->video_stream->codec; - AVPacket packet = {0}; + AVPacket *packet = av_packet_alloc(); - av_init_packet(&packet); + AVCodecContext *c = context->video_codec; frame->pts = cfra; - ret = avcodec_encode_video2(c, &packet, frame, &got_output); + ret = avcodec_send_frame(c, frame); + if (ret < 0) { + /* Can't send frame to encoder. This shouldn't happen. */ + fprintf(stderr, "Can't send video frame: %s\n", av_err2str(ret)); + success = -1; + } - if (ret >= 0 && got_output) { - if (packet.pts != AV_NOPTS_VALUE) { - packet.pts = av_rescale_q(packet.pts, c->time_base, context->video_stream->time_base); - PRINT("Video Frame PTS: %d\n", (int)packet.pts); - } - else { - PRINT("Video Frame PTS: not set\n"); - } - if (packet.dts != AV_NOPTS_VALUE) { - packet.dts = av_rescale_q(packet.dts, c->time_base, context->video_stream->time_base); - PRINT("Video Frame DTS: %d\n", (int)packet.dts); + while (ret >= 0) { + ret = avcodec_receive_packet(c, packet); + + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + /* No more packets available. */ + break; } - else { - PRINT("Video Frame DTS: not set\n"); + if (ret < 0) { + fprintf(stderr, "Error encoding frame: %s\n", av_err2str(ret)); + break; } - packet.stream_index = context->video_stream->index; - ret = av_interleaved_write_frame(context->outfile, &packet); - success = (ret == 0); - } - else if (ret < 0) { - success = 0; + packet->stream_index = context->video_stream->index; + av_packet_rescale_ts(packet, c->time_base, context->video_stream->time_base); + if (av_interleaved_write_frame(context->outfile, packet) != 0) { + success = -1; + break; + } } if (!success) { BKE_report(reports, RPT_ERROR, "Error writing frame"); + PRINT("Error writing frame: %s\n", av_err2str(ret)); } + av_packet_free(&packet); + return success; } /* read and encode a frame of audio from the buffer */ -static AVFrame *generate_video_frame(FFMpegContext *context, - const uint8_t *pixels, - ReportList *reports) +static AVFrame *generate_video_frame(FFMpegContext *context, const uint8_t *pixels) { - AVCodecContext *c = context->video_stream->codec; - int height = c->height; + AVCodecParameters *codec = context->video_stream->codecpar; + int height = codec->height; AVFrame *rgb_frame; if (context->img_convert_frame != NULL) { @@ -437,7 +414,7 @@ static AVFrame *generate_video_frame(FFMpegContext *context, (const uint8_t *const *)rgb_frame->data, rgb_frame->linesize, 0, - c->height, + codec->height, context->current_frame->data, context->current_frame->linesize); } @@ -445,9 +422,7 @@ static AVFrame *generate_video_frame(FFMpegContext *context, return context->current_frame; } -static void set_ffmpeg_property_option(AVCodecContext *c, - IDProperty *prop, - AVDictionary **dictionary) +static void set_ffmpeg_property_option(IDProperty *prop, AVDictionary **dictionary) { char name[128]; char *param; @@ -535,7 +510,7 @@ static void set_ffmpeg_properties(RenderData *rd, for (curr = prop->data.group.first; curr; curr = curr->next) { if (ffmpeg_proprty_valid(c, prop_name, curr)) { - set_ffmpeg_property_option(c, curr, dictionary); + set_ffmpeg_property_option(curr, dictionary); } } } @@ -552,7 +527,6 @@ static AVStream *alloc_video_stream(FFMpegContext *context, int error_size) { AVStream *st; - AVCodecContext *c; AVCodec *codec; AVDictionary *opts = NULL; @@ -566,7 +540,8 @@ static AVStream *alloc_video_stream(FFMpegContext *context, /* Set up the codec context */ - c = st->codec; + context->video_codec = avcodec_alloc_context3(NULL); + AVCodecContext *c = context->video_codec; c->codec_id = codec_id; c->codec_type = AVMEDIA_TYPE_VIDEO; @@ -649,11 +624,9 @@ static AVStream *alloc_video_stream(FFMpegContext *context, } } - /* Deprecated and not doing anything since July 2015, deleted in recent ffmpeg */ - // c->me_method = ME_EPZS; - codec = avcodec_find_encoder(c->codec_id); if (!codec) { + avcodec_free_context(&c); return NULL; } @@ -713,7 +686,7 @@ static AVStream *alloc_video_stream(FFMpegContext *context, if ((of->oformat->flags & AVFMT_GLOBALHEADER)) { PRINT("Using global header\n"); - c->flags |= CODEC_FLAG_GLOBAL_HEADER; + c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } /* xasp & yasp got float lately... */ @@ -741,6 +714,7 @@ static AVStream *alloc_video_stream(FFMpegContext *context, if (avcodec_open2(c, codec, &opts) < 0) { BLI_strncpy(error, IMB_ffmpeg_last_error(), error_size); av_dict_free(&opts); + avcodec_free_context(&c); return NULL; } av_dict_free(&opts); @@ -768,6 +742,8 @@ static AVStream *alloc_video_stream(FFMpegContext *context, NULL); } + avcodec_parameters_from_context(st->codecpar, c); + return st; } @@ -779,7 +755,6 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, int error_size) { AVStream *st; - AVCodecContext *c; AVCodec *codec; AVDictionary *opts = NULL; @@ -791,7 +766,8 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, } st->id = 1; - c = st->codec; + context->audio_codec = avcodec_alloc_context3(NULL); + AVCodecContext *c = context->audio_codec; c->thread_count = BLI_system_thread_count(); c->thread_type = FF_THREAD_SLICE; @@ -803,7 +779,6 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, c->sample_fmt = AV_SAMPLE_FMT_S16; c->channels = rd->ffcodecdata.audio_channels; -# ifdef FFMPEG_HAVE_FRAME_CHANNEL_LAYOUT switch (rd->ffcodecdata.audio_channels) { case FFM_CHANNELS_MONO: c->channel_layout = AV_CH_LAYOUT_MONO; @@ -821,7 +796,6 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, c->channel_layout = AV_CH_LAYOUT_7POINT1; break; } -# endif if (request_float_audio_buffer(codec_id)) { /* mainly for AAC codec which is experimental */ @@ -832,6 +806,7 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, codec = avcodec_find_encoder(c->codec_id); if (!codec) { // XXX error("Couldn't find a valid audio codec"); + avcodec_free_context(&c); return NULL; } @@ -843,13 +818,13 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, * Float samples in particular are not always supported. */ const enum AVSampleFormat *p = codec->sample_fmts; for (; *p != -1; p++) { - if (*p == st->codec->sample_fmt) { + if (*p == c->sample_fmt) { break; } } if (*p == -1) { /* sample format incompatible with codec. Defaulting to a format known to work */ - st->codec->sample_fmt = codec->sample_fmts[0]; + c->sample_fmt = codec->sample_fmts[0]; } } @@ -858,18 +833,18 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, int best = 0; int best_dist = INT_MAX; for (; *p; p++) { - int dist = abs(st->codec->sample_rate - *p); + int dist = abs(c->sample_rate - *p); if (dist < best_dist) { best_dist = dist; best = *p; } } /* best is the closest supported sample rate (same as selected if best_dist == 0) */ - st->codec->sample_rate = best; + c->sample_rate = best; } if (of->oformat->flags & AVFMT_GLOBALHEADER) { - c->flags |= CODEC_FLAG_GLOBAL_HEADER; + c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } set_ffmpeg_properties(rd, c, "audio", &opts); @@ -878,32 +853,25 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, // XXX error("Couldn't initialize audio codec"); BLI_strncpy(error, IMB_ffmpeg_last_error(), error_size); av_dict_free(&opts); + avcodec_free_context(&c); return NULL; } av_dict_free(&opts); /* need to prevent floating point exception when using vorbis audio codec, * initialize this value in the same way as it's done in FFmpeg itself (sergey) */ - st->codec->time_base.num = 1; - st->codec->time_base.den = st->codec->sample_rate; - -# ifndef FFMPEG_HAVE_ENCODE_AUDIO2 - context->audio_outbuf_size = FF_MIN_BUFFER_SIZE; -# endif + c->time_base.num = 1; + c->time_base.den = c->sample_rate; if (c->frame_size == 0) { /* Used to be if ((c->codec_id >= CODEC_ID_PCM_S16LE) && (c->codec_id <= CODEC_ID_PCM_DVD)) * not sure if that is needed anymore, so let's try out if there are any * complaints regarding some FFmpeg versions users might have. */ - context->audio_input_samples = FF_MIN_BUFFER_SIZE * 8 / c->bits_per_coded_sample / c->channels; + context->audio_input_samples = AV_INPUT_BUFFER_MIN_SIZE * 8 / c->bits_per_coded_sample / + c->channels; } else { context->audio_input_samples = c->frame_size; -# ifndef FFMPEG_HAVE_ENCODE_AUDIO2 - if (c->frame_size * c->channels * sizeof(int16_t) * 4 > context->audio_outbuf_size) { - context->audio_outbuf_size = c->frame_size * c->channels * sizeof(int16_t) * 4; - } -# endif } context->audio_deinterleave = av_sample_fmt_is_planar(c->sample_fmt); @@ -912,10 +880,6 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, context->audio_input_buffer = (uint8_t *)av_malloc(context->audio_input_samples * c->channels * context->audio_sample_size); -# ifndef FFMPEG_HAVE_ENCODE_AUDIO2 - context->audio_output_buffer = (uint8_t *)av_malloc(context->audio_outbuf_size); -# endif - if (context->audio_deinterleave) { context->audio_deinterleave_buffer = (uint8_t *)av_malloc( context->audio_input_samples * c->channels * context->audio_sample_size); @@ -923,6 +887,8 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, context->audio_time = 0.0f; + avcodec_parameters_from_context(st->codecpar, c); + return st; } /* essential functions -- start, append, end */ @@ -948,7 +914,7 @@ static void ffmpeg_dict_set_float(AVDictionary **dict, const char *key, float va static void ffmpeg_add_metadata_callback(void *data, const char *propname, char *propvalue, - int len) + int UNUSED(len)) { AVDictionary **metadata = (AVDictionary **)data; av_dict_set(metadata, propname, propvalue, 0); @@ -1039,7 +1005,7 @@ static int start_ffmpeg_impl(FFMpegContext *context, fmt->audio_codec = context->ffmpeg_audio_codec; - BLI_strncpy(of->filename, name, sizeof(of->filename)); + of->url = av_strdup(name); /* set the codec to the user's selection */ switch (context->ffmpeg_type) { case FFMPEG_AVI: @@ -1104,9 +1070,11 @@ static int start_ffmpeg_impl(FFMpegContext *context, if (!context->video_stream) { if (error[0]) { BKE_report(reports, RPT_ERROR, error); + PRINT("Video stream error: %s\n", error); } else { BKE_report(reports, RPT_ERROR, "Error initializing video stream"); + PRINT("Error initializing video stream"); } goto fail; } @@ -1118,9 +1086,11 @@ static int start_ffmpeg_impl(FFMpegContext *context, if (!context->audio_stream) { if (error[0]) { BKE_report(reports, RPT_ERROR, error); + PRINT("Audio stream error: %s\n", error); } else { BKE_report(reports, RPT_ERROR, "Error initializing audio stream"); + PRINT("Error initializing audio stream"); } goto fail; } @@ -1128,6 +1098,7 @@ static int start_ffmpeg_impl(FFMpegContext *context, if (!(fmt->flags & AVFMT_NOFILE)) { if (avio_open(&of->pb, name, AVIO_FLAG_WRITE) < 0) { BKE_report(reports, RPT_ERROR, "Could not open file for writing"); + PRINT("Could not open file for writing\n"); goto fail; } } @@ -1137,10 +1108,12 @@ static int start_ffmpeg_impl(FFMpegContext *context, &of->metadata, context->stamp_data, ffmpeg_add_metadata_callback, false); } - if (avformat_write_header(of, NULL) < 0) { + int ret = avformat_write_header(of, NULL); + if (ret < 0) { BKE_report(reports, RPT_ERROR, "Could not initialize streams, probably unsupported codec combination"); + PRINT("Could not write media header: %s\n", av_err2str(ret)); goto fail; } @@ -1155,13 +1128,11 @@ fail: avio_close(of->pb); } - if (context->video_stream && context->video_stream->codec) { - avcodec_close(context->video_stream->codec); + if (context->video_stream) { context->video_stream = NULL; } - if (context->audio_stream && context->audio_stream->codec) { - avcodec_close(context->audio_stream->codec); + if (context->audio_stream) { context->audio_stream = NULL; } @@ -1189,46 +1160,36 @@ fail: */ static void flush_ffmpeg(FFMpegContext *context) { - int ret = 0; + AVCodecContext *c = context->video_codec; + AVPacket *packet = av_packet_alloc(); - AVCodecContext *c = context->video_stream->codec; - /* get the delayed frames */ - while (1) { - int got_output; - AVPacket packet = {0}; - av_init_packet(&packet); + avcodec_send_frame(c, NULL); - ret = avcodec_encode_video2(c, &packet, NULL, &got_output); - if (ret < 0) { - fprintf(stderr, "Error encoding delayed frame %d\n", ret); + /* Get the packets frames. */ + int ret = 1; + while (ret >= 0) { + ret = avcodec_receive_packet(c, packet); + + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + /* No more packets to flush. */ break; } - if (!got_output) { + if (ret < 0) { + fprintf(stderr, "Error encoding delayed frame: %s\n", av_err2str(ret)); break; } - if (packet.pts != AV_NOPTS_VALUE) { - packet.pts = av_rescale_q(packet.pts, c->time_base, context->video_stream->time_base); - PRINT("Video Frame PTS: %d\n", (int)packet.pts); - } - else { - PRINT("Video Frame PTS: not set\n"); - } - if (packet.dts != AV_NOPTS_VALUE) { - packet.dts = av_rescale_q(packet.dts, c->time_base, context->video_stream->time_base); - PRINT("Video Frame DTS: %d\n", (int)packet.dts); - } - else { - PRINT("Video Frame DTS: not set\n"); - } - packet.stream_index = context->video_stream->index; - ret = av_interleaved_write_frame(context->outfile, &packet); - if (ret != 0) { - fprintf(stderr, "Error writing delayed frame %d\n", ret); + packet->stream_index = context->video_stream->index; + av_packet_rescale_ts(packet, c->time_base, context->video_stream->time_base); + + int write_ret = av_interleaved_write_frame(context->outfile, packet); + if (write_ret != 0) { + fprintf(stderr, "Error writing delayed frame: %s\n", av_err2str(write_ret)); break; } } - avcodec_flush_buffers(context->video_stream->codec); + + av_packet_free(&packet); } /* ********************************************************************** @@ -1326,7 +1287,8 @@ int BKE_ffmpeg_start(void *context_v, success = start_ffmpeg_impl(context, rd, rectx, recty, suffix, reports); # ifdef WITH_AUDASPACE if (context->audio_stream) { - AVCodecContext *c = context->audio_stream->codec; + AVCodecContext *c = context->audio_codec; + AUD_DeviceSpecs specs; specs.channels = c->channels; @@ -1353,10 +1315,6 @@ int BKE_ffmpeg_start(void *context_v, specs.rate = rd->ffcodecdata.audio_mixrate; context->audio_mixdown_device = BKE_sound_mixdown( scene, specs, preview ? rd->psfra : rd->sfra, rd->ffcodecdata.audio_volume); -# ifdef FFMPEG_CODEC_TIME_BASE - c->time_base.den = specs.rate; - c->time_base.num = 1; -# endif } # endif return success; @@ -1397,8 +1355,8 @@ int BKE_ffmpeg_append(void *context_v, // write_audio_frames(frame / (((double)rd->frs_sec) / rd->frs_sec_base)); if (context->video_stream) { - avframe = generate_video_frame(context, (unsigned char *)pixels, reports); - success = (avframe && write_video_frame(context, rd, frame - start_frame, avframe, reports)); + avframe = generate_video_frame(context, (unsigned char *)pixels); + success = (avframe && write_video_frame(context, frame - start_frame, avframe, reports)); if (context->ffmpeg_autosplit) { if (avio_tell(context->outfile->pb) > FFMPEG_AUTOSPLIT_SIZE) { @@ -1427,9 +1385,11 @@ static void end_ffmpeg_impl(FFMpegContext *context, int is_autosplit) context->audio_mixdown_device = NULL; } } +# else + UNUSED_VARS(is_autosplit); # endif - if (context->video_stream && context->video_stream->codec) { + if (context->video_stream) { PRINT("Flushing delayed frames...\n"); flush_ffmpeg(context); } @@ -1440,14 +1400,12 @@ static void end_ffmpeg_impl(FFMpegContext *context, int is_autosplit) /* Close the video codec */ - if (context->video_stream != NULL && context->video_stream->codec != NULL) { - avcodec_close(context->video_stream->codec); + if (context->video_stream != NULL) { PRINT("zero video stream %p\n", context->video_stream); context->video_stream = NULL; } - if (context->audio_stream != NULL && context->audio_stream->codec != NULL) { - avcodec_close(context->audio_stream->codec); + if (context->audio_stream != NULL) { context->audio_stream = NULL; } @@ -1466,6 +1424,16 @@ static void end_ffmpeg_impl(FFMpegContext *context, int is_autosplit) avio_close(context->outfile->pb); } } + + if (context->video_codec != NULL) { + avcodec_free_context(&context->video_codec); + context->video_codec = NULL; + } + if (context->audio_codec != NULL) { + avcodec_free_context(&context->audio_codec); + context->audio_codec = NULL; + } + if (context->outfile != NULL) { avformat_free_context(context->outfile); context->outfile = NULL; @@ -1474,12 +1442,6 @@ static void end_ffmpeg_impl(FFMpegContext *context, int is_autosplit) av_free(context->audio_input_buffer); context->audio_input_buffer = NULL; } -# ifndef FFMPEG_HAVE_ENCODE_AUDIO2 - if (context->audio_output_buffer != NULL) { - av_free(context->audio_output_buffer); - context->audio_output_buffer = NULL; - } -# endif if (context->audio_deinterleave_buffer != NULL) { av_free(context->audio_deinterleave_buffer); @@ -1559,12 +1521,12 @@ static IDProperty *BKE_ffmpeg_property_add(RenderData *rd, switch (o->type) { case AV_OPT_TYPE_INT: case AV_OPT_TYPE_INT64: - val.i = FFMPEG_DEF_OPT_VAL_INT(o); + val.i = o->default_val.i64; idp_type = IDP_INT; break; case AV_OPT_TYPE_DOUBLE: case AV_OPT_TYPE_FLOAT: - val.f = FFMPEG_DEF_OPT_VAL_DOUBLE(o); + val.f = o->default_val.dbl; idp_type = IDP_FLOAT; break; case AV_OPT_TYPE_STRING: @@ -1706,16 +1668,9 @@ static void ffmpeg_set_expert_options(RenderData *rd) BKE_ffmpeg_property_add_string(rd, "video", "trellis:0"); BKE_ffmpeg_property_add_string(rd, "video", "weightb:1"); -# ifdef FFMPEG_HAVE_DEPRECATED_FLAGS2 - BKE_ffmpeg_property_add_string(rd, "video", "flags2:dct8x8"); - BKE_ffmpeg_property_add_string(rd, "video", "directpred:3"); - BKE_ffmpeg_property_add_string(rd, "video", "flags2:fastpskip"); - BKE_ffmpeg_property_add_string(rd, "video", "flags2:wpred"); -# else BKE_ffmpeg_property_add_string(rd, "video", "8x8dct:1"); BKE_ffmpeg_property_add_string(rd, "video", "fast-pskip:1"); BKE_ffmpeg_property_add_string(rd, "video", "wpredp:2"); -# endif } else if (codec_id == AV_CODEC_ID_DNXHD) { if (rd->ffcodecdata.flags & FFMPEG_LOSSLESS_OUTPUT) { @@ -1870,14 +1825,12 @@ bool BKE_ffmpeg_alpha_channel_is_supported(const RenderData *rd) { int codec = rd->ffcodecdata.codec; -# ifdef FFMPEG_FFV1_ALPHA_SUPPORTED - /* Visual Studio 2019 doesn't like #ifdef within ELEM(). */ - if (codec == AV_CODEC_ID_FFV1) { - return true; - } -# endif - - return ELEM(codec, AV_CODEC_ID_QTRLE, AV_CODEC_ID_PNG, AV_CODEC_ID_VP9, AV_CODEC_ID_HUFFYUV); + return ELEM(codec, + AV_CODEC_ID_FFV1, + AV_CODEC_ID_QTRLE, + AV_CODEC_ID_PNG, + AV_CODEC_ID_VP9, + AV_CODEC_ID_HUFFYUV); } void *BKE_ffmpeg_context_create(void) diff --git a/source/blender/blenkernel/nla_private.h b/source/blender/blenkernel/nla_private.h index 706bcac4f17..71b5a74ddf7 100644 --- a/source/blender/blenkernel/nla_private.h +++ b/source/blender/blenkernel/nla_private.h @@ -82,6 +82,10 @@ typedef struct NlaEvalChannelSnapshot { /** For an upper snapshot channel, marks values that should be blended. */ NlaValidMask blend_domain; + /** Only used for keyframe remapping. Any values not in the \a remap_domain will not be used + * for keyframe remapping. */ + NlaValidMask remap_domain; + int length; /* Number of values in the property. */ bool is_base; /* Base snapshot of the channel. */ @@ -196,6 +200,13 @@ void nlasnapshot_blend(NlaEvalData *eval_data, const float upper_influence, NlaEvalSnapshot *r_blended_snapshot); +void nlasnapshot_blend_get_inverted_upper_snapshot(NlaEvalData *eval_data, + NlaEvalSnapshot *lower_snapshot, + NlaEvalSnapshot *blended_snapshot, + const short upper_blendmode, + const float upper_influence, + NlaEvalSnapshot *r_upper_snapshot); + #ifdef __cplusplus } #endif |