diff options
Diffstat (limited to 'source')
677 files changed, 28888 insertions, 10154 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_attribute_access.hh b/source/blender/blenkernel/BKE_attribute_access.hh index 120b4e08b9c..c3bc4d3ca4a 100644 --- a/source/blender/blenkernel/BKE_attribute_access.hh +++ b/source/blender/blenkernel/BKE_attribute_access.hh @@ -20,299 +20,326 @@ #include "FN_cpp_type.hh" #include "FN_generic_span.hh" +#include "FN_generic_virtual_array.hh" #include "BKE_attribute.h" #include "BLI_color.hh" #include "BLI_float2.hh" #include "BLI_float3.hh" - -namespace blender::bke { - -using fn::CPPType; - -const CPPType *custom_data_type_to_cpp_type(const CustomDataType type); -CustomDataType cpp_type_to_custom_data_type(const CPPType &type); -CustomDataType attribute_data_type_highest_complexity(Span<CustomDataType> data_types); -AttributeDomain attribute_domain_highest_priority(Span<AttributeDomain> domains); +#include "BLI_function_ref.hh" /** - * This class offers an indirection for reading an attribute. - * This is useful for the following reasons: - * - Blender does not store all attributes the same way. - * The simplest case are custom data layers with primitive types. - * A bit more complex are mesh attributes like the position of vertices, - * which are embedded into the MVert struct. - * Even more complex to access are vertex weights. - * - Sometimes attributes are stored on one domain, but we want to access - * the attribute on a different domain. Therefore, we have to interpolate - * between the domains. + * 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, ...). */ -class ReadAttribute { - protected: - const AttributeDomain domain_; - const CPPType &cpp_type_; - const CustomDataType custom_data_type_; - const int64_t size_; - - /* Protects the span below, so that no two threads initialize it at the same time. */ - mutable std::mutex span_mutex_; - /* When it is not null, it points to the attribute array or a temporary array that contains all - * the attribute values. */ - mutable void *array_buffer_ = nullptr; - /* Is true when the buffer above is owned by the attribute accessor. */ - mutable bool array_is_temporary_ = false; +struct AttributeMetaData { + AttributeDomain domain; + CustomDataType data_type; - public: - ReadAttribute(AttributeDomain domain, const CPPType &cpp_type, const int64_t size) - : domain_(domain), - cpp_type_(cpp_type), - custom_data_type_(cpp_type_to_custom_data_type(cpp_type)), - size_(size) + constexpr friend bool operator==(AttributeMetaData a, AttributeMetaData b) { + return (a.domain == b.domain) && (a.data_type == b.data_type); } +}; - virtual ~ReadAttribute(); - - AttributeDomain domain() const +/** + * 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) { - return domain_; } +}; - const CPPType &cpp_type() const +/** + * 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) { - return cpp_type_; } +}; - CustomDataType custom_data_type() const - { - return custom_data_type_; - } +/** + * 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; - int64_t size() const + AttributeInitVArray(const blender::fn::GVArray *varray) + : AttributeInit(Type::VArray), varray(varray) { - return size_; } +}; + +/** + * 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; - void get(const int64_t index, void *r_value) const + AttributeInitMove(void *data) : AttributeInit(Type::MoveArray), data(data) { - BLI_assert(index < size_); - this->get_internal(index, r_value); } +}; - /* Get a span that contains all attribute values. */ - fn::GSpan get_span() const; +/* Returns false when the iteration should be stopped. */ +using AttributeForeachCallback = blender::FunctionRef<bool(blender::StringRefNull attribute_name, + const AttributeMetaData &meta_data)>; - template<typename T> Span<T> get_span() const +namespace blender::bke { + +using fn::CPPType; +using fn::GVArray; +using fn::GVArrayPtr; +using fn::GVMutableArray; +using fn::GVMutableArrayPtr; + +const CPPType *custom_data_type_to_cpp_type(const CustomDataType type); +CustomDataType cpp_type_to_custom_data_type(const CPPType &type); +CustomDataType attribute_data_type_highest_complexity(Span<CustomDataType> data_types); +AttributeDomain attribute_domain_highest_priority(Span<AttributeDomain> domains); + +/** + * Used when looking up a "plain attribute" based on a name for reading from it. + */ +struct ReadAttributeLookup { + /* The virtual array that is used to read from this attribute. */ + GVArrayPtr varray; + /* Domain the attribute lives on in the geometry. */ + AttributeDomain domain; + + /* Convenience function to check if the attribute has been found. */ + operator bool() const { - return this->get_span().typed<T>(); + return this->varray.get() != nullptr; } +}; - protected: - /* r_value is expected to be uninitialized. */ - virtual void get_internal(const int64_t index, void *r_value) const = 0; - - virtual void initialize_span() const; +/** + * Used when looking up a "plain attribute" based on a name for reading from it and writing to it. + */ +struct WriteAttributeLookup { + /* The virtual array that is used to read from and write to the attribute. */ + GVMutableArrayPtr varray; + /* Domain the attributes lives on in the geometry. */ + AttributeDomain domain; + + /* Convenience function to check if the attribute has been found. */ + operator bool() const + { + return this->varray.get() != nullptr; + } }; /** - * This exists for similar reasons as the ReadAttribute class, except that - * it does not deal with interpolation between domains. + * An output attribute allows writing to an attribute (and optionally reading as well). It adds + * some convenience features on top of `GVMutableArray` that are very commonly used. + * + * Supported convenience features: + * - Implicit type conversion when writing to builtin attributes. + * - Supports simple access to a span containing the attribute values (that avoids the use of + * VMutableArray_Span in many cases). + * - An output attribute can live side by side with an existing attribute with a different domain + * or data type. The old attribute will only be overwritten when the #save function is called. */ -class WriteAttribute { - protected: - const AttributeDomain domain_; - const CPPType &cpp_type_; - const CustomDataType custom_data_type_; - const int64_t size_; - - /* When not null, this points either to the attribute array or to a temporary array. */ - void *array_buffer_ = nullptr; - /* True, when the buffer points to a temporary array. */ - bool array_is_temporary_ = false; - /* This helps to protect against forgetting to apply changes done to the array. */ - bool array_should_be_applied_ = false; +class OutputAttribute { + public: + using SaveFn = std::function<void(OutputAttribute &)>; + + private: + GVMutableArrayPtr varray_; + AttributeDomain domain_; + SaveFn save_; + std::optional<fn::GVMutableArray_GSpan> optional_span_varray_; + bool ignore_old_values_ = false; + bool save_has_been_called_ = false; public: - WriteAttribute(AttributeDomain domain, const CPPType &cpp_type, const int64_t size) - : domain_(domain), - cpp_type_(cpp_type), - custom_data_type_(cpp_type_to_custom_data_type(cpp_type)), - size_(size) + OutputAttribute() = default; + + OutputAttribute(GVMutableArrayPtr varray, + AttributeDomain domain, + SaveFn save, + const bool ignore_old_values) + : varray_(std::move(varray)), + domain_(domain), + save_(std::move(save)), + ignore_old_values_(ignore_old_values) { } - virtual ~WriteAttribute(); + OutputAttribute(OutputAttribute &&other) = default; - AttributeDomain domain() const + ~OutputAttribute(); + + operator bool() const { - return domain_; + return varray_.get() != nullptr; } - const CPPType &cpp_type() const + GVMutableArray &operator*() { - return cpp_type_; + return *varray_; } - CustomDataType custom_data_type() const + GVMutableArray *operator->() { - return custom_data_type_; + return varray_.get(); } - int64_t size() const + GVMutableArray &varray() { - return size_; + return *varray_; } - void get(const int64_t index, void *r_value) const + AttributeDomain domain() const { - BLI_assert(index < size_); - this->get_internal(index, r_value); + return domain_; } - void set(const int64_t index, const void *value) + const CPPType &cpp_type() const { - BLI_assert(index < size_); - this->set_internal(index, value); + return varray_->type(); } - /* Get a span that new attribute values can be written into. When all values have been changed, - * #apply_span has to be called. */ - fn::GMutableSpan get_span(); - /* The span returned by this method might not contain the current attribute values. */ - fn::GMutableSpan get_span_for_write_only(); - /* Write the changes to the span into the actual attribute, if they aren't already. */ - void apply_span(); - - template<typename T> MutableSpan<T> get_span() + CustomDataType custom_data_type() const { - return this->get_span().typed<T>(); + return cpp_type_to_custom_data_type(this->cpp_type()); } - template<typename T> MutableSpan<T> get_span_for_write_only() + fn::GMutableSpan as_span() { - return this->get_span_for_write_only().typed<T>(); + if (!optional_span_varray_.has_value()) { + const bool materialize_old_values = !ignore_old_values_; + optional_span_varray_.emplace(*varray_, materialize_old_values); + } + fn::GVMutableArray_GSpan &span_varray = *optional_span_varray_; + return span_varray; } - protected: - virtual void get_internal(const int64_t index, void *r_value) const = 0; - virtual void set_internal(const int64_t index, const void *value) = 0; + template<typename T> MutableSpan<T> as_span() + { + return this->as_span().typed<T>(); + } - virtual void initialize_span(const bool write_only); - virtual void apply_span_if_necessary(); + void save(); }; -using ReadAttributePtr = std::unique_ptr<ReadAttribute>; -using WriteAttributePtr = std::unique_ptr<WriteAttribute>; - -/* This provides type safe access to an attribute. - * The underlying ReadAttribute is owned optionally. */ -template<typename T> class TypedReadAttribute { +/** + * Same as OutputAttribute, but should be used when the data type is known at compile time. + */ +template<typename T> class OutputAttribute_Typed { private: - std::unique_ptr<const ReadAttribute> owned_attribute_; - const ReadAttribute *attribute_; + OutputAttribute attribute_; + std::optional<fn::GVMutableArray_Typed<T>> optional_varray_; + VMutableArray<T> *varray_ = nullptr; public: - TypedReadAttribute(ReadAttributePtr attribute) : TypedReadAttribute(*attribute) + OutputAttribute_Typed(OutputAttribute attribute) : attribute_(std::move(attribute)) { - owned_attribute_ = std::move(attribute); - BLI_assert(owned_attribute_); + if (attribute_) { + optional_varray_.emplace(attribute_.varray()); + varray_ = &**optional_varray_; + } } - TypedReadAttribute(const ReadAttribute &attribute) : attribute_(&attribute) + operator bool() const { - BLI_assert(attribute_->cpp_type().is<T>()); + return varray_ != nullptr; } - int64_t size() const + VMutableArray<T> &operator*() { - return attribute_->size(); + return *varray_; } - T operator[](const int64_t index) const + VMutableArray<T> *operator->() { - BLI_assert(index < attribute_->size()); - T value; - value.~T(); - attribute_->get(index, &value); - return value; + return varray_; } - /* Get a span to that contains all attribute values for faster and more convenient access. */ - Span<T> get_span() const + VMutableArray<T> &varray() { - return attribute_->get_span().template typed<T>(); + return *varray_; } -}; - -/* This provides type safe access to an attribute. - * The underlying WriteAttribute is owned optionally. */ -template<typename T> class TypedWriteAttribute { - private: - std::unique_ptr<WriteAttribute> owned_attribute_; - WriteAttribute *attribute_; - public: - TypedWriteAttribute(WriteAttributePtr attribute) : TypedWriteAttribute(*attribute) + AttributeDomain domain() const { - owned_attribute_ = std::move(attribute); - BLI_assert(owned_attribute_); + return attribute_.domain(); } - TypedWriteAttribute(WriteAttribute &attribute) : attribute_(&attribute) + const CPPType &cpp_type() const { - BLI_assert(attribute_->cpp_type().is<T>()); + return CPPType::get<T>(); } - int64_t size() const + CustomDataType custom_data_type() const { - return attribute_->size(); + return cpp_type_to_custom_data_type(this->cpp_type()); } - T operator[](const int64_t index) const + MutableSpan<T> as_span() { - BLI_assert(index < attribute_->size()); - T value; - value.~T(); - attribute_->get(index, &value); - return value; + return attribute_.as_span<T>(); } - void set(const int64_t index, const T &value) + void save() { - attribute_->set(index, &value); + attribute_.save(); } +}; - /* Get a span that new values can be written into. Once all values have been updated #apply_span - * has to be called. */ - MutableSpan<T> get_span() - { - return attribute_->get_span().typed<T>(); - } - /* The span returned by this method might not contain the current attribute values. */ - MutableSpan<T> get_span_for_write_only() - { - return attribute_->get_span_for_write_only().typed<T>(); - } +/** + * 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_; - /* Write back all changes to the actual attribute, if necessary. */ - void apply_span() - { - attribute_->apply_span(); - } -}; + public: + CustomData data; + + CustomDataAttributes(); + ~CustomDataAttributes(); + CustomDataAttributes(const CustomDataAttributes &other); + CustomDataAttributes(CustomDataAttributes &&other); + + void reallocate(const int size); -using BooleanReadAttribute = TypedReadAttribute<bool>; -using FloatReadAttribute = TypedReadAttribute<float>; -using Float2ReadAttribute = TypedReadAttribute<float2>; -using Float3ReadAttribute = TypedReadAttribute<float3>; -using Int32ReadAttribute = TypedReadAttribute<int>; -using Color4fReadAttribute = TypedReadAttribute<Color4f>; -using BooleanWriteAttribute = TypedWriteAttribute<bool>; -using FloatWriteAttribute = TypedWriteAttribute<float>; -using Float2WriteAttribute = TypedWriteAttribute<float2>; -using Float3WriteAttribute = TypedWriteAttribute<float3>; -using Int32WriteAttribute = TypedWriteAttribute<int>; -using Color4fWriteAttribute = TypedWriteAttribute<Color4f>; + 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 16fc0db60fb..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" @@ -21,13 +23,17 @@ #include "DNA_customdata_types.h" +#include "FN_cpp_type.hh" + namespace blender::attribute_math { +using fn::CPPType; + /** * Utility function that simplifies calling a templated function based on a custom data type. */ template<typename Func> -void convert_to_static_type(const CustomDataType data_type, const Func &func) +inline void convert_to_static_type(const CustomDataType data_type, const Func &func) { switch (data_type) { case CD_PROP_FLOAT: @@ -46,7 +52,7 @@ void convert_to_static_type(const CustomDataType data_type, const Func &func) func(bool()); break; case CD_PROP_COLOR: - func(Color4f()); + func(ColorGeometry4f()); break; default: BLI_assert_unreachable(); @@ -54,6 +60,32 @@ void convert_to_static_type(const CustomDataType data_type, const Func &func) } } +template<typename Func> +inline void convert_to_static_type(const fn::CPPType &cpp_type, const Func &func) +{ + if (cpp_type.is<float>()) { + func(float()); + } + else if (cpp_type.is<float2>()) { + func(float2()); + } + else if (cpp_type.is<float3>()) { + func(float3()); + } + else if (cpp_type.is<int>()) { + func(int()); + } + else if (cpp_type.is<bool>()) { + func(bool()); + } + else if (cpp_type.is<ColorGeometry4f>()) { + func(ColorGeometry4f()); + } + else { + BLI_assert_unreachable(); + } +} + /* -------------------------------------------------------------------- */ /** \name Mix three values of the same type. * @@ -91,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; } @@ -101,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 @@ -153,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: @@ -198,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(); }; @@ -223,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 f74f7fe84de..0bab980cfcd 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -31,15 +31,15 @@ extern "C" { */ /* Blender major and minor version. */ -#define BLENDER_VERSION 293 +#define BLENDER_VERSION 300 /* Blender patch version for bugfix releases. */ #define BLENDER_VERSION_PATCH 0 /** Blender release cycle stage: alpha/beta/rc/release. */ -#define BLENDER_VERSION_CYCLE beta +#define BLENDER_VERSION_CYCLE alpha /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 20 +#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_context.h b/source/blender/blenkernel/BKE_context.h index 3d30188e517..50aa6027840 100644 --- a/source/blender/blenkernel/BKE_context.h +++ b/source/blender/blenkernel/BKE_context.h @@ -206,8 +206,25 @@ void CTX_wm_area_set(bContext *C, struct ScrArea *area); void CTX_wm_region_set(bContext *C, struct ARegion *region); void CTX_wm_menu_set(bContext *C, struct ARegion *menu); void CTX_wm_gizmo_group_set(bContext *C, struct wmGizmoGroup *gzgroup); -const char *CTX_wm_operator_poll_msg_get(struct bContext *C); + +/** + * Values to create the message that describes the reason poll failed. + * + * \note This must be called in the same context as the poll function that created it. + */ +struct bContextPollMsgDyn_Params { + /** The result is allocated . */ + char *(*get_fn)(bContext *C, void *user_data); + /** Optionally free the user-data. */ + void (*free_fn)(bContext *C, void *user_data); + void *user_data; +}; + +const char *CTX_wm_operator_poll_msg_get(struct bContext *C, bool *r_free); void CTX_wm_operator_poll_msg_set(struct bContext *C, const char *msg); +void CTX_wm_operator_poll_msg_set_dynamic(bContext *C, + const struct bContextPollMsgDyn_Params *params); +void CTX_wm_operator_poll_msg_clear(struct bContext *C); /* Data Context * 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 d94b2e7902b..b2342a5fd96 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,8 @@ struct Mesh; struct Object; struct PointCloud; struct Volume; +struct Curve; +struct CurveEval; enum class GeometryOwnershipType { /* The geometry is owned. This implies that it can be changed. */ @@ -56,74 +58,6 @@ class ComponentAttributeProviders; class GeometryComponent; /** - * An #OutputAttributePtr wraps a #WriteAttributePtr that might not be stored in its final - * destination yet. Therefore, once the attribute has been filled with data, the #save method has - * to be called, to store the attribute where it belongs (possibly by replacing an existing - * attribute with the same name). - * - * This is useful for example in the Attribute Color Ramp node, when the same attribute name is - * used as input and output. Typically the input is a float attribute, and the output is a color. - * Those two attributes cannot exist at the same time, due to a name collision. To handle this - * situation well, first the output colors have to be computed before the input floats are deleted. - * Therefore, the outputs have to be written to a temporary buffer that replaces the existing - * attribute once all computations are done. - */ -class OutputAttributePtr { - private: - blender::bke::WriteAttributePtr attribute_; - - public: - OutputAttributePtr() = default; - OutputAttributePtr(blender::bke::WriteAttributePtr attribute); - OutputAttributePtr(GeometryComponent &component, - AttributeDomain domain, - std::string name, - CustomDataType data_type); - - ~OutputAttributePtr(); - - /* Returns false, when this wrapper is empty. */ - operator bool() const - { - return static_cast<bool>(attribute_); - } - - /* Get a reference to the underlying #WriteAttribute. */ - blender::bke::WriteAttribute &get() - { - BLI_assert(attribute_); - return *attribute_; - } - - blender::bke::WriteAttribute &operator*() - { - return *attribute_; - } - - blender::bke::WriteAttribute *operator->() - { - return attribute_.get(); - } - - void save(); - void apply_span_and_save(); -}; - -/** - * 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)>; - -/** * This is the base class for specialized geometry component types. */ class GeometryComponent { @@ -156,26 +90,34 @@ class GeometryComponent { /* Return true when any attribute with this name exists, including built in attributes. */ bool attribute_exists(const blender::StringRef attribute_name) const; + /* Return the data type and domain of an attribute with the given name if it exists. */ + std::optional<AttributeMetaData> attribute_get_meta_data( + const blender::StringRef attribute_name) const; + /* Returns true when the geometry component supports this attribute domain. */ bool attribute_domain_supported(const AttributeDomain domain) const; /* Can only be used with supported domain types. */ virtual int attribute_domain_size(const AttributeDomain domain) const; + bool attribute_is_builtin(const blender::StringRef attribute_name) const; + /* Get read-only access to the highest priority attribute with the given name. * Returns null if the attribute does not exist. */ - blender::bke::ReadAttributePtr attribute_try_get_for_read( + blender::bke::ReadAttributeLookup attribute_try_get_for_read( const blender::StringRef attribute_name) const; /* Get read and write access to the highest priority attribute with the given name. * Returns null if the attribute does not exist. */ - blender::bke::WriteAttributePtr attribute_try_get_for_write( + blender::bke::WriteAttributeLookup attribute_try_get_for_write( const blender::StringRef attribute_name); /* Get a read-only attribute for the domain based on the given attribute. This can be used to * interpolate from one domain to another. * Returns null if the interpolation is not implemented. */ - virtual blender::bke::ReadAttributePtr attribute_try_adapt_domain( - blender::bke::ReadAttributePtr attribute, const AttributeDomain new_domain) const; + virtual std::unique_ptr<blender::fn::GVArray> attribute_try_adapt_domain( + std::unique_ptr<blender::fn::GVArray> varray, + const AttributeDomain from_domain, + const AttributeDomain to_domain) const; /* Returns true when the attribute has been deleted. */ bool attribute_try_delete(const blender::StringRef attribute_name); @@ -183,82 +125,104 @@ class GeometryComponent { /* Returns true when the attribute has been created. */ bool attribute_try_create(const blender::StringRef attribute_name, const AttributeDomain domain, - const CustomDataType data_type); + const CustomDataType data_type, + const AttributeInit &initializer); + + /* Try to create the builtin attribute with the given name. No data type or domain has to be + * provided, because those are fixed for builtin attributes. */ + bool attribute_try_create_builtin(const blender::StringRef attribute_name, + const AttributeInit &initializer); blender::Set<std::string> attribute_names() const; bool attribute_foreach(const AttributeForeachCallback callback) const; virtual bool is_empty() const; - /* Get a read-only attribute for the given domain and data type. - * Returns null when it does not exist. */ - blender::bke::ReadAttributePtr attribute_try_get_for_read( + /* Get a virtual array to read the data of an attribute on the given domain and data type. + * Returns null when the attribute does not exist or cannot be converted to the requested domain + * and data type. */ + std::unique_ptr<blender::fn::GVArray> attribute_try_get_for_read( const blender::StringRef attribute_name, const AttributeDomain domain, const CustomDataType data_type) const; - /* Get a read-only attribute interpolated to the input domain, leaving the data type unchanged. - * Returns null when the attribute does not exist. */ - blender::bke::ReadAttributePtr attribute_try_get_for_read( + /* Get a virtual array to read the data of an attribute on the given domain. The data type is + * left unchanged. Returns null when the attribute does not exist or cannot be adapted to the + * requested domain. */ + std::unique_ptr<blender::fn::GVArray> attribute_try_get_for_read( const blender::StringRef attribute_name, const AttributeDomain domain) const; - /* Get a read-only attribute for the given domain and data type. - * Returns a constant attribute based on the default value if the attribute does not exist. - * Never returns null. */ - blender::bke::ReadAttributePtr attribute_get_for_read(const blender::StringRef attribute_name, - const AttributeDomain domain, - const CustomDataType data_type, - const void *default_value) const; + /* Get a virtual array to read data of an attribute with the given data type. The domain is + * left unchanged. Returns null when the attribute does not exist or cannot be converted to the + * requested data type. */ + blender::bke::ReadAttributeLookup attribute_try_get_for_read( + const blender::StringRef attribute_name, const CustomDataType data_type) const; - /* Get a typed read-only attribute for the given domain and type. */ - template<typename T> - blender::bke::TypedReadAttribute<T> attribute_get_for_read( + /* Get a virtual array to read the data of an attribute. If that is not possible, the returned + * virtual array will contain a default value. This never returns null. */ + std::unique_ptr<blender::fn::GVArray> attribute_get_for_read( const blender::StringRef attribute_name, const AttributeDomain domain, - const T &default_value) const + const CustomDataType data_type, + const void *default_value = nullptr) const; + + /* Should be used instead of the method above when the requested data type is known at compile + * time for better type safety. */ + template<typename T> + blender::fn::GVArray_Typed<T> attribute_get_for_read(const blender::StringRef attribute_name, + const AttributeDomain domain, + const T &default_value) const { const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>(); const CustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type); - return this->attribute_get_for_read(attribute_name, domain, type, &default_value); + std::unique_ptr varray = this->attribute_get_for_read( + attribute_name, domain, type, &default_value); + return blender::fn::GVArray_Typed<T>(std::move(varray)); } - /* Get a read-only dummy attribute that always returns the same value. */ - blender::bke::ReadAttributePtr attribute_get_constant_for_read(const AttributeDomain domain, - const CustomDataType data_type, - const void *value) const; + /** + * Returns an "output attribute", which is essentially a mutable virtual array with some commonly + * used convince features. The returned output attribute might be empty if requested attribute + * cannot exist on the geometry. + * + * The included convenience features are: + * - Implicit type conversion when writing to builtin attributes. + * - If the attribute name exists already, but has a different type/domain, a temporary attribute + * is created that will overwrite the existing attribute in the end. + */ + blender::bke::OutputAttribute attribute_try_get_for_output( + const blender::StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type, + const void *default_value = nullptr); - /* Create a read-only dummy attribute that always returns the same value. - * The given value is converted to the correct type if necessary. */ - blender::bke::ReadAttributePtr attribute_get_constant_for_read_converted( + /* Same as attribute_try_get_for_output, but should be used when the original values in the + * attributes are not read, i.e. the attribute is used only for output. Since values are not read + * from this attribute, no default value is necessary. */ + blender::bke::OutputAttribute attribute_try_get_for_output_only( + const blender::StringRef attribute_name, const AttributeDomain domain, - const CustomDataType in_data_type, - const CustomDataType out_data_type, - const void *value) const; + const CustomDataType data_type); - /* Get a read-only dummy attribute that always returns the same value. */ + /* Statically typed method corresponding to the equally named generic one. */ template<typename T> - blender::bke::TypedReadAttribute<T> attribute_get_constant_for_read(const AttributeDomain domain, - const T &value) const + blender::bke::OutputAttribute_Typed<T> attribute_try_get_for_output( + const blender::StringRef attribute_name, const AttributeDomain domain, const T default_value) { const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>(); - const CustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type); - return this->attribute_get_constant_for_read(domain, type, &value); + const CustomDataType data_type = blender::bke::cpp_type_to_custom_data_type(cpp_type); + return this->attribute_try_get_for_output(attribute_name, domain, data_type, &default_value); } - /** - * If an attribute with the given params exist, it is returned. - * If no attribute with the given name exists, create it and - * fill it with the default value if it is provided. - * If an attribute with the given name but different domain or type exists, a temporary attribute - * is created that has to be saved after the output has been computed. This avoids deleting - * another attribute, before a computation is finished. - * - * This might return no attribute when the attribute cannot exist on the component. - */ - OutputAttributePtr attribute_try_get_for_output(const blender::StringRef attribute_name, - const AttributeDomain domain, - const CustomDataType data_type, - const void *default_value = nullptr); + /* Statically typed method corresponding to the equally named generic one. */ + template<typename T> + blender::bke::OutputAttribute_Typed<T> attribute_try_get_for_output_only( + const blender::StringRef attribute_name, const AttributeDomain domain) + { + const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>(); + const CustomDataType data_type = blender::bke::cpp_type_to_custom_data_type(cpp_type); + return this->attribute_try_get_for_output_only(attribute_name, domain, data_type); + } private: virtual const blender::bke::ComponentAttributeProviders *get_attribute_providers() const; @@ -328,23 +292,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. */ @@ -377,8 +352,10 @@ class MeshComponent : public GeometryComponent { Mesh *get_for_write(); int attribute_domain_size(const AttributeDomain domain) const final; - blender::bke::ReadAttributePtr attribute_try_adapt_domain( - blender::bke::ReadAttributePtr attribute, const AttributeDomain new_domain) const final; + std::unique_ptr<blender::fn::GVArray> attribute_try_adapt_domain( + std::unique_ptr<blender::fn::GVArray> varray, + const AttributeDomain from_domain, + const AttributeDomain to_domain) const final; bool is_empty() const final; @@ -424,12 +401,128 @@ 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; + + /** + * Curve data necessary to hold the draw cache for rendering, consistent over multiple redraws. + * This is necessary because Blender assumes that objects evaluate to an object data type, and + * we use #CurveEval rather than #Curve here. It also allows us to mostly reuse the same + * batch cache implementation. + */ + mutable Curve *curve_for_render_ = nullptr; + mutable std::mutex curve_for_render_mutex_; + + 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; + std::unique_ptr<blender::fn::GVArray> attribute_try_adapt_domain( + std::unique_ptr<blender::fn::GVArray> varray, + const AttributeDomain from_domain, + const AttributeDomain to_domain) const final; + + bool is_empty() const final; + + bool owns_direct_data() const override; + void ensure_owns_direct_data() override; + + const Curve *get_curve_for_render() const; + + 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 @@ -444,14 +537,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_lib_id.h b/source/blender/blenkernel/BKE_lib_id.h index 6b706f3bcd0..7ac45ac4883 100644 --- a/source/blender/blenkernel/BKE_lib_id.h +++ b/source/blender/blenkernel/BKE_lib_id.h @@ -273,7 +273,7 @@ void BKE_main_id_tag_all(struct Main *mainvar, const int tag, const bool value); void BKE_main_id_flag_listbase(struct ListBase *lb, const int flag, const bool value); void BKE_main_id_flag_all(struct Main *bmain, const int flag, const bool value); -void BKE_main_id_clear_newpoins(struct Main *bmain); +void BKE_main_id_newptr_and_tag_clear(struct Main *bmain); void BKE_main_id_refcount_recompute(struct Main *bmain, const bool do_linked_only); diff --git a/source/blender/blenkernel/BKE_lib_override.h b/source/blender/blenkernel/BKE_lib_override.h index b9a478f8227..0275c2c235c 100644 --- a/source/blender/blenkernel/BKE_lib_override.h +++ b/source/blender/blenkernel/BKE_lib_override.h @@ -47,6 +47,7 @@ struct ID; struct IDOverrideLibrary; struct IDOverrideLibraryProperty; struct IDOverrideLibraryPropertyOperation; +struct Library; struct Main; struct Object; struct PointerRNA; @@ -68,7 +69,9 @@ bool BKE_lib_override_library_is_user_edited(struct ID *id); struct ID *BKE_lib_override_library_create_from_id(struct Main *bmain, struct ID *reference_id, const bool do_tagged_remap); -bool BKE_lib_override_library_create_from_tag(struct Main *bmain); +bool BKE_lib_override_library_create_from_tag(struct Main *bmain, + const struct Library *reference_library, + const bool do_no_main); bool BKE_lib_override_library_create(struct Main *bmain, struct Scene *scene, struct ViewLayer *view_layer, @@ -85,10 +88,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_remap.h b/source/blender/blenkernel/BKE_lib_remap.h index 705d2b030e5..e806dedc14c 100644 --- a/source/blender/blenkernel/BKE_lib_remap.h +++ b/source/blender/blenkernel/BKE_lib_remap.h @@ -85,6 +85,10 @@ enum { * freed ones). */ ID_REMAP_FORCE_INTERNAL_RUNTIME_POINTERS = 1 << 7, + /** Force handling user count even for IDs that are outside of Main (used in some cases when + * dealing with IDs temporarily out of Main, but which will be put in it ultimately). + */ + ID_REMAP_FORCE_USER_REFCOUNT = 1 << 8, }; /* Note: Requiring new_id to be non-null, this *may* not be the case ultimately, 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 22ce197088a..62837c4f2a7 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -578,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_mesh_sample.hh b/source/blender/blenkernel/BKE_mesh_sample.hh new file mode 100644 index 00000000000..f504650e349 --- /dev/null +++ b/source/blender/blenkernel/BKE_mesh_sample.hh @@ -0,0 +1,55 @@ +/* + * 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 "FN_generic_virtual_array.hh" + +#include "BLI_float3.hh" + +#include "BKE_attribute.h" + +struct Mesh; + +namespace blender::bke::mesh_surface_sample { + +using fn::CPPType; +using fn::GMutableSpan; +using fn::GSpan; +using fn::GVArray; + +void sample_point_attribute(const Mesh &mesh, + Span<int> looptri_indices, + Span<float3> bary_coords, + const GVArray &data_in, + GMutableSpan data_out); + +void sample_corner_attribute(const Mesh &mesh, + Span<int> looptri_indices, + Span<float3> bary_coords, + const GVArray &data_in, + GMutableSpan data_out); + +void sample_face_attribute(const Mesh &mesh, + Span<int> looptri_indices, + const GVArray &data_in, + GMutableSpan data_out); + +} // namespace blender::bke::mesh_surface_sample diff --git a/source/blender/blenkernel/BKE_modifier.h b/source/blender/blenkernel/BKE_modifier.h index aea07c45412..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 *********************/ /** 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 d6c4ad037e2..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; @@ -1413,6 +1417,15 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_ATTRIBUTE_MAP_RANGE 1040 #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..4ec165aad8c 100644 --- a/source/blender/blenkernel/BKE_node_ui_storage.hh +++ b/source/blender/blenkernel/BKE_node_ui_storage.hh @@ -100,8 +100,8 @@ struct NodeUIStorage { }; struct NodeTreeUIStorage { + std::mutex mutex; blender::Map<NodeTreeEvaluationContext, blender::Map<std::string, NodeUIStorage>> context_map; - std::mutex context_map_mutex; /** * Attribute search uses this to store the fake info for the string typed into a node, in order 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..ef76c699cbb --- /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_left, + const blender::float3 handle_position_left, + const HandleType handle_type_right, + const blender::float3 handle_position_right, + 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. + */ +struct 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 59e2c74ead1..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 @@ -190,6 +192,7 @@ set(SRC intern/mesh_remap.c intern/mesh_remesh_voxel.c intern/mesh_runtime.c + intern/mesh_sample.cc intern/mesh_tangent.c intern/mesh_validate.c intern/mesh_validate.cc @@ -240,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 @@ -321,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 @@ -379,6 +387,7 @@ set(SRC BKE_mesh_remap.h BKE_mesh_remesh_voxel.h BKE_mesh_runtime.h + BKE_mesh_sample.hh BKE_mesh_tangent.h BKE_mesh_types.h BKE_mesh_wrapper.h @@ -397,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 @@ -583,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) @@ -765,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 5bf87f53f75..d4dd7e248d5 100644 --- a/source/blender/blenkernel/intern/DerivedMesh.cc +++ b/source/blender/blenkernel/intern/DerivedMesh.cc @@ -1861,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/appdir.c b/source/blender/blenkernel/intern/appdir.c index 1075a46e72b..bcfd34ab42f 100644 --- a/source/blender/blenkernel/intern/appdir.c +++ b/source/blender/blenkernel/intern/appdir.c @@ -137,7 +137,7 @@ static char *blender_version_decimal(const int version) { static char version_str[5]; BLI_assert(version < 1000); - BLI_snprintf(version_str, sizeof(version_str), "%d.%02d", version / 100, version % 100); + BLI_snprintf(version_str, sizeof(version_str), "%d.%d", version / 100, version % 100); return version_str; } diff --git a/source/blender/blenkernel/intern/armature.c b/source/blender/blenkernel/intern/armature.c index da8a3b49f3c..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; @@ -2562,7 +2562,7 @@ void BKE_pose_rebuild(Main *bmain, Object *ob, bArmature *arm, const bool do_id_ /* and a check for garbage */ 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. */ @@ -2881,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/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index ac582fc30e7..d36e9ed3e86 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -44,196 +44,11 @@ using blender::float3; using blender::Set; using blender::StringRef; using blender::StringRefNull; -using blender::bke::ReadAttributePtr; -using blender::bke::WriteAttributePtr; using blender::fn::GMutableSpan; +using blender::fn::GSpan; namespace blender::bke { -/* -------------------------------------------------------------------- */ -/** \name Attribute Accessor implementations - * \{ */ - -ReadAttribute::~ReadAttribute() -{ - if (array_is_temporary_ && array_buffer_ != nullptr) { - cpp_type_.destruct_n(array_buffer_, size_); - MEM_freeN(array_buffer_); - } -} - -fn::GSpan ReadAttribute::get_span() const -{ - if (size_ == 0) { - return fn::GSpan(cpp_type_); - } - if (array_buffer_ == nullptr) { - std::lock_guard lock{span_mutex_}; - if (array_buffer_ == nullptr) { - this->initialize_span(); - } - } - return fn::GSpan(cpp_type_, array_buffer_, size_); -} - -void ReadAttribute::initialize_span() const -{ - const int element_size = cpp_type_.size(); - array_buffer_ = MEM_mallocN_aligned(size_ * element_size, cpp_type_.alignment(), __func__); - array_is_temporary_ = true; - for (const int i : IndexRange(size_)) { - this->get_internal(i, POINTER_OFFSET(array_buffer_, i * element_size)); - } -} - -WriteAttribute::~WriteAttribute() -{ - if (array_should_be_applied_) { - CLOG_ERROR(&LOG, "Forgot to call apply_span."); - } - if (array_is_temporary_ && array_buffer_ != nullptr) { - cpp_type_.destruct_n(array_buffer_, size_); - MEM_freeN(array_buffer_); - } -} - -/** - * Get a mutable span that can be modified. When all modifications to the attribute are done, - * #apply_span should be called. */ -fn::GMutableSpan WriteAttribute::get_span() -{ - if (size_ == 0) { - return fn::GMutableSpan(cpp_type_); - } - if (array_buffer_ == nullptr) { - this->initialize_span(false); - } - array_should_be_applied_ = true; - return fn::GMutableSpan(cpp_type_, array_buffer_, size_); -} - -fn::GMutableSpan WriteAttribute::get_span_for_write_only() -{ - if (size_ == 0) { - return fn::GMutableSpan(cpp_type_); - } - if (array_buffer_ == nullptr) { - this->initialize_span(true); - } - array_should_be_applied_ = true; - return fn::GMutableSpan(cpp_type_, array_buffer_, size_); -} - -void WriteAttribute::initialize_span(const bool write_only) -{ - const int element_size = cpp_type_.size(); - array_buffer_ = MEM_mallocN_aligned(element_size * size_, cpp_type_.alignment(), __func__); - array_is_temporary_ = true; - if (write_only) { - /* This does nothing for trivial types, but is necessary for general correctness. */ - cpp_type_.construct_default_n(array_buffer_, size_); - } - else { - for (const int i : IndexRange(size_)) { - this->get(i, POINTER_OFFSET(array_buffer_, i * element_size)); - } - } -} - -void WriteAttribute::apply_span() -{ - this->apply_span_if_necessary(); - array_should_be_applied_ = false; -} - -void WriteAttribute::apply_span_if_necessary() -{ - /* Only works when the span has been initialized beforehand. */ - BLI_assert(array_buffer_ != nullptr); - - const int element_size = cpp_type_.size(); - for (const int i : IndexRange(size_)) { - this->set_internal(i, POINTER_OFFSET(array_buffer_, i * element_size)); - } -} - -/* This is used by the #OutputAttributePtr class. */ -class TemporaryWriteAttribute final : public WriteAttribute { - public: - GMutableSpan data; - GeometryComponent &component; - std::string final_name; - - TemporaryWriteAttribute(AttributeDomain domain, - GMutableSpan data, - GeometryComponent &component, - std::string final_name) - : WriteAttribute(domain, data.type(), data.size()), - data(data), - component(component), - final_name(std::move(final_name)) - { - } - - ~TemporaryWriteAttribute() override - { - if (data.data() != nullptr) { - cpp_type_.destruct_n(data.data(), data.size()); - MEM_freeN(data.data()); - } - } - - void get_internal(const int64_t index, void *r_value) const override - { - data.type().copy_to_uninitialized(data[index], r_value); - } - - void set_internal(const int64_t index, const void *value) override - { - data.type().copy_to_initialized(value, data[index]); - } - - void initialize_span(const bool UNUSED(write_only)) override - { - array_buffer_ = data.data(); - array_is_temporary_ = false; - } - - void apply_span_if_necessary() override - { - /* Do nothing, because the span contains the attribute itself already. */ - } -}; - -class ConvertedReadAttribute final : public ReadAttribute { - private: - const CPPType &from_type_; - const CPPType &to_type_; - ReadAttributePtr base_attribute_; - void (*convert_)(const void *src, void *dst); - - public: - ConvertedReadAttribute(ReadAttributePtr base_attribute, const CPPType &to_type) - : ReadAttribute(base_attribute->domain(), to_type, base_attribute->size()), - from_type_(base_attribute->cpp_type()), - to_type_(to_type), - base_attribute_(std::move(base_attribute)) - { - const nodes::DataTypeConversions &conversions = nodes::get_implicit_type_conversions(); - convert_ = conversions.get_conversion_functions(base_attribute_->cpp_type(), to_type) - ->convert_single_to_uninitialized; - } - - void get_internal(const int64_t index, void *r_value) const override - { - BUFFER_FOR_CPP_TYPE_VALUE(from_type_, buffer); - base_attribute_->get(index, buffer); - convert_(buffer, r_value); - } -}; - -/** \} */ - const blender::fn::CPPType *custom_data_type_to_cpp_type(const CustomDataType type) { switch (type) { @@ -246,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: @@ -269,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>()) { @@ -329,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: @@ -368,7 +181,27 @@ AttributeDomain attribute_domain_highest_priority(Span<AttributeDomain> domains) return highest_priority_domain; } -ReadAttributePtr BuiltinCustomDataLayerProvider::try_get_for_read( +void OutputAttribute::save() +{ + save_has_been_called_ = true; + if (optional_span_varray_.has_value()) { + optional_span_varray_->save(); + } + if (save_) { + save_(*this); + } +} + +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 { const CustomData *custom_data = custom_data_access_.get_const_custom_data(component); @@ -384,7 +217,7 @@ ReadAttributePtr BuiltinCustomDataLayerProvider::try_get_for_read( return as_read_attribute_(data, domain_size); } -WriteAttributePtr BuiltinCustomDataLayerProvider::try_get_for_write( +GVMutableArrayPtr BuiltinCustomDataLayerProvider::try_get_for_write( GeometryComponent &component) const { if (writable_ != Writable) { @@ -430,7 +263,43 @@ bool BuiltinCustomDataLayerProvider::try_delete(GeometryComponent &component) co return delete_success; } -bool BuiltinCustomDataLayerProvider::try_create(GeometryComponent &component) const +static bool add_custom_data_layer_from_attribute_init(CustomData &custom_data, + const CustomDataType data_type, + const int domain_size, + const AttributeInit &initializer) +{ + switch (initializer.type) { + case AttributeInit::Type::Default: { + void *data = CustomData_add_layer(&custom_data, data_type, CD_DEFAULT, nullptr, domain_size); + return data != nullptr; + } + case AttributeInit::Type::VArray: { + void *data = CustomData_add_layer(&custom_data, data_type, CD_DEFAULT, nullptr, domain_size); + if (data == nullptr) { + return false; + } + const GVArray *varray = static_cast<const AttributeInitVArray &>(initializer).varray; + varray->materialize_to_uninitialized(IndexRange(varray->size()), data); + return true; + } + case AttributeInit::Type::MoveArray: { + void *source_data = static_cast<const AttributeInitMove &>(initializer).data; + void *data = CustomData_add_layer( + &custom_data, data_type, CD_ASSIGN, source_data, domain_size); + if (data == nullptr) { + MEM_freeN(source_data); + return false; + } + return true; + } + } + + BLI_assert_unreachable(); + return false; +} + +bool BuiltinCustomDataLayerProvider::try_create(GeometryComponent &component, + const AttributeInit &initializer) const { if (createable_ != Creatable) { return false; @@ -443,10 +312,10 @@ bool BuiltinCustomDataLayerProvider::try_create(GeometryComponent &component) co /* Exists already. */ return false; } + const int domain_size = component.attribute_domain_size(domain_); - const void *data = CustomData_add_layer( - custom_data, stored_type_, CD_DEFAULT, nullptr, domain_size); - const bool success = data != nullptr; + const bool success = add_custom_data_layer_from_attribute_init( + *custom_data, stored_type_, domain_size, initializer); if (success) { custom_data_access_.update_custom_data_pointers(component); } @@ -463,7 +332,7 @@ bool BuiltinCustomDataLayerProvider::exists(const GeometryComponent &component) return data != nullptr; } -ReadAttributePtr CustomDataAttributeProvider::try_get_for_read( +ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read( const GeometryComponent &component, const StringRef attribute_name) const { const CustomData *custom_data = custom_data_access_.get_const_custom_data(component); @@ -486,7 +355,7 @@ ReadAttributePtr 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: @@ -496,7 +365,7 @@ ReadAttributePtr CustomDataAttributeProvider::try_get_for_read( return {}; } -WriteAttributePtr CustomDataAttributeProvider::try_get_for_write( +WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write( GeometryComponent &component, const StringRef attribute_name) const { CustomData *custom_data = custom_data_access_.get_custom_data(component); @@ -520,7 +389,7 @@ WriteAttributePtr 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: @@ -548,10 +417,52 @@ bool CustomDataAttributeProvider::try_delete(GeometryComponent &component, return false; } +static bool add_named_custom_data_layer_from_attribute_init(const StringRef attribute_name, + CustomData &custom_data, + const CustomDataType data_type, + const int domain_size, + const AttributeInit &initializer) +{ + char attribute_name_c[MAX_NAME]; + attribute_name.copy(attribute_name_c); + + switch (initializer.type) { + case AttributeInit::Type::Default: { + void *data = CustomData_add_layer_named( + &custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c); + return data != nullptr; + } + case AttributeInit::Type::VArray: { + void *data = CustomData_add_layer_named( + &custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c); + if (data == nullptr) { + return false; + } + const GVArray *varray = static_cast<const AttributeInitVArray &>(initializer).varray; + varray->materialize_to_uninitialized(IndexRange(varray->size()), data); + return true; + } + case AttributeInit::Type::MoveArray: { + void *source_data = static_cast<const AttributeInitMove &>(initializer).data; + void *data = CustomData_add_layer_named( + &custom_data, data_type, CD_ASSIGN, source_data, domain_size, attribute_name_c); + if (data == nullptr) { + MEM_freeN(source_data); + return false; + } + return true; + } + } + + BLI_assert_unreachable(); + return false; +} + bool CustomDataAttributeProvider::try_create(GeometryComponent &component, const StringRef attribute_name, const AttributeDomain domain, - const CustomDataType data_type) const + const CustomDataType data_type, + const AttributeInit &initializer) const { if (domain_ != domain) { return false; @@ -569,10 +480,8 @@ bool CustomDataAttributeProvider::try_create(GeometryComponent &component, } } const int domain_size = component.attribute_domain_size(domain_); - char attribute_name_c[MAX_NAME]; - attribute_name.copy(attribute_name_c); - CustomData_add_layer_named( - custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c); + add_named_custom_data_layer_from_attribute_init( + attribute_name, *custom_data, data_type, domain_size, initializer); return true; } @@ -595,7 +504,7 @@ bool CustomDataAttributeProvider::foreach_attribute(const GeometryComponent &com return true; } -ReadAttributePtr NamedLegacyCustomDataProvider::try_get_for_read( +ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read( const GeometryComponent &component, const StringRef attribute_name) const { const CustomData *custom_data = custom_data_access_.get_const_custom_data(component); @@ -606,14 +515,14 @@ ReadAttributePtr NamedLegacyCustomDataProvider::try_get_for_read( if (layer.type == stored_type_) { if (layer.name == attribute_name) { const int domain_size = component.attribute_domain_size(domain_); - return as_read_attribute_(layer.data, domain_size); + return {as_read_attribute_(layer.data, domain_size), domain_}; } } } return {}; } -WriteAttributePtr NamedLegacyCustomDataProvider::try_get_for_write( +WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write( GeometryComponent &component, const StringRef attribute_name) const { CustomData *custom_data = custom_data_access_.get_custom_data(component); @@ -630,7 +539,7 @@ WriteAttributePtr NamedLegacyCustomDataProvider::try_get_for_write( if (data_old != data_new) { custom_data_access_.update_custom_data_pointers(component); } - return as_write_attribute_(layer.data, domain_size); + return {as_write_attribute_(layer.data, domain_size), domain_}; } } } @@ -682,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 /* -------------------------------------------------------------------- */ @@ -708,7 +716,17 @@ int GeometryComponent::attribute_domain_size(const AttributeDomain UNUSED(domain return 0; } -ReadAttributePtr GeometryComponent::attribute_try_get_for_read( +bool GeometryComponent::attribute_is_builtin(const blender::StringRef attribute_name) const +{ + using namespace blender::bke; + const ComponentAttributeProviders *providers = this->get_attribute_providers(); + if (providers == nullptr) { + return false; + } + return providers->builtin_attribute_providers().contains_as(attribute_name); +} + +blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read( const StringRef attribute_name) const { using namespace blender::bke; @@ -719,11 +737,11 @@ ReadAttributePtr GeometryComponent::attribute_try_get_for_read( const BuiltinAttributeProvider *builtin_provider = providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr); if (builtin_provider != nullptr) { - return builtin_provider->try_get_for_read(*this); + return {builtin_provider->try_get_for_read(*this), builtin_provider->domain()}; } for (const DynamicAttributesProvider *dynamic_provider : providers->dynamic_attribute_providers()) { - ReadAttributePtr attribute = dynamic_provider->try_get_for_read(*this, attribute_name); + ReadAttributeLookup attribute = dynamic_provider->try_get_for_read(*this, attribute_name); if (attribute) { return attribute; } @@ -731,16 +749,19 @@ ReadAttributePtr GeometryComponent::attribute_try_get_for_read( return {}; } -ReadAttributePtr GeometryComponent::attribute_try_adapt_domain( - ReadAttributePtr attribute, const AttributeDomain new_domain) const +std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_adapt_domain( + std::unique_ptr<blender::fn::GVArray> varray, + const AttributeDomain from_domain, + const AttributeDomain to_domain) const { - if (attribute && attribute->domain() == new_domain) { - return attribute; + if (from_domain == to_domain) { + return varray; } return {}; } -WriteAttributePtr GeometryComponent::attribute_try_get_for_write(const StringRef attribute_name) +blender::bke::WriteAttributeLookup GeometryComponent::attribute_try_get_for_write( + const StringRef attribute_name) { using namespace blender::bke; const ComponentAttributeProviders *providers = this->get_attribute_providers(); @@ -750,11 +771,11 @@ WriteAttributePtr GeometryComponent::attribute_try_get_for_write(const StringRef const BuiltinAttributeProvider *builtin_provider = providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr); if (builtin_provider != nullptr) { - return builtin_provider->try_get_for_write(*this); + return {builtin_provider->try_get_for_write(*this), builtin_provider->domain()}; } for (const DynamicAttributesProvider *dynamic_provider : providers->dynamic_attribute_providers()) { - WriteAttributePtr attribute = dynamic_provider->try_get_for_write(*this, attribute_name); + WriteAttributeLookup attribute = dynamic_provider->try_get_for_write(*this, attribute_name); if (attribute) { return attribute; } @@ -784,7 +805,8 @@ bool GeometryComponent::attribute_try_delete(const StringRef attribute_name) bool GeometryComponent::attribute_try_create(const StringRef attribute_name, const AttributeDomain domain, - const CustomDataType data_type) + const CustomDataType data_type, + const AttributeInit &initializer) { using namespace blender::bke; if (attribute_name.is_empty()) { @@ -803,17 +825,36 @@ bool GeometryComponent::attribute_try_create(const StringRef attribute_name, if (builtin_provider->data_type() != data_type) { return false; } - return builtin_provider->try_create(*this); + return builtin_provider->try_create(*this, initializer); } for (const DynamicAttributesProvider *dynamic_provider : providers->dynamic_attribute_providers()) { - if (dynamic_provider->try_create(*this, attribute_name, domain, data_type)) { + if (dynamic_provider->try_create(*this, attribute_name, domain, data_type, initializer)) { return true; } } return false; } +bool GeometryComponent::attribute_try_create_builtin(const blender::StringRef attribute_name, + const AttributeInit &initializer) +{ + using namespace blender::bke; + if (attribute_name.is_empty()) { + return false; + } + const ComponentAttributeProviders *providers = this->get_attribute_providers(); + if (providers == nullptr) { + return false; + } + const BuiltinAttributeProvider *builtin_provider = + providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr); + if (builtin_provider == nullptr) { + return false; + } + return builtin_provider->try_create(*this, initializer); +} + Set<std::string> GeometryComponent::attribute_names() const { Set<std::string> attributes; @@ -867,264 +908,283 @@ bool GeometryComponent::attribute_foreach(const AttributeForeachCallback callbac bool GeometryComponent::attribute_exists(const blender::StringRef attribute_name) const { - ReadAttributePtr attribute = this->attribute_try_get_for_read(attribute_name); + blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name); if (attribute) { return true; } return false; } -static ReadAttributePtr try_adapt_data_type(ReadAttributePtr attribute, - const blender::fn::CPPType &to_type) +std::optional<AttributeMetaData> GeometryComponent::attribute_get_meta_data( + const StringRef attribute_name) const { - const blender::fn::CPPType &from_type = attribute->cpp_type(); - if (from_type == to_type) { - return attribute; - } + std::optional<AttributeMetaData> result{std::nullopt}; + this->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) { + if (attribute_name == name) { + result = meta_data; + return false; + } + return true; + }); + return result; +} +static std::unique_ptr<blender::fn::GVArray> try_adapt_data_type( + std::unique_ptr<blender::fn::GVArray> varray, const blender::fn::CPPType &to_type) +{ const blender::nodes::DataTypeConversions &conversions = blender::nodes::get_implicit_type_conversions(); - if (!conversions.is_convertible(from_type, to_type)) { - return {}; - } - - return std::make_unique<blender::bke::ConvertedReadAttribute>(std::move(attribute), to_type); + return conversions.try_convert(std::move(varray), to_type); } -ReadAttributePtr GeometryComponent::attribute_try_get_for_read( +std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_get_for_read( const StringRef attribute_name, const AttributeDomain domain, const CustomDataType data_type) const { - ReadAttributePtr attribute = this->attribute_try_get_for_read(attribute_name); + blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name); if (!attribute) { return {}; } - if (domain != ATTR_DOMAIN_AUTO && attribute->domain() != domain) { - attribute = this->attribute_try_adapt_domain(std::move(attribute), domain); - if (!attribute) { + std::unique_ptr<blender::fn::GVArray> varray = std::move(attribute.varray); + if (domain != ATTR_DOMAIN_AUTO && attribute.domain != domain) { + varray = this->attribute_try_adapt_domain(std::move(varray), attribute.domain, domain); + if (!varray) { return {}; } } const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type); BLI_assert(cpp_type != nullptr); - if (attribute->cpp_type() != *cpp_type) { - attribute = try_adapt_data_type(std::move(attribute), *cpp_type); - if (!attribute) { + if (varray->type() != *cpp_type) { + varray = try_adapt_data_type(std::move(varray), *cpp_type); + if (!varray) { return {}; } } - return attribute; + return varray; } -ReadAttributePtr GeometryComponent::attribute_try_get_for_read(const StringRef attribute_name, - const AttributeDomain domain) const +std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_try_get_for_read( + const StringRef attribute_name, const AttributeDomain domain) const { if (!this->attribute_domain_supported(domain)) { return {}; } - ReadAttributePtr attribute = this->attribute_try_get_for_read(attribute_name); + blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name); if (!attribute) { return {}; } - if (attribute->domain() != domain) { - attribute = this->attribute_try_adapt_domain(std::move(attribute), domain); - if (!attribute) { - return {}; - } + if (attribute.domain != domain) { + return this->attribute_try_adapt_domain(std::move(attribute.varray), attribute.domain, domain); } - return attribute; + return std::move(attribute.varray); } -ReadAttributePtr GeometryComponent::attribute_get_for_read(const StringRef attribute_name, - const AttributeDomain domain, - const CustomDataType data_type, - const void *default_value) const +blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read( + const blender::StringRef attribute_name, const CustomDataType data_type) const { - ReadAttributePtr attribute = this->attribute_try_get_for_read(attribute_name, domain, data_type); - if (attribute) { - return attribute; + blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name); + if (!attribute) { + return {}; } - return this->attribute_get_constant_for_read(domain, data_type, default_value); -} - -blender::bke::ReadAttributePtr GeometryComponent::attribute_get_constant_for_read( - const AttributeDomain domain, const CustomDataType data_type, const void *value) const -{ - BLI_assert(this->attribute_domain_supported(domain)); - const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type); - BLI_assert(cpp_type != nullptr); - if (value == nullptr) { - value = cpp_type->default_value(); + const blender::fn::CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type); + BLI_assert(type != nullptr); + if (attribute.varray->type() == *type) { + return attribute; } - const int domain_size = this->attribute_domain_size(domain); - return std::make_unique<blender::bke::ConstantReadAttribute>( - domain, domain_size, *cpp_type, value); + const blender::nodes::DataTypeConversions &conversions = + blender::nodes::get_implicit_type_conversions(); + return {conversions.try_convert(std::move(attribute.varray), *type), attribute.domain}; } -blender::bke::ReadAttributePtr GeometryComponent::attribute_get_constant_for_read_converted( +std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_get_for_read( + const StringRef attribute_name, const AttributeDomain domain, - const CustomDataType in_data_type, - const CustomDataType out_data_type, - const void *value) const + const CustomDataType data_type, + const void *default_value) const { - BLI_assert(this->attribute_domain_supported(domain)); - if (value == nullptr || in_data_type == out_data_type) { - return this->attribute_get_constant_for_read(domain, out_data_type, value); + std::unique_ptr<blender::bke::GVArray> varray = this->attribute_try_get_for_read( + attribute_name, domain, data_type); + if (varray) { + return varray; + } + const blender::fn::CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type); + if (default_value == nullptr) { + default_value = type->default_value(); } - - const blender::fn::CPPType *in_cpp_type = blender::bke::custom_data_type_to_cpp_type( - in_data_type); - const blender::fn::CPPType *out_cpp_type = blender::bke::custom_data_type_to_cpp_type( - out_data_type); - BLI_assert(in_cpp_type != nullptr); - BLI_assert(out_cpp_type != nullptr); - - const blender::nodes::DataTypeConversions &conversions = - blender::nodes::get_implicit_type_conversions(); - BLI_assert(conversions.is_convertible(*in_cpp_type, *out_cpp_type)); - - void *out_value = alloca(out_cpp_type->size()); - conversions.convert_to_uninitialized(*in_cpp_type, *out_cpp_type, value, out_value); - const int domain_size = this->attribute_domain_size(domain); - blender::bke::ReadAttributePtr attribute = std::make_unique<blender::bke::ConstantReadAttribute>( - domain, domain_size, *out_cpp_type, out_value); - - out_cpp_type->destruct(out_value); - return attribute; + return std::make_unique<blender::fn::GVArray_For_SingleValue>(*type, domain_size, default_value); } -OutputAttributePtr GeometryComponent::attribute_try_get_for_output(const StringRef attribute_name, - const AttributeDomain domain, - const CustomDataType data_type, - const void *default_value) -{ - const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type); - BLI_assert(cpp_type != nullptr); - - WriteAttributePtr attribute = this->attribute_try_get_for_write(attribute_name); +class GVMutableAttribute_For_OutputAttribute + : public blender::fn::GVMutableArray_For_GMutableSpan { + public: + GeometryComponent *component; + std::string final_name; - /* If the attribute doesn't exist, make a new one with the correct type. */ - if (!attribute) { - this->attribute_try_create(attribute_name, domain, data_type); - attribute = this->attribute_try_get_for_write(attribute_name); - if (attribute && default_value != nullptr) { - void *data = attribute->get_span_for_write_only().data(); - cpp_type->fill_initialized(default_value, data, attribute->size()); - attribute->apply_span(); - } - return OutputAttributePtr(std::move(attribute)); + GVMutableAttribute_For_OutputAttribute(GMutableSpan data, + GeometryComponent &component, + std::string final_name) + : blender::fn::GVMutableArray_For_GMutableSpan(data), + component(&component), + final_name(std::move(final_name)) + { } - /* If an existing attribute has a matching domain and type, just use that. */ - if (attribute->domain() == domain && attribute->cpp_type() == *cpp_type) { - return OutputAttributePtr(std::move(attribute)); + ~GVMutableAttribute_For_OutputAttribute() override + { + type_->destruct_n(data_, size_); + MEM_freeN(data_); } +}; - /* Otherwise create a temporary buffer to use before saving the new attribute. */ - return OutputAttributePtr(*this, domain, attribute_name, data_type); -} - -/* Construct from an attribute that already exists in the geometry component. */ -OutputAttributePtr::OutputAttributePtr(WriteAttributePtr attribute) - : attribute_(std::move(attribute)) +static void save_output_attribute(blender::bke::OutputAttribute &output_attribute) { -} + using namespace blender; + using namespace blender::fn; + using namespace blender::bke; -/* Construct a temporary attribute that has to replace an existing one later on. */ -OutputAttributePtr::OutputAttributePtr(GeometryComponent &component, - AttributeDomain domain, - std::string final_name, - CustomDataType data_type) -{ - const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type); - BLI_assert(cpp_type != nullptr); + GVMutableAttribute_For_OutputAttribute &varray = + dynamic_cast<GVMutableAttribute_For_OutputAttribute &>(output_attribute.varray()); - const int domain_size = component.attribute_domain_size(domain); - void *buffer = MEM_malloc_arrayN(domain_size, cpp_type->size(), __func__); - GMutableSpan new_span{*cpp_type, buffer, domain_size}; + GeometryComponent &component = *varray.component; + const StringRefNull name = varray.final_name; + const AttributeDomain domain = output_attribute.domain(); + const CustomDataType data_type = output_attribute.custom_data_type(); + const CPPType &cpp_type = output_attribute.cpp_type(); - /* Copy converted values from conflicting attribute, in case the value is read. - * TODO: An optimization could be to not do this, when the caller says that the attribute will - * only be written. */ - ReadAttributePtr src_attribute = component.attribute_get_for_read( - final_name, domain, data_type, nullptr); - for (const int i : blender::IndexRange(domain_size)) { - src_attribute->get(i, new_span[i]); + component.attribute_try_delete(name); + if (!component.attribute_try_create( + varray.final_name, domain, data_type, AttributeInitDefault())) { + CLOG_WARN(&LOG, + "Could not create the '%s' attribute with type '%s'.", + name.c_str(), + cpp_type.name().c_str()); + return; + } + WriteAttributeLookup write_attribute = component.attribute_try_get_for_write(name); + BUFFER_FOR_CPP_TYPE_VALUE(varray.type(), buffer); + for (const int i : IndexRange(varray.size())) { + varray.get(i, buffer); + write_attribute.varray->set_by_relocate(i, buffer); } - - attribute_ = std::make_unique<blender::bke::TemporaryWriteAttribute>( - domain, new_span, component, std::move(final_name)); } -/* Store the computed attribute. If it was stored from the beginning already, nothing is done. This - * might delete another attribute with the same name. */ -void OutputAttributePtr::save() +static blender::bke::OutputAttribute create_output_attribute( + GeometryComponent &component, + const blender::StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type, + const bool ignore_old_values, + const void *default_value) { - if (!attribute_) { - CLOG_WARN(&LOG, "Trying to save an attribute that does not exist anymore."); - return; + using namespace blender; + using namespace blender::fn; + using namespace blender::bke; + + if (attribute_name.is_empty()) { + return {}; } - blender::bke::TemporaryWriteAttribute *attribute = - dynamic_cast<blender::bke::TemporaryWriteAttribute *>(attribute_.get()); + const CPPType *cpp_type = custom_data_type_to_cpp_type(data_type); + BLI_assert(cpp_type != nullptr); + const nodes::DataTypeConversions &conversions = nodes::get_implicit_type_conversions(); - if (attribute == nullptr) { - /* The attribute is saved already. */ - attribute_.reset(); - return; + if (component.attribute_is_builtin(attribute_name)) { + WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_name); + if (!attribute) { + if (default_value) { + const int64_t domain_size = component.attribute_domain_size(domain); + const GVArray_For_SingleValueRef default_varray{*cpp_type, domain_size, default_value}; + component.attribute_try_create_builtin(attribute_name, + AttributeInitVArray(&default_varray)); + } + else { + component.attribute_try_create_builtin(attribute_name, AttributeInitDefault()); + } + attribute = component.attribute_try_get_for_write(attribute_name); + if (!attribute) { + /* Builtin attribute does not exist and can't be created. */ + return {}; + } + } + if (attribute.domain != domain) { + /* Builtin attribute is on different domain. */ + return {}; + } + GVMutableArrayPtr varray = std::move(attribute.varray); + if (varray->type() == *cpp_type) { + /* Builtin attribute matches exactly. */ + return OutputAttribute(std::move(varray), domain, {}, ignore_old_values); + } + /* Builtin attribute is on the same domain but has a different data type. */ + varray = conversions.try_convert(std::move(varray), *cpp_type); + return OutputAttribute(std::move(varray), domain, {}, ignore_old_values); } - StringRefNull name = attribute->final_name; - const blender::fn::CPPType &cpp_type = attribute->cpp_type(); + const int domain_size = component.attribute_domain_size(domain); - /* Delete an existing attribute with the same name if necessary. */ - attribute->component.attribute_try_delete(name); + WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_name); + if (!attribute) { + if (default_value) { + const GVArray_For_SingleValueRef default_varray{*cpp_type, domain_size, default_value}; + component.attribute_try_create( + attribute_name, domain, data_type, AttributeInitVArray(&default_varray)); + } + else { + component.attribute_try_create(attribute_name, domain, data_type, AttributeInitDefault()); + } - if (!attribute->component.attribute_try_create( - name, attribute_->domain(), attribute_->custom_data_type())) { - /* Cannot create the target attribute for some reason. */ - CLOG_WARN(&LOG, - "Creating the '%s' attribute with type '%s' failed.", - name.c_str(), - cpp_type.name().c_str()); - attribute_.reset(); - return; + attribute = component.attribute_try_get_for_write(attribute_name); + if (!attribute) { + /* Can't create the attribute. */ + return {}; + } + } + if (attribute.domain == domain && attribute.varray->type() == *cpp_type) { + /* Existing generic attribute matches exactly. */ + return OutputAttribute(std::move(attribute.varray), domain, {}, ignore_old_values); } - WriteAttributePtr new_attribute = attribute->component.attribute_try_get_for_write(name); - - GMutableSpan temp_span = attribute->data; - GMutableSpan new_span = new_attribute->get_span_for_write_only(); - BLI_assert(temp_span.size() == new_span.size()); - - /* Currently we copy over the attribute. In the future we want to reuse the buffer. */ - cpp_type.move_to_initialized_n(temp_span.data(), new_span.data(), new_span.size()); - new_attribute->apply_span(); + /* Allocate a new array that lives next to the existing attribute. It will overwrite the existing + * attribute after processing is done. */ + void *data = MEM_mallocN_aligned( + cpp_type->size() * domain_size, cpp_type->alignment(), __func__); + if (ignore_old_values) { + /* This does nothing for trivially constructible types, but is necessary for correctness. */ + cpp_type->construct_default_n(data, domain); + } + else { + /* Fill the temporary array with values from the existing attribute. */ + GVArrayPtr old_varray = component.attribute_get_for_read( + attribute_name, domain, data_type, default_value); + old_varray->materialize_to_uninitialized(IndexRange(domain_size), data); + } + GVMutableArrayPtr varray = std::make_unique<GVMutableAttribute_For_OutputAttribute>( + GMutableSpan{*cpp_type, data, domain_size}, component, attribute_name); - attribute_.reset(); + return OutputAttribute(std::move(varray), domain, save_output_attribute, true); } -OutputAttributePtr::~OutputAttributePtr() +blender::bke::OutputAttribute GeometryComponent::attribute_try_get_for_output( + const StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type, + const void *default_value) { - if (attribute_) { - CLOG_ERROR(&LOG, "Forgot to call #save or #apply_span_and_save."); - } + return create_output_attribute(*this, attribute_name, domain, data_type, false, default_value); } -/* Utility function to call #apply_span and #save in the right order. */ -void OutputAttributePtr::apply_span_and_save() +blender::bke::OutputAttribute GeometryComponent::attribute_try_get_for_output_only( + const blender::StringRef attribute_name, + const AttributeDomain domain, + const CustomDataType data_type) { - BLI_assert(attribute_); - attribute_->apply_span(); - this->save(); + return create_output_attribute(*this, attribute_name, domain, data_type, true, nullptr); } - -/** \} */ diff --git a/source/blender/blenkernel/intern/attribute_access_intern.hh b/source/blender/blenkernel/intern/attribute_access_intern.hh index 806d10e9e89..b3a795faa30 100644 --- a/source/blender/blenkernel/intern/attribute_access_intern.hh +++ b/source/blender/blenkernel/intern/attribute_access_intern.hh @@ -24,166 +24,8 @@ namespace blender::bke { -class ConstantReadAttribute final : public ReadAttribute { - private: - void *value_; - - public: - ConstantReadAttribute(AttributeDomain domain, - const int64_t size, - const CPPType &type, - const void *value) - : ReadAttribute(domain, type, size) - { - value_ = MEM_mallocN_aligned(type.size(), type.alignment(), __func__); - type.copy_to_uninitialized(value, value_); - } - - ~ConstantReadAttribute() override - { - this->cpp_type_.destruct(value_); - MEM_freeN(value_); - } - - void get_internal(const int64_t UNUSED(index), void *r_value) const override - { - this->cpp_type_.copy_to_uninitialized(value_, r_value); - } - - void initialize_span() const override - { - const int element_size = cpp_type_.size(); - array_buffer_ = MEM_mallocN_aligned(size_ * element_size, cpp_type_.alignment(), __func__); - array_is_temporary_ = true; - cpp_type_.fill_uninitialized(value_, array_buffer_, size_); - } -}; - -template<typename T> class ArrayReadAttribute final : public ReadAttribute { - private: - Span<T> data_; - - public: - ArrayReadAttribute(AttributeDomain domain, Span<T> data) - : ReadAttribute(domain, CPPType::get<T>(), data.size()), data_(data) - { - } - - void get_internal(const int64_t index, void *r_value) const override - { - new (r_value) T(data_[index]); - } - - void initialize_span() const override - { - /* The data will not be modified, so this const_cast is fine. */ - array_buffer_ = const_cast<T *>(data_.data()); - array_is_temporary_ = false; - } -}; - -template<typename T> class OwnedArrayReadAttribute final : public ReadAttribute { - private: - Array<T> data_; - - public: - OwnedArrayReadAttribute(AttributeDomain domain, Array<T> data) - : ReadAttribute(domain, CPPType::get<T>(), data.size()), data_(std::move(data)) - { - } - - void get_internal(const int64_t index, void *r_value) const override - { - new (r_value) T(data_[index]); - } - - void initialize_span() const override - { - /* The data will not be modified, so this const_cast is fine. */ - array_buffer_ = const_cast<T *>(data_.data()); - array_is_temporary_ = false; - } -}; - -template<typename StructT, typename ElemT, ElemT (*GetFunc)(const StructT &)> -class DerivedArrayReadAttribute final : public ReadAttribute { - private: - Span<StructT> data_; - - public: - DerivedArrayReadAttribute(AttributeDomain domain, Span<StructT> data) - : ReadAttribute(domain, CPPType::get<ElemT>(), data.size()), data_(data) - { - } - - void get_internal(const int64_t index, void *r_value) const override - { - const StructT &struct_value = data_[index]; - const ElemT value = GetFunc(struct_value); - new (r_value) ElemT(value); - } -}; - -template<typename T> class ArrayWriteAttribute final : public WriteAttribute { - private: - MutableSpan<T> data_; - - public: - ArrayWriteAttribute(AttributeDomain domain, MutableSpan<T> data) - : WriteAttribute(domain, CPPType::get<T>(), data.size()), data_(data) - { - } - - void get_internal(const int64_t index, void *r_value) const override - { - new (r_value) T(data_[index]); - } - - void set_internal(const int64_t index, const void *value) override - { - data_[index] = *reinterpret_cast<const T *>(value); - } - - void initialize_span(const bool UNUSED(write_only)) override - { - array_buffer_ = data_.data(); - array_is_temporary_ = false; - } - - void apply_span_if_necessary() override - { - /* Do nothing, because the span contains the attribute itself already. */ - } -}; - -template<typename StructT, - typename ElemT, - ElemT (*GetFunc)(const StructT &), - void (*SetFunc)(StructT &, const ElemT &)> -class DerivedArrayWriteAttribute final : public WriteAttribute { - private: - MutableSpan<StructT> data_; - - public: - DerivedArrayWriteAttribute(AttributeDomain domain, MutableSpan<StructT> data) - : WriteAttribute(domain, CPPType::get<ElemT>(), data.size()), data_(data) - { - } - - void get_internal(const int64_t index, void *r_value) const override - { - const StructT &struct_value = data_[index]; - const ElemT value = GetFunc(struct_value); - new (r_value) ElemT(value); - } - - void set_internal(const int64_t index, const void *value) override - { - StructT &struct_value = data_[index]; - const ElemT &typed_value = *reinterpret_cast<const ElemT *>(value); - SetFunc(struct_value, typed_value); - } -}; +using fn::GVArrayPtr; +using fn::GVMutableArrayPtr; /** * Utility to group together multiple functions that are used to access custom data on geometry @@ -244,10 +86,11 @@ class BuiltinAttributeProvider { { } - virtual ReadAttributePtr try_get_for_read(const GeometryComponent &component) const = 0; - virtual WriteAttributePtr try_get_for_write(GeometryComponent &component) const = 0; + virtual GVArrayPtr try_get_for_read(const GeometryComponent &component) const = 0; + virtual GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const = 0; virtual bool try_delete(GeometryComponent &component) const = 0; - virtual bool try_create(GeometryComponent &UNUSED(component)) const = 0; + virtual bool try_create(GeometryComponent &UNUSED(component), + const AttributeInit &UNUSED(initializer)) const = 0; virtual bool exists(const GeometryComponent &component) const = 0; StringRefNull name() const @@ -272,15 +115,16 @@ class BuiltinAttributeProvider { */ class DynamicAttributesProvider { public: - virtual ReadAttributePtr try_get_for_read(const GeometryComponent &component, - const StringRef attribute_name) const = 0; - virtual WriteAttributePtr try_get_for_write(GeometryComponent &component, - const StringRef attribute_name) const = 0; + virtual ReadAttributeLookup try_get_for_read(const GeometryComponent &component, + const StringRef attribute_name) const = 0; + virtual WriteAttributeLookup try_get_for_write(GeometryComponent &component, + const StringRef attribute_name) const = 0; virtual bool try_delete(GeometryComponent &component, const StringRef attribute_name) const = 0; virtual bool try_create(GeometryComponent &UNUSED(component), const StringRef UNUSED(attribute_name), const AttributeDomain UNUSED(domain), - const CustomDataType UNUSED(data_type)) const + const CustomDataType UNUSED(data_type), + const AttributeInit &UNUSED(initializer)) const { /* Some providers should not create new attributes. */ return false; @@ -309,18 +153,19 @@ class CustomDataAttributeProvider final : public DynamicAttributesProvider { { } - ReadAttributePtr try_get_for_read(const GeometryComponent &component, - const StringRef attribute_name) const final; + ReadAttributeLookup try_get_for_read(const GeometryComponent &component, + const StringRef attribute_name) const final; - WriteAttributePtr try_get_for_write(GeometryComponent &component, - const StringRef attribute_name) const final; + WriteAttributeLookup try_get_for_write(GeometryComponent &component, + const StringRef attribute_name) const final; bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final; bool try_create(GeometryComponent &component, const StringRef attribute_name, const AttributeDomain domain, - const CustomDataType data_type) const final; + const CustomDataType data_type, + const AttributeInit &initializer) const final; bool foreach_attribute(const GeometryComponent &component, const AttributeForeachCallback callback) const final; @@ -332,18 +177,21 @@ class CustomDataAttributeProvider final : public DynamicAttributesProvider { private: template<typename T> - ReadAttributePtr layer_to_read_attribute(const CustomDataLayer &layer, - const int domain_size) const + ReadAttributeLookup layer_to_read_attribute(const CustomDataLayer &layer, + const int domain_size) const { - return std::make_unique<ArrayReadAttribute<T>>( - domain_, Span(static_cast<const T *>(layer.data), domain_size)); + return {std::make_unique<fn::GVArray_For_Span<T>>( + Span(static_cast<const T *>(layer.data), domain_size)), + domain_}; } template<typename T> - WriteAttributePtr layer_to_write_attribute(CustomDataLayer &layer, const int domain_size) const + WriteAttributeLookup layer_to_write_attribute(CustomDataLayer &layer, + const int domain_size) const { - return std::make_unique<ArrayWriteAttribute<T>>( - domain_, MutableSpan(static_cast<T *>(layer.data), domain_size)); + return {std::make_unique<fn::GVMutableArray_For_MutableSpan<T>>( + MutableSpan(static_cast<T *>(layer.data), domain_size)), + domain_}; } bool type_is_supported(CustomDataType data_type) const @@ -357,8 +205,8 @@ class CustomDataAttributeProvider final : public DynamicAttributesProvider { */ class NamedLegacyCustomDataProvider final : public DynamicAttributesProvider { private: - using AsReadAttribute = ReadAttributePtr (*)(const void *data, const int domain_size); - using AsWriteAttribute = WriteAttributePtr (*)(void *data, const int domain_size); + using AsReadAttribute = GVArrayPtr (*)(const void *data, const int domain_size); + using AsWriteAttribute = GVMutableArrayPtr (*)(void *data, const int domain_size); const AttributeDomain domain_; const CustomDataType attribute_type_; const CustomDataType stored_type_; @@ -382,10 +230,10 @@ class NamedLegacyCustomDataProvider final : public DynamicAttributesProvider { { } - ReadAttributePtr try_get_for_read(const GeometryComponent &component, - const StringRef attribute_name) const final; - WriteAttributePtr try_get_for_write(GeometryComponent &component, - const StringRef attribute_name) const final; + ReadAttributeLookup try_get_for_read(const GeometryComponent &component, + const StringRef attribute_name) const final; + WriteAttributeLookup try_get_for_write(GeometryComponent &component, + const StringRef attribute_name) const final; bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final; bool foreach_attribute(const GeometryComponent &component, const AttributeForeachCallback callback) const final; @@ -398,8 +246,8 @@ class NamedLegacyCustomDataProvider final : public DynamicAttributesProvider { * the #MVert struct, but is exposed as float3 attribute. */ class BuiltinCustomDataLayerProvider final : public BuiltinAttributeProvider { - using AsReadAttribute = ReadAttributePtr (*)(const void *data, const int domain_size); - using AsWriteAttribute = WriteAttributePtr (*)(void *data, const int domain_size); + using AsReadAttribute = GVArrayPtr (*)(const void *data, const int domain_size); + using AsWriteAttribute = GVMutableArrayPtr (*)(void *data, const int domain_size); using UpdateOnRead = void (*)(const GeometryComponent &component); using UpdateOnWrite = void (*)(GeometryComponent &component); const CustomDataType stored_type_; @@ -430,10 +278,10 @@ class BuiltinCustomDataLayerProvider final : public BuiltinAttributeProvider { { } - ReadAttributePtr try_get_for_read(const GeometryComponent &component) const final; - WriteAttributePtr try_get_for_write(GeometryComponent &component) const final; + GVArrayPtr try_get_for_read(const GeometryComponent &component) const final; + GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const final; bool try_delete(GeometryComponent &component) const final; - bool try_create(GeometryComponent &component) const final; + bool try_create(GeometryComponent &component, const AttributeInit &initializer) const final; bool exists(const GeometryComponent &component) const final; }; 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/blender.c b/source/blender/blenkernel/intern/blender.c index e8879cdda8f..e84b485c466 100644 --- a/source/blender/blenkernel/intern/blender.c +++ b/source/blender/blenkernel/intern/blender.c @@ -132,7 +132,7 @@ static void blender_version_init(void) BLI_snprintf(blender_version_string, ARRAY_SIZE(blender_version_string), - "%d.%02d.%d%s", + "%d.%01d.%d%s", BLENDER_VERSION / 100, BLENDER_VERSION % 100, BLENDER_VERSION_PATCH, 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 3170c3aa65c..d8fbdf26d93 100644 --- a/source/blender/blenkernel/intern/collection.c +++ b/source/blender/blenkernel/intern/collection.c @@ -694,8 +694,7 @@ Collection *BKE_collection_duplicate(Main *bmain, const bool is_subprocess = (duplicate_options & LIB_ID_DUPLICATE_IS_SUBPROCESS) != 0; if (!is_subprocess) { - BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false); - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); /* In case root duplicated ID is linked, assume we want to get a local copy of it and duplicate * all expected linked data. */ if (ID_IS_LINKED(collection)) { @@ -726,8 +725,7 @@ Collection *BKE_collection_duplicate(Main *bmain, #endif /* Cleanup. */ - BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false); - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); BKE_main_collection_sync(bmain); } diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c index bcd2c75be0d..9293a2b449a 100644 --- a/source/blender/blenkernel/intern/constraint.c +++ b/source/blender/blenkernel/intern/constraint.c @@ -2840,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/context.c b/source/blender/blenkernel/intern/context.c index cbf7a4483c0..81830f5bb61 100644 --- a/source/blender/blenkernel/intern/context.c +++ b/source/blender/blenkernel/intern/context.c @@ -80,7 +80,17 @@ struct bContext { struct ARegion *menu; struct wmGizmoGroup *gizmo_group; struct bContextStore *store; - const char *operator_poll_msg; /* reason for poll failing */ + + /* Operator poll. */ + /** + * Store the reason the poll function fails (static string, not allocated). + * For more advanced formatting use `operator_poll_msg_dyn_params`. + */ + const char *operator_poll_msg; + /** + * Store values to dynamically to create the string (called when a tool-tip is shown). + */ + struct bContextPollMsgDyn_Params operator_poll_msg_dyn_params; } wm; /* data context */ @@ -113,11 +123,16 @@ bContext *CTX_copy(const bContext *C) { bContext *newC = MEM_dupallocN((void *)C); + memset(&newC->wm.operator_poll_msg_dyn_params, 0, sizeof(newC->wm.operator_poll_msg_dyn_params)); + return newC; } void CTX_free(bContext *C) { + /* This may contain a dynamically allocated message, free. */ + CTX_wm_operator_poll_msg_clear(C); + MEM_freeN(C); } @@ -1003,13 +1018,45 @@ void CTX_wm_gizmo_group_set(bContext *C, struct wmGizmoGroup *gzgroup) C->wm.gizmo_group = gzgroup; } +void CTX_wm_operator_poll_msg_clear(bContext *C) +{ + struct bContextPollMsgDyn_Params *params = &C->wm.operator_poll_msg_dyn_params; + if (params->free_fn != NULL) { + params->free_fn(C, params->user_data); + } + params->get_fn = NULL; + params->free_fn = NULL; + params->user_data = NULL; + + C->wm.operator_poll_msg = NULL; +} void CTX_wm_operator_poll_msg_set(bContext *C, const char *msg) { + CTX_wm_operator_poll_msg_clear(C); + C->wm.operator_poll_msg = msg; } -const char *CTX_wm_operator_poll_msg_get(bContext *C) +void CTX_wm_operator_poll_msg_set_dynamic(bContext *C, + const struct bContextPollMsgDyn_Params *params) { + CTX_wm_operator_poll_msg_clear(C); + + C->wm.operator_poll_msg_dyn_params = *params; +} + +const char *CTX_wm_operator_poll_msg_get(bContext *C, bool *r_free) +{ + struct bContextPollMsgDyn_Params *params = &C->wm.operator_poll_msg_dyn_params; + if (params->get_fn != NULL) { + char *msg = params->get_fn(C, params->user_data); + if (msg != NULL) { + *r_free = true; + } + return msg; + } + + *r_free = false; return IFACE_(C->wm.operator_poll_msg); } 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/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..de8dc355557 --- /dev/null +++ b/source/blender/blenkernel/intern/geometry_component_curve.cc @@ -0,0 +1,1199 @@ +/* + * 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 "DNA_ID_enums.h" +#include "DNA_curve_types.h" + +#include "BKE_attribute_access.hh" +#include "BKE_attribute_math.hh" +#include "BKE_curve.h" +#include "BKE_geometry_set.hh" +#include "BKE_lib_id.h" +#include "BKE_spline.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::GVArrayPtr; +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_; + } + if (curve_for_render_ != nullptr) { + BKE_id_free(nullptr, curve_for_render_); + curve_for_render_ = nullptr; + } + + 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; + } +} + +/** + * Create empty curve data used for rendering the spline's wire edges. + * \note See comment on #curve_for_render_ for further explanation. + */ +const Curve *CurveComponent::get_curve_for_render() const +{ + if (curve_ == nullptr) { + return nullptr; + } + if (curve_for_render_ != nullptr) { + return curve_for_render_; + } + std::lock_guard lock{curve_for_render_mutex_}; + if (curve_for_render_ != nullptr) { + return curve_for_render_; + } + + curve_for_render_ = (Curve *)BKE_id_new_nomain(ID_CU, nullptr); + curve_for_render_->curve_eval = curve_; + + return curve_for_render_; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \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; +} + +namespace blender::bke { + +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}; +} + +/** + * Mix together all of a spline's control point values. + * + * \note Theoretically this interpolation does not need to compute all values at once. + * However, doing that makes the implementation simpler, and this can be optimized in the future if + * only some values are required. + */ +template<typename T> +static void adapt_curve_domain_point_to_spline_impl(const CurveEval &curve, + const VArray<T> &old_values, + MutableSpan<T> r_values) +{ + const int splines_len = curve.splines().size(); + Array<int> offsets = curve.control_point_offsets(); + BLI_assert(r_values.size() == splines_len); + attribute_math::DefaultMixer<T> mixer(r_values); + + for (const int i_spline : IndexRange(splines_len)) { + const int spline_offset = offsets[i_spline]; + const int spline_point_len = offsets[i_spline + 1] - spline_offset; + for (const int i_point : IndexRange(spline_point_len)) { + const T value = old_values[spline_offset + i_point]; + mixer.mix_in(i_spline, value); + } + } + + mixer.finalize(); +} + +static GVArrayPtr adapt_curve_domain_point_to_spline(const CurveEval &curve, GVArrayPtr varray) +{ + GVArrayPtr new_varray; + 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(curve.splines().size()); + adapt_curve_domain_point_to_spline_impl<T>(curve, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); + } + }); + return new_varray; +} + +/** + * A virtual array implementation for the conversion of spline attributes to control point + * attributes. The goal is to avoid copying the spline value for every one of its control points + * unless it is necessary (in that case the materialize functions will be called). + */ +template<typename T> class VArray_For_SplineToPoint final : public VArray<T> { + /* Store existing data materialized if it was not already a span. This is expected + * to be worth it because a single spline's value will likely be accessed many times. */ + VArray_Span<T> original_data_; + Array<int> offsets_; + + public: + VArray_For_SplineToPoint(const VArray<T> &original_varray, Array<int> offsets) + : VArray<T>(offsets.last()), original_data_(original_varray), offsets_(std::move(offsets)) + { + } + + T get_impl(const int64_t index) const final + { + const PointIndices indices = lookup_point_indices(offsets_, index); + return original_data_[indices.spline_index]; + } + + void materialize_impl(const IndexMask mask, MutableSpan<T> r_span) const final + { + const int total_size = offsets_.last(); + if (mask.is_range() && mask.as_range() == IndexRange(total_size)) { + for (const int spline_index : original_data_.index_range()) { + const int offset = offsets_[spline_index]; + const int next_offset = offsets_[spline_index + 1]; + r_span.slice(offset, next_offset - offset).fill(original_data_[spline_index]); + } + } + else { + int spline_index = 0; + for (const int dst_index : mask) { + while (offsets_[spline_index] < dst_index) { + spline_index++; + } + r_span[dst_index] = original_data_[spline_index]; + } + } + } + + void materialize_to_uninitialized_impl(const IndexMask mask, MutableSpan<T> r_span) const final + { + 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 : original_data_.index_range()) { + const int offset = offsets_[spline_index]; + const int next_offset = offsets_[spline_index + 1]; + uninitialized_fill_n(dst + offset, next_offset - offset, original_data_[spline_index]); + } + } + else { + int spline_index = 0; + for (const int dst_index : mask) { + while (offsets_[spline_index] < dst_index) { + spline_index++; + } + new (dst + dst_index) T(original_data_[spline_index]); + } + } + } +}; + +static GVArrayPtr adapt_curve_domain_spline_to_point(const CurveEval &curve, GVArrayPtr varray) +{ + GVArrayPtr new_varray; + attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { + using T = decltype(dummy); + + Array<int> offsets = curve.control_point_offsets(); + new_varray = std::make_unique<fn::GVArray_For_EmbeddedVArray<T, VArray_For_SplineToPoint<T>>>( + offsets.last(), *varray->typed<T>(), std::move(offsets)); + }); + return new_varray; +} + +} // namespace blender::bke + +GVArrayPtr CurveComponent::attribute_try_adapt_domain(GVArrayPtr varray, + const AttributeDomain from_domain, + const AttributeDomain to_domain) const +{ + if (!varray) { + return {}; + } + if (varray->size() == 0) { + return {}; + } + if (from_domain == to_domain) { + return varray; + } + + if (from_domain == ATTR_DOMAIN_POINT && to_domain == ATTR_DOMAIN_CURVE) { + return blender::bke::adapt_curve_domain_point_to_spline(*curve_, std::move(varray)); + } + if (from_domain == ATTR_DOMAIN_CURVE && to_domain == ATTR_DOMAIN_POINT) { + return blender::bke::adapt_curve_domain_spline_to_point(*curve_, std::move(varray)); + } + + return {}; +} + +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. + * \{ */ + +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]; + r_span.slice(offset, next_offset - offset).copy_from(data[spline_index]); + } + } + else { + int spline_index = 0; + for (const int dst_index : mask) { + 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 dst_index : mask) { + 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 feb30e8575a..3b1b7456162 100644 --- a/source/blender/blenkernel/intern/geometry_component_instances.cc +++ b/source/blender/blenkernel/intern/geometry_component_instances.cc @@ -42,72 +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->ids_ = ids_; + 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(); - ids_.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 @@ -178,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 5697fb2ccde..42f3a854aec 100644 --- a/source/blender/blenkernel/intern/geometry_component_mesh.cc +++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc @@ -32,7 +32,7 @@ /* Can't include BKE_object_deform.h right now, due to an enum forward declaration. */ extern "C" MDeformVert *BKE_object_defgroup_data_create(ID *id); -using blender::bke::ReadAttributePtr; +using blender::fn::GVArray; /* -------------------------------------------------------------------- */ /** \name Geometry Component Implementation @@ -201,14 +201,14 @@ namespace blender::bke { template<typename T> static void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh, - const TypedReadAttribute<T> &attribute, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totvert); attribute_math::DefaultMixer<T> mixer(r_values); for (const int loop_index : IndexRange(mesh.totloop)) { - const T value = attribute[loop_index]; + const T value = old_values[loop_index]; const MLoop &loop = mesh.mloop[loop_index]; const int point_index = loop.v; mixer.mix_in(point_index, value); @@ -216,55 +216,49 @@ static void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_corner_to_point(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_corner_to_point(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + 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 * iterate over all loops anyway. */ Array<T> values(mesh.totvert); - adapt_mesh_domain_corner_to_point_impl<T>(mesh, *attribute, values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_corner_to_point_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } template<typename T> static void adapt_mesh_domain_point_to_corner_impl(const Mesh &mesh, - const TypedReadAttribute<T> &attribute, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totloop); for (const int loop_index : IndexRange(mesh.totloop)) { const int vertex_index = mesh.mloop[loop_index].v; - r_values[loop_index] = attribute[vertex_index]; + r_values[loop_index] = old_values[vertex_index]; } } -static ReadAttributePtr adapt_mesh_domain_point_to_corner(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_point_to_corner(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + 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 * when an algorithm only accesses very few of the corner values. However, for the algorithms * we currently have, precomputing the array is fine. Also, it is easier to implement. */ Array<T> values(mesh.totloop); - adapt_mesh_domain_point_to_corner_impl<T>(mesh, *attribute, values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_CORNER, - std::move(values)); + adapt_mesh_domain_point_to_corner_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); }); - return new_attribute; + return new_varray; } /** @@ -274,7 +268,7 @@ static ReadAttributePtr adapt_mesh_domain_point_to_corner(const Mesh &mesh, */ template<typename T> static void adapt_mesh_domain_corner_to_face_impl(const Mesh &mesh, - Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totpoly); @@ -291,26 +285,23 @@ static void adapt_mesh_domain_corner_to_face_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_corner_to_face(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_corner_to_face(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + 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); - adapt_mesh_domain_corner_to_face_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_corner_to_face_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } template<typename T> static void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh, - Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totedge); @@ -332,26 +323,23 @@ static void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_corner_to_edge(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_corner_to_edge(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + 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); - adapt_mesh_domain_corner_to_edge_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_corner_to_edge_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } template<typename T> void adapt_mesh_domain_face_to_point_impl(const Mesh &mesh, - Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totvert); @@ -370,26 +358,23 @@ void adapt_mesh_domain_face_to_point_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_face_to_point(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_face_to_point(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + 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); - adapt_mesh_domain_face_to_point_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_face_to_point_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } template<typename T> void adapt_mesh_domain_face_to_corner_impl(const Mesh &mesh, - const Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totloop); @@ -401,26 +386,23 @@ void adapt_mesh_domain_face_to_corner_impl(const Mesh &mesh, } } -static ReadAttributePtr adapt_mesh_domain_face_to_corner(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_face_to_corner(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + 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); - adapt_mesh_domain_face_to_corner_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_face_to_corner_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } template<typename T> void adapt_mesh_domain_face_to_edge_impl(const Mesh &mesh, - const Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totedge); @@ -437,21 +419,18 @@ void adapt_mesh_domain_face_to_edge_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_face_to_edge(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_face_to_edge(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + 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); - adapt_mesh_domain_face_to_edge_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_face_to_edge_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } /** @@ -461,7 +440,7 @@ static ReadAttributePtr adapt_mesh_domain_face_to_edge(const Mesh &mesh, */ template<typename T> static void adapt_mesh_domain_point_to_face_impl(const Mesh &mesh, - const Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totpoly); @@ -478,21 +457,18 @@ static void adapt_mesh_domain_point_to_face_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_point_to_face(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_point_to_face(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + 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); - adapt_mesh_domain_point_to_face_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_point_to_face_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } /** @@ -502,7 +478,7 @@ static ReadAttributePtr adapt_mesh_domain_point_to_face(const Mesh &mesh, */ template<typename T> static void adapt_mesh_domain_point_to_edge_impl(const Mesh &mesh, - const Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totedge); @@ -517,26 +493,23 @@ static void adapt_mesh_domain_point_to_edge_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_point_to_edge(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_point_to_edge(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + 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); - adapt_mesh_domain_point_to_edge_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_point_to_edge_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } template<typename T> void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh, - const Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totloop); @@ -558,26 +531,23 @@ void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_edge_to_corner(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_edge_to_corner(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + 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); - adapt_mesh_domain_edge_to_corner_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_edge_to_corner_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } template<typename T> static void adapt_mesh_domain_edge_to_point_impl(const Mesh &mesh, - const Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totvert); @@ -593,21 +563,18 @@ static void adapt_mesh_domain_edge_to_point_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_edge_to_point(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_edge_to_point(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + 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); - adapt_mesh_domain_edge_to_point_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_edge_to_point_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } /** @@ -617,7 +584,7 @@ static ReadAttributePtr adapt_mesh_domain_edge_to_point(const Mesh &mesh, */ template<typename T> static void adapt_mesh_domain_edge_to_face_impl(const Mesh &mesh, - const Span<T> old_values, + const VArray<T> &old_values, MutableSpan<T> r_values) { BLI_assert(r_values.size() == mesh.totpoly); @@ -634,87 +601,85 @@ static void adapt_mesh_domain_edge_to_face_impl(const Mesh &mesh, mixer.finalize(); } -static ReadAttributePtr adapt_mesh_domain_edge_to_face(const Mesh &mesh, - ReadAttributePtr attribute) +static GVArrayPtr adapt_mesh_domain_edge_to_face(const Mesh &mesh, GVArrayPtr varray) { - ReadAttributePtr new_attribute; - const CustomDataType data_type = attribute->custom_data_type(); - attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + GVArrayPtr new_varray; + 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); - adapt_mesh_domain_edge_to_face_impl<T>(mesh, attribute->get_span<T>(), values); - new_attribute = std::make_unique<OwnedArrayReadAttribute<T>>(ATTR_DOMAIN_POINT, - std::move(values)); + adapt_mesh_domain_edge_to_face_impl<T>(mesh, varray->typed<T>(), values); + new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); } }); - return new_attribute; + return new_varray; } } // namespace blender::bke -ReadAttributePtr MeshComponent::attribute_try_adapt_domain(ReadAttributePtr attribute, - const AttributeDomain new_domain) const +blender::fn::GVArrayPtr MeshComponent::attribute_try_adapt_domain( + blender::fn::GVArrayPtr varray, + const AttributeDomain from_domain, + const AttributeDomain to_domain) const { - if (!attribute) { + if (!varray) { return {}; } - if (attribute->size() == 0) { + if (varray->size() == 0) { return {}; } - const AttributeDomain old_domain = attribute->domain(); - if (old_domain == new_domain) { - return attribute; + if (from_domain == to_domain) { + return varray; } - switch (old_domain) { + switch (from_domain) { case ATTR_DOMAIN_CORNER: { - switch (new_domain) { + switch (to_domain) { case ATTR_DOMAIN_POINT: - return blender::bke::adapt_mesh_domain_corner_to_point(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_corner_to_point(*mesh_, std::move(varray)); case ATTR_DOMAIN_FACE: - return blender::bke::adapt_mesh_domain_corner_to_face(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_corner_to_face(*mesh_, std::move(varray)); case ATTR_DOMAIN_EDGE: - return blender::bke::adapt_mesh_domain_corner_to_edge(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_corner_to_edge(*mesh_, std::move(varray)); default: break; } break; } case ATTR_DOMAIN_POINT: { - switch (new_domain) { + switch (to_domain) { case ATTR_DOMAIN_CORNER: - return blender::bke::adapt_mesh_domain_point_to_corner(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_point_to_corner(*mesh_, std::move(varray)); case ATTR_DOMAIN_FACE: - return blender::bke::adapt_mesh_domain_point_to_face(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_point_to_face(*mesh_, std::move(varray)); case ATTR_DOMAIN_EDGE: - return blender::bke::adapt_mesh_domain_point_to_edge(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_point_to_edge(*mesh_, std::move(varray)); default: break; } break; } case ATTR_DOMAIN_FACE: { - switch (new_domain) { + switch (to_domain) { case ATTR_DOMAIN_POINT: - return blender::bke::adapt_mesh_domain_face_to_point(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_face_to_point(*mesh_, std::move(varray)); case ATTR_DOMAIN_CORNER: - return blender::bke::adapt_mesh_domain_face_to_corner(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_face_to_corner(*mesh_, std::move(varray)); case ATTR_DOMAIN_EDGE: - return blender::bke::adapt_mesh_domain_face_to_edge(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_face_to_edge(*mesh_, std::move(varray)); default: break; } break; } case ATTR_DOMAIN_EDGE: { - switch (new_domain) { + switch (to_domain) { case ATTR_DOMAIN_CORNER: - return blender::bke::adapt_mesh_domain_edge_to_corner(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_edge_to_corner(*mesh_, std::move(varray)); case ATTR_DOMAIN_POINT: - return blender::bke::adapt_mesh_domain_edge_to_point(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_edge_to_point(*mesh_, std::move(varray)); case ATTR_DOMAIN_FACE: - return blender::bke::adapt_mesh_domain_edge_to_face(*mesh_, std::move(attribute)); + return blender::bke::adapt_mesh_domain_edge_to_face(*mesh_, std::move(varray)); default: break; } @@ -743,25 +708,21 @@ static const Mesh *get_mesh_from_component_for_read(const GeometryComponent &com namespace blender::bke { -template<typename StructT, - typename ElemT, - ElemT (*GetFunc)(const StructT &), - AttributeDomain Domain> -static ReadAttributePtr make_derived_read_attribute(const void *data, const int domain_size) +template<typename StructT, typename ElemT, ElemT (*GetFunc)(const StructT &)> +static GVArrayPtr make_derived_read_attribute(const void *data, const int domain_size) { - return std::make_unique<DerivedArrayReadAttribute<StructT, ElemT, GetFunc>>( - Domain, Span<StructT>((const StructT *)data, domain_size)); + return std::make_unique<fn::GVArray_For_DerivedSpan<StructT, ElemT, GetFunc>>( + Span<StructT>((const StructT *)data, domain_size)); } template<typename StructT, typename ElemT, ElemT (*GetFunc)(const StructT &), - void (*SetFunc)(StructT &, const ElemT &), - AttributeDomain Domain> -static WriteAttributePtr make_derived_write_attribute(void *data, const int domain_size) + void (*SetFunc)(StructT &, ElemT)> +static GVMutableArrayPtr make_derived_write_attribute(void *data, const int domain_size) { - return std::make_unique<DerivedArrayWriteAttribute<StructT, ElemT, GetFunc, SetFunc>>( - Domain, MutableSpan<StructT>((StructT *)data, domain_size)); + return std::make_unique<fn::GVMutableArray_For_DerivedSpan<StructT, ElemT, GetFunc, SetFunc>>( + MutableSpan<StructT>((StructT *)data, domain_size)); } static float3 get_vertex_position(const MVert &vert) @@ -769,7 +730,7 @@ static float3 get_vertex_position(const MVert &vert) return float3(vert.co); } -static void set_vertex_position(MVert &vert, const float3 &position) +static void set_vertex_position(MVert &vert, float3 position) { copy_v3_v3(vert.co, position); } @@ -787,7 +748,7 @@ static int get_material_index(const MPoly &mpoly) return static_cast<int>(mpoly.mat_nr); } -static void set_material_index(MPoly &mpoly, const int &index) +static void set_material_index(MPoly &mpoly, int index) { mpoly.mat_nr = static_cast<short>(std::clamp(index, 0, SHRT_MAX)); } @@ -797,7 +758,7 @@ static bool get_shade_smooth(const MPoly &mpoly) return mpoly.flag & ME_SMOOTH; } -static void set_shade_smooth(MPoly &mpoly, const bool &value) +static void set_shade_smooth(MPoly &mpoly, bool value) { SET_FLAG_FROM_TEST(mpoly.flag, value, ME_SMOOTH); } @@ -807,23 +768,25 @@ static float2 get_loop_uv(const MLoopUV &uv) return float2(uv.uv); } -static void set_loop_uv(MLoopUV &uv, const float2 &co) +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, const 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) @@ -831,71 +794,62 @@ static float get_crease(const MEdge &edge) return edge.crease / 255.0f; } -static void set_crease(MEdge &edge, const float &value) +static void set_crease(MEdge &edge, float value) { edge.crease = round_fl_to_uchar_clamp(value * 255.0f); } -class VertexWeightWriteAttribute final : public WriteAttribute { +class VMutableArray_For_VertexWeights final : public VMutableArray<float> { private: MDeformVert *dverts_; const int dvert_index_; public: - VertexWeightWriteAttribute(MDeformVert *dverts, const int totvert, const int dvert_index) - : WriteAttribute(ATTR_DOMAIN_POINT, CPPType::get<float>(), totvert), - dverts_(dverts), - dvert_index_(dvert_index) + VMutableArray_For_VertexWeights(MDeformVert *dverts, const int totvert, const int dvert_index) + : VMutableArray<float>(totvert), dverts_(dverts), dvert_index_(dvert_index) { } - void get_internal(const int64_t index, void *r_value) const override + float get_impl(const int64_t index) const override { - get_internal(dverts_, dvert_index_, index, r_value); + return get_internal(dverts_, dvert_index_, index); } - void set_internal(const int64_t index, const void *value) override + void set_impl(const int64_t index, const float value) override { MDeformWeight *weight = BKE_defvert_ensure_index(&dverts_[index], dvert_index_); - weight->weight = *reinterpret_cast<const float *>(value); + weight->weight = value; } - static void get_internal(const MDeformVert *dverts, - const int dvert_index, - const int64_t index, - void *r_value) + static float get_internal(const MDeformVert *dverts, const int dvert_index, const int64_t index) { if (dverts == nullptr) { - *(float *)r_value = 0.0f; - return; + return 0.0f; } const MDeformVert &dvert = dverts[index]; for (const MDeformWeight &weight : Span(dvert.dw, dvert.totweight)) { if (weight.def_nr == dvert_index) { - *(float *)r_value = weight.weight; - return; + return weight.weight; } } - *(float *)r_value = 0.0f; + return 0.0f; } }; -class VertexWeightReadAttribute final : public ReadAttribute { +class VArray_For_VertexWeights final : public VArray<float> { private: const MDeformVert *dverts_; const int dvert_index_; public: - VertexWeightReadAttribute(const MDeformVert *dverts, const int totvert, const int dvert_index) - : ReadAttribute(ATTR_DOMAIN_POINT, CPPType::get<float>(), totvert), - dverts_(dverts), - dvert_index_(dvert_index) + VArray_For_VertexWeights(const MDeformVert *dverts, const int totvert, const int dvert_index) + : VArray<float>(totvert), dverts_(dverts), dvert_index_(dvert_index) { } - void get_internal(const int64_t index, void *r_value) const override + float get_impl(const int64_t index) const override { - VertexWeightWriteAttribute::get_internal(dverts_, dvert_index_, index, r_value); + return VMutableArray_For_VertexWeights::get_internal(dverts_, dvert_index_, index); } }; @@ -904,8 +858,8 @@ class VertexWeightReadAttribute final : public ReadAttribute { */ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { public: - ReadAttributePtr try_get_for_read(const GeometryComponent &component, - const StringRef attribute_name) const final + ReadAttributeLookup try_get_for_read(const GeometryComponent &component, + const StringRef attribute_name) const final { BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); @@ -917,15 +871,17 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { } if (mesh == nullptr || mesh->dvert == nullptr) { static const float default_value = 0.0f; - return std::make_unique<ConstantReadAttribute>( - ATTR_DOMAIN_POINT, mesh->totvert, CPPType::get<float>(), &default_value); + return {std::make_unique<fn::GVArray_For_SingleValueRef>( + CPPType::get<float>(), mesh->totvert, &default_value), + ATTR_DOMAIN_POINT}; } - return std::make_unique<VertexWeightReadAttribute>( - mesh->dvert, mesh->totvert, vertex_group_index); + return {std::make_unique<fn::GVArray_For_EmbeddedVArray<float, VArray_For_VertexWeights>>( + mesh->totvert, mesh->dvert, mesh->totvert, vertex_group_index), + ATTR_DOMAIN_POINT}; } - WriteAttributePtr try_get_for_write(GeometryComponent &component, - const StringRef attribute_name) const final + WriteAttributeLookup try_get_for_write(GeometryComponent &component, + const StringRef attribute_name) const final { BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH); MeshComponent &mesh_component = static_cast<MeshComponent &>(component); @@ -946,8 +902,11 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { mesh->dvert = (MDeformVert *)CustomData_duplicate_referenced_layer( &mesh->vdata, CD_MDEFORMVERT, mesh->totvert); } - return std::make_unique<blender::bke::VertexWeightWriteAttribute>( - mesh->dvert, mesh->totvert, vertex_group_index); + return { + std::make_unique< + fn::GVMutableArray_For_EmbeddedVMutableArray<float, VMutableArray_For_VertexWeights>>( + mesh->totvert, mesh->dvert, mesh->totvert, vertex_group_index), + ATTR_DOMAIN_POINT}; } bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final @@ -1009,7 +968,7 @@ class NormalAttributeProvider final : public BuiltinAttributeProvider { { } - ReadAttributePtr try_get_for_read(const GeometryComponent &component) const final + GVArrayPtr try_get_for_read(const GeometryComponent &component) const final { const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); const Mesh *mesh = mesh_component.get_for_read(); @@ -1022,8 +981,8 @@ class NormalAttributeProvider final : public BuiltinAttributeProvider { CustomData_has_layer(&mesh->pdata, CD_NORMAL)) { const void *data = CustomData_get_layer(&mesh->pdata, CD_NORMAL); - return std::make_unique<ArrayReadAttribute<float3>>( - ATTR_DOMAIN_FACE, Span<float3>((const float3 *)data, mesh->totpoly)); + return std::make_unique<fn::GVArray_For_Span<float3>>( + Span<float3>((const float3 *)data, mesh->totpoly)); } Array<float3> normals(mesh->totpoly); @@ -1032,10 +991,10 @@ class NormalAttributeProvider final : public BuiltinAttributeProvider { BKE_mesh_calc_poly_normal(poly, &mesh->mloop[poly->loopstart], mesh->mvert, normals[i]); } - return std::make_unique<OwnedArrayReadAttribute<float3>>(ATTR_DOMAIN_FACE, std::move(normals)); + return std::make_unique<fn::GVArray_For_ArrayContainer<Array<float3>>>(std::move(normals)); } - WriteAttributePtr try_get_for_write(GeometryComponent &UNUSED(component)) const final + GVMutableArrayPtr try_get_for_write(GeometryComponent &UNUSED(component)) const final { return {}; } @@ -1045,7 +1004,8 @@ class NormalAttributeProvider final : public BuiltinAttributeProvider { return false; } - bool try_create(GeometryComponent &UNUSED(component)) const final + bool try_create(GeometryComponent &UNUSED(component), + const AttributeInit &UNUSED(initializer)) const final { return false; } @@ -1105,12 +1065,8 @@ static ComponentAttributeProviders create_attribute_providers_for_mesh() BuiltinAttributeProvider::Writable, BuiltinAttributeProvider::NonDeletable, point_access, - make_derived_read_attribute<MVert, float3, get_vertex_position, ATTR_DOMAIN_POINT>, - make_derived_write_attribute<MVert, - float3, - get_vertex_position, - set_vertex_position, - ATTR_DOMAIN_POINT>, + make_derived_read_attribute<MVert, float3, get_vertex_position>, + make_derived_write_attribute<MVert, float3, get_vertex_position, set_vertex_position>, tag_normals_dirty_when_writing_position); static NormalAttributeProvider normal; @@ -1124,12 +1080,8 @@ static ComponentAttributeProviders create_attribute_providers_for_mesh() BuiltinAttributeProvider::Writable, BuiltinAttributeProvider::NonDeletable, face_access, - make_derived_read_attribute<MPoly, int, get_material_index, ATTR_DOMAIN_FACE>, - make_derived_write_attribute<MPoly, - int, - get_material_index, - set_material_index, - ATTR_DOMAIN_FACE>, + make_derived_read_attribute<MPoly, int, get_material_index>, + make_derived_write_attribute<MPoly, int, get_material_index, set_material_index>, nullptr); static BuiltinCustomDataLayerProvider shade_smooth( @@ -1141,12 +1093,8 @@ static ComponentAttributeProviders create_attribute_providers_for_mesh() BuiltinAttributeProvider::Writable, BuiltinAttributeProvider::NonDeletable, face_access, - make_derived_read_attribute<MPoly, bool, get_shade_smooth, ATTR_DOMAIN_FACE>, - make_derived_write_attribute<MPoly, - bool, - get_shade_smooth, - set_shade_smooth, - ATTR_DOMAIN_FACE>, + make_derived_read_attribute<MPoly, bool, get_shade_smooth>, + make_derived_write_attribute<MPoly, bool, get_shade_smooth, set_shade_smooth>, nullptr); static BuiltinCustomDataLayerProvider crease( @@ -1158,8 +1106,8 @@ static ComponentAttributeProviders create_attribute_providers_for_mesh() BuiltinAttributeProvider::Writable, BuiltinAttributeProvider::NonDeletable, edge_access, - make_derived_read_attribute<MEdge, float, get_crease, ATTR_DOMAIN_EDGE>, - make_derived_write_attribute<MEdge, float, get_crease, set_crease, ATTR_DOMAIN_EDGE>, + make_derived_read_attribute<MEdge, float, get_crease>, + make_derived_write_attribute<MEdge, float, get_crease, set_crease>, nullptr); static NamedLegacyCustomDataProvider uvs( @@ -1167,20 +1115,16 @@ static ComponentAttributeProviders create_attribute_providers_for_mesh() CD_PROP_FLOAT2, CD_MLOOPUV, corner_access, - make_derived_read_attribute<MLoopUV, float2, get_loop_uv, ATTR_DOMAIN_CORNER>, - make_derived_write_attribute<MLoopUV, float2, get_loop_uv, set_loop_uv, ATTR_DOMAIN_CORNER>); + make_derived_read_attribute<MLoopUV, float2, get_loop_uv>, + make_derived_write_attribute<MLoopUV, float2, get_loop_uv, set_loop_uv>); static NamedLegacyCustomDataProvider vertex_colors( ATTR_DOMAIN_CORNER, CD_PROP_COLOR, CD_MLOOPCOL, corner_access, - make_derived_read_attribute<MLoopCol, Color4f, get_loop_color, ATTR_DOMAIN_CORNER>, - make_derived_write_attribute<MLoopCol, - Color4f, - get_loop_color, - set_loop_color, - ATTR_DOMAIN_CORNER>); + 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_component_pointcloud.cc b/source/blender/blenkernel/intern/geometry_component_pointcloud.cc index 135de14b4f7..6c4af7a6d23 100644 --- a/source/blender/blenkernel/intern/geometry_component_pointcloud.cc +++ b/source/blender/blenkernel/intern/geometry_component_pointcloud.cc @@ -140,16 +140,17 @@ int PointCloudComponent::attribute_domain_size(const AttributeDomain domain) con namespace blender::bke { -template<typename T, AttributeDomain Domain> -static ReadAttributePtr make_array_read_attribute(const void *data, const int domain_size) +template<typename T> +static GVArrayPtr make_array_read_attribute(const void *data, const int domain_size) { - return std::make_unique<ArrayReadAttribute<T>>(Domain, Span<T>((const T *)data, domain_size)); + return std::make_unique<fn::GVArray_For_Span<T>>(Span<T>((const T *)data, domain_size)); } -template<typename T, AttributeDomain Domain> -static WriteAttributePtr make_array_write_attribute(void *data, const int domain_size) +template<typename T> +static GVMutableArrayPtr make_array_write_attribute(void *data, const int domain_size) { - return std::make_unique<ArrayWriteAttribute<T>>(Domain, MutableSpan<T>((T *)data, domain_size)); + return std::make_unique<fn::GVMutableArray_For_MutableSpan<T>>( + MutableSpan<T>((T *)data, domain_size)); } /** @@ -179,30 +180,28 @@ static ComponentAttributeProviders create_attribute_providers_for_point_cloud() }, update_custom_data_pointers}; - static BuiltinCustomDataLayerProvider position( - "position", - ATTR_DOMAIN_POINT, - CD_PROP_FLOAT3, - CD_PROP_FLOAT3, - BuiltinAttributeProvider::NonCreatable, - BuiltinAttributeProvider::Writable, - BuiltinAttributeProvider::NonDeletable, - point_access, - make_array_read_attribute<float3, ATTR_DOMAIN_POINT>, - make_array_write_attribute<float3, ATTR_DOMAIN_POINT>, - nullptr); - static BuiltinCustomDataLayerProvider radius( - "radius", - ATTR_DOMAIN_POINT, - CD_PROP_FLOAT, - CD_PROP_FLOAT, - BuiltinAttributeProvider::Creatable, - BuiltinAttributeProvider::Writable, - BuiltinAttributeProvider::Deletable, - point_access, - make_array_read_attribute<float, ATTR_DOMAIN_POINT>, - make_array_write_attribute<float, ATTR_DOMAIN_POINT>, - nullptr); + static BuiltinCustomDataLayerProvider position("position", + ATTR_DOMAIN_POINT, + CD_PROP_FLOAT3, + CD_PROP_FLOAT3, + BuiltinAttributeProvider::NonCreatable, + BuiltinAttributeProvider::Writable, + BuiltinAttributeProvider::NonDeletable, + point_access, + make_array_read_attribute<float3>, + make_array_write_attribute<float3>, + nullptr); + static BuiltinCustomDataLayerProvider radius("radius", + ATTR_DOMAIN_POINT, + CD_PROP_FLOAT, + CD_PROP_FLOAT, + BuiltinAttributeProvider::Creatable, + BuiltinAttributeProvider::Writable, + BuiltinAttributeProvider::Deletable, + point_access, + make_array_read_attribute<float>, + make_array_write_attribute<float>, + nullptr); static CustomDataAttributeProvider point_custom_data(ATTR_DOMAIN_POINT, point_access); return ComponentAttributeProviders({&position, &radius}, {&point_custom_data}); } 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 07d0e520c93..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; @@ -449,13 +501,15 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups, const CPPType *cpp_type = bke::custom_data_type_to_cpp_type(data_type_output); BLI_assert(cpp_type != nullptr); - result.attribute_try_create(entry.key, domain_output, data_type_output); - WriteAttributePtr write_attribute = result.attribute_try_get_for_write(name); - if (!write_attribute || &write_attribute->cpp_type() != cpp_type || - write_attribute->domain() != domain_output) { + result.attribute_try_create( + entry.key, domain_output, data_type_output, AttributeInitDefault()); + WriteAttributeLookup write_attribute = result.attribute_try_get_for_write(name); + if (!write_attribute || &write_attribute.varray->type() != cpp_type || + write_attribute.domain != domain_output) { continue; } - fn::GMutableSpan dst_span = write_attribute->get_span_for_write_only(); + + fn::GVMutableArray_GSpan dst_span{*write_attribute.varray}; int offset = 0; for (const GeometryInstanceGroup &set_group : set_groups) { @@ -467,11 +521,11 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups, if (domain_size == 0) { continue; /* Domain size is 0, so no need to increment the offset. */ } - ReadAttributePtr source_attribute = component.attribute_try_get_for_read( + GVArrayPtr source_attribute = component.attribute_try_get_for_read( name, domain_output, data_type_output); if (source_attribute) { - fn::GSpan src_span = source_attribute->get_span(); + fn::GVArray_GSpan src_span{*source_attribute}; const void *src_buffer = src_span.data(); for (const int UNUSED(i) : set_group.transforms.index_range()) { void *dst_buffer = dst_span[offset]; @@ -486,8 +540,41 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups, } } - write_attribute->apply_span(); + dst_span.save(); + } +} + +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, @@ -556,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()) { @@ -587,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 182d06f8f72..6d1476485ca 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 */ @@ -2616,6 +2624,11 @@ static bool gpencil_is_layer_mask(ViewLayer *view_layer, bGPdata *gpd, bGPDlayer continue; } + /* Skip if masks are disabled for this view layer. */ + if (gpl->flag & GP_LAYER_DISABLE_MASKS_IN_VIEWLAYER) { + continue; + } + LISTBASE_FOREACH (bGPDlayer_Mask *, mask, &gpl->mask_layers) { if (STREQ(gpl_mask->info, mask->name)) { return true; @@ -2675,7 +2688,7 @@ void BKE_gpencil_visible_stroke_iter(ViewLayer *view_layer, * This is used only in final render and never in Viewport. */ if ((view_layer != NULL) && (gpl->viewlayername[0] != '\0') && (!STREQ(view_layer->name, gpl->viewlayername))) { - /* Do not skip masks when rendering the viewlayer so that it can still be used to clip + /* Do not skip masks when rendering the view-layer so that it can still be used to clip * other layers. Instead set their opacity to zero. */ if (gpencil_is_layer_mask(view_layer, gpd, gpl)) { gpl->opacity = 0.0f; 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 fb6500cfe1b..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; + 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]; - int i = 1; - while (len1 < threshold && gps->totpoints > i) { - next_pt = &pt[i]; - len1 = len_v3v3(&next_pt->x, &pt->x); - i++; + interp_v3_v3v3(result1, &next_pt->x, &pt->x, extend1); + copy_v3_v3(&pt->x, result1); } - i = 2; - while (len2 < threshold && gps->totpoints >= i) { - second_last = &pt[gps->totpoints - i]; - len2 = len_v3v3(&last_pt->x, &second_last->x); - i++; - } - - 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,7 +1064,7 @@ void BKE_gpencil_stroke_2d_flat(const bGPDspoint *points, normalize_v3(locx); normalize_v3(locy); - /* Calculcate last point first. */ + /* Calculate last point first. */ const bGPDspoint *pt_last = &points[totpoints - 1]; float tmp[3]; sub_v3_v3v3(tmp, &pt_last->x, &pt0->x); @@ -2439,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); @@ -2492,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/key.c b/source/blender/blenkernel/intern/key.c index f2893e162cb..073276b7011 100644 --- a/source/blender/blenkernel/intern/key.c +++ b/source/blender/blenkernel/intern/key.c @@ -105,6 +105,11 @@ static void shapekey_foreach_id(ID *id, LibraryForeachIDData *data) BKE_LIB_FOREACHID_PROCESS_ID(data, key->from, IDWALK_CB_LOOPBACK); } +static ID *shapekey_owner_get(Main *UNUSED(bmain), ID *id) +{ + return ((Key *)id)->from; +} + static void shapekey_blend_write(BlendWriter *writer, ID *id, const void *id_address) { Key *key = (Key *)id; @@ -216,7 +221,9 @@ IDTypeInfo IDType_ID_KE = { .make_local = NULL, .foreach_id = shapekey_foreach_id, .foreach_cache = NULL, - .owner_get = NULL, /* Could have one actually? */ + /* A bit weird, due to shapekeys not being strictly speaking embedded data... But they also + * share a lot with those (non linkable, only ever used by one owner ID, etc.). */ + .owner_get = shapekey_owner_get, .blend_write = shapekey_blend_write, .blend_read_data = shapekey_blend_read_data, diff --git a/source/blender/blenkernel/intern/lib_id.c b/source/blender/blenkernel/intern/lib_id.c index 7429fe050dc..f93bf494ee9 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; } @@ -1215,14 +1221,6 @@ void BKE_libblock_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int ori BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) != 0 || bmain != NULL); BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) != 0 || (flag & LIB_ID_CREATE_NO_ALLOCATE) == 0); BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) != 0 || (flag & LIB_ID_CREATE_LOCAL) == 0); - if (!is_private_id_data) { - /* When we are handling private ID data, we might still want to manage usercounts, even - * though that ID data-block is actually outside of Main... */ - BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) == 0 || - (flag & LIB_ID_CREATE_NO_USER_REFCOUNT) != 0); - } - /* Never implicitly copy shapekeys when generating temp data outside of Main database. */ - BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) == 0 || (flag & LIB_ID_COPY_SHAPEKEY) == 0); /* 'Private ID' data handling. */ if ((bmain != NULL) && is_private_id_data) { @@ -1355,12 +1353,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; @@ -1368,7 +1366,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; @@ -1383,16 +1381,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 @@ -1404,7 +1419,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; } @@ -1412,12 +1427,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); @@ -1685,12 +1706,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 @@ -1731,7 +1754,7 @@ bool BKE_id_new_name_validate(ListBase *lb, ID *id, const char *tname) } /* next to indirect usage in read/writefile also in editobject.c scene.c */ -void BKE_main_id_clear_newpoins(Main *bmain) +void BKE_main_id_newptr_and_tag_clear(Main *bmain) { ID *id; @@ -2145,7 +2168,7 @@ void BKE_library_make_local(Main *bmain, TIMEIT_VALUE_PRINT(make_local); #endif - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); BLI_memarena_free(linklist_mem); #ifdef DEBUG_TIME 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 54d14e33209..0066aab03c2 100644 --- a/source/blender/blenkernel/intern/lib_override.c +++ b/source/blender/blenkernel/intern/lib_override.c @@ -48,6 +48,7 @@ #include "BKE_lib_query.h" #include "BKE_lib_remap.h" #include "BKE_main.h" +#include "BKE_node.h" #include "BKE_report.h" #include "BKE_scene.h" @@ -80,6 +81,19 @@ static void lib_override_library_property_clear(IDOverrideLibraryProperty *op); static void lib_override_library_property_operation_clear( IDOverrideLibraryPropertyOperation *opop); +/** Get override data for a given ID. Needed because of our beloved shape keys snowflake. */ +BLI_INLINE IDOverrideLibrary *lib_override_get(Main *bmain, ID *id) +{ + if (id->flag & LIB_EMBEDDED_DATA_LIB_OVERRIDE) { + const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id); + if (id_type->owner_get != NULL) { + return id_type->owner_get(bmain, id)->override_library; + } + BLI_assert(!"IDTypeInfo of liboverride-embedded ID with no owner getter"); + } + return id->override_library; +} + /** Initialize empty overriding of \a reference_id by \a local_id. */ IDOverrideLibrary *BKE_lib_override_library_init(ID *local_id, ID *reference_id) { @@ -194,12 +208,17 @@ void BKE_lib_override_library_free(struct IDOverrideLibrary **override, const bo *override = NULL; } -static ID *lib_override_library_create_from(Main *bmain, ID *reference_id) +static ID *lib_override_library_create_from(Main *bmain, + ID *reference_id, + const int lib_id_copy_flags) { /* Note: We do not want to copy possible override data from reference here (whether it is an * override template, or already an override of some other ref data). */ - ID *local_id = BKE_id_copy_ex( - bmain, reference_id, NULL, LIB_ID_COPY_DEFAULT | LIB_ID_COPY_NO_LIB_OVERRIDE); + ID *local_id = BKE_id_copy_ex(bmain, + reference_id, + NULL, + LIB_ID_COPY_DEFAULT | LIB_ID_COPY_NO_LIB_OVERRIDE | + lib_id_copy_flags); if (local_id == NULL) { return NULL; @@ -257,7 +276,7 @@ ID *BKE_lib_override_library_create_from_id(Main *bmain, BLI_assert(reference_id != NULL); BLI_assert(reference_id->lib != NULL); - ID *local_id = lib_override_library_create_from(bmain, reference_id); + ID *local_id = lib_override_library_create_from(bmain, reference_id, 0); if (do_tagged_remap) { Key *reference_key, *local_key = NULL; @@ -302,9 +321,17 @@ ID *BKE_lib_override_library_create_from_id(Main *bmain, * main. You can add more local IDs to be remapped to use new overriding ones by setting their * LIB_TAG_DOIT tag. * + * \param reference_library the library from which the linked data being overridden come from + * (i.e. the library of the linked reference ID). + * + * \param do_no_main Create the new override data outside of Main database. Used for resyncing of + * linked overrides. + * * \return \a true on success, \a false otherwise. */ -bool BKE_lib_override_library_create_from_tag(Main *bmain) +bool BKE_lib_override_library_create_from_tag(Main *bmain, + const Library *reference_library, + const bool do_no_main) { ID *reference_id; bool success = true; @@ -314,7 +341,7 @@ bool BKE_lib_override_library_create_from_tag(Main *bmain) /* Get all IDs we want to override. */ FOREACH_MAIN_ID_BEGIN (bmain, reference_id) { - if ((reference_id->tag & LIB_TAG_DOIT) != 0 && reference_id->lib != NULL && + if ((reference_id->tag & LIB_TAG_DOIT) != 0 && reference_id->lib == reference_library && BKE_idtype_idcode_is_linkable(GS(reference_id->name))) { todo_id_iter = MEM_callocN(sizeof(*todo_id_iter), __func__); todo_id_iter->data = reference_id; @@ -326,10 +353,16 @@ bool BKE_lib_override_library_create_from_tag(Main *bmain) /* Override the IDs. */ for (todo_id_iter = todo_ids.first; todo_id_iter != NULL; todo_id_iter = todo_id_iter->next) { reference_id = todo_id_iter->data; + + /* If `newid` is already set, assume it has been handled by calling code. + * Only current use case: re-using proxy ID when converting to liboverride. */ if (reference_id->newid == NULL) { - /* If `newid` is already set, assume it has been handled by calling code. - * Only current use case: re-using proxy ID when converting to liboverride. */ - if ((reference_id->newid = lib_override_library_create_from(bmain, reference_id)) == NULL) { + /* Note: `no main` case is used during resync procedure, to support recursive resync. + * This requires extra care further down the resync process, + * see: #BKE_lib_override_library_resync. */ + reference_id->newid = lib_override_library_create_from( + bmain, reference_id, do_no_main ? LIB_ID_CREATE_NO_MAIN : 0); + if (reference_id->newid == NULL) { success = false; break; } @@ -369,23 +402,42 @@ bool BKE_lib_override_library_create_from_tag(Main *bmain) /* Still checking the whole Main, that way we can tag other local IDs as needing to be * remapped to use newly created overriding IDs, if needed. */ - FOREACH_MAIN_ID_BEGIN (bmain, other_id) { - if ((other_id->tag & LIB_TAG_DOIT) != 0 && other_id->lib == NULL) { - /* Note that using ID_REMAP_SKIP_INDIRECT_USAGE below is superfluous, as we only remap - * local IDs usages anyway. */ + ID *id; + FOREACH_MAIN_ID_BEGIN (bmain, id) { + /* In case we created new overrides as 'no main', they are not accessible directly in this + * loop, but we can get to them through their reference's `newid` pointer. */ + if (do_no_main && id->lib == reference_id->lib && id->newid != NULL) { + other_id = id->newid; + /* Otherwise we cannot properly distinguish between IDs that are actually from the + * linked library (and should not be remapped), and IDs that are overrides re-generated + * from the reference from the linked library, and must therefore be remapped. + * + * This is reset afterwards at the end of this loop. */ + other_id->lib = NULL; + } + else { + other_id = id; + } + + /* If other ID is a linked one, but not from the same library as our reference, then we + * consider we should also remap it, as part of recursive resync. */ + if ((other_id->tag & LIB_TAG_DOIT) != 0 && other_id->lib != reference_id->lib) { BKE_libblock_relink_ex(bmain, other_id, reference_id, local_id, - ID_REMAP_SKIP_INDIRECT_USAGE | ID_REMAP_SKIP_OVERRIDE_LIBRARY); + ID_REMAP_SKIP_OVERRIDE_LIBRARY | ID_REMAP_FORCE_USER_REFCOUNT); if (reference_key != NULL) { BKE_libblock_relink_ex(bmain, other_id, &reference_key->id, &local_key->id, - ID_REMAP_SKIP_INDIRECT_USAGE | ID_REMAP_SKIP_OVERRIDE_LIBRARY); + ID_REMAP_SKIP_OVERRIDE_LIBRARY | ID_REMAP_FORCE_USER_REFCOUNT); } } + if (other_id != id) { + other_id->lib = reference_id->lib; + } } FOREACH_MAIN_ID_END; } @@ -585,31 +637,22 @@ static void lib_override_local_group_tag_recursive(LibOverrideGroupTagData *data if (ELEM(to_id, NULL, id_owner)) { continue; } - if (!ID_IS_OVERRIDE_LIBRARY(to_id) || ID_IS_LINKED(to_id)) { + if (!ID_IS_OVERRIDE_LIBRARY(to_id) || (to_id->lib != id_owner->lib)) { continue; } - /* Do not tag 'virtual' overrides (shape keys here, as we already rejected embedded case - * above). */ - if (ID_IS_OVERRIDE_LIBRARY_REAL(to_id)) { - Library *reference_lib = NULL; - if (GS(id_owner->name) == ID_KE) { - reference_lib = ((Key *)id_owner)->from->override_library->reference->lib; - } - else { - reference_lib = id_owner->override_library->reference->lib; - } - if (to_id->override_library->reference->lib != reference_lib) { - /* We do not override data-blocks from other libraries, nor do we process them. */ - continue; - } + Library *reference_lib = lib_override_get(bmain, id_owner)->reference->lib; + ID *to_id_reference = lib_override_get(bmain, to_id)->reference; + if (to_id_reference->lib != reference_lib) { + /* We do not override data-blocks from other libraries, nor do we process them. */ + continue; + } - if (to_id->override_library->reference->tag & LIB_TAG_MISSING) { - to_id->tag |= missing_tag; - } - else { - to_id->tag |= tag; - } + if (to_id_reference->tag & LIB_TAG_MISSING) { + to_id->tag |= missing_tag; + } + else { + to_id->tag |= tag; } /* Recursively process the dependencies. */ @@ -623,7 +666,7 @@ static void lib_override_local_group_tag_recursive(LibOverrideGroupTagData *data static void lib_override_local_group_tag(LibOverrideGroupTagData *data) { ID *id_root = data->id_root; - BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root) && !ID_IS_LINKED(id_root)); + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root)); if ((id_root->override_library->reference->tag & LIB_TAG_MISSING)) { id_root->tag |= data->missing_tag; @@ -648,7 +691,7 @@ static bool lib_override_library_create_do(Main *bmain, ID *id_root) BKE_main_relations_free(bmain); - return BKE_lib_override_library_create_from_tag(bmain); + return BKE_lib_override_library_create_from_tag(bmain, id_root->lib, false); } static void lib_override_library_create_post_process(Main *bmain, @@ -659,6 +702,9 @@ static void lib_override_library_create_post_process(Main *bmain, Collection *residual_storage, const bool is_resync) { + /* NOTE: We only care about local IDs here, if a linked object is not instantiated in any way we + * do not do anything about it. */ + BKE_main_collection_sync(bmain); /* We create a set of all objects referenced into the scene by its hierarchy of collections. @@ -668,7 +714,7 @@ static void lib_override_library_create_post_process(Main *bmain, /* Instantiating the root collection or object should never be needed in resync case, since the * old override would be remapped to the new one. */ - if (!is_resync && id_root != NULL && id_root->newid != NULL) { + if (!is_resync && id_root != NULL && id_root->newid != NULL && !ID_IS_LINKED(id_root->newid)) { switch (GS(id_root->name)) { case ID_GR: { Object *ob_reference = id_reference != NULL && GS(id_reference->name) == ID_OB ? @@ -713,56 +759,58 @@ static void lib_override_library_create_post_process(Main *bmain, Collection *default_instantiating_collection = residual_storage; LISTBASE_FOREACH (Object *, ob, &bmain->objects) { Object *ob_new = (Object *)ob->id.newid; - if (ob_new != NULL) { - BLI_assert(ob_new->id.override_library != NULL && - ob_new->id.override_library->reference == &ob->id); - - if (BLI_gset_lookup(all_objects_in_scene, ob_new) == NULL) { - if (id_root != NULL && default_instantiating_collection == NULL) { - 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 htere 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. */ - default_instantiating_collection->flag |= COLLECTION_RESTRICT_VIEWPORT | - COLLECTION_RESTRICT_RENDER; - break; + if (ob_new == NULL || ob_new->id.lib != NULL) { + continue; + } + + BLI_assert(ob_new->id.override_library != NULL && + ob_new->id.override_library->reference == &ob->id); + + if (BLI_gset_lookup(all_objects_in_scene, ob_new) == NULL) { + if (id_root != NULL && default_instantiating_collection == NULL) { + 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; } - 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_ref = (Object *)id_ref; - LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { - 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; - } + default_instantiating_collection = BKE_collection_add( + bmain, (Collection *)id_root, "OVERRIDE_HIDDEN"); + /* Hide the collection from viewport and render. */ + default_instantiating_collection->flag |= COLLECTION_RESTRICT_VIEWPORT | + COLLECTION_RESTRICT_RENDER; + break; + } + 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_ref = (Object *)id_ref; + LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { + 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; } - break; } - default: - BLI_assert(0); + break; } + default: + BLI_assert(0); } - if (default_instantiating_collection == NULL) { - default_instantiating_collection = scene->master_collection; - } - - BKE_collection_object_add(bmain, default_instantiating_collection, ob_new); - DEG_id_tag_update_ex(bmain, &ob_new->id, ID_RECALC_TRANSFORM | ID_RECALC_BASE_FLAGS); } + if (default_instantiating_collection == NULL) { + default_instantiating_collection = scene->master_collection; + } + + BKE_collection_object_add(bmain, default_instantiating_collection, ob_new); + DEG_id_tag_update_ex(bmain, &ob_new->id, ID_RECALC_TRANSFORM | ID_RECALC_BASE_FLAGS); } } @@ -797,7 +845,7 @@ bool BKE_lib_override_library_create( bmain, scene, view_layer, id_root, id_reference, NULL, false); /* Cleanup. */ - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); /* We need to rebuild some of the deleted override rules (for UI feedback purpose). */ @@ -878,10 +926,10 @@ 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)); ID *id_root_reference = id_root->override_library->reference; @@ -915,12 +963,35 @@ bool BKE_lib_override_library_resync(Main *bmain, id->tag |= LIB_TAG_MISSING; } - if (id->tag & LIB_TAG_DOIT && !ID_IS_LINKED(id) && ID_IS_OVERRIDE_LIBRARY_REAL(id)) { + if (id->tag & LIB_TAG_DOIT && (id->lib == id_root->lib) && ID_IS_OVERRIDE_LIBRARY(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 * same linked ID in a same hierarchy. */ - if (!BLI_ghash_haskey(linkedref_to_old_override, id->override_library->reference)) { - BLI_ghash_insert(linkedref_to_old_override, id->override_library->reference, id); + IDOverrideLibrary *id_override_library = lib_override_get(bmain, id); + ID *reference_id = id_override_library->reference; + if (GS(reference_id->name) != GS(id->name)) { + switch (GS(id->name)) { + case ID_KE: + reference_id = (ID *)BKE_key_from_id(reference_id); + break; + case ID_GR: + BLI_assert(GS(reference_id->name) == ID_SCE); + reference_id = (ID *)((Scene *)reference_id)->master_collection; + break; + case ID_NT: + reference_id = (ID *)ntreeFromID(id); + break; + default: + break; + } + } + BLI_assert(GS(reference_id->name) == GS(id->name)); + + if (!BLI_ghash_haskey(linkedref_to_old_override, reference_id)) { + BLI_ghash_insert(linkedref_to_old_override, reference_id, id); + if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) { + continue; + } if ((id->override_library->reference->tag & LIB_TAG_DOIT) == 0) { /* We have an override, but now it does not seem to be necessary to override that ID * anymore. Check if there are some actual overrides from the user, otherwise assume @@ -959,7 +1030,8 @@ bool BKE_lib_override_library_resync(Main *bmain, /* Note that this call also remaps all pointers of tagged IDs from old override IDs to new * override IDs (including within the old overrides themselves, since those are tagged too * above). */ - const bool success = BKE_lib_override_library_create_from_tag(bmain); + const bool success = BKE_lib_override_library_create_from_tag( + bmain, id_root_reference->lib, true); if (!success) { return success; @@ -968,55 +1040,100 @@ bool BKE_lib_override_library_resync(Main *bmain, ListBase *lb; FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb) { FOREACH_MAIN_LISTBASE_ID_BEGIN (lb, id) { - if (id->tag & LIB_TAG_DOIT && id->newid != NULL && ID_IS_LINKED(id)) { + if (id->tag & LIB_TAG_DOIT && id->newid != NULL && id->lib == id_root_reference->lib) { ID *id_override_new = id->newid; ID *id_override_old = BLI_ghash_lookup(linkedref_to_old_override, id); BLI_assert((id_override_new->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) == 0); + /* We need to 'move back' newly created override into its proper library (since it was + * duplicated from the reference ID with 'no main' option, it should currently be the same + * as the reference ID one). */ + BLI_assert(/*id_override_new->lib == NULL || */ id_override_new->lib == id->lib); + BLI_assert(id_override_old == NULL || id_override_old->lib == id_root->lib); + id_override_new->lib = id_root->lib; + if (id_override_old != NULL) { /* Swap the names between old override ID and new one. */ char id_name_buf[MAX_ID_NAME]; memcpy(id_name_buf, id_override_old->name, sizeof(id_name_buf)); memcpy(id_override_old->name, id_override_new->name, sizeof(id_override_old->name)); memcpy(id_override_new->name, id_name_buf, sizeof(id_override_new->name)); - /* Note that this is a very efficient way to keep BMain IDs ordered as expected after - * swapping their names. - * However, one has to be very careful with this when iterating over the listbase at the - * same time. Here it works because we only execute this code when we are in the linked - * IDs, which are always *after* all local ones, and we only affect local IDs. */ - BLI_listbase_swaplinks(lb, id_override_old, id_override_new); - - /* Remap the whole local IDs to use the new override. */ - BKE_libblock_remap( - bmain, id_override_old, id_override_new, ID_REMAP_SKIP_INDIRECT_USAGE); - - /* Copy over overrides rules from old override ID to new one. */ - BLI_duplicatelist(&id_override_new->override_library->properties, - &id_override_old->override_library->properties); - for (IDOverrideLibraryProperty * - op_new = id_override_new->override_library->properties.first, - *op_old = id_override_old->override_library->properties.first; - op_new; - op_new = op_new->next, op_old = op_old->next) { - lib_override_library_property_copy(op_new, op_old); + + BLI_insertlinkreplace(lb, id_override_old, id_override_new); + id_override_old->tag |= LIB_TAG_NO_MAIN; + id_override_new->tag &= ~LIB_TAG_NO_MAIN; + + if (ID_IS_OVERRIDE_LIBRARY_REAL(id_override_new)) { + BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_override_old)); + + /* Copy over overrides rules from old override ID to new one. */ + BLI_duplicatelist(&id_override_new->override_library->properties, + &id_override_old->override_library->properties); + IDOverrideLibraryProperty *op_new = + id_override_new->override_library->properties.first; + IDOverrideLibraryProperty *op_old = + id_override_old->override_library->properties.first; + for (; op_new; op_new = op_new->next, op_old = op_old->next) { + lib_override_library_property_copy(op_new, op_old); + } } } + else { + /* Add to proper main list, ensure unique name for local ID, sort, and clear relevant + * tags. */ + BKE_libblock_management_main_add(bmain, id_override_new); + } } } FOREACH_MAIN_LISTBASE_ID_END; } FOREACH_MAIN_LISTBASE_END; + /* We need to remap old to new override usages in a separate loop, after all new overrides have + * been added to Main. */ + FOREACH_MAIN_ID_BEGIN (bmain, id) { + if (id->tag & LIB_TAG_DOIT && id->newid != NULL && id->lib == id_root_reference->lib) { + ID *id_override_new = id->newid; + ID *id_override_old = BLI_ghash_lookup(linkedref_to_old_override, id); + + if (id_override_old != NULL) { + /* Remap all IDs to use the new override. */ + BKE_libblock_remap(bmain, id_override_old, id_override_new, 0); + /* Remap no-main override IDs we just created too. */ + GHashIterator linkedref_to_old_override_iter; + GHASH_ITER (linkedref_to_old_override_iter, linkedref_to_old_override) { + ID *id_override_old_iter = BLI_ghashIterator_getValue(&linkedref_to_old_override_iter); + if (id_override_old_iter->tag & LIB_TAG_NO_MAIN) { + BKE_libblock_relink_ex(bmain, + id_override_old_iter, + id_override_old, + id_override_new, + ID_REMAP_FORCE_USER_REFCOUNT | ID_REMAP_FORCE_NEVER_NULL_USAGE); + } + } + } + } + } + FOREACH_MAIN_ID_END; + + BKE_main_collection_sync(bmain); + /* We need to apply override rules in a separate loop, after all ID pointers have been properly * remapped, and all new local override IDs have gotten their proper original names, otherwise * override operations based on those ID names would fail. */ FOREACH_MAIN_ID_BEGIN (bmain, id) { - if (id->tag & LIB_TAG_DOIT && id->newid != NULL && ID_IS_LINKED(id)) { + if (id->tag & LIB_TAG_DOIT && id->newid != NULL && id->lib == id_root_reference->lib) { ID *id_override_new = id->newid; + if (!ID_IS_OVERRIDE_LIBRARY_REAL(id_override_new)) { + continue; + } ID *id_override_old = BLI_ghash_lookup(linkedref_to_old_override, id); - if (id_override_old != NULL) { + if (id_override_old == NULL) { + continue; + } + if (ID_IS_OVERRIDE_LIBRARY_REAL(id_override_old)) { /* Apply rules on new override ID using old one as 'source' data. */ /* Note that since we already remapped ID pointers in old override IDs to new ones, we * can also apply ID pointer override rules safely here. */ @@ -1050,29 +1167,44 @@ bool BKE_lib_override_library_resync(Main *bmain, RNA_OVERRIDE_APPLY_FLAG_IGNORE_ID_POINTERS : RNA_OVERRIDE_APPLY_FLAG_NOP); } + + /* Once overrides have been properly 'transferred' from old to new ID, we can clear ID usages + * of the old one. + * This is necessary in case said old ID is not in Main anymore. */ + BKE_libblock_relink_ex(bmain, + id_override_old, + NULL, + NULL, + ID_REMAP_FORCE_USER_REFCOUNT | ID_REMAP_FORCE_NEVER_NULL_USAGE); + id_override_old->tag |= LIB_TAG_NO_USER_REFCOUNT; } } FOREACH_MAIN_ID_END; /* 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. */ + * 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), - * so we will only ever tag an old override ID after we have already checked it in this loop, - * hence we cannot untag it later. */ - if (id->newid != NULL && ID_IS_LINKED(id)) { + /* Note that this works because linked IDs are always after local ones (including + * overrides), so we will only ever tag an old override ID after we have already checked it + * in this loop, hence we cannot untag it later. */ + if (id->newid != NULL && id->lib == id_root_reference->lib) { ID *id_override_old = BLI_ghash_lookup(linkedref_to_old_override, id); if (id_override_old != NULL) { id->newid->tag &= ~LIB_TAG_DOIT; id_override_old->tag |= LIB_TAG_DOIT; + if (id_override_old->tag & LIB_TAG_NO_MAIN) { + BKE_id_free(bmain, id_override_old); + } } } id->tag &= ~LIB_TAG_DOIT; } - /* Also deal with old overrides that went missing in new linked data. */ + /* Also deal with old overrides that went missing in new linked data - only for real local + * overrides for now, not those who are linked. */ else if (id->tag & LIB_TAG_MISSING && !ID_IS_LINKED(id)) { BLI_assert(ID_IS_OVERRIDE_LIBRARY(id)); if (!BKE_lib_override_library_is_user_edited(id)) { @@ -1081,6 +1213,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); @@ -1088,9 +1221,24 @@ 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; + + /* Cleanup, many pointers in this GHash are already invalid now. */ + BLI_ghash_free(linkedref_to_old_override, NULL, NULL); + BKE_id_multi_tagged_delete(bmain); /* At this point, `id_root` has very likely been deleted, we need to update it to its new @@ -1098,6 +1246,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 @@ -1114,14 +1271,70 @@ bool BKE_lib_override_library_resync(Main *bmain, } /* Cleanup. */ - BLI_ghash_free(linkedref_to_old_override, NULL, NULL); - - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); /* That one should not be needed in fact. */ return success; } +static int lib_override_sort_libraries_func(LibraryIDLinkCallbackData *cb_data) +{ + if (cb_data->cb_flag & IDWALK_CB_LOOPBACK) { + return IDWALK_RET_NOP; + } + ID *id_owner = cb_data->id_owner; + ID *id = *cb_data->id_pointer; + if (id != NULL && ID_IS_LINKED(id) && id->lib != id_owner->lib) { + const int owner_library_indirect_level = id_owner->lib != NULL ? id_owner->lib->temp_index : 0; + if (owner_library_indirect_level > 10000) { + CLOG_ERROR( + &LOG, + "Levels of indirect usages of libraries is way too high, skipping further building " + "loops (Involves at least '%s' and '%s')", + id_owner->lib->filepath, + id->lib->filepath); + BLI_assert(0); + return IDWALK_RET_NOP; + } + + if (owner_library_indirect_level >= id->lib->temp_index) { + id->lib->temp_index = owner_library_indirect_level + 1; + *(bool *)cb_data->user_data = true; + } + } + return IDWALK_RET_NOP; +} + +/** Define the `temp_index` of libraries from their highest level of indirect usage. + * + * E.g. if lib_a uses lib_b, lib_c and lib_d, and lib_b also uses lib_d, then lib_a has an index of + * 1, lib_b and lib_c an index of 2, and lib_d an index of 3. */ +static int lib_override_libraries_index_define(Main *bmain) +{ + LISTBASE_FOREACH (Library *, library, &bmain->libraries) { + /* index 0 is reserved for local data. */ + library->temp_index = 1; + } + bool do_continue = true; + while (do_continue) { + do_continue = false; + ID *id; + FOREACH_MAIN_ID_BEGIN (bmain, id) { + BKE_library_foreach_ID_link( + bmain, id, lib_override_sort_libraries_func, &do_continue, IDWALK_READONLY); + } + FOREACH_MAIN_ID_END; + } + + int library_indirect_level_max = 0; + LISTBASE_FOREACH (Library *, library, &bmain->libraries) { + if (library->temp_index > library_indirect_level_max) { + library_indirect_level_max = library->temp_index; + } + } + return library_indirect_level_max; +} + /** * Detect and handle required resync of overrides data, when relations between reference linked IDs * have changed. @@ -1136,7 +1349,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. */ @@ -1166,7 +1382,7 @@ void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer * * those used by current existing overrides. */ ID *id; FOREACH_MAIN_ID_BEGIN (bmain, id) { - if (!ID_IS_OVERRIDE_LIBRARY_REAL(id) || ID_IS_LINKED(id)) { + if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) { continue; } if (id->tag & (LIB_TAG_DOIT | LIB_TAG_MISSING)) { @@ -1188,12 +1404,12 @@ void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer * /* Now check existing overrides, those needing resync will be the one either already tagged as * such, or the one using linked data that is now tagged as needing override. */ FOREACH_MAIN_ID_BEGIN (bmain, id) { - if (!ID_IS_OVERRIDE_LIBRARY_REAL(id) || ID_IS_LINKED(id)) { + if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) { continue; } if (id->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) { - CLOG_INFO(&LOG, 4, "ID %s was already tagged as needing resync", id->name); + CLOG_INFO(&LOG, 4, "ID %s (%p) was already tagged as needing resync", id->name, id->lib); continue; } @@ -1208,14 +1424,16 @@ void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer * ID *id_to = *entry_item->id_pointer.to; /* Case where this ID pointer was to a linked ID, that now needs to be overridden. */ - if (ID_IS_LINKED(id_to) && (id_to->tag & LIB_TAG_DOIT) != 0) { + if (ID_IS_LINKED(id_to) && (id_to->lib != id->lib) && (id_to->tag & LIB_TAG_DOIT) != 0) { id->tag |= LIB_TAG_LIB_OVERRIDE_NEED_RESYNC; CLOG_INFO(&LOG, 3, - "ID %s now tagged as needing resync because they use linked %s that now needs " - "to be overridden", + "ID %s (%p) now tagged as needing resync because they use linked %s (%p) that " + "now needs to be overridden", id->name, - id_to->name); + id->lib, + id_to->name, + id_to->lib); break; } } @@ -1225,6 +1443,8 @@ void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer * BKE_main_relations_free(bmain); BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); + int library_indirect_level = lib_override_libraries_index_define(bmain); + /* And do the actual resync for all IDs detected as needing it. * NOTE: Since this changes `bmain` (adding **and** removing IDs), we cannot use * `FOREACH_MAIN_ID_BEGIN/END` here, and need special multi-loop processing. */ @@ -1234,13 +1454,12 @@ void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer * do_continue = false; FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb) { FOREACH_MAIN_LISTBASE_ID_BEGIN (lb, id) { - if ((id->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) == 0) { + if ((id->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) == 0 || + (ID_IS_LINKED(id) && id->lib->temp_index < library_indirect_level) || + (!ID_IS_LINKED(id) && library_indirect_level != 0)) { continue; } BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id)); - if (ID_IS_LINKED(id)) { - continue; - } do_continue = true; /* In complex non-supported cases, with several different override hierarchies sharing @@ -1250,9 +1469,9 @@ void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer * * 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); + CLOG_INFO(&LOG, 2, "Resyncing %s (%p)...", id->name, id->lib); 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; } @@ -1262,6 +1481,13 @@ void BKE_lib_override_library_main_resync(Main *bmain, Scene *scene, ViewLayer * } } FOREACH_MAIN_LISTBASE_END; + + if (!do_continue && library_indirect_level != 0) { + /* We are done with overrides from that level of indirect linking, we can keep going with + * those 'less' indirectly linked now. */ + library_indirect_level--; + do_continue = true; + } } /* Essentially ensures that potentially new overrides of new objects will be instantiated. */ @@ -1643,7 +1869,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) { @@ -1677,7 +1903,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; @@ -1935,7 +2161,7 @@ bool BKE_lib_override_library_main_operations_create(Main *bmain, const bool for TaskPool *task_pool = BLI_task_pool_create(&create_pool_data, TASK_PRIORITY_HIGH); FOREACH_MAIN_ID_BEGIN (bmain, id) { - if (ID_IS_OVERRIDE_LIBRARY_REAL(id) && + if (!ID_IS_LINKED(id) && ID_IS_OVERRIDE_LIBRARY_REAL(id) && (force_auto || (id->tag & LIB_TAG_OVERRIDE_LIBRARY_AUTOREFRESH))) { /* Usual issue with pose, it's quiet rare but sometimes they may not be up to date when this * function is called. */ diff --git a/source/blender/blenkernel/intern/lib_query.c b/source/blender/blenkernel/intern/lib_query.c index cbbe07f99d8..b748061ef8a 100644 --- a/source/blender/blenkernel/intern/lib_query.c +++ b/source/blender/blenkernel/intern/lib_query.c @@ -56,7 +56,7 @@ typedef struct LibraryForeachIDData { */ ID *self_id; - /** Flags controlling the bahaviour of the 'foreach id' looping code. */ + /** 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; @@ -244,9 +244,10 @@ static void library_foreach_ID_link(Main *bmain, * (the node tree), but re-use those generated for the 'owner' ID (the material). */ if (inherit_data == NULL) { data.cb_flag = ID_IS_LINKED(id) ? IDWALK_CB_INDIRECT_USAGE : 0; - /* When an ID is not in Main database, it should never refcount IDs it is using. - * Exceptions: NodeTrees (yeah!) directly used by Materials. */ - data.cb_flag_clear = (id->tag & LIB_TAG_NO_MAIN) ? IDWALK_CB_USER | IDWALK_CB_USER_ONE : 0; + /* When an ID is defined as not refcounting its ID usages, it should never do it. */ + data.cb_flag_clear = (id->tag & LIB_TAG_NO_USER_REFCOUNT) ? + IDWALK_CB_USER | IDWALK_CB_USER_ONE : + 0; } else { data.cb_flag = inherit_data->cb_flag; diff --git a/source/blender/blenkernel/intern/lib_remap.c b/source/blender/blenkernel/intern/lib_remap.c index b32b97dc250..2641208897e 100644 --- a/source/blender/blenkernel/intern/lib_remap.c +++ b/source/blender/blenkernel/intern/lib_remap.c @@ -137,6 +137,7 @@ static int foreach_libblock_remap_callback(LibraryIDLinkCallbackData *cb_data) (id_remap_data->flag & ID_REMAP_FORCE_NEVER_NULL_USAGE) == 0); const bool skip_reference = (id_remap_data->flag & ID_REMAP_SKIP_OVERRIDE_LIBRARY) != 0; const bool skip_never_null = (id_remap_data->flag & ID_REMAP_SKIP_NEVER_NULL_USAGE) != 0; + const bool force_user_refcount = (id_remap_data->flag & ID_REMAP_FORCE_USER_REFCOUNT) != 0; #ifdef DEBUG_PRINT printf( @@ -203,16 +204,16 @@ static int foreach_libblock_remap_callback(LibraryIDLinkCallbackData *cb_data) } } if (cb_flag & IDWALK_CB_USER) { - /* NOTE: We don't user-count IDs which are not in the main database. + /* NOTE: by default we don't user-count IDs which are not in the main database. * This is because in certain conditions we can have data-blocks in * the main which are referencing data-blocks outside of it. * For example, BKE_mesh_new_from_object() called on an evaluated * object will cause such situation. */ - if ((old_id->tag & LIB_TAG_NO_MAIN) == 0) { + if (force_user_refcount || (old_id->tag & LIB_TAG_NO_MAIN) == 0) { id_us_min(old_id); } - if (new_id != NULL && (new_id->tag & LIB_TAG_NO_MAIN) == 0) { + if (new_id != NULL && (force_user_refcount || (new_id->tag & LIB_TAG_NO_MAIN) == 0)) { /* We do not want to handle LIB_TAG_INDIRECT/LIB_TAG_EXTERN here. */ new_id->us++; } 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 d013becb372..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) @@ -1555,7 +1567,7 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, * check whether it is still true with Mesh */ Mesh tmp = *mesh_dst; int totvert, totedge /*, totface */ /* UNUSED */, totloop, totpoly; - int did_shapekeys = 0; + bool did_shapekeys = false; eCDAllocType alloctype = CD_DUPLICATE; if (take_ownership /* && dm->type == DM_TYPE_CDDM && dm->needsFree */) { @@ -1610,7 +1622,7 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, } shapekey_layers_to_keyblocks(mesh_src, mesh_dst, uid); - did_shapekeys = 1; + did_shapekeys = true; } /* copy texture space */ @@ -1639,13 +1651,18 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, totedge); } if (!CustomData_has_layer(&tmp.pdata, CD_MPOLY)) { - /* TODO(Sybren): assignment to tmp.mxxx is probably not necessary due to the - * BKE_mesh_update_customdata_pointers() call below. */ - tmp.mloop = (alloctype == CD_ASSIGN) ? mesh_src->mloop : MEM_dupallocN(mesh_src->mloop); - tmp.mpoly = (alloctype == CD_ASSIGN) ? mesh_src->mpoly : MEM_dupallocN(mesh_src->mpoly); - - CustomData_add_layer(&tmp.ldata, CD_MLOOP, CD_ASSIGN, tmp.mloop, tmp.totloop); - CustomData_add_layer(&tmp.pdata, CD_MPOLY, CD_ASSIGN, tmp.mpoly, tmp.totpoly); + CustomData_add_layer(&tmp.ldata, + CD_MLOOP, + CD_ASSIGN, + (alloctype == CD_ASSIGN) ? mesh_src->mloop : + MEM_dupallocN(mesh_src->mloop), + tmp.totloop); + CustomData_add_layer(&tmp.pdata, + CD_MPOLY, + CD_ASSIGN, + (alloctype == CD_ASSIGN) ? mesh_src->mpoly : + MEM_dupallocN(mesh_src->mpoly), + tmp.totpoly); } /* object had got displacement layer, should copy this layer to save sculpted data */ @@ -1664,9 +1681,6 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, /* yes, must be before _and_ after tessellate */ BKE_mesh_update_customdata_pointers(&tmp, false); - /* since 2.65 caller must do! */ - // BKE_mesh_tessface_calc(&tmp); - CustomData_free(&mesh_dst->vdata, mesh_dst->totvert); CustomData_free(&mesh_dst->edata, mesh_dst->totedge); CustomData_free(&mesh_dst->fdata, mesh_dst->totface); diff --git a/source/blender/blenkernel/intern/mesh_sample.cc b/source/blender/blenkernel/intern/mesh_sample.cc new file mode 100644 index 00000000000..91c9951ae89 --- /dev/null +++ b/source/blender/blenkernel/intern/mesh_sample.cc @@ -0,0 +1,158 @@ +/* + * 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_attribute_math.hh" +#include "BKE_mesh_runtime.h" +#include "BKE_mesh_sample.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +namespace blender::bke::mesh_surface_sample { + +static Span<MLoopTri> get_mesh_looptris(const Mesh &mesh) +{ + /* This only updates a cache and can be considered to be logically const. */ + const MLoopTri *looptris = BKE_mesh_runtime_looptri_ensure(const_cast<Mesh *>(&mesh)); + const int looptris_len = BKE_mesh_runtime_looptri_len(&mesh); + return {looptris, looptris_len}; +} + +template<typename T> +BLI_NOINLINE static void sample_point_attribute(const Mesh &mesh, + const Span<int> looptri_indices, + const Span<float3> bary_coords, + const VArray<T> &data_in, + const MutableSpan<T> data_out) +{ + const Span<MLoopTri> looptris = get_mesh_looptris(mesh); + + for (const int i : bary_coords.index_range()) { + const int looptri_index = looptri_indices[i]; + const MLoopTri &looptri = looptris[looptri_index]; + const float3 &bary_coord = bary_coords[i]; + + const int v0_index = mesh.mloop[looptri.tri[0]].v; + const int v1_index = mesh.mloop[looptri.tri[1]].v; + const int v2_index = mesh.mloop[looptri.tri[2]].v; + + const T v0 = data_in[v0_index]; + const T v1 = data_in[v1_index]; + const T v2 = data_in[v2_index]; + + const T interpolated_value = attribute_math::mix3(bary_coord, v0, v1, v2); + data_out[i] = interpolated_value; + } +} + +void sample_point_attribute(const Mesh &mesh, + const Span<int> looptri_indices, + const Span<float3> bary_coords, + const GVArray &data_in, + const GMutableSpan data_out) +{ + BLI_assert(data_out.size() == looptri_indices.size()); + BLI_assert(data_out.size() == bary_coords.size()); + BLI_assert(data_in.size() == mesh.totvert); + BLI_assert(data_in.type() == data_out.type()); + + const CPPType &type = data_in.type(); + attribute_math::convert_to_static_type(type, [&](auto dummy) { + using T = decltype(dummy); + sample_point_attribute<T>( + mesh, looptri_indices, bary_coords, data_in.typed<T>(), data_out.typed<T>()); + }); +} + +template<typename T> +BLI_NOINLINE static void sample_corner_attribute(const Mesh &mesh, + const Span<int> looptri_indices, + const Span<float3> bary_coords, + const VArray<T> &data_in, + const MutableSpan<T> data_out) +{ + Span<MLoopTri> looptris = get_mesh_looptris(mesh); + + for (const int i : bary_coords.index_range()) { + const int looptri_index = looptri_indices[i]; + const MLoopTri &looptri = looptris[looptri_index]; + const float3 &bary_coord = bary_coords[i]; + + const int loop_index_0 = looptri.tri[0]; + const int loop_index_1 = looptri.tri[1]; + const int loop_index_2 = looptri.tri[2]; + + const T v0 = data_in[loop_index_0]; + const T v1 = data_in[loop_index_1]; + const T v2 = data_in[loop_index_2]; + + const T interpolated_value = attribute_math::mix3(bary_coord, v0, v1, v2); + data_out[i] = interpolated_value; + } +} + +void sample_corner_attribute(const Mesh &mesh, + const Span<int> looptri_indices, + const Span<float3> bary_coords, + const GVArray &data_in, + const GMutableSpan data_out) +{ + BLI_assert(data_out.size() == looptri_indices.size()); + BLI_assert(data_out.size() == bary_coords.size()); + BLI_assert(data_in.size() == mesh.totloop); + BLI_assert(data_in.type() == data_out.type()); + + const CPPType &type = data_in.type(); + attribute_math::convert_to_static_type(type, [&](auto dummy) { + using T = decltype(dummy); + sample_corner_attribute<T>( + mesh, looptri_indices, bary_coords, data_in.typed<T>(), data_out.typed<T>()); + }); +} + +template<typename T> +void sample_face_attribute(const Mesh &mesh, + const Span<int> looptri_indices, + const VArray<T> &data_in, + const MutableSpan<T> data_out) +{ + Span<MLoopTri> looptris = get_mesh_looptris(mesh); + + for (const int i : data_out.index_range()) { + const int looptri_index = looptri_indices[i]; + const MLoopTri &looptri = looptris[looptri_index]; + const int poly_index = looptri.poly; + data_out[i] = data_in[poly_index]; + } +} + +void sample_face_attribute(const Mesh &mesh, + const Span<int> looptri_indices, + const GVArray &data_in, + const GMutableSpan data_out) +{ + BLI_assert(data_out.size() == looptri_indices.size()); + BLI_assert(data_in.size() == mesh.totpoly); + BLI_assert(data_in.type() == data_out.type()); + + const CPPType &type = data_in.type(); + attribute_math::convert_to_static_type(type, [&](auto dummy) { + using T = decltype(dummy); + sample_face_attribute<T>(mesh, looptri_indices, data_in.typed<T>(), data_out.typed<T>()); + }); +} + +} // namespace blender::bke::mesh_surface_sample 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 68ae6963a3e..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; } @@ -4241,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) { @@ -4949,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(); @@ -4956,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(); @@ -4983,6 +5077,7 @@ static void registerGeometryNodes() register_node_type_geo_sample_texture(); register_node_type_geo_subdivide(); register_node_type_geo_subdivision_surface(); + register_node_type_geo_switch(); register_node_type_geo_transform(); register_node_type_geo_triangulate(); register_node_type_geo_volume_to_mesh(); diff --git a/source/blender/blenkernel/intern/node_ui_storage.cc b/source/blender/blenkernel/intern/node_ui_storage.cc index cc910bab6ac..e5e9f00c7c3 100644 --- a/source/blender/blenkernel/intern/node_ui_storage.cc +++ b/source/blender/blenkernel/intern/node_ui_storage.cc @@ -39,7 +39,7 @@ using blender::Vector; * bNodeTree struct in DNA. This could change if the node tree had a runtime struct. */ static std::mutex global_ui_storage_mutex; -static void ui_storage_ensure(bNodeTree &ntree) +static NodeTreeUIStorage &ui_storage_ensure(bNodeTree &ntree) { /* As an optimization, only acquire a lock if the UI storage doesn't exist, * because it only needs to be allocated once for every node tree. */ @@ -50,6 +50,7 @@ static void ui_storage_ensure(bNodeTree &ntree) ntree.ui_storage = new NodeTreeUIStorage(); } } + return *ntree.ui_storage; } const NodeUIStorage *BKE_node_tree_ui_storage_get_from_context(const bContext *C, @@ -90,7 +91,7 @@ void BKE_nodetree_ui_storage_free_for_context(bNodeTree &ntree, { NodeTreeUIStorage *ui_storage = ntree.ui_storage; if (ui_storage != nullptr) { - std::lock_guard<std::mutex> lock(ui_storage->context_map_mutex); + std::lock_guard<std::mutex> lock(ui_storage->mutex); ui_storage->context_map.remove(context); } } @@ -126,20 +127,14 @@ static void node_error_message_log(bNodeTree &ntree, } } -static NodeUIStorage &node_ui_storage_ensure(bNodeTree &ntree, +static NodeUIStorage &node_ui_storage_ensure(NodeTreeUIStorage &locked_ui_storage, const NodeTreeEvaluationContext &context, const bNode &node) { - ui_storage_ensure(ntree); - NodeTreeUIStorage &ui_storage = *ntree.ui_storage; - - std::lock_guard<std::mutex> lock(ui_storage.context_map_mutex); Map<std::string, NodeUIStorage> &node_tree_ui_storage = - ui_storage.context_map.lookup_or_add_default(context); - + locked_ui_storage.context_map.lookup_or_add_default(context); NodeUIStorage &node_ui_storage = node_tree_ui_storage.lookup_or_add_default_as( StringRef(node.name)); - return node_ui_storage; } @@ -149,9 +144,12 @@ void BKE_nodetree_error_message_add(bNodeTree &ntree, const NodeWarningType type, std::string message) { + NodeTreeUIStorage &ui_storage = ui_storage_ensure(ntree); + std::lock_guard lock{ui_storage.mutex}; + node_error_message_log(ntree, node, message, type); - NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ntree, context, node); + NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ui_storage, context, node); node_ui_storage.warnings.append({type, std::move(message)}); } @@ -162,7 +160,10 @@ void BKE_nodetree_attribute_hint_add(bNodeTree &ntree, const AttributeDomain domain, const CustomDataType data_type) { - NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ntree, context, node); + NodeTreeUIStorage &ui_storage = ui_storage_ensure(ntree); + std::lock_guard lock{ui_storage.mutex}; + + NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ui_storage, context, node); 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 34c77cd9155..b73f6a5b78c 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); - } 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; @@ -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); @@ -2625,8 +2622,8 @@ void BKE_object_transform_copy(Object *ob_tar, const Object *ob_src) * * \note This function does not do any remapping to new IDs, caller must do it * (\a #BKE_libblock_relink_to_newid()). - * \note Caller MUST free \a newid pointers itself (#BKE_main_id_clear_newpoins()) and call updates - * of DEG too (#DAG_relations_tag_update()). + * \note Caller MUST free \a newid pointers itself (#BKE_main_id_newptr_and_tag_clear()) and call + * updates of DEG too (#DAG_relations_tag_update()). */ Object *BKE_object_duplicate(Main *bmain, Object *ob, @@ -2636,8 +2633,7 @@ Object *BKE_object_duplicate(Main *bmain, const bool is_subprocess = (duplicate_options & LIB_ID_DUPLICATE_IS_SUBPROCESS) != 0; if (!is_subprocess) { - BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false); - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); /* In case root duplicated ID is linked, assume we want to get a local copy of it and duplicate * all expected linked data. */ if (ID_IS_LINKED(ob)) { @@ -2776,8 +2772,7 @@ Object *BKE_object_duplicate(Main *bmain, #endif /* Cleanup. */ - BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false); - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); } if (obn->type == OB_ARMATURE) { @@ -4350,7 +4345,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); } @@ -5121,6 +5116,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) diff --git a/source/blender/blenkernel/intern/object_dupli.cc b/source/blender/blenkernel/intern/object_dupli.cc index 835b62c06bf..768fa9373c1 100644 --- a/source/blender/blenkernel/intern/object_dupli.cc +++ b/source/blender/blenkernel/intern/object_dupli.cc @@ -842,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; } @@ -885,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; } } } 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/scene.c b/source/blender/blenkernel/intern/scene.c index a4ab64a8a02..a58db89ad0c 100644 --- a/source/blender/blenkernel/intern/scene.c +++ b/source/blender/blenkernel/intern/scene.c @@ -1985,8 +1985,7 @@ Scene *BKE_scene_duplicate(Main *bmain, Scene *sce, eSceneCopyMethod type) const bool is_subprocess = false; if (!is_subprocess) { - BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false); - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); /* In case root duplicated ID is linked, assume we want to get a local copy of it and * duplicate all expected linked data. */ if (ID_IS_LINKED(sce)) { @@ -2027,8 +2026,7 @@ Scene *BKE_scene_duplicate(Main *bmain, Scene *sce, eSceneCopyMethod type) #endif /* Cleanup. */ - BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false); - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); BKE_main_collection_sync(bmain); } 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..4f2625c14d3 --- /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_left, + const float3 handle_position_left, + const HandleType handle_type_right, + const float3 handle_position_right, + const float radius, + const float tilt) +{ + handle_types_left_.append(handle_type_left); + handle_positions_left_.append(handle_position_left); + positions_.append(position); + handle_types_right_.append(handle_type_right); + handle_positions_right_.append(handle_position_right); + 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/unit.c b/source/blender/blenkernel/intern/unit.c index 9ae1c754846..3612a26315c 100644 --- a/source/blender/blenkernel/intern/unit.c +++ b/source/blender/blenkernel/intern/unit.c @@ -943,7 +943,7 @@ static int unit_scale_str(char *str, /* Add the addition sign, the bias, and the close parenthesis after the value. */ int value_end_ofs = find_end_of_value_chars(str, len_max, prev_op_ofs + 2); - int len_bias_num = BLI_snprintf(str_tmp, TEMP_STR_SIZE, "+%.9g)", unit->bias); + int len_bias_num = BLI_snprintf_rlen(str_tmp, TEMP_STR_SIZE, "+%.9g)", unit->bias); if (value_end_ofs + len_bias_num < len_max) { memmove(str + value_end_ofs + len_bias_num, str + value_end_ofs, len - value_end_ofs + 1); memcpy(str + value_end_ofs, str_tmp, len_bias_num); @@ -957,7 +957,8 @@ static int unit_scale_str(char *str, int len_move = (len - (found_ofs + len_name)) + 1; /* 1+ to copy the string terminator. */ /* "#" Removed later */ - int len_num = BLI_snprintf(str_tmp, TEMP_STR_SIZE, "*%.9g" SEP_STR, unit->scalar / scale_pref); + int len_num = BLI_snprintf_rlen( + str_tmp, TEMP_STR_SIZE, "*%.9g" SEP_STR, unit->scalar / scale_pref); if (len_num > len_max) { len_num = len_max; diff --git a/source/blender/blenkernel/intern/volume.cc b/source/blender/blenkernel/intern/volume.cc index 7b03839f659..c0ce57818d1 100644 --- a/source/blender/blenkernel/intern/volume.cc +++ b/source/blender/blenkernel/intern/volume.cc @@ -39,6 +39,7 @@ #include "BLI_utildefines.h" #include "BKE_anim_data.h" +#include "BKE_geometry_set.hh" #include "BKE_global.h" #include "BKE_idtype.h" #include "BKE_lib_id.h" @@ -1003,13 +1004,11 @@ static void volume_update_simplify_level(Volume *volume, const Depsgraph *depsgr #endif } -static Volume *volume_evaluate_modifiers(struct Depsgraph *depsgraph, - struct Scene *scene, - Object *object, - Volume *volume_input) +static void volume_evaluate_modifiers(struct Depsgraph *depsgraph, + struct Scene *scene, + Object *object, + GeometrySet &geometry_set) { - Volume *volume = volume_input; - /* Modifier evaluation modes. */ const bool use_render = (DEG_get_mode(depsgraph) == DAG_EVAL_RENDER); const int required_mode = use_render ? eModifierMode_Render : eModifierMode_Realtime; @@ -1030,25 +1029,10 @@ static Volume *volume_evaluate_modifiers(struct Depsgraph *depsgraph, continue; } - if (mti->modifyVolume) { - /* Ensure we are not modifying the input. */ - if (volume == volume_input) { - volume = BKE_volume_copy_for_eval(volume, true); - } - - Volume *volume_next = mti->modifyVolume(md, &mectx, volume); - - if (volume_next && volume_next != volume) { - /* If the modifier returned a new volume, release the old one. */ - if (volume != volume_input) { - BKE_id_free(nullptr, volume); - } - volume = volume_next; - } + if (mti->modifyGeometrySet) { + mti->modifyGeometrySet(md, &mectx, &geometry_set); } } - - return volume; } void BKE_volume_eval_geometry(struct Depsgraph *depsgraph, Volume *volume) @@ -1072,6 +1056,24 @@ void BKE_volume_eval_geometry(struct Depsgraph *depsgraph, Volume *volume) } } +static Volume *take_volume_ownership_from_geometry_set(GeometrySet &geometry_set) +{ + if (!geometry_set.has<VolumeComponent>()) { + return nullptr; + } + VolumeComponent &volume_component = geometry_set.get_component_for_write<VolumeComponent>(); + Volume *volume = volume_component.release(); + if (volume != nullptr) { + /* Add back, but only as read-only non-owning component. */ + volume_component.replace(volume, GeometryOwnershipType::ReadOnly); + } + else { + /* The component was empty, we can remove it. */ + geometry_set.remove<VolumeComponent>(); + } + return volume; +} + void BKE_volume_data_update(struct Depsgraph *depsgraph, struct Scene *scene, Object *object) { /* Free any evaluated data and restore original data. */ @@ -1079,11 +1081,21 @@ void BKE_volume_data_update(struct Depsgraph *depsgraph, struct Scene *scene, Ob /* Evaluate modifiers. */ Volume *volume = (Volume *)object->data; - Volume *volume_eval = volume_evaluate_modifiers(depsgraph, scene, object, volume); + GeometrySet 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); + + /* If the geometry set did not contain a volume, we still create an empty one. */ + if (volume_eval == nullptr) { + volume_eval = BKE_volume_new_for_eval(volume); + } /* Assign evaluated object. */ - const bool is_owned = (volume != volume_eval); - BKE_object_eval_assign_data(object, &volume_eval->id, is_owned); + const bool eval_is_owned = (volume != volume_eval); + BKE_object_eval_assign_data(object, &volume_eval->id, eval_is_owned); + object->runtime.geometry_set_eval = new GeometrySet(std::move(geometry_set)); } void BKE_volume_grids_backup_restore(Volume *volume, VolumeGridVector *grids, const char *filepath) diff --git a/source/blender/blenkernel/intern/writeffmpeg.c b/source/blender/blenkernel/intern/writeffmpeg.c index e5550cee124..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,33 +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; frame->channels = c->channels; -# ifdef FFMPEG_HAVE_FRAME_CHANNEL_LAYOUT frame->channel_layout = c->channel_layout; -# endif if (context->audio_deinterleave) { int channel, i; @@ -195,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 */ @@ -265,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; @@ -342,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) { @@ -438,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); } @@ -446,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; @@ -536,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); } } } @@ -553,7 +527,6 @@ static AVStream *alloc_video_stream(FFMpegContext *context, int error_size) { AVStream *st; - AVCodecContext *c; AVCodec *codec; AVDictionary *opts = NULL; @@ -567,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; @@ -650,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; } @@ -714,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... */ @@ -742,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); @@ -769,6 +742,8 @@ static AVStream *alloc_video_stream(FFMpegContext *context, NULL); } + avcodec_parameters_from_context(st->codecpar, c); + return st; } @@ -780,7 +755,6 @@ static AVStream *alloc_audio_stream(FFMpegContext *context, int error_size) { AVStream *st; - AVCodecContext *c; AVCodec *codec; AVDictionary *opts = NULL; @@ -792,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; @@ -804,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; @@ -822,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 */ @@ -833,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; } @@ -844,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]; } } @@ -859,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); @@ -879,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); @@ -913,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); @@ -924,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 */ @@ -949,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); @@ -1040,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: @@ -1105,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; } @@ -1119,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; } @@ -1129,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; } } @@ -1138,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; } @@ -1156,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; } @@ -1190,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); } /* ********************************************************************** @@ -1327,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; @@ -1354,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; @@ -1398,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) { @@ -1428,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); } @@ -1441,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; } @@ -1467,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; @@ -1475,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); @@ -1560,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: @@ -1707,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) { @@ -1871,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 diff --git a/source/blender/blenlib/BLI_color.hh b/source/blender/blenlib/BLI_color.hh index e57a5109a66..3b01bbfb86e 100644 --- a/source/blender/blenlib/BLI_color.hh +++ b/source/blender/blenlib/BLI_color.hh @@ -22,41 +22,122 @@ namespace blender { -struct Color4f { - float r, g, b, a; +/** + * CPP based color structures. + * + * Strongly typed color storage structures with space and alpha association. + * Will increase readability and visibility of typical mistakes when + * working with colors. + * + * The storage structs can hold 4 channels (r, g, b and a). + * + * Usage: + * + * Convert a theme byte color to a linearrgb premultiplied. + * ``` + * ColorTheme4b theme_color; + * ColorSceneLinear4f<eAlpha::Premultiplied> linearrgb_color = + * BLI_color_convert_to_scene_linear(theme_color).premultiply_alpha(); + * ``` + * + * The API is structured to make most use of inlining. Most notable are space + * conversions done via `BLI_color_convert_to*` functions. + * + * - Conversions between spaces (theme <=> scene linear) should always be done by + * invoking the `BLI_color_convert_to*` methods. + * - Encoding colors (compressing to store colors inside a less precision storage) + * should be done by invoking the `encode` and `decode` methods. + * - Changing alpha association should be done by invoking `premultiply_alpha` or + * `unpremultiply_alpha` methods. + * + * # Encoding. + * + * Color encoding is used to store colors with less precision as in using `uint8_t` in + * stead of `float`. This encoding is supported for `eSpace::SceneLinear`. + * To make this clear to the developer the `eSpace::SceneLinearByteEncoded` + * space is added. + * + * # Precision + * + * Colors can be stored using `uint8_t` or `float` colors. The conversion + * between the two precisions are available as methods. (`to_4b` and + * `to_4f`). + * + * # Alpha conversion + * + * Alpha conversion is only supported in SceneLinear space. + * + * Extending this file: + * - This file can be extended with `ColorHex/Hsl/Hsv` for different representations + * of rgb based colors. `ColorHsl4f<eSpace::SceneLinear, eAlpha::Premultiplied>` + * - Add non RGB spaces/storages ColorXyz. + */ + +/* Enumeration containing the different alpha modes. */ +enum class eAlpha { + /* Color and alpha are unassociated. */ + Straight, + /* Color and alpha are associated. */ + Premultiplied, +}; +std::ostream &operator<<(std::ostream &stream, const eAlpha &space); - Color4f() = default; +/* Enumeration containing internal spaces. */ +enum class eSpace { + /* Blender theme color space (sRGB). */ + Theme, + /* Blender internal scene linear color space (maps to SceneReference role in OCIO). */ + SceneLinear, + /* Blender internal scene linear color space compressed to be stored in 4 uint8_t. */ + SceneLinearByteEncoded, +}; +std::ostream &operator<<(std::ostream &stream, const eSpace &space); - Color4f(const float *rgba) : r(rgba[0]), g(rgba[1]), b(rgba[2]), a(rgba[3]) +/* Template class to store RGBA values with different precision, space and alpha association. */ +template<typename ChannelStorageType, eSpace Space, eAlpha Alpha> class ColorRGBA { + public: + ChannelStorageType r, g, b, a; + constexpr ColorRGBA() = default; + + constexpr ColorRGBA(const ChannelStorageType rgba[4]) + : r(rgba[0]), g(rgba[1]), b(rgba[2]), a(rgba[3]) { } - Color4f(float r, float g, float b, float a) : r(r), g(g), b(b), a(a) + constexpr ColorRGBA(const ChannelStorageType r, + const ChannelStorageType g, + const ChannelStorageType b, + const ChannelStorageType a) + : r(r), g(g), b(b), a(a) { } - operator float *() + operator ChannelStorageType *() { return &r; } - operator const float *() const + operator const ChannelStorageType *() const { return &r; } - friend std::ostream &operator<<(std::ostream &stream, Color4f c) + friend std::ostream &operator<<(std::ostream &stream, + const ColorRGBA<ChannelStorageType, Space, Alpha> &c) { - stream << "(" << c.r << ", " << c.g << ", " << c.b << ", " << c.a << ")"; + + stream << Space << Alpha << "(" << c.r << ", " << c.g << ", " << c.b << ", " << c.a << ")"; return stream; } - friend bool operator==(const Color4f &a, const Color4f &b) + friend bool operator==(const ColorRGBA<ChannelStorageType, Space, Alpha> &a, + const ColorRGBA<ChannelStorageType, Space, Alpha> &b) { return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a; } - friend bool operator!=(const Color4f &a, const Color4f &b) + friend bool operator!=(const ColorRGBA<ChannelStorageType, Space, Alpha> &a, + const ColorRGBA<ChannelStorageType, Space, Alpha> &b) { return !(a == b); } @@ -71,58 +152,209 @@ struct Color4f { } }; -struct Color4b { - uint8_t r, g, b, a; +/* Forward declarations of concrete color classes. */ +template<eAlpha Alpha> class ColorSceneLinear4f; +template<eAlpha Alpha> class ColorSceneLinearByteEncoded4b; +template<typename ChannelStorageType> class ColorTheme4; - Color4b() = default; +/* Forward declaration of precision conversion methods. */ +BLI_INLINE ColorTheme4<float> BLI_color_convert_to_theme4f(const ColorTheme4<uint8_t> &srgb4b); +BLI_INLINE ColorTheme4<uint8_t> BLI_color_convert_to_theme4b(const ColorTheme4<float> &srgb4f); - Color4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a) : r(r), g(g), b(b), a(a) +template<eAlpha Alpha> +class ColorSceneLinear4f final : public ColorRGBA<float, eSpace::SceneLinear, Alpha> { + public: + constexpr ColorSceneLinear4f<Alpha>() : ColorRGBA<float, eSpace::SceneLinear, Alpha>() { } - Color4b(Color4f other) + constexpr ColorSceneLinear4f<Alpha>(const float *rgba) + : ColorRGBA<float, eSpace::SceneLinear, Alpha>(rgba) { - rgba_float_to_uchar(*this, other); } - operator Color4f() const + constexpr ColorSceneLinear4f<Alpha>(float r, float g, float b, float a) + : ColorRGBA<float, eSpace::SceneLinear, Alpha>(r, g, b, a) { - Color4f result; - rgba_uchar_to_float(result, *this); - return result; } - operator uint8_t *() + /** + * Convert to its byte encoded counter space. + **/ + ColorSceneLinearByteEncoded4b<Alpha> encode() const { - return &r; + ColorSceneLinearByteEncoded4b<Alpha> encoded; + linearrgb_to_srgb_uchar4(encoded, *this); + return encoded; } - operator const uint8_t *() const + /** + * Convert color and alpha association to premultiplied alpha. + * + * Does nothing when color has already a premultiplied alpha. + */ + ColorSceneLinear4f<eAlpha::Premultiplied> premultiply_alpha() const { - return &r; + if constexpr (Alpha == eAlpha::Straight) { + ColorSceneLinear4f<eAlpha::Premultiplied> premultiplied; + straight_to_premul_v4_v4(premultiplied, *this); + return premultiplied; + } + else { + return *this; + } } - friend std::ostream &operator<<(std::ostream &stream, Color4b c) + /** + * Convert color and alpha association to straight alpha. + * + * Does nothing when color has straighten alpha. + */ + ColorSceneLinear4f<eAlpha::Straight> unpremultiply_alpha() const { - stream << "(" << c.r << ", " << c.g << ", " << c.b << ", " << c.a << ")"; - return stream; + if constexpr (Alpha == eAlpha::Premultiplied) { + ColorSceneLinear4f<eAlpha::Straight> straighten; + premul_to_straight_v4_v4(straighten, *this); + return straighten; + } + else { + return *this; + } } +}; - friend bool operator==(const Color4b &a, const Color4b &b) +template<eAlpha Alpha> +class ColorSceneLinearByteEncoded4b final + : public ColorRGBA<uint8_t, eSpace::SceneLinearByteEncoded, Alpha> { + public: + constexpr ColorSceneLinearByteEncoded4b() = default; + + constexpr ColorSceneLinearByteEncoded4b(const uint8_t *rgba) + : ColorRGBA<uint8_t, eSpace::SceneLinearByteEncoded, Alpha>(rgba) { - return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a; } - friend bool operator!=(const Color4b &a, const Color4b &b) + constexpr ColorSceneLinearByteEncoded4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a) + : ColorRGBA<uint8_t, eSpace::SceneLinearByteEncoded, Alpha>(r, g, b, a) { - return !(a == b); } - uint64_t hash() const + /** + * Convert to back to float color. + */ + ColorSceneLinear4f<Alpha> decode() const + { + ColorSceneLinear4f<Alpha> decoded; + srgb_to_linearrgb_uchar4(decoded, *this); + return decoded; + } +}; + +/** + * Theme color template class. + * + * Don't use directly, but use `ColorTheme4b/ColorTheme4b`. + * + * This has been implemented as a template to improve inlining. When implemented as concrete + * classes (ColorTheme4b/f) the functions would be hidden in a compile unit what wouldn't be + * inlined. + */ +template<typename ChannelStorageType> +class ColorTheme4 final : public ColorRGBA<ChannelStorageType, eSpace::Theme, eAlpha::Straight> { + public: + constexpr ColorTheme4() : ColorRGBA<ChannelStorageType, eSpace::Theme, eAlpha::Straight>(){}; + + constexpr ColorTheme4(const ChannelStorageType *rgba) + : ColorRGBA<ChannelStorageType, eSpace::Theme, eAlpha::Straight>(rgba) + { + } + + constexpr ColorTheme4(ChannelStorageType r, + ChannelStorageType g, + ChannelStorageType b, + ChannelStorageType a) + : ColorRGBA<ChannelStorageType, eSpace::Theme, eAlpha::Straight>(r, g, b, a) + { + } + + /** + * Change precision of color to float. + */ + ColorTheme4<float> to_4f() const { - return static_cast<uint64_t>(r * 1283591) ^ static_cast<uint64_t>(g * 850177) ^ - static_cast<uint64_t>(b * 735391) ^ static_cast<uint64_t>(a * 442319); + if constexpr ((std::is_same_v<ChannelStorageType, uint8_t>)) { + return BLI_color_convert_to_theme4f(*this); + } + else { + return *this; + } + } + + /** + * Change precision of color to uint8_t. + */ + ColorTheme4<uint8_t> to_4b() const + { + if constexpr ((std::is_same_v<ChannelStorageType, float>)) { + return BLI_color_convert_to_theme4b(*this); + } + else { + return *this; + } } }; +using ColorTheme4b = ColorTheme4<uint8_t>; +using ColorTheme4f = ColorTheme4<float>; + +BLI_INLINE ColorTheme4b BLI_color_convert_to_theme4b(const ColorTheme4f &theme4f) +{ + ColorTheme4b theme4b; + rgba_float_to_uchar(theme4b, theme4f); + return theme4b; +} + +BLI_INLINE ColorTheme4f BLI_color_convert_to_theme4f(const ColorTheme4b &theme4b) +{ + ColorTheme4f theme4f; + rgba_uchar_to_float(theme4f, theme4b); + return theme4f; +} + +BLI_INLINE ColorSceneLinear4f<eAlpha::Straight> BLI_color_convert_to_scene_linear( + const ColorTheme4f &theme4f) +{ + ColorSceneLinear4f<eAlpha::Straight> scene_linear; + srgb_to_linearrgb_v4(scene_linear, theme4f); + return scene_linear; +} + +BLI_INLINE ColorSceneLinear4f<eAlpha::Straight> BLI_color_convert_to_scene_linear( + const ColorTheme4b &theme4b) +{ + ColorSceneLinear4f<eAlpha::Straight> scene_linear; + srgb_to_linearrgb_uchar4(scene_linear, theme4b); + return scene_linear; +} + +BLI_INLINE ColorTheme4f +BLI_color_convert_to_theme4f(const ColorSceneLinear4f<eAlpha::Straight> &scene_linear) +{ + ColorTheme4f theme4f; + linearrgb_to_srgb_v4(theme4f, scene_linear); + return theme4f; +} + +BLI_INLINE ColorTheme4b +BLI_color_convert_to_theme4b(const ColorSceneLinear4f<eAlpha::Straight> &scene_linear) +{ + ColorTheme4b theme4b; + linearrgb_to_srgb_uchar4(theme4b, scene_linear); + return theme4b; +} + +/* Internal roles. For convenience to shorten the type names and hide complexity. */ +using ColorGeometry4f = ColorSceneLinear4f<eAlpha::Premultiplied>; +using ColorGeometry4b = ColorSceneLinearByteEncoded4b<eAlpha::Premultiplied>; + } // namespace blender diff --git a/source/blender/blenlib/BLI_compiler_attrs.h b/source/blender/blenlib/BLI_compiler_attrs.h index 680c4bc78da..4b5a7d671f2 100644 --- a/source/blender/blenlib/BLI_compiler_attrs.h +++ b/source/blender/blenlib/BLI_compiler_attrs.h @@ -98,3 +98,10 @@ #else # define ATTR_ALIGN(x) __attribute__((aligned(x))) #endif + +/* Alignment directive */ +#ifdef _WIN64 +# define ALIGN_STRUCT __declspec(align(64)) +#else +# define ALIGN_STRUCT +#endif diff --git a/source/blender/blenlib/BLI_enumerable_thread_specific.hh b/source/blender/blenlib/BLI_enumerable_thread_specific.hh new file mode 100644 index 00000000000..89be4cad848 --- /dev/null +++ b/source/blender/blenlib/BLI_enumerable_thread_specific.hh @@ -0,0 +1,73 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#pragma once + +#ifdef WITH_TBB +# include <tbb/enumerable_thread_specific.h> +#endif + +#include <atomic> +#include <mutex> + +#include "BLI_map.hh" +#include "BLI_utility_mixins.hh" + +namespace blender { + +namespace enumerable_thread_specific_utils { +inline std::atomic<int> next_id = 0; +inline thread_local int thread_id = next_id.fetch_add(1, std::memory_order_relaxed); +} // namespace enumerable_thread_specific_utils + +/** + * This is mainly a wrapper for `tbb::enumerable_thread_specific`. The wrapper is needed because we + * want to be able to build without tbb. + * + * More features of the tbb version can be wrapped when they are used. + */ +template<typename T> class EnumerableThreadSpecific : NonCopyable, NonMovable { +#ifdef WITH_TBB + + private: + tbb::enumerable_thread_specific<T> values_; + + public: + T &local() + { + return values_.local(); + } + +#else /* WITH_TBB */ + + private: + std::mutex mutex_; + /* Maps thread ids to their corresponding values. The values are not embedded in the map, so that + * their addresses do not change when the map grows. */ + Map<int, std::unique_ptr<T>> values_; + + public: + T &local() + { + const int thread_id = enumerable_thread_specific_utils::thread_id; + std::lock_guard lock{mutex_}; + return *values_.lookup_or_add_cb(thread_id, []() { return std::make_unique<T>(); }); + } + +#endif /* WITH_TBB */ +}; + +} // namespace blender diff --git a/source/blender/blenlib/BLI_fileops.h b/source/blender/blenlib/BLI_fileops.h index df80e720363..bf18dd1070e 100644 --- a/source/blender/blenlib/BLI_fileops.h +++ b/source/blender/blenlib/BLI_fileops.h @@ -87,7 +87,7 @@ typedef enum eFileAttributes { FILE_ATTR_RESTRICTED = 1 << 6, /* Protected by OS. */ FILE_ATTR_TEMPORARY = 1 << 7, /* Used for temporary storage. */ FILE_ATTR_SPARSE_FILE = 1 << 8, /* Sparse File. */ - FILE_ATTR_OFFLINE = 1 << 9, /* Data is not immediately available. */ + FILE_ATTR_OFFLINE = 1 << 9, /* Contents available after a short delay. */ FILE_ATTR_ALIAS = 1 << 10, /* Mac Alias or Windows LNK. File-based redirection. */ FILE_ATTR_REPARSE_POINT = 1 << 11, /* File has associated re-parse point. */ FILE_ATTR_SYMLINK = 1 << 12, /* Reference to another file. */ diff --git a/source/blender/blenlib/BLI_float3.hh b/source/blender/blenlib/BLI_float3.hh index cbc4d4ed366..04aae375889 100644 --- a/source/blender/blenlib/BLI_float3.hh +++ b/source/blender/blenlib/BLI_float3.hh @@ -245,6 +245,13 @@ struct float3 { return result; } + static float3 cross(const float3 &a, const float3 &b) + { + float3 result; + cross_v3_v3v3(result, a, b); + return result; + } + static float3 project(const float3 &a, const float3 &b) { float3 result; diff --git a/source/blender/blenlib/BLI_float4x4.hh b/source/blender/blenlib/BLI_float4x4.hh index f5ba91bc12c..396b0b1bd21 100644 --- a/source/blender/blenlib/BLI_float4x4.hh +++ b/source/blender/blenlib/BLI_float4x4.hh @@ -45,6 +45,37 @@ struct float4x4 { return mat; } + static float4x4 from_normalized_axis_data(const float3 location, + const float3 forward, + const float3 up) + { + BLI_ASSERT_UNIT_V3(forward); + BLI_ASSERT_UNIT_V3(up); + float4x4 matrix; + const float3 cross = float3::cross(forward, up); + matrix.values[0][0] = forward.x; + matrix.values[1][0] = cross.x; + matrix.values[2][0] = up.x; + matrix.values[3][0] = location.x; + + matrix.values[0][1] = forward.y; + matrix.values[1][1] = cross.y; + matrix.values[2][1] = up.y; + matrix.values[3][1] = location.y; + + matrix.values[0][2] = forward.z; + matrix.values[1][2] = cross.z; + matrix.values[2][2] = up.z; + matrix.values[3][2] = location.z; + + matrix.values[0][3] = 0.0f; + matrix.values[1][3] = 0.0f; + matrix.values[2][3] = 0.0f; + matrix.values[3][3] = 1.0f; + + return matrix; + } + static float4x4 identity() { float4x4 mat; @@ -116,6 +147,19 @@ struct float4x4 { return scale; } + void apply_scale(const float scale) + { + values[0][0] *= scale; + values[0][1] *= scale; + values[0][2] *= scale; + values[1][0] *= scale; + values[1][1] *= scale; + values[1][2] *= scale; + values[2][0] *= scale; + values[2][1] *= scale; + values[2][2] *= scale; + } + float4x4 inverted() const { float4x4 result; diff --git a/source/blender/blenlib/BLI_hash.hh b/source/blender/blenlib/BLI_hash.hh index 4022c2baa1f..fbed321534c 100644 --- a/source/blender/blenlib/BLI_hash.hh +++ b/source/blender/blenlib/BLI_hash.hh @@ -85,9 +85,12 @@ namespace blender { /** - * If there is no other specialization of #DefaultHash for a given type, try to call `hash()` on - * the value. If there is no such method, this will result in a compiler error. Usually that means - * that you have to implement a hash function using one of three strategies listed above. + * If there is no other specialization of #DefaultHash for a given type, look for a hash function + * on the type itself. Implementing a `hash()` method on a type is often significantly easier than + * specializing #DefaultHash. + * + * To support heterogeneous lookup, a type can also implement a static `hash_as(const OtherType &)` + * function. * * In the case of an enum type, the default hash is just to cast the enum value to an integer. */ @@ -95,12 +98,25 @@ template<typename T> struct DefaultHash { uint64_t operator()(const T &value) const { if constexpr (std::is_enum_v<T>) { + /* For enums use the value as hash directly. */ return (uint64_t)value; } else { + /* Try to call the `hash()` function on the value. */ + /* If this results in a compiler error, no hash function for the type has been found. */ return value.hash(); } } + + template<typename U> uint64_t operator()(const U &value) const + { + /* Try calling the static `T::hash_as(value)` function with the given value. The returned hash + * should be "compatible" with `T::hash()`. Usually that means that if `value` is converted to + * `T` its hash does not change. */ + /* If this results in a compiler error, no hash function for the heterogeneous lookup has been + * found. */ + return T::hash_as(value); + } }; /** diff --git a/source/blender/blenlib/BLI_linear_allocator.hh b/source/blender/blenlib/BLI_linear_allocator.hh index 6aa97d5c5e7..7de6bcfdd98 100644 --- a/source/blender/blenlib/BLI_linear_allocator.hh +++ b/source/blender/blenlib/BLI_linear_allocator.hh @@ -129,6 +129,21 @@ template<typename Allocator = GuardedAllocator> class LinearAllocator : NonCopya } /** + * Construct multiple instances of a type in an array. The constructor of is called with the + * given arguments. The caller is responsible for calling the destructor (and not `delete`) on + * the constructed elements. + */ + template<typename T, typename... Args> + MutableSpan<T> construct_array(int64_t size, Args &&... args) + { + MutableSpan<T> array = this->allocate_array<T>(size); + for (const int64_t i : IndexRange(size)) { + new (&array[i]) T(std::forward<Args>(args)...); + } + return array; + } + + /** * Copy the given array into a memory buffer provided by this allocator. */ template<typename T> MutableSpan<T> construct_array_copy(Span<T> src) diff --git a/source/blender/blenlib/BLI_map.hh b/source/blender/blenlib/BLI_map.hh index 9fa69853e44..4d254960f34 100644 --- a/source/blender/blenlib/BLI_map.hh +++ b/source/blender/blenlib/BLI_map.hh @@ -247,11 +247,11 @@ class Map { { this->add_new_as(std::move(key), std::move(value)); } - template<typename ForwardKey, typename ForwardValue> - void add_new_as(ForwardKey &&key, ForwardValue &&value) + template<typename ForwardKey, typename... ForwardValue> + void add_new_as(ForwardKey &&key, ForwardValue &&... value) { this->add_new__impl( - std::forward<ForwardKey>(key), std::forward<ForwardValue>(value), hash_(key)); + std::forward<ForwardKey>(key), hash_(key), std::forward<ForwardValue>(value)...); } /** @@ -277,11 +277,11 @@ class Map { { return this->add_as(std::move(key), std::move(value)); } - template<typename ForwardKey, typename ForwardValue> - bool add_as(ForwardKey &&key, ForwardValue &&value) + template<typename ForwardKey, typename... ForwardValue> + bool add_as(ForwardKey &&key, ForwardValue &&... value) { return this->add__impl( - std::forward<ForwardKey>(key), std::forward<ForwardValue>(value), hash_(key)); + std::forward<ForwardKey>(key), hash_(key), std::forward<ForwardValue>(value)...); } /** @@ -307,11 +307,11 @@ class Map { { return this->add_overwrite_as(std::move(key), std::move(value)); } - template<typename ForwardKey, typename ForwardValue> - bool add_overwrite_as(ForwardKey &&key, ForwardValue &&value) + template<typename ForwardKey, typename... ForwardValue> + bool add_overwrite_as(ForwardKey &&key, ForwardValue &&... value) { return this->add_overwrite__impl( - std::forward<ForwardKey>(key), std::forward<ForwardValue>(value), hash_(key)); + std::forward<ForwardKey>(key), hash_(key), std::forward<ForwardValue>(value)...); } /** @@ -413,12 +413,12 @@ class Map { { return this->pop_default_as(key, std::move(default_value)); } - template<typename ForwardKey, typename ForwardValue> - Value pop_default_as(const ForwardKey &key, ForwardValue &&default_value) + template<typename ForwardKey, typename... ForwardValue> + Value pop_default_as(const ForwardKey &key, ForwardValue &&... default_value) { Slot *slot = this->lookup_slot_ptr(key, hash_(key)); if (slot == nullptr) { - return std::forward<ForwardValue>(default_value); + return Value(std::forward<ForwardValue>(default_value)...); } Value value = std::move(*slot->value()); slot->remove(); @@ -525,15 +525,15 @@ class Map { { return this->lookup_default_as(key, default_value); } - template<typename ForwardKey, typename ForwardValue> - Value lookup_default_as(const ForwardKey &key, ForwardValue &&default_value) const + template<typename ForwardKey, typename... ForwardValue> + Value lookup_default_as(const ForwardKey &key, ForwardValue &&... default_value) const { const Value *ptr = this->lookup_ptr_as(key); if (ptr != nullptr) { return *ptr; } else { - return std::forward<ForwardValue>(default_value); + return Value(std::forward<ForwardValue>(default_value)...); } } @@ -557,11 +557,11 @@ class Map { { return this->lookup_or_add_as(std::move(key), std::move(value)); } - template<typename ForwardKey, typename ForwardValue> - Value &lookup_or_add_as(ForwardKey &&key, ForwardValue &&value) + template<typename ForwardKey, typename... ForwardValue> + Value &lookup_or_add_as(ForwardKey &&key, ForwardValue &&... value) { return this->lookup_or_add__impl( - std::forward<ForwardKey>(key), std::forward<ForwardValue>(value), hash_(key)); + std::forward<ForwardKey>(key), hash_(key), std::forward<ForwardValue>(value)...); } /** @@ -605,6 +605,37 @@ class Map { } /** + * Returns the key that is stored in the set that compares equal to the given key. This invokes + * undefined behavior when the key is not in the map. + */ + const Key &lookup_key(const Key &key) const + { + return this->lookup_key_as(key); + } + template<typename ForwardKey> const Key &lookup_key_as(const ForwardKey &key) const + { + const Slot &slot = this->lookup_slot(key, hash_(key)); + return *slot.key(); + } + + /** + * Returns a pointer to the key that is stored in the map that compares equal to the given key. + * If the key is not in the map, null is returned. + */ + const Key *lookup_key_ptr(const Key &key) const + { + return this->lookup_key_ptr_as(key); + } + template<typename ForwardKey> const Key *lookup_key_ptr_as(const ForwardKey &key) const + { + const Slot *slot = this->lookup_slot_ptr(key, hash_(key)); + if (slot == nullptr) { + return nullptr; + } + return slot->key(); + } + + /** * Calls the provided callback for every key-value-pair in the map. The callback is expected * to take a `const Key &` as first and a `const Value &` as second parameter. */ @@ -621,19 +652,23 @@ class Map { } } - /** - * A utility iterator that reduces the amount of code when implementing the actual iterators. - * This uses the "curiously recurring template pattern" (CRTP). - */ - template<typename SubIterator> struct BaseIterator { + /* Common base class for all iterators below. */ + struct BaseIterator { + public: using iterator_category = std::forward_iterator_tag; using difference_type = std::ptrdiff_t; + protected: + /* We could have separate base iterators for const and non-const iterators, but that would add + * more complexity than benefits right now. */ Slot *slots_; int64_t total_slots_; int64_t current_slot_; - BaseIterator(const Slot *slots, int64_t total_slots, int64_t current_slot) + friend Map; + + public: + BaseIterator(const Slot *slots, const int64_t total_slots, const int64_t current_slot) : slots_(const_cast<Slot *>(slots)), total_slots_(total_slots), current_slot_(current_slot) { } @@ -667,11 +702,29 @@ class Map { return !(a != b); } + protected: + Slot ¤t_slot() const + { + return slots_[current_slot_]; + } + }; + + /** + * A utility iterator that reduces the amount of code when implementing the actual iterators. + * This uses the "curiously recurring template pattern" (CRTP). + */ + template<typename SubIterator> class BaseIteratorRange : public BaseIterator { + public: + BaseIteratorRange(const Slot *slots, int64_t total_slots, int64_t current_slot) + : BaseIterator(slots, total_slots, current_slot) + { + } + SubIterator begin() const { - for (int64_t i = 0; i < total_slots_; i++) { - if (slots_[i].is_occupied()) { - return SubIterator(slots_, total_slots_, i); + for (int64_t i = 0; i < this->total_slots_; i++) { + if (this->slots_[i].is_occupied()) { + return SubIterator(this->slots_, this->total_slots_, i); } } return this->end(); @@ -679,23 +732,18 @@ class Map { SubIterator end() const { - return SubIterator(slots_, total_slots_, total_slots_); - } - - Slot ¤t_slot() const - { - return slots_[current_slot_]; + return SubIterator(this->slots_, this->total_slots_, this->total_slots_); } }; - class KeyIterator final : public BaseIterator<KeyIterator> { + class KeyIterator final : public BaseIteratorRange<KeyIterator> { public: using value_type = Key; using pointer = const Key *; using reference = const Key &; KeyIterator(const Slot *slots, int64_t total_slots, int64_t current_slot) - : BaseIterator<KeyIterator>(slots, total_slots, current_slot) + : BaseIteratorRange<KeyIterator>(slots, total_slots, current_slot) { } @@ -705,14 +753,14 @@ class Map { } }; - class ValueIterator final : public BaseIterator<ValueIterator> { + class ValueIterator final : public BaseIteratorRange<ValueIterator> { public: using value_type = Value; using pointer = const Value *; using reference = const Value &; ValueIterator(const Slot *slots, int64_t total_slots, int64_t current_slot) - : BaseIterator<ValueIterator>(slots, total_slots, current_slot) + : BaseIteratorRange<ValueIterator>(slots, total_slots, current_slot) { } @@ -722,14 +770,14 @@ class Map { } }; - class MutableValueIterator final : public BaseIterator<MutableValueIterator> { + class MutableValueIterator final : public BaseIteratorRange<MutableValueIterator> { public: using value_type = Value; using pointer = Value *; using reference = Value &; - MutableValueIterator(const Slot *slots, int64_t total_slots, int64_t current_slot) - : BaseIterator<MutableValueIterator>(slots, total_slots, current_slot) + MutableValueIterator(Slot *slots, int64_t total_slots, int64_t current_slot) + : BaseIteratorRange<MutableValueIterator>(slots, total_slots, current_slot) { } @@ -754,14 +802,14 @@ class Map { } }; - class ItemIterator final : public BaseIterator<ItemIterator> { + class ItemIterator final : public BaseIteratorRange<ItemIterator> { public: using value_type = Item; using pointer = Item *; using reference = Item &; ItemIterator(const Slot *slots, int64_t total_slots, int64_t current_slot) - : BaseIterator<ItemIterator>(slots, total_slots, current_slot) + : BaseIteratorRange<ItemIterator>(slots, total_slots, current_slot) { } @@ -772,14 +820,14 @@ class Map { } }; - class MutableItemIterator final : public BaseIterator<MutableItemIterator> { + class MutableItemIterator final : public BaseIteratorRange<MutableItemIterator> { public: using value_type = MutableItem; using pointer = MutableItem *; using reference = MutableItem &; - MutableItemIterator(const Slot *slots, int64_t total_slots, int64_t current_slot) - : BaseIterator<MutableItemIterator>(slots, total_slots, current_slot) + MutableItemIterator(Slot *slots, int64_t total_slots, int64_t current_slot) + : BaseIteratorRange<MutableItemIterator>(slots, total_slots, current_slot) { } @@ -840,6 +888,19 @@ class Map { } /** + * Remove the key-value-pair that the iterator is currently pointing at. + * It is valid to call this method while iterating over the map. However, after this method has + * been called, the removed element must not be accessed anymore. + */ + void remove(const BaseIterator &iterator) + { + Slot &slot = iterator.current_slot(); + BLI_assert(slot.is_occupied()); + slot.remove(); + removed_slots_++; + } + + /** * Print common statistics like size and collision count. This is useful for debugging purposes. */ void print_stats(StringRef name = "") const @@ -982,7 +1043,7 @@ class Map { SLOT_PROBING_BEGIN (ProbingStrategy, hash, new_slot_mask, slot_index) { Slot &slot = new_slots[slot_index]; if (slot.is_empty()) { - slot.occupy(std::move(*old_slot.key()), std::move(*old_slot.value()), hash); + slot.occupy(std::move(*old_slot.key()), hash, std::move(*old_slot.value())); return; } } @@ -996,8 +1057,8 @@ class Map { new (this) Map(NoExceptConstructor(), allocator); } - template<typename ForwardKey, typename ForwardValue> - void add_new__impl(ForwardKey &&key, ForwardValue &&value, uint64_t hash) + template<typename ForwardKey, typename... ForwardValue> + void add_new__impl(ForwardKey &&key, uint64_t hash, ForwardValue &&... value) { BLI_assert(!this->contains_as(key)); @@ -1005,7 +1066,7 @@ class Map { MAP_SLOT_PROBING_BEGIN (hash, slot) { if (slot.is_empty()) { - slot.occupy(std::forward<ForwardKey>(key), std::forward<ForwardValue>(value), hash); + slot.occupy(std::forward<ForwardKey>(key), hash, std::forward<ForwardValue>(value)...); occupied_and_removed_slots_++; return; } @@ -1013,14 +1074,14 @@ class Map { MAP_SLOT_PROBING_END(); } - template<typename ForwardKey, typename ForwardValue> - bool add__impl(ForwardKey &&key, ForwardValue &&value, uint64_t hash) + template<typename ForwardKey, typename... ForwardValue> + bool add__impl(ForwardKey &&key, uint64_t hash, ForwardValue &&... value) { this->ensure_can_add(); MAP_SLOT_PROBING_BEGIN (hash, slot) { if (slot.is_empty()) { - slot.occupy(std::forward<ForwardKey>(key), std::forward<ForwardValue>(value), hash); + slot.occupy(std::forward<ForwardKey>(key), hash, std::forward<ForwardValue>(value)...); occupied_and_removed_slots_++; return true; } @@ -1075,7 +1136,7 @@ class Map { MAP_SLOT_PROBING_BEGIN (hash, slot) { if (slot.is_empty()) { - slot.occupy(std::forward<ForwardKey>(key), create_value(), hash); + slot.occupy(std::forward<ForwardKey>(key), hash, create_value()); occupied_and_removed_slots_++; return *slot.value(); } @@ -1086,14 +1147,14 @@ class Map { MAP_SLOT_PROBING_END(); } - template<typename ForwardKey, typename ForwardValue> - Value &lookup_or_add__impl(ForwardKey &&key, ForwardValue &&value, uint64_t hash) + template<typename ForwardKey, typename... ForwardValue> + Value &lookup_or_add__impl(ForwardKey &&key, uint64_t hash, ForwardValue &&... value) { this->ensure_can_add(); MAP_SLOT_PROBING_BEGIN (hash, slot) { if (slot.is_empty()) { - slot.occupy(std::forward<ForwardKey>(key), std::forward<ForwardValue>(value), hash); + slot.occupy(std::forward<ForwardKey>(key), hash, std::forward<ForwardValue>(value)...); occupied_and_removed_slots_++; return *slot.value(); } @@ -1104,15 +1165,15 @@ class Map { MAP_SLOT_PROBING_END(); } - template<typename ForwardKey, typename ForwardValue> - bool add_overwrite__impl(ForwardKey &&key, ForwardValue &&value, uint64_t hash) + template<typename ForwardKey, typename... ForwardValue> + bool add_overwrite__impl(ForwardKey &&key, uint64_t hash, ForwardValue &&... value) { auto create_func = [&](Value *ptr) { - new (static_cast<void *>(ptr)) Value(std::forward<ForwardValue>(value)); + new (static_cast<void *>(ptr)) Value(std::forward<ForwardValue>(value)...); return true; }; auto modify_func = [&](Value *ptr) { - *ptr = std::forward<ForwardValue>(value); + *ptr = Value(std::forward<ForwardValue>(value)...); return false; }; return this->add_or_modify__impl( @@ -1221,16 +1282,18 @@ template<typename Key, typename Value> class StdUnorderedMapWrapper { map_.reserve(n); } - template<typename ForwardKey, typename ForwardValue> - void add_new(ForwardKey &&key, ForwardValue &&value) + template<typename ForwardKey, typename... ForwardValue> + void add_new(ForwardKey &&key, ForwardValue &&... value) { - map_.insert({std::forward<ForwardKey>(key), std::forward<ForwardValue>(value)}); + map_.insert({std::forward<ForwardKey>(key), Value(std::forward<ForwardValue>(value)...)}); } - template<typename ForwardKey, typename ForwardValue> - bool add(ForwardKey &&key, ForwardValue &&value) + template<typename ForwardKey, typename... ForwardValue> + bool add(ForwardKey &&key, ForwardValue &&... value) { - return map_.insert({std::forward<ForwardKey>(key), std::forward<ForwardValue>(value)}).second; + return map_ + .insert({std::forward<ForwardKey>(key), Value(std::forward<ForwardValue>(value)...)}) + .second; } bool contains(const Key &key) const diff --git a/source/blender/blenlib/BLI_map_slots.hh b/source/blender/blenlib/BLI_map_slots.hh index c0cb3091a8e..1b4ca11af41 100644 --- a/source/blender/blenlib/BLI_map_slots.hh +++ b/source/blender/blenlib/BLI_map_slots.hh @@ -195,11 +195,11 @@ template<typename Key, typename Value> class SimpleMapSlot { * Change the state of this slot from empty/removed to occupied. The key/value has to be * constructed by calling the constructor with the given key/value as parameter. */ - template<typename ForwardKey, typename ForwardValue> - void occupy(ForwardKey &&key, ForwardValue &&value, uint64_t hash) + template<typename ForwardKey, typename... ForwardValue> + void occupy(ForwardKey &&key, uint64_t hash, ForwardValue &&... value) { BLI_assert(!this->is_occupied()); - new (&value_buffer_) Value(std::forward<ForwardValue>(value)); + new (&value_buffer_) Value(std::forward<ForwardValue>(value)...); this->occupy_no_value(std::forward<ForwardKey>(key), hash); state_ = Occupied; } @@ -315,12 +315,12 @@ template<typename Key, typename Value, typename KeyInfo> class IntrusiveMapSlot return is_equal(key, key_); } - template<typename ForwardKey, typename ForwardValue> - void occupy(ForwardKey &&key, ForwardValue &&value, uint64_t hash) + template<typename ForwardKey, typename... ForwardValue> + void occupy(ForwardKey &&key, uint64_t hash, ForwardValue &&... value) { BLI_assert(!this->is_occupied()); BLI_assert(KeyInfo::is_not_empty_or_removed(key)); - new (&value_buffer_) Value(std::forward<ForwardValue>(value)); + new (&value_buffer_) Value(std::forward<ForwardValue>(value)...); this->occupy_no_value(std::forward<ForwardKey>(key), hash); } diff --git a/source/blender/blenlib/BLI_math_color.h b/source/blender/blenlib/BLI_math_color.h index 26d2f1fcb29..28257ba418a 100644 --- a/source/blender/blenlib/BLI_math_color.h +++ b/source/blender/blenlib/BLI_math_color.h @@ -105,8 +105,8 @@ int constrain_rgb(float *r, float *g, float *b); void minmax_rgb(short c[3]); void hsv_clamp_v(float hsv[3], float v_max); -void rgb_float_set_hue_float_offset(float *rgb, float hue_offset); -void rgb_byte_set_hue_float_offset(unsigned char *rgb, float hue_offset); +void rgb_float_set_hue_float_offset(float rgb[3], float hue_offset); +void rgb_byte_set_hue_float_offset(unsigned char rgb[3], float hue_offset); void rgb_uchar_to_float(float r_col[3], const unsigned char col_ub[3]); void rgba_uchar_to_float(float r_col[4], const unsigned char col_ub[4]); @@ -143,6 +143,7 @@ MINLINE void rgba_uchar_args_test_set(unsigned char col[4], MINLINE void cpack_cpy_3ub(unsigned char r_col[3], const unsigned int pack); void blackbody_temperature_to_rgb_table(float *r_table, int width, float min, float max); +void wavelength_to_xyz_table(float *r_table, int width); /********* lift/gamma/gain / ASC-CDL conversion ***********/ diff --git a/source/blender/blenlib/BLI_math_geom.h b/source/blender/blenlib/BLI_math_geom.h index d767c2924d1..c744c5d13d3 100644 --- a/source/blender/blenlib/BLI_math_geom.h +++ b/source/blender/blenlib/BLI_math_geom.h @@ -119,10 +119,10 @@ float dist_signed_to_plane_v3(const float p[3], const float plane[4]); float dist_to_plane_v3(const float p[3], const float plane[4]); /* plane3 versions */ -float dist_signed_squared_to_plane3_v3(const float p[3], const float plane[4]); -float dist_squared_to_plane3_v3(const float p[3], const float plane[4]); -float dist_signed_to_plane3_v3(const float p[3], const float plane[4]); -float dist_to_plane3_v3(const float p[3], const float plane[4]); +float dist_signed_squared_to_plane3_v3(const float p[3], const float plane[3]); +float dist_squared_to_plane3_v3(const float p[3], const float plane[3]); +float dist_signed_to_plane3_v3(const float p[3], const float plane[3]); +float dist_to_plane3_v3(const float p[3], const float plane[3]); float dist_squared_to_line_segment_v3(const float p[3], const float l1[3], const float l2[3]); float dist_to_line_segment_v3(const float p[3], const float l1[3], const float l2[3]); @@ -778,7 +778,7 @@ MINLINE float dot_shsh(const float a[9], const float b[9]); MINLINE float eval_shv3(float r[9], const float v[3]); MINLINE float diffuse_shv3(float r[9], const float v[3]); MINLINE void vec_fac_to_sh(float r[9], const float v[3], const float f); -MINLINE void madd_sh_shfl(float r[9], const float sh[3], const float f); +MINLINE void madd_sh_shfl(float r[9], const float sh[9], const float f); /********************************* Form Factor *******************************/ diff --git a/source/blender/blenlib/BLI_math_matrix.h b/source/blender/blenlib/BLI_math_matrix.h index 378095589e8..54df88ca541 100644 --- a/source/blender/blenlib/BLI_math_matrix.h +++ b/source/blender/blenlib/BLI_math_matrix.h @@ -362,7 +362,7 @@ void loc_quat_size_to_mat4(float R[4][4], const float size[3]); void loc_axisangle_size_to_mat4(float R[4][4], const float loc[3], - const float axis[4], + const float axis[3], const float angle, const float size[3]); diff --git a/source/blender/blenlib/BLI_math_solvers.h b/source/blender/blenlib/BLI_math_solvers.h index 13481e27e2a..39a79efc7e2 100644 --- a/source/blender/blenlib/BLI_math_solvers.h +++ b/source/blender/blenlib/BLI_math_solvers.h @@ -41,7 +41,7 @@ bool BLI_eigen_solve_selfadjoint_m3(const float m3[3][3], float r_eigen_values[3], float r_eigen_vectors[3][3]); -void BLI_svd_m3(const float m3[3][3], float r_U[3][3], float r_S[], float r_V[3][3]); +void BLI_svd_m3(const float m3[3][3], float r_U[3][3], float r_S[3], float r_V[3][3]); /***************************** Simple Solvers ************************************/ diff --git a/source/blender/blenlib/BLI_math_vector.h b/source/blender/blenlib/BLI_math_vector.h index bb1e1a1c38d..b43f86af670 100644 --- a/source/blender/blenlib/BLI_math_vector.h +++ b/source/blender/blenlib/BLI_math_vector.h @@ -146,7 +146,7 @@ MINLINE void mul_v3_v3(float r[3], const float a[3]); MINLINE void mul_v3_v3v3(float r[3], const float a[3], const float b[3]); MINLINE void mul_v4_fl(float r[4], float f); MINLINE void mul_v4_v4(float r[4], const float a[4]); -MINLINE void mul_v4_v4fl(float r[3], const float a[4], float f); +MINLINE void mul_v4_v4fl(float r[4], const float a[4], float f); MINLINE void mul_v2_v2_cw(float r[2], const float mat[2], const float vec[2]); MINLINE void mul_v2_v2_ccw(float r[2], const float mat[2], const float vec[2]); MINLINE float mul_project_m4_v3_zfac(const float mat[4][4], @@ -177,7 +177,7 @@ MINLINE void negate_v2_v2(float r[2], const float a[2]); MINLINE void negate_v3(float r[3]); MINLINE void negate_v3_v3(float r[3], const float a[3]); MINLINE void negate_v4(float r[4]); -MINLINE void negate_v4_v4(float r[4], const float a[3]); +MINLINE void negate_v4_v4(float r[4], const float a[4]); MINLINE void negate_v3_short(short r[3]); MINLINE void negate_v3_db(double r[3]); @@ -323,11 +323,11 @@ void flip_v2_v2v2(float v[2], const float v1[2], const float v2[2]); /********************************* Comparison ********************************/ -MINLINE bool is_zero_v2(const float a[3]) ATTR_WARN_UNUSED_RESULT; +MINLINE bool is_zero_v2(const float a[2]) ATTR_WARN_UNUSED_RESULT; MINLINE bool is_zero_v3(const float a[3]) ATTR_WARN_UNUSED_RESULT; MINLINE bool is_zero_v4(const float a[4]) ATTR_WARN_UNUSED_RESULT; -bool is_finite_v2(const float a[3]) ATTR_WARN_UNUSED_RESULT; +bool is_finite_v2(const float a[2]) ATTR_WARN_UNUSED_RESULT; bool is_finite_v3(const float a[3]) ATTR_WARN_UNUSED_RESULT; bool is_finite_v4(const float a[4]) ATTR_WARN_UNUSED_RESULT; diff --git a/source/blender/blenlib/BLI_span.hh b/source/blender/blenlib/BLI_span.hh index fe511793c46..c3876d4eaf8 100644 --- a/source/blender/blenlib/BLI_span.hh +++ b/source/blender/blenlib/BLI_span.hh @@ -94,7 +94,7 @@ template<typename T> class Span { using iterator = const T *; using size_type = int64_t; - private: + protected: const T *data_ = nullptr; int64_t size_ = 0; @@ -477,7 +477,7 @@ template<typename T> class MutableSpan { using iterator = T *; using size_type = int64_t; - private: + protected: T *data_; int64_t size_; @@ -662,6 +662,16 @@ template<typename T> class MutableSpan { } /** + * Return a reference to the first element in the array. This invokes undefined behavior when the + * array is empty. + */ + constexpr T &first() const + { + BLI_assert(size_ > 0); + return data_[0]; + } + + /** * Returns a reference to the last element. This invokes undefined behavior when the array is * empty. */ diff --git a/source/blender/blenlib/BLI_stack.hh b/source/blender/blenlib/BLI_stack.hh index 19f77078c5b..d66316a95d9 100644 --- a/source/blender/blenlib/BLI_stack.hh +++ b/source/blender/blenlib/BLI_stack.hh @@ -232,13 +232,14 @@ class Stack { { this->push_as(std::move(value)); } - template<typename ForwardT> void push_as(ForwardT &&value) + /* This is similar to `std::stack::emplace`. */ + template<typename... ForwardT> void push_as(ForwardT &&... value) { if (top_ == top_chunk_->capacity_end) { this->activate_next_chunk(1); } try { - new (top_) T(std::forward<ForwardT>(value)); + new (top_) T(std::forward<ForwardT>(value)...); top_++; size_++; } diff --git a/source/blender/blenlib/BLI_vector.hh b/source/blender/blenlib/BLI_vector.hh index 328d623787b..8bea2584ca7 100644 --- a/source/blender/blenlib/BLI_vector.hh +++ b/source/blender/blenlib/BLI_vector.hh @@ -437,13 +437,17 @@ class Vector { */ void append(const T &value) { - this->ensure_space_for_one(); - this->append_unchecked(value); + this->append_as(value); } void append(T &&value) { + this->append_as(std::move(value)); + } + /* This is similar to `std::vector::emplace_back`. */ + template<typename... ForwardValue> void append_as(ForwardValue &&... value) + { this->ensure_space_for_one(); - this->append_unchecked(std::move(value)); + this->append_unchecked_as(std::forward<ForwardValue>(value)...); } /** @@ -474,10 +478,18 @@ class Vector { * behavior when not enough capacity has been reserved beforehand. Only use this in performance * critical code. */ - template<typename ForwardT> void append_unchecked(ForwardT &&value) + void append_unchecked(const T &value) + { + this->append_unchecked_as(value); + } + void append_unchecked(T &&value) + { + this->append_unchecked_as(std::move(value)); + } + template<typename... ForwardT> void append_unchecked_as(ForwardT &&... value) { BLI_assert(end_ < capacity_end_); - new (end_) T(std::forward<ForwardT>(value)); + new (end_) T(std::forward<ForwardT>(value)...); end_++; UPDATE_VECTOR_SIZE(this); } @@ -657,6 +669,21 @@ class Vector { } /** + * Return a reference to the first element in the vector. + * This invokes undefined behavior when the vector is empty. + */ + const T &first() const + { + BLI_assert(this->size() > 0); + return *begin_; + } + T &first() + { + BLI_assert(this->size() > 0); + return *begin_; + } + + /** * Return how many values are currently stored in the vector. */ int64_t size() const @@ -713,11 +740,12 @@ class Vector { BLI_assert(index >= 0); BLI_assert(index < this->size()); T *element_to_remove = begin_ + index; - if (element_to_remove < end_) { - *element_to_remove = std::move(*(end_ - 1)); + T *last_element = end_ - 1; + if (element_to_remove < last_element) { + *element_to_remove = std::move(*last_element); } - end_--; - end_->~T(); + end_ = last_element; + last_element->~T(); UPDATE_VECTOR_SIZE(this); } diff --git a/source/blender/blenlib/BLI_vector_set.hh b/source/blender/blenlib/BLI_vector_set.hh index 14b38d564cb..567e4fd8128 100644 --- a/source/blender/blenlib/BLI_vector_set.hh +++ b/source/blender/blenlib/BLI_vector_set.hh @@ -398,6 +398,55 @@ class VectorSet { } /** + * Return the index of the key in the vector. If the key is not in the set, add it and return its + * index. + */ + int64_t index_of_or_add(const Key &key) + { + return this->index_of_or_add_as(key); + } + int64_t index_of_or_add(Key &&key) + { + return this->index_of_or_add_as(std::move(key)); + } + template<typename ForwardKey> int64_t index_of_or_add_as(ForwardKey &&key) + { + return this->index_of_or_add__impl(std::forward<ForwardKey>(key), hash_(key)); + } + + /** + * Returns the key that is stored in the vector set that compares equal to the given key. This + * invokes undefined behavior when the key is not in the set. + */ + const Key &lookup_key(const Key &key) const + { + return this->lookup_key_as(key); + } + template<typename ForwardKey> const Key &lookup_key_as(const ForwardKey &key) const + { + const Key *key_ptr = this->lookup_key_ptr_as(key); + BLI_assert(key_ptr != nullptr); + return *key_ptr; + } + + /** + * Returns a pointer to the key that is stored in the vector set that compares equal to the given + * key. If the key is not in the set, null is returned. + */ + const Key *lookup_key_ptr(const Key &key) const + { + return this->lookup_key_ptr_as(key); + } + template<typename ForwardKey> const Key *lookup_key_ptr_as(const ForwardKey &key) const + { + const int64_t index = this->index_of_try__impl(key, hash_(key)); + if (index >= 0) { + return keys_ + index; + } + return nullptr; + } + + /** * Get a pointer to the beginning of the array containing all keys. */ const Key *data() const @@ -484,6 +533,14 @@ class VectorSet { } /** + * Remove all keys from the vector set. + */ + void clear() + { + this->noexcept_reset(); + } + + /** * Get the number of collisions that the probing strategy has to go through to find the key or * determine that it is not in the set. */ @@ -652,6 +709,26 @@ class VectorSet { VECTOR_SET_SLOT_PROBING_END(); } + template<typename ForwardKey> + int64_t index_of_or_add__impl(ForwardKey &&key, const uint64_t hash) + { + this->ensure_can_add(); + + VECTOR_SET_SLOT_PROBING_BEGIN (hash, slot) { + if (slot.contains(key, is_equal_, hash, keys_)) { + return slot.index(); + } + if (slot.is_empty()) { + const int64_t index = this->size(); + new (keys_ + index) Key(std::forward<ForwardKey>(key)); + slot.occupy(index, hash); + occupied_and_removed_slots_++; + return index; + } + } + VECTOR_SET_SLOT_PROBING_END(); + } + Key pop__impl() { BLI_assert(this->size() > 0); diff --git a/source/blender/blenlib/BLI_virtual_array.hh b/source/blender/blenlib/BLI_virtual_array.hh index f9b0aaa7de6..1c02bce8411 100644 --- a/source/blender/blenlib/BLI_virtual_array.hh +++ b/source/blender/blenlib/BLI_virtual_array.hh @@ -37,6 +37,8 @@ * see of the increased compile time and binary size is worth it. */ +#include "BLI_array.hh" +#include "BLI_index_mask.hh" #include "BLI_span.hh" namespace blender { @@ -71,6 +73,11 @@ template<typename T> class VArray { return size_ == 0; } + IndexRange index_range() const + { + return IndexRange(size_); + } + /* Returns true when the virtual array is stored as a span internally. */ bool is_span() const { @@ -82,13 +89,13 @@ template<typename T> class VArray { /* Returns the internally used span of the virtual array. This invokes undefined behavior is the * virtual array is not stored as a span internally. */ - Span<T> get_span() const + Span<T> get_internal_span() const { BLI_assert(this->is_span()); if (size_ == 0) { return {}; } - return this->get_span_impl(); + return this->get_internal_span_impl(); } /* Returns true when the virtual array returns the same value for every index. */ @@ -102,20 +109,46 @@ template<typename T> class VArray { /* Returns the value that is returned for every index. This invokes undefined behavior if the * virtual array would not return the same value for every index. */ - T get_single() const + T get_internal_single() const { BLI_assert(this->is_single()); if (size_ == 1) { return this->get(0); } - return this->get_single_impl(); + return this->get_internal_single_impl(); } + /* Get the element at a specific index. Note that this operator cannot be used to assign values + * to an index, because the return value is not a reference. */ T operator[](const int64_t index) const { return this->get(index); } + /* Copy the entire virtual array into a span. */ + void materialize(MutableSpan<T> r_span) const + { + this->materialize(IndexMask(size_), r_span); + } + + /* Copy some indices of the virtual array into a span. */ + void materialize(IndexMask mask, MutableSpan<T> r_span) const + { + BLI_assert(mask.min_array_size() <= size_); + this->materialize_impl(mask, r_span); + } + + void materialize_to_uninitialized(MutableSpan<T> r_span) const + { + this->materialize_to_uninitialized(IndexMask(size_), r_span); + } + + void materialize_to_uninitialized(IndexMask mask, MutableSpan<T> r_span) const + { + BLI_assert(mask.min_array_size() <= size_); + this->materialize_to_uninitialized_impl(mask, r_span); + } + protected: virtual T get_impl(const int64_t index) const = 0; @@ -124,7 +157,7 @@ template<typename T> class VArray { return false; } - virtual Span<T> get_span_impl() const + virtual Span<T> get_internal_span_impl() const { BLI_assert_unreachable(); return {}; @@ -135,56 +168,196 @@ template<typename T> class VArray { return false; } - virtual T get_single_impl() const + virtual T get_internal_single_impl() const { /* Provide a default implementation, so that subclasses don't have to provide it. This method * should never be called because `is_single_impl` returns false by default. */ BLI_assert_unreachable(); return T(); } + + virtual void materialize_impl(IndexMask mask, MutableSpan<T> r_span) const + { + T *dst = r_span.data(); + if (this->is_span()) { + const T *src = this->get_internal_span().data(); + mask.foreach_index([&](const int64_t i) { dst[i] = src[i]; }); + } + else if (this->is_single()) { + const T single = this->get_internal_single(); + mask.foreach_index([&](const int64_t i) { dst[i] = single; }); + } + else { + mask.foreach_index([&](const int64_t i) { dst[i] = this->get(i); }); + } + } + + virtual void materialize_to_uninitialized_impl(IndexMask mask, MutableSpan<T> r_span) const + { + T *dst = r_span.data(); + if (this->is_span()) { + const T *src = this->get_internal_span().data(); + mask.foreach_index([&](const int64_t i) { new (dst + i) T(src[i]); }); + } + else if (this->is_single()) { + const T single = this->get_internal_single(); + mask.foreach_index([&](const int64_t i) { new (dst + i) T(single); }); + } + else { + mask.foreach_index([&](const int64_t i) { new (dst + i) T(this->get(i)); }); + } + } +}; + +/* Similar to VArray, but the elements are mutable. */ +template<typename T> class VMutableArray : public VArray<T> { + public: + VMutableArray(const int64_t size) : VArray<T>(size) + { + } + + void set(const int64_t index, T value) + { + BLI_assert(index >= 0); + BLI_assert(index < this->size_); + this->set_impl(index, std::move(value)); + } + + /* Copy the values from the source span to all elements in the virtual array. */ + void set_all(Span<T> src) + { + BLI_assert(src.size() == this->size_); + this->set_all_impl(src); + } + + MutableSpan<T> get_internal_span() + { + BLI_assert(this->is_span()); + Span<T> span = static_cast<const VArray<T> *>(this)->get_internal_span(); + return MutableSpan<T>(const_cast<T *>(span.data()), span.size()); + } + + protected: + virtual void set_impl(const int64_t index, T value) = 0; + + virtual void set_all_impl(Span<T> src) + { + if (this->is_span()) { + const MutableSpan<T> span = this->get_internal_span(); + initialized_copy_n(src.data(), this->size_, span.data()); + } + else { + const int64_t size = this->size_; + for (int64_t i = 0; i < size; i++) { + this->set(i, src[i]); + } + } + } }; +template<typename T> using VArrayPtr = std::unique_ptr<VArray<T>>; +template<typename T> using VMutableArrayPtr = std::unique_ptr<VMutableArray<T>>; + /** - * A virtual array implementation for a span. This class is final so that it can be devirtualized - * by the compiler in some cases (e.g. when #devirtualize_varray is used). + * A virtual array implementation for a span. Methods in this class are final so that it can be + * devirtualized by the compiler in some cases (e.g. when #devirtualize_varray is used). */ -template<typename T> class VArrayForSpan final : public VArray<T> { - private: - const T *data_; +template<typename T> class VArray_For_Span : public VArray<T> { + protected: + const T *data_ = nullptr; public: - VArrayForSpan(const Span<T> data) : VArray<T>(data.size()), data_(data.data()) + VArray_For_Span(const Span<T> data) : VArray<T>(data.size()), data_(data.data()) { } protected: - T get_impl(const int64_t index) const override + VArray_For_Span(const int64_t size) : VArray<T>(size) + { + } + + T get_impl(const int64_t index) const final + { + return data_[index]; + } + + bool is_span_impl() const final + { + return true; + } + + Span<T> get_internal_span_impl() const final + { + return Span<T>(data_, this->size_); + } +}; + +template<typename T> class VMutableArray_For_MutableSpan : public VMutableArray<T> { + protected: + T *data_ = nullptr; + + public: + VMutableArray_For_MutableSpan(const MutableSpan<T> data) + : VMutableArray<T>(data.size()), data_(data.data()) + { + } + + protected: + VMutableArray_For_MutableSpan(const int64_t size) : VMutableArray<T>(size) + { + } + + T get_impl(const int64_t index) const final { return data_[index]; } + void set_impl(const int64_t index, T value) final + { + data_[index] = value; + } + bool is_span_impl() const override { return true; } - Span<T> get_span_impl() const override + Span<T> get_internal_span_impl() const override { return Span<T>(data_, this->size_); } }; /** + * A variant of `VArray_For_Span` that owns the underlying data. + * The `Container` type has to implement a `size()` and `data()` method. + * The `data()` method has to return a pointer to the first element in the continuous array of + * elements. + */ +template<typename Container, typename T = typename Container::value_type> +class VArray_For_ArrayContainer : public VArray_For_Span<T> { + private: + Container container_; + + public: + VArray_For_ArrayContainer(Container container) + : VArray_For_Span<T>((int64_t)container.size()), container_(std::move(container)) + { + this->data_ = container_.data(); + } +}; + +/** * A virtual array implementation that returns the same value for every index. This class is final * so that it can be devirtualized by the compiler in some cases (e.g. when #devirtualize_varray is * used). */ -template<typename T> class VArrayForSingle final : public VArray<T> { +template<typename T> class VArray_For_Single final : public VArray<T> { private: T value_; public: - VArrayForSingle(T value, const int64_t size) : VArray<T>(size), value_(std::move(value)) + VArray_For_Single(T value, const int64_t size) : VArray<T>(size), value_(std::move(value)) { } @@ -199,7 +372,7 @@ template<typename T> class VArrayForSingle final : public VArray<T> { return this->size_ == 1; } - Span<T> get_span_impl() const override + Span<T> get_internal_span_impl() const override { return Span<T>(&value_, 1); } @@ -209,13 +382,207 @@ template<typename T> class VArrayForSingle final : public VArray<T> { return true; } - T get_single_impl() const override + T get_internal_single_impl() const override { return value_; } }; /** + * In many cases a virtual array is a span internally. In those cases, access to individual could + * be much more efficient than calling a virtual method. When the underlying virtual array is not a + * span, this class allocates a new array and copies the values over. + * + * This should be used in those cases: + * - All elements in the virtual array are accessed multiple times. + * - In most cases, the underlying virtual array is a span, so no copy is necessary to benefit + * from faster access. + * - An API is called, that does not accept virtual arrays, but only spans. + */ +template<typename T> class VArray_Span final : public Span<T> { + private: + const VArray<T> &varray_; + Array<T> owned_data_; + + public: + VArray_Span(const VArray<T> &varray) : Span<T>(), varray_(varray) + { + this->size_ = varray_.size(); + if (varray_.is_span()) { + this->data_ = varray_.get_internal_span().data(); + } + else { + owned_data_.~Array(); + new (&owned_data_) Array<T>(varray_.size(), NoInitialization{}); + varray_.materialize_to_uninitialized(owned_data_); + this->data_ = owned_data_.data(); + } + } +}; + +/** + * Same as VArray_Span, but for a mutable span. + * The important thing to note is that when changing this span, the results might not be + * immediately reflected in the underlying virtual array (only when the virtual array is a span + * internally). The #save method can be used to write all changes to the underlying virtual array, + * if necessary. + */ +template<typename T> class VMutableArray_Span final : public MutableSpan<T> { + private: + VMutableArray<T> &varray_; + Array<T> owned_data_; + bool save_has_been_called_ = false; + bool show_not_saved_warning_ = true; + + public: + /* Create a span for any virtual array. This is cheap when the virtual array is a span itself. If + * not, a new array has to be allocated as a wrapper for the underlying virtual array. */ + VMutableArray_Span(VMutableArray<T> &varray, const bool copy_values_to_span = true) + : MutableSpan<T>(), varray_(varray) + { + this->size_ = varray_.size(); + if (varray_.is_span()) { + this->data_ = varray_.get_internal_span().data(); + } + else { + if (copy_values_to_span) { + owned_data_.~Array(); + new (&owned_data_) Array<T>(varray_.size(), NoInitialization{}); + varray_.materialize_to_uninitialized(owned_data_); + } + else { + owned_data_.reinitialize(varray_.size()); + } + this->data_ = owned_data_.data(); + } + } + + ~VMutableArray_Span() + { + if (show_not_saved_warning_) { + if (!save_has_been_called_) { + std::cout << "Warning: Call `save()` to make sure that changes persist in all cases.\n"; + } + } + } + + /* Write back all values from a temporary allocated array to the underlying virtual array. */ + void save() + { + save_has_been_called_ = true; + if (this->data_ != owned_data_.data()) { + return; + } + varray_.set_all(owned_data_); + } + + void disable_not_applied_warning() + { + show_not_saved_warning_ = false; + } +}; + +/** + * This class makes it easy to create a virtual array for an existing function or lambda. The + * `GetFunc` should take a single `index` argument and return the value at that index. + */ +template<typename T, typename GetFunc> class VArray_For_Func final : public VArray<T> { + private: + GetFunc get_func_; + + public: + VArray_For_Func(const int64_t size, GetFunc get_func) + : VArray<T>(size), get_func_(std::move(get_func)) + { + } + + private: + T get_impl(const int64_t index) const override + { + return get_func_(index); + } + + void materialize_impl(IndexMask mask, MutableSpan<T> r_span) const override + { + T *dst = r_span.data(); + mask.foreach_index([&](const int64_t i) { dst[i] = get_func_(i); }); + } + + void materialize_to_uninitialized_impl(IndexMask mask, MutableSpan<T> r_span) const override + { + T *dst = r_span.data(); + mask.foreach_index([&](const int64_t i) { new (dst + i) T(get_func_(i)); }); + } +}; + +template<typename StructT, typename ElemT, ElemT (*GetFunc)(const StructT &)> +class VArray_For_DerivedSpan : public VArray<ElemT> { + private: + const StructT *data_; + + public: + VArray_For_DerivedSpan(const Span<StructT> data) : VArray<ElemT>(data.size()), data_(data.data()) + { + } + + private: + ElemT get_impl(const int64_t index) const override + { + return GetFunc(data_[index]); + } + + void materialize_impl(IndexMask mask, MutableSpan<ElemT> r_span) const override + { + ElemT *dst = r_span.data(); + mask.foreach_index([&](const int64_t i) { dst[i] = GetFunc(data_[i]); }); + } + + void materialize_to_uninitialized_impl(IndexMask mask, MutableSpan<ElemT> r_span) const override + { + ElemT *dst = r_span.data(); + mask.foreach_index([&](const int64_t i) { new (dst + i) ElemT(GetFunc(data_[i])); }); + } +}; + +template<typename StructT, + typename ElemT, + ElemT (*GetFunc)(const StructT &), + void (*SetFunc)(StructT &, ElemT)> +class VMutableArray_For_DerivedSpan : public VMutableArray<ElemT> { + private: + StructT *data_; + + public: + VMutableArray_For_DerivedSpan(const MutableSpan<StructT> data) + : VMutableArray<ElemT>(data.size()), data_(data.data()) + { + } + + private: + ElemT get_impl(const int64_t index) const override + { + return GetFunc(data_[index]); + } + + void set_impl(const int64_t index, ElemT value) override + { + SetFunc(data_[index], std::move(value)); + } + + void materialize_impl(IndexMask mask, MutableSpan<ElemT> r_span) const override + { + ElemT *dst = r_span.data(); + mask.foreach_index([&](const int64_t i) { dst[i] = GetFunc(data_[i]); }); + } + + void materialize_to_uninitialized_impl(IndexMask mask, MutableSpan<ElemT> r_span) const override + { + ElemT *dst = r_span.data(); + mask.foreach_index([&](const int64_t i) { new (dst + i) ElemT(GetFunc(data_[i])); }); + } +}; + +/** * Generate multiple versions of the given function optimized for different virtual arrays. * One has to be careful with nesting multiple devirtualizations, because that results in an * exponential number of function instantiations (increasing compile time and binary size). @@ -229,14 +596,14 @@ inline void devirtualize_varray(const VArray<T> &varray, const Func &func, bool /* Support disabling the devirtualization to simplify benchmarking. */ if (enable) { if (varray.is_single()) { - /* `VArrayForSingle` can be used for devirtualization, because it is declared `final`. */ - const VArrayForSingle<T> varray_single{varray.get_single(), varray.size()}; + /* `VArray_For_Single` can be used for devirtualization, because it is declared `final`. */ + const VArray_For_Single<T> varray_single{varray.get_internal_single(), varray.size()}; func(varray_single); return; } if (varray.is_span()) { - /* `VArrayForSpan` can be used for devirtualization, because it is declared `final`. */ - const VArrayForSpan<T> varray_span{varray.get_span()}; + /* `VArray_For_Span` can be used for devirtualization, because it is declared `final`. */ + const VArray_For_Span<T> varray_span{varray.get_internal_span()}; func(varray_span); return; } @@ -262,26 +629,26 @@ inline void devirtualize_varray2(const VArray<T1> &varray1, const bool is_single1 = varray1.is_single(); const bool is_single2 = varray2.is_single(); if (is_span1 && is_span2) { - const VArrayForSpan<T1> varray1_span{varray1.get_span()}; - const VArrayForSpan<T2> varray2_span{varray2.get_span()}; + const VArray_For_Span<T1> varray1_span{varray1.get_internal_span()}; + const VArray_For_Span<T2> varray2_span{varray2.get_internal_span()}; func(varray1_span, varray2_span); return; } if (is_span1 && is_single2) { - const VArrayForSpan<T1> varray1_span{varray1.get_span()}; - const VArrayForSingle<T2> varray2_single{varray2.get_single(), varray2.size()}; + const VArray_For_Span<T1> varray1_span{varray1.get_internal_span()}; + const VArray_For_Single<T2> varray2_single{varray2.get_internal_single(), varray2.size()}; func(varray1_span, varray2_single); return; } if (is_single1 && is_span2) { - const VArrayForSingle<T1> varray1_single{varray1.get_single(), varray1.size()}; - const VArrayForSpan<T2> varray2_span{varray2.get_span()}; + const VArray_For_Single<T1> varray1_single{varray1.get_internal_single(), varray1.size()}; + const VArray_For_Span<T2> varray2_span{varray2.get_internal_span()}; func(varray1_single, varray2_span); return; } if (is_single1 && is_single2) { - const VArrayForSingle<T1> varray1_single{varray1.get_single(), varray1.size()}; - const VArrayForSingle<T2> varray2_single{varray2.get_single(), varray2.size()}; + const VArray_For_Single<T1> varray1_single{varray1.get_internal_single(), varray1.size()}; + const VArray_For_Single<T2> varray2_single{varray2.get_internal_single(), varray2.size()}; func(varray1_single, varray2_single); return; } diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index e66049c9bd6..f3dc343ee20 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -39,6 +39,7 @@ set(SRC intern/BLI_args.c intern/BLI_array.c intern/BLI_assert.c + intern/BLI_color.cc intern/BLI_dial_2d.c intern/BLI_dynstr.c intern/BLI_filelist.c @@ -185,6 +186,7 @@ set(SRC BLI_edgehash.h BLI_endian_switch.h BLI_endian_switch_inline.h + BLI_enumerable_thread_specific.hh BLI_expr_pylike_eval.h BLI_fileops.h BLI_fileops_types.h @@ -388,6 +390,7 @@ if(WITH_GTESTS) tests/BLI_array_store_test.cc tests/BLI_array_test.cc tests/BLI_array_utils_test.cc + tests/BLI_color_test.cc tests/BLI_delaunay_2d_test.cc tests/BLI_disjoint_set_test.cc tests/BLI_edgehash_test.cc diff --git a/source/blender/blenlib/intern/BLI_color.cc b/source/blender/blenlib/intern/BLI_color.cc new file mode 100644 index 00000000000..6dcef4f4688 --- /dev/null +++ b/source/blender/blenlib/intern/BLI_color.cc @@ -0,0 +1,55 @@ +/* + * 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_color.hh" + +namespace blender { + +std::ostream &operator<<(std::ostream &stream, const eAlpha &space) +{ + switch (space) { + case eAlpha::Straight: { + stream << "Straight"; + break; + } + case eAlpha::Premultiplied: { + stream << "Premultiplied"; + break; + } + } + return stream; +} + +std::ostream &operator<<(std::ostream &stream, const eSpace &space) +{ + switch (space) { + case eSpace::Theme: { + stream << "Theme"; + break; + } + case eSpace::SceneLinear: { + stream << "SceneLinear"; + break; + } + case eSpace::SceneLinearByteEncoded: { + stream << "SceneLinearByteEncoded"; + break; + } + } + return stream; +} + +} // namespace blender diff --git a/source/blender/blenlib/intern/BLI_dial_2d.c b/source/blender/blenlib/intern/BLI_dial_2d.c index b5f1d7818cf..786fdd219a3 100644 --- a/source/blender/blenlib/intern/BLI_dial_2d.c +++ b/source/blender/blenlib/intern/BLI_dial_2d.c @@ -78,7 +78,7 @@ float BLI_dial_angle(Dial *dial, const float current_position[2]) cosval = dot_v2v2(current_direction, dial->initial_direction); sinval = cross_v2v2(current_direction, dial->initial_direction); - /* clamp to avoid nans in #acos */ + /* Clamp to avoid NAN's in #acos */ angle = atan2f(sinval, cosval); /* change of sign, we passed the 180 degree threshold. This means we need to add a turn. diff --git a/source/blender/blenlib/intern/math_color.c b/source/blender/blenlib/intern/math_color.c index 8fd2802a547..abcb3139dc7 100644 --- a/source/blender/blenlib/intern/math_color.c +++ b/source/blender/blenlib/intern/math_color.c @@ -713,3 +713,75 @@ void blackbody_temperature_to_rgb_table(float *r_table, int width, float min, fl r_table[i * 4 + 3] = 0.0f; } } + +/* ****************************** wavelength ******************************** */ +/* Wavelength to RGB. */ + +/* CIE colour matching functions xBar, yBar, and zBar for + * wavelengths from 380 through 780 nanometers, every 5 + * nanometers. + * For a wavelength lambda in this range: + * cie_colour_match[(lambda - 380) / 5][0] = xBar + * cie_colour_match[(lambda - 380) / 5][1] = yBar + * cie_colour_match[(lambda - 380) / 5][2] = zBar */ + +static float cie_colour_match[81][3] = { + {0.0014f, 0.0000f, 0.0065f}, {0.0022f, 0.0001f, 0.0105f}, {0.0042f, 0.0001f, 0.0201f}, + {0.0076f, 0.0002f, 0.0362f}, {0.0143f, 0.0004f, 0.0679f}, {0.0232f, 0.0006f, 0.1102f}, + {0.0435f, 0.0012f, 0.2074f}, {0.0776f, 0.0022f, 0.3713f}, {0.1344f, 0.0040f, 0.6456f}, + {0.2148f, 0.0073f, 1.0391f}, {0.2839f, 0.0116f, 1.3856f}, {0.3285f, 0.0168f, 1.6230f}, + {0.3483f, 0.0230f, 1.7471f}, {0.3481f, 0.0298f, 1.7826f}, {0.3362f, 0.0380f, 1.7721f}, + {0.3187f, 0.0480f, 1.7441f}, {0.2908f, 0.0600f, 1.6692f}, {0.2511f, 0.0739f, 1.5281f}, + {0.1954f, 0.0910f, 1.2876f}, {0.1421f, 0.1126f, 1.0419f}, {0.0956f, 0.1390f, 0.8130f}, + {0.0580f, 0.1693f, 0.6162f}, {0.0320f, 0.2080f, 0.4652f}, {0.0147f, 0.2586f, 0.3533f}, + {0.0049f, 0.3230f, 0.2720f}, {0.0024f, 0.4073f, 0.2123f}, {0.0093f, 0.5030f, 0.1582f}, + {0.0291f, 0.6082f, 0.1117f}, {0.0633f, 0.7100f, 0.0782f}, {0.1096f, 0.7932f, 0.0573f}, + {0.1655f, 0.8620f, 0.0422f}, {0.2257f, 0.9149f, 0.0298f}, {0.2904f, 0.9540f, 0.0203f}, + {0.3597f, 0.9803f, 0.0134f}, {0.4334f, 0.9950f, 0.0087f}, {0.5121f, 1.0000f, 0.0057f}, + {0.5945f, 0.9950f, 0.0039f}, {0.6784f, 0.9786f, 0.0027f}, {0.7621f, 0.9520f, 0.0021f}, + {0.8425f, 0.9154f, 0.0018f}, {0.9163f, 0.8700f, 0.0017f}, {0.9786f, 0.8163f, 0.0014f}, + {1.0263f, 0.7570f, 0.0011f}, {1.0567f, 0.6949f, 0.0010f}, {1.0622f, 0.6310f, 0.0008f}, + {1.0456f, 0.5668f, 0.0006f}, {1.0026f, 0.5030f, 0.0003f}, {0.9384f, 0.4412f, 0.0002f}, + {0.8544f, 0.3810f, 0.0002f}, {0.7514f, 0.3210f, 0.0001f}, {0.6424f, 0.2650f, 0.0000f}, + {0.5419f, 0.2170f, 0.0000f}, {0.4479f, 0.1750f, 0.0000f}, {0.3608f, 0.1382f, 0.0000f}, + {0.2835f, 0.1070f, 0.0000f}, {0.2187f, 0.0816f, 0.0000f}, {0.1649f, 0.0610f, 0.0000f}, + {0.1212f, 0.0446f, 0.0000f}, {0.0874f, 0.0320f, 0.0000f}, {0.0636f, 0.0232f, 0.0000f}, + {0.0468f, 0.0170f, 0.0000f}, {0.0329f, 0.0119f, 0.0000f}, {0.0227f, 0.0082f, 0.0000f}, + {0.0158f, 0.0057f, 0.0000f}, {0.0114f, 0.0041f, 0.0000f}, {0.0081f, 0.0029f, 0.0000f}, + {0.0058f, 0.0021f, 0.0000f}, {0.0041f, 0.0015f, 0.0000f}, {0.0029f, 0.0010f, 0.0000f}, + {0.0020f, 0.0007f, 0.0000f}, {0.0014f, 0.0005f, 0.0000f}, {0.0010f, 0.0004f, 0.0000f}, + {0.0007f, 0.0002f, 0.0000f}, {0.0005f, 0.0002f, 0.0000f}, {0.0003f, 0.0001f, 0.0000f}, + {0.0002f, 0.0001f, 0.0000f}, {0.0002f, 0.0001f, 0.0000f}, {0.0001f, 0.0000f, 0.0000f}, + {0.0001f, 0.0000f, 0.0000f}, {0.0001f, 0.0000f, 0.0000f}, {0.0000f, 0.0000f, 0.0000f}}; + +static void wavelength_to_xyz(float xyz[3], float lambda_nm) +{ + float ii = (lambda_nm - 380.0f) * (1.0f / 5.0f); /* Scaled 0..80. */ + int i = (int)ii; + + if (i < 0 || i >= 80) { + xyz[0] = 0.0f; + xyz[1] = 0.0f; + xyz[2] = 0.0f; + } + else { + ii -= (float)i; + const float *c = cie_colour_match[i]; + xyz[0] = c[0] + ii * (c[3] - c[0]); + xyz[1] = c[1] + ii * (c[4] - c[1]); + xyz[2] = c[2] + ii * (c[5] - c[2]); + } +} + +void wavelength_to_xyz_table(float *r_table, int width) +{ + for (int i = 0; i < width; i++) { + float temperature = 380 + 400 / (float)width * (float)i; + + float rgb[3]; + wavelength_to_xyz(rgb, temperature); + + copy_v3_v3(&r_table[i * 4], rgb); + r_table[i * 4 + 3] = 0.0f; + } +} diff --git a/source/blender/blenlib/intern/math_geom.c b/source/blender/blenlib/intern/math_geom.c index 01cda6c9e4a..508de506ae8 100644 --- a/source/blender/blenlib/intern/math_geom.c +++ b/source/blender/blenlib/intern/math_geom.c @@ -2353,7 +2353,7 @@ bool isect_planes_v3_fn( for (i_test = 0; i_test < planes_len; i_test++) { const float *np_test = planes[i_test]; if (((dot_v3v3(np_test, co_test) + np_test[3]) > eps_isect)) { - /* For low epsilon values the point could intersect it's own plane. */ + /* For low epsilon values the point could intersect its own plane. */ if (!ELEM(i_test, i, j, k)) { break; } @@ -5393,7 +5393,7 @@ void accumulate_vertex_normals_poly_v3(float **vertnos, void tangent_from_uv_v3(const float uv1[2], const float uv2[2], - const float uv3[3], + const float uv3[2], const float co1[3], const float co2[3], const float co3[3], diff --git a/source/blender/blenlib/intern/math_vector.c b/source/blender/blenlib/intern/math_vector.c index a21e0c8f092..fb7b96fde78 100644 --- a/source/blender/blenlib/intern/math_vector.c +++ b/source/blender/blenlib/intern/math_vector.c @@ -412,7 +412,7 @@ bool is_finite_v4(const float v[4]) * this would return the angle at the elbow. * * note that when v1/v2/v3 represent 3 points along a straight line - * that the angle returned will be pi (180deg), rather then 0.0 + * that the angle returned will be pi (180deg), rather than 0.0 */ float angle_v3v3v3(const float a[3], const float b[3], const float c[3]) { diff --git a/source/blender/blenlib/intern/mesh_intersect.cc b/source/blender/blenlib/intern/mesh_intersect.cc index 3558dfad81d..636209883c3 100644 --- a/source/blender/blenlib/intern/mesh_intersect.cc +++ b/source/blender/blenlib/intern/mesh_intersect.cc @@ -1744,9 +1744,7 @@ static inline std::pair<int, int> sorted_int_pair(std::pair<int, int> pair) if (pair.first <= pair.second) { return pair; } - else { - return std::pair<int, int>(pair.second, pair.first); - } + return std::pair<int, int>(pair.second, pair.first); } /** diff --git a/source/blender/blenlib/intern/storage.c b/source/blender/blenlib/intern/storage.c index cb2634e6fda..5f823396ed9 100644 --- a/source/blender/blenlib/intern/storage.c +++ b/source/blender/blenlib/intern/storage.c @@ -266,7 +266,8 @@ eFileAttributes BLI_file_attributes(const char *path) if (attr & FILE_ATTRIBUTE_SPARSE_FILE) { ret |= FILE_ATTR_SPARSE_FILE; } - if (attr & FILE_ATTRIBUTE_OFFLINE) { + if (attr & FILE_ATTRIBUTE_OFFLINE || attr & FILE_ATTRIBUTE_RECALL_ON_OPEN || + attr & FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS) { ret |= FILE_ATTR_OFFLINE; } if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { diff --git a/source/blender/blenlib/intern/timecode.c b/source/blender/blenlib/intern/timecode.c index 9586da941a4..7d7436411ac 100644 --- a/source/blender/blenlib/intern/timecode.c +++ b/source/blender/blenlib/intern/timecode.c @@ -216,10 +216,10 @@ size_t BLI_timecode_string_from_time_simple(char *str, const int hun = ((int)(fmod(time_seconds, 1.0) * 100)); if (hr) { - rlen = BLI_snprintf(str, maxncpy, "%.2d:%.2d:%.2d.%.2d", hr, min, sec, hun); + rlen = BLI_snprintf_rlen(str, maxncpy, "%.2d:%.2d:%.2d.%.2d", hr, min, sec, hun); } else { - rlen = BLI_snprintf(str, maxncpy, "%.2d:%.2d.%.2d", min, sec, hun); + rlen = BLI_snprintf_rlen(str, maxncpy, "%.2d:%.2d.%.2d", min, sec, hun); } return rlen; diff --git a/source/blender/blenlib/intern/uvproject.c b/source/blender/blenlib/intern/uvproject.c index 329c4d48fe8..093d08e643d 100644 --- a/source/blender/blenlib/intern/uvproject.c +++ b/source/blender/blenlib/intern/uvproject.c @@ -134,7 +134,7 @@ void BLI_uvproject_from_view(float target[2], /* 'rotmat' can be `obedit->obmat` when uv project is used. * 'winx' and 'winy' can be from `scene->r.xsch/ysch` */ -ProjCameraInfo *BLI_uvproject_camera_info(Object *ob, float (*rotmat)[4], float winx, float winy) +ProjCameraInfo *BLI_uvproject_camera_info(Object *ob, float rotmat[4][4], float winx, float winy) { ProjCameraInfo uci; Camera *camera = ob->data; diff --git a/source/blender/blenlib/intern/winstuff.c b/source/blender/blenlib/intern/winstuff.c index 333b6783087..3aa61d1fec5 100644 --- a/source/blender/blenlib/intern/winstuff.c +++ b/source/blender/blenlib/intern/winstuff.c @@ -94,9 +94,9 @@ void BLI_windows_register_blend_extension(const bool background) GetModuleFileName(0, BlPath, MAX_PATH); /* Replace the actual app name with the wrapper. */ - blender_app = strstr(BlPath, "blender-app.exe"); + blender_app = strstr(BlPath, "blender.exe"); if (blender_app != NULL) { - strcpy(blender_app, "blender.exe"); + strcpy(blender_app, "blender-launcher.exe"); } /* root is HKLM by default */ diff --git a/source/blender/blenlib/tests/BLI_color_test.cc b/source/blender/blenlib/tests/BLI_color_test.cc new file mode 100644 index 00000000000..14796e6bf71 --- /dev/null +++ b/source/blender/blenlib/tests/BLI_color_test.cc @@ -0,0 +1,133 @@ +/* Apache License, Version 2.0 */ + +#include "testing/testing.h" + +#include "BLI_color.hh" + +namespace blender::tests { + +/** + * \name Conversions + * \{ */ + +TEST(color, ThemeByteToFloat) +{ + ColorTheme4b theme_byte(192, 128, 64, 128); + ColorTheme4f theme_float = theme_byte.to_4f(); + EXPECT_NEAR(0.75f, theme_float.r, 0.01f); + EXPECT_NEAR(0.5f, theme_float.g, 0.01f); + EXPECT_NEAR(0.25f, theme_float.b, 0.01f); + EXPECT_NEAR(0.5f, theme_float.a, 0.01f); +} + +TEST(color, SrgbStraightFloatToByte) +{ + ColorTheme4f theme_float(0.75f, 0.5f, 0.25f, 0.5f); + ColorTheme4b theme_byte = theme_float.to_4b(); + EXPECT_EQ(191, theme_byte.r); + EXPECT_EQ(128, theme_byte.g); + EXPECT_EQ(64, theme_byte.b); + EXPECT_EQ(128, theme_byte.a); +} + +TEST(color, SrgbStraightToSceneLinearPremultiplied) +{ + BLI_init_srgb_conversion(); + + ColorTheme4b theme(192, 128, 64, 128); + ColorSceneLinear4f<eAlpha::Premultiplied> linear = + BLI_color_convert_to_scene_linear(theme).premultiply_alpha(); + EXPECT_NEAR(0.26f, linear.r, 0.01f); + EXPECT_NEAR(0.11f, linear.g, 0.01f); + EXPECT_NEAR(0.02f, linear.b, 0.01f); + EXPECT_NEAR(0.5f, linear.a, 0.01f); +} + +TEST(color, SceneLinearStraightToPremultiplied) +{ + ColorSceneLinear4f<eAlpha::Straight> straight(0.75f, 0.5f, 0.25f, 0.5f); + ColorSceneLinear4f<eAlpha::Premultiplied> premultiplied = straight.premultiply_alpha(); + EXPECT_NEAR(0.37f, premultiplied.r, 0.01f); + EXPECT_NEAR(0.25f, premultiplied.g, 0.01f); + EXPECT_NEAR(0.12f, premultiplied.b, 0.01f); + EXPECT_NEAR(0.5f, premultiplied.a, 0.01f); +} + +TEST(color, SceneLinearPremultipliedToStraight) +{ + ColorSceneLinear4f<eAlpha::Premultiplied> premultiplied(0.75f, 0.5f, 0.25f, 0.5f); + ColorSceneLinear4f<eAlpha::Straight> straight = premultiplied.unpremultiply_alpha(); + EXPECT_NEAR(1.5f, straight.r, 0.01f); + EXPECT_NEAR(1.0f, straight.g, 0.01f); + EXPECT_NEAR(0.5f, straight.b, 0.01f); + EXPECT_NEAR(0.5f, straight.a, 0.01f); +} + +TEST(color, SceneLinearStraightSrgbFloat) +{ + BLI_init_srgb_conversion(); + ColorSceneLinear4f<eAlpha::Straight> linear(0.75f, 0.5f, 0.25f, 0.5f); + ColorTheme4f theme = BLI_color_convert_to_theme4f(linear); + EXPECT_NEAR(0.88f, theme.r, 0.01); + EXPECT_NEAR(0.73f, theme.g, 0.01); + EXPECT_NEAR(0.53f, theme.b, 0.01); + EXPECT_NEAR(0.5f, theme.a, 0.01); +} + +TEST(color, SceneLinearPremultipliedToSrgbFloat) +{ + BLI_init_srgb_conversion(); + ColorSceneLinear4f<eAlpha::Premultiplied> linear(0.75f, 0.5f, 0.25f, 0.5f); + ColorTheme4f theme = BLI_color_convert_to_theme4f(linear.unpremultiply_alpha()); + + EXPECT_NEAR(1.19f, theme.r, 0.01); + EXPECT_NEAR(1.0f, theme.g, 0.01); + EXPECT_NEAR(0.74f, theme.b, 0.01); + EXPECT_NEAR(0.5f, theme.a, 0.01); +} + +TEST(color, SceneLinearStraightSrgbByte) +{ + BLI_init_srgb_conversion(); + ColorSceneLinear4f<eAlpha::Straight> linear(0.75f, 0.5f, 0.25f, 0.5f); + ColorTheme4b theme = BLI_color_convert_to_theme4b(linear); + EXPECT_EQ(225, theme.r); + EXPECT_EQ(188, theme.g); + EXPECT_EQ(137, theme.b); + EXPECT_EQ(128, theme.a); +} + +TEST(color, SceneLinearPremultipliedToSrgbByte) +{ + BLI_init_srgb_conversion(); + ColorSceneLinear4f<eAlpha::Premultiplied> linear(0.75f, 0.5f, 0.25f, 0.5f); + ColorTheme4b theme = BLI_color_convert_to_theme4b(linear.unpremultiply_alpha()); + EXPECT_EQ(255, theme.r); + EXPECT_EQ(255, theme.g); + EXPECT_EQ(188, theme.b); + EXPECT_EQ(128, theme.a); +} + +TEST(color, SceneLinearByteEncoding) +{ + ColorSceneLinear4f<eAlpha::Premultiplied> linear(0.75f, 0.5f, 0.25f, 0.5f); + ColorSceneLinearByteEncoded4b<eAlpha::Premultiplied> encoded = linear.encode(); + EXPECT_EQ(225, encoded.r); + EXPECT_EQ(188, encoded.g); + EXPECT_EQ(137, encoded.b); + EXPECT_EQ(128, encoded.a); +} + +TEST(color, SceneLinearByteDecoding) +{ + ColorSceneLinearByteEncoded4b<eAlpha::Premultiplied> encoded(225, 188, 137, 128); + ColorSceneLinear4f<eAlpha::Premultiplied> decoded = encoded.decode(); + EXPECT_NEAR(0.75f, decoded.r, 0.01f); + EXPECT_NEAR(0.5f, decoded.g, 0.01f); + EXPECT_NEAR(0.25f, decoded.b, 0.01f); + EXPECT_NEAR(0.5f, decoded.a, 0.01f); +} + +/* \} */ + +} // namespace blender::tests diff --git a/source/blender/blenlib/tests/BLI_linear_allocator_test.cc b/source/blender/blenlib/tests/BLI_linear_allocator_test.cc index 977e5dba497..0e0145e592a 100644 --- a/source/blender/blenlib/tests/BLI_linear_allocator_test.cc +++ b/source/blender/blenlib/tests/BLI_linear_allocator_test.cc @@ -136,4 +136,17 @@ TEST(linear_allocator, ManyAllocations) } } +TEST(linear_allocator, ConstructArray) +{ + LinearAllocator<> allocator; + MutableSpan<std::string> strings = allocator.construct_array<std::string>(4, "hello"); + EXPECT_EQ(strings[0], "hello"); + EXPECT_EQ(strings[1], "hello"); + EXPECT_EQ(strings[2], "hello"); + EXPECT_EQ(strings[3], "hello"); + for (std::string &string : strings) { + string.~basic_string(); + } +} + } // namespace blender::tests diff --git a/source/blender/blenlib/tests/BLI_map_test.cc b/source/blender/blenlib/tests/BLI_map_test.cc index f1ae8fb3921..679a10e9ce0 100644 --- a/source/blender/blenlib/tests/BLI_map_test.cc +++ b/source/blender/blenlib/tests/BLI_map_test.cc @@ -604,6 +604,55 @@ TEST(map, GenericAlgorithms) EXPECT_EQ(std::count(map.keys().begin(), map.keys().end(), 7), 1); } +TEST(map, AddAsVariadic) +{ + Map<int, StringRef> map; + map.add_as(3, "hello", 2); + map.add_as(2, "test", 1); + EXPECT_EQ(map.lookup(3), "he"); + EXPECT_EQ(map.lookup(2), "t"); +} + +TEST(map, RemoveDuringIteration) +{ + Map<int, int> map; + map.add(2, 1); + map.add(5, 2); + map.add(1, 2); + map.add(6, 0); + map.add(3, 3); + + EXPECT_EQ(map.size(), 5); + + using Iter = Map<int, int>::MutableItemIterator; + Iter begin = map.items().begin(); + Iter end = map.items().end(); + for (Iter iter = begin; iter != end; ++iter) { + Map<int, int>::MutableItem item = *iter; + if (item.value == 2) { + map.remove(iter); + } + } + + EXPECT_EQ(map.size(), 3); + EXPECT_EQ(map.lookup(2), 1); + EXPECT_EQ(map.lookup(6), 0); + EXPECT_EQ(map.lookup(3), 3); +} + +TEST(map, LookupKey) +{ + Map<std::string, int> map; + map.add("a", 0); + map.add("b", 1); + map.add("c", 2); + EXPECT_EQ(map.lookup_key("a"), "a"); + EXPECT_EQ(map.lookup_key_as("c"), "c"); + EXPECT_EQ(map.lookup_key_ptr_as("d"), nullptr); + EXPECT_EQ(map.lookup_key_ptr_as("b")->size(), 1); + EXPECT_EQ(map.lookup_key_ptr("a"), map.lookup_key_ptr_as("a")); +} + /** * Set this to 1 to activate the benchmark. It is disabled by default, because it prints a lot. */ diff --git a/source/blender/blenlib/tests/BLI_stack_cxx_test.cc b/source/blender/blenlib/tests/BLI_stack_cxx_test.cc index f1fcdae3a52..b3108381d78 100644 --- a/source/blender/blenlib/tests/BLI_stack_cxx_test.cc +++ b/source/blender/blenlib/tests/BLI_stack_cxx_test.cc @@ -93,6 +93,15 @@ TEST(stack, Push) EXPECT_EQ(stack.size(), 2); } +TEST(stack, PushAs) +{ + Stack<StringRef> stack; + stack.push_as("hello", 3); + stack.push_as("world", 1); + EXPECT_EQ(stack.pop(), "w"); + EXPECT_EQ(stack.pop(), "hel"); +} + TEST(stack, PushMultiple) { Stack<int> stack; diff --git a/source/blender/blenlib/tests/BLI_vector_set_test.cc b/source/blender/blenlib/tests/BLI_vector_set_test.cc index bbbe96f9b7e..c4016ca75e1 100644 --- a/source/blender/blenlib/tests/BLI_vector_set_test.cc +++ b/source/blender/blenlib/tests/BLI_vector_set_test.cc @@ -232,4 +232,43 @@ TEST(vector_set, PopExceptions) EXPECT_EQ(set.size(), 4); } +TEST(vector_set, IndexOfOrAdd) +{ + VectorSet<int> set; + EXPECT_EQ(set.index_of_or_add(3), 0); + EXPECT_EQ(set.index_of_or_add(3), 0); + EXPECT_EQ(set.index_of_or_add(2), 1); + EXPECT_EQ(set.index_of_or_add(0), 2); + EXPECT_EQ(set.index_of_or_add(2), 1); + EXPECT_EQ(set.index_of_or_add(3), 0); + EXPECT_EQ(set.index_of_or_add(5), 3); + EXPECT_EQ(set.index_of_or_add(8), 4); + EXPECT_EQ(set.index_of_or_add(5), 3); +} + +TEST(vector_set, Clear) +{ + VectorSet<int> set = {4, 6, 2, 4}; + EXPECT_EQ(set.size(), 3); + set.clear(); + EXPECT_EQ(set.size(), 0); + set.add_multiple({4, 1, 6, 8, 3, 6, 9, 3}); + EXPECT_EQ(set.size(), 6); + set.clear(); + EXPECT_EQ(set.size(), 0); +} + +TEST(vector_set, LookupKey) +{ + VectorSet<std::string> set; + set.add("a"); + set.add("b"); + set.add("c"); + EXPECT_EQ(set.lookup_key("a"), "a"); + EXPECT_EQ(set.lookup_key_as("c"), "c"); + EXPECT_EQ(set.lookup_key_ptr_as("d"), nullptr); + EXPECT_EQ(set.lookup_key_ptr_as("b")->size(), 1); + EXPECT_EQ(set.lookup_key_ptr("a"), set.lookup_key_ptr_as("a")); +} + } // namespace blender::tests diff --git a/source/blender/blenlib/tests/BLI_vector_test.cc b/source/blender/blenlib/tests/BLI_vector_test.cc index 462f13c15ab..e8636168308 100644 --- a/source/blender/blenlib/tests/BLI_vector_test.cc +++ b/source/blender/blenlib/tests/BLI_vector_test.cc @@ -248,6 +248,15 @@ TEST(vector, Append) EXPECT_EQ(vec[2], 7); } +TEST(vector, AppendAs) +{ + Vector<StringRef> vec; + vec.append_as("hello", 2); + vec.append_as("world", 3); + EXPECT_EQ(vec[0], "he"); + EXPECT_EQ(vec[1], "wor"); +} + TEST(vector, AppendAndGetIndex) { Vector<int> vec; diff --git a/source/blender/blenlib/tests/BLI_virtual_array_test.cc b/source/blender/blenlib/tests/BLI_virtual_array_test.cc index ac25229cd69..a6d2ca10315 100644 --- a/source/blender/blenlib/tests/BLI_virtual_array_test.cc +++ b/source/blender/blenlib/tests/BLI_virtual_array_test.cc @@ -1,26 +1,29 @@ /* Apache License, Version 2.0 */ +#include "BLI_array.hh" #include "BLI_strict_flags.h" +#include "BLI_vector.hh" +#include "BLI_vector_set.hh" #include "BLI_virtual_array.hh" #include "testing/testing.h" namespace blender::tests { -TEST(virtual_array, ForSpan) +TEST(virtual_array, Span) { std::array<int, 5> data = {3, 4, 5, 6, 7}; - VArrayForSpan<int> varray{data}; + VArray_For_Span<int> varray{data}; EXPECT_EQ(varray.size(), 5); EXPECT_EQ(varray.get(0), 3); EXPECT_EQ(varray.get(4), 7); EXPECT_TRUE(varray.is_span()); EXPECT_FALSE(varray.is_single()); - EXPECT_EQ(varray.get_span().data(), data.data()); + EXPECT_EQ(varray.get_internal_span().data(), data.data()); } -TEST(virtual_array, ForSingle) +TEST(virtual_array, Single) { - VArrayForSingle<int> varray{10, 4}; + VArray_For_Single<int> varray{10, 4}; EXPECT_EQ(varray.size(), 4); EXPECT_EQ(varray.get(0), 10); EXPECT_EQ(varray.get(3), 10); @@ -28,4 +31,124 @@ TEST(virtual_array, ForSingle) EXPECT_TRUE(varray.is_single()); } +TEST(virtual_array, Array) +{ + Array<int> array = {1, 2, 3, 5, 8}; + { + VArray_For_ArrayContainer varray{array}; + EXPECT_EQ(varray.size(), 5); + EXPECT_EQ(varray[0], 1); + EXPECT_EQ(varray[2], 3); + EXPECT_EQ(varray[3], 5); + EXPECT_TRUE(varray.is_span()); + } + { + VArray_For_ArrayContainer varray{std::move(array)}; + EXPECT_EQ(varray.size(), 5); + EXPECT_EQ(varray[0], 1); + EXPECT_EQ(varray[2], 3); + EXPECT_EQ(varray[3], 5); + EXPECT_TRUE(varray.is_span()); + } + { + VArray_For_ArrayContainer varray{array}; /* NOLINT: bugprone-use-after-move */ + EXPECT_TRUE(varray.is_empty()); + } +} + +TEST(virtual_array, Vector) +{ + Vector<int> vector = {9, 8, 7, 6}; + VArray_For_ArrayContainer varray{std::move(vector)}; + EXPECT_EQ(varray.size(), 4); + EXPECT_EQ(varray[0], 9); + EXPECT_EQ(varray[3], 6); +} + +TEST(virtual_array, StdVector) +{ + std::vector<int> vector = {5, 6, 7, 8}; + VArray_For_ArrayContainer varray{std::move(vector)}; + EXPECT_EQ(varray.size(), 4); + EXPECT_EQ(varray[0], 5); + EXPECT_EQ(varray[1], 6); +} + +TEST(virtual_array, StdArray) +{ + std::array<int, 4> array = {2, 3, 4, 5}; + VArray_For_ArrayContainer varray{array}; + EXPECT_EQ(varray.size(), 4); + EXPECT_EQ(varray[0], 2); + EXPECT_EQ(varray[1], 3); +} + +TEST(virtual_array, VectorSet) +{ + VectorSet<int> vector_set = {5, 3, 7, 3, 3, 5, 1}; + VArray_For_ArrayContainer varray{std::move(vector_set)}; + EXPECT_TRUE(vector_set.is_empty()); /* NOLINT: bugprone-use-after-move. */ + EXPECT_EQ(varray.size(), 4); + EXPECT_EQ(varray[0], 5); + EXPECT_EQ(varray[1], 3); + EXPECT_EQ(varray[2], 7); + EXPECT_EQ(varray[3], 1); +} + +TEST(virtual_array, Func) +{ + auto func = [](int64_t index) { return (int)(index * index); }; + VArray_For_Func<int, decltype(func)> varray{10, func}; + EXPECT_EQ(varray.size(), 10); + EXPECT_EQ(varray[0], 0); + EXPECT_EQ(varray[3], 9); + EXPECT_EQ(varray[9], 81); +} + +TEST(virtual_array, AsSpan) +{ + auto func = [](int64_t index) { return (int)(10 * index); }; + VArray_For_Func<int, decltype(func)> func_varray{10, func}; + VArray_Span span_varray{func_varray}; + EXPECT_EQ(span_varray.size(), 10); + Span<int> span = span_varray; + EXPECT_EQ(span.size(), 10); + EXPECT_EQ(span[0], 0); + EXPECT_EQ(span[3], 30); + EXPECT_EQ(span[6], 60); +} + +static int get_x(const std::array<int, 3> &item) +{ + return item[0]; +} + +static void set_x(std::array<int, 3> &item, int value) +{ + item[0] = value; +} + +TEST(virtual_array, DerivedSpan) +{ + Vector<std::array<int, 3>> vector; + vector.append({3, 4, 5}); + vector.append({1, 1, 1}); + { + VArray_For_DerivedSpan<std::array<int, 3>, int, get_x> varray{vector}; + EXPECT_EQ(varray.size(), 2); + EXPECT_EQ(varray[0], 3); + EXPECT_EQ(varray[1], 1); + } + { + VMutableArray_For_DerivedSpan<std::array<int, 3>, int, get_x, set_x> varray{vector}; + EXPECT_EQ(varray.size(), 2); + EXPECT_EQ(varray[0], 3); + EXPECT_EQ(varray[1], 1); + varray.set(0, 10); + varray.set(1, 20); + EXPECT_EQ(vector[0][0], 10); + EXPECT_EQ(vector[1][0], 20); + } +} + } // namespace blender::tests diff --git a/source/blender/blenloader/CMakeLists.txt b/source/blender/blenloader/CMakeLists.txt index ee9b9a49768..36802fc8842 100644 --- a/source/blender/blenloader/CMakeLists.txt +++ b/source/blender/blenloader/CMakeLists.txt @@ -57,6 +57,7 @@ set(SRC intern/versioning_270.c intern/versioning_280.c intern/versioning_290.c + intern/versioning_300.c intern/versioning_cycles.c intern/versioning_defaults.c intern/versioning_dna.c diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 19ae0014bb8..fe7d50bfa15 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -111,6 +111,7 @@ #include "SEQ_iterator.h" #include "SEQ_modifier.h" #include "SEQ_sequencer.h" +#include "SEQ_utils.h" #include "readfile.h" @@ -2696,7 +2697,7 @@ static int lib_link_seq_clipboard_cb(Sequence *seq, void *arg_pt) static void lib_link_clipboard_restore(struct IDNameLib_Map *id_map) { /* update IDs stored in sequencer clipboard */ - SEQ_iterator_seqbase_recursive_apply(&seqbase_clipboard, lib_link_seq_clipboard_cb, id_map); + SEQ_seqbase_recursive_apply(&seqbase_clipboard, lib_link_seq_clipboard_cb, id_map); } static int lib_link_main_data_restore_cb(LibraryIDLinkCallbackData *cb_data) @@ -3868,6 +3869,7 @@ static void do_versions(FileData *fd, Library *lib, Main *main) blo_do_versions_270(fd, lib, main); blo_do_versions_280(fd, lib, main); blo_do_versions_290(fd, lib, main); + blo_do_versions_300(fd, lib, main); blo_do_versions_cycles(fd, lib, main); /* WATCH IT!!!: pointers from libdata have not been converted yet here! */ @@ -3891,6 +3893,7 @@ static void do_versions_after_linking(Main *main, ReportList *reports) do_versions_after_linking_270(main); do_versions_after_linking_280(main, reports); do_versions_after_linking_290(main, reports); + do_versions_after_linking_300(main, reports); do_versions_after_linking_cycles(main); main->is_locked_for_linking = false; diff --git a/source/blender/blenloader/intern/readfile.h b/source/blender/blenloader/intern/readfile.h index 9682b5456d2..d1d4e0b3256 100644 --- a/source/blender/blenloader/intern/readfile.h +++ b/source/blender/blenloader/intern/readfile.h @@ -210,6 +210,7 @@ void blo_do_versions_260(struct FileData *fd, struct Library *lib, struct Main * void blo_do_versions_270(struct FileData *fd, struct Library *lib, struct Main *bmain); void blo_do_versions_280(struct FileData *fd, struct Library *lib, struct Main *bmain); void blo_do_versions_290(struct FileData *fd, struct Library *lib, struct Main *bmain); +void blo_do_versions_300(struct FileData *fd, struct Library *lib, struct Main *bmain); void blo_do_versions_cycles(struct FileData *fd, struct Library *lib, struct Main *bmain); void do_versions_after_linking_250(struct Main *bmain); @@ -217,6 +218,7 @@ void do_versions_after_linking_260(struct Main *bmain); void do_versions_after_linking_270(struct Main *bmain); void do_versions_after_linking_280(struct Main *bmain, struct ReportList *reports); void do_versions_after_linking_290(struct Main *bmain, struct ReportList *reports); +void do_versions_after_linking_300(struct Main *bmain, struct ReportList *reports); void do_versions_after_linking_cycles(struct Main *bmain); /* This is rather unfortunate to have to expose this here, but better use that nasty hack in diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c new file mode 100644 index 00000000000..8c5e86eadd3 --- /dev/null +++ b/source/blender/blenloader/intern/versioning_300.c @@ -0,0 +1,135 @@ +/* + * 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. + */ + +/** \file + * \ingroup blenloader + */ +/* allow readfile to use deprecated functionality */ +#define DNA_DEPRECATED_ALLOW + +#include "BLI_listbase.h" +#include "BLI_math_vector.h" +#include "BLI_utildefines.h" + +#include "DNA_brush_types.h" +#include "DNA_genfile.h" +#include "DNA_modifier_types.h" +#include "DNA_text_types.h" + +#include "BKE_lib_id.h" +#include "BKE_main.h" +#include "BKE_node.h" + +#include "BLO_readfile.h" +#include "readfile.h" + +void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports)) +{ + if (MAIN_VERSION_ATLEAST(bmain, 300, 0) && !MAIN_VERSION_ATLEAST(bmain, 300, 1)) { + /* Set zero user text objects to have a fake user. */ + LISTBASE_FOREACH (Text *, text, &bmain->texts) { + if (text->id.us == 0) { + id_fake_user_set(&text->id); + } + } + } + /** + * Versioning code until next subversion bump goes here. + * + * \note Be sure to check when bumping the version: + * - #blo_do_versions_300 in this file. + * - "versioning_userdef.c", #blo_do_versions_userdef + * - "versioning_userdef.c", #do_versions_theme + * + * \note Keep this message at the bottom of the function. + */ + { + /* Keep this block, even when empty. */ + + /* Use new texture socket in Attribute Sample Texture node. */ + LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { + if (ntree->type != NTREE_GEOMETRY) { + continue; + } + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->type != GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE) { + continue; + } + if (node->id == NULL) { + continue; + } + LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { + if (socket->type == SOCK_TEXTURE) { + bNodeSocketValueTexture *socket_value = (bNodeSocketValueTexture *) + socket->default_value; + socket_value->value = (Tex *)node->id; + break; + } + } + node->id = NULL; + } + } + } +} + +/* NOLINTNEXTLINE: readability-function-size */ +void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) +{ + if (!MAIN_VERSION_ATLEAST(bmain, 300, 1)) { + /* Set default value for the new bisect_threshold parameter in the mirror modifier. */ + if (!DNA_struct_elem_find(fd->filesdna, "MirrorModifierData", "float", "bisect_threshold")) { + LISTBASE_FOREACH (Object *, ob, &bmain->objects) { + LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) { + if (md->type == eModifierType_Mirror) { + MirrorModifierData *mmd = (MirrorModifierData *)md; + /* This was the previous hard-coded value. */ + mmd->bisect_threshold = 0.001f; + } + } + } + } + /* Grease Pencil: Set default value for dilate pixels. */ + if (!DNA_struct_elem_find(fd->filesdna, "BrushGpencilSettings", "int", "dilate_pixels")) { + LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) { + if (brush->gpencil_settings) { + brush->gpencil_settings->dilate_pixels = 1; + } + } + } + } + /** + * Versioning code until next subversion bump goes here. + * + * \note Be sure to check when bumping the version: + * - "versioning_userdef.c", #blo_do_versions_userdef + * - "versioning_userdef.c", #do_versions_theme + * + * \note Keep this message at the bottom of the function. + */ + { + /* Keep this block, even when empty. */ + if (!DNA_struct_elem_find(fd->filesdna, "bPoseChannel", "float", "custom_scale_xyz[3]")) { + LISTBASE_FOREACH (Object *, ob, &bmain->objects) { + if (ob->pose == NULL) { + continue; + } + LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) { + copy_v3_fl(pchan->custom_scale_xyz, pchan->custom_scale); + } + } + } + } +} diff --git a/source/blender/blenloader/intern/versioning_legacy.c b/source/blender/blenloader/intern/versioning_legacy.c index f2e73e161ca..56b2f18f8a6 100644 --- a/source/blender/blenloader/intern/versioning_legacy.c +++ b/source/blender/blenloader/intern/versioning_legacy.c @@ -1278,12 +1278,6 @@ void blo_do_versions_pre250(FileData *fd, Library *lib, Main *bmain) if (ob->soft->physics_speed == 0.0f) { ob->soft->physics_speed = 1.0f; } - - if (ob->soft->interval == 0) { - ob->soft->interval = 2; - ob->soft->sfra = 1; - ob->soft->efra = 100; - } } if (ob->soft && ob->soft->vertgroup == 0) { bDeformGroup *locGroup = BKE_object_defgroup_find_name(ob, "SOFTGOAL"); diff --git a/source/blender/bmesh/intern/bmesh_polygon_edgenet.c b/source/blender/bmesh/intern/bmesh_polygon_edgenet.c index dcf9717465c..0754564fa47 100644 --- a/source/blender/bmesh/intern/bmesh_polygon_edgenet.c +++ b/source/blender/bmesh/intern/bmesh_polygon_edgenet.c @@ -106,7 +106,7 @@ static void normalize_v2_m3_v3v3(float out[2], /** * \note Be sure to update #bm_face_split_edgenet_find_loop_pair_exists - * when making changed to edge picking logic. + * when making changes to edge picking logic. */ static bool bm_face_split_edgenet_find_loop_pair(BMVert *v_init, const float face_normal[3], @@ -456,8 +456,8 @@ static bool bm_face_split_edgenet_find_loop(BMVert *v_init, * Splits a face into many smaller faces defined by an edge-net. * handle customdata and degenerate cases. * - * - isolated holes or unsupported face configurations, will be ignored. - * - customdata calculations aren't efficient + * - Isolated holes or unsupported face configurations, will be ignored. + * - Customdata calculations aren't efficient * (need to calculate weights for each vert). */ bool BM_face_split_edgenet(BMesh *bm, @@ -593,7 +593,7 @@ bool BM_face_split_edgenet(BMesh *bm, BMIter iter; BMLoop *l_other; - /* see: #BM_loop_interp_from_face for similar logic */ + /* See: #BM_loop_interp_from_face for similar logic. */ void **blocks = BLI_array_alloca(blocks, f->len); float(*cos_2d)[2] = BLI_array_alloca(cos_2d, f->len); float *w = BLI_array_alloca(w, f->len); @@ -1064,7 +1064,7 @@ static int bm_face_split_edgenet_find_connection(const struct EdgeGroup_FindConn #ifdef USE_PARTIAL_CONNECT /** - * Used to identify edges that get split off when making island from partial connection. + * Used to identify edges that get split off when making island from partial connection. * fptr should be a BMFace*, but is a void* for general interface to BM_vert_separate_tested_edges */ static bool test_tagged_and_notface(BMEdge *e, void *fptr) @@ -1211,7 +1211,7 @@ static bool bm_vert_partial_connect_check_overlap(const int *remap, const int v_a_index, const int v_b_index) { - /* connected to eachother */ + /* Connected to each other. */ if (UNLIKELY((remap[v_a_index] == v_b_index) || (remap[v_b_index] == v_a_index))) { return true; } @@ -1226,7 +1226,7 @@ static bool bm_vert_partial_connect_check_overlap(const int *remap, * \param use_partial_connect: Support for handling islands connected by only a single edge, * \note that this is quite slow so avoid using where possible. * \param mem_arena: Avoids many small allocs & should be cleared after each use. - * take care since \a r_edge_net_new is stored in \a r_edge_net_new. + * take care since \a edge_net_new is stored in \a r_edge_net_new. */ bool BM_face_split_edgenet_connect_islands(BMesh *bm, BMFace *f, @@ -1246,7 +1246,7 @@ bool BM_face_split_edgenet_connect_islands(BMesh *bm, * * Keep the first part fast since it will run very often for edge-nets that have no holes. * - * \note Don't use the mem_arena unless he have holes to fill. + * \note Don't use the mem_arena unless we have holes to fill. * (avoid thrashing the area when the initial check isn't so intensive on the stack). */ @@ -1572,7 +1572,7 @@ bool BM_face_split_edgenet_connect_islands(BMesh *bm, if (g->has_prev_edge == false) { BMVert *v_origin = g->vert_span.min; - + /* Index of BMVert for the edge group connection with `v_origin`. */ const int index_other = bm_face_split_edgenet_find_connection(&args, v_origin, false); // BLI_assert(index_other >= 0 && index_other < (int)vert_arr_len); @@ -1598,7 +1598,7 @@ bool BM_face_split_edgenet_connect_islands(BMesh *bm, { BMVert *v_origin = g->vert_span.max; - + /* Index of BMVert for the edge group connection with `v_origin`. */ const int index_other = bm_face_split_edgenet_find_connection(&args, v_origin, true); // BLI_assert(index_other >= 0 && index_other < (int)vert_arr_len); @@ -1660,7 +1660,7 @@ finally: struct TempVertPair *tvp = temp_vert_pairs.list; do { /* its _very_ unlikely the edge exists, - * however splicing may case this. see: T48012 */ + * however splicing may cause this. see: T48012 */ if (!BM_edge_exists(tvp->v_orig, tvp->v_temp)) { BM_vert_splice(bm, tvp->v_orig, tvp->v_temp); } diff --git a/source/blender/bmesh/tools/bmesh_bisect_plane.c b/source/blender/bmesh/tools/bmesh_bisect_plane.c index f840a3770ff..e7d0fe6a0c6 100644 --- a/source/blender/bmesh/tools/bmesh_bisect_plane.c +++ b/source/blender/bmesh/tools/bmesh_bisect_plane.c @@ -39,12 +39,13 @@ #include "BLI_utildefines_stack.h" #include "bmesh.h" -#include "bmesh_bisect_plane.h" /* own include */ +#include "bmesh_bisect_plane.h" /* Own include. */ -#include "BLI_strict_flags.h" /* keep last */ +#include "BLI_strict_flags.h" /* Keep last. */ /* -------------------------------------------------------------------- */ -/* Math utils */ +/** \name Math Functions + * \{ */ static short plane_point_test_v3(const float plane[4], const float co[3], @@ -63,10 +64,15 @@ static short plane_point_test_v3(const float plane[4], return 0; } +/** \} */ + /* -------------------------------------------------------------------- */ -/* Wrappers to hide internal data-structure abuse, +/** \name BMesh Element Accessors + * + * Wrappers to hide internal data-structure abuse, * later we may want to move this into some hash lookup - * to a separate struct, but for now we can store in BMesh data */ + * to a separate struct, but for now we can store in #BMesh data. + * \{ */ #define BM_VERT_DIR(v) ((short *)(&(v)->head.index))[0] /* Direction -1/0/1 */ #define BM_VERT_SKIP(v) ((short *)(&(v)->head.index))[1] /* Skip Vert 0/1 */ @@ -75,12 +81,16 @@ static short plane_point_test_v3(const float plane[4], #define BM_VERT_LOOPINDEX(v) /* The verts index within a face (temp var) */ \ (*((uint *)(&(v)->no[2]))) -/** +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name BMesh Flag Accessors + * * Hide flag access - * (for more readable code since same flag is used differently for vert/edgeface)... + * (for more readable code since same flag is used differently for vert/edge-face). */ -/* enable when vertex is in the center and its faces have been added to the stack */ +/** Enable when vertex is in the center and its faces have been added to the stack. */ BLI_INLINE void vert_is_center_enable(BMVert *v) { BM_elem_flag_enable(v, BM_ELEM_TAG); @@ -100,7 +110,7 @@ BLI_INLINE bool vert_pair_adjacent_in_orig_face(BMVert *v_a, BMVert *v_b, const return ELEM(delta, 1, (uint)(f_len_orig - 1)); } -/* enable when the edge can be cut */ +/** Enable when the edge can be cut. */ BLI_INLINE void edge_is_cut_enable(BMEdge *e) { BM_elem_flag_enable(e, BM_ELEM_TAG); @@ -114,7 +124,7 @@ BLI_INLINE bool edge_is_cut_test(BMEdge *e) return (BM_elem_flag_test(e, BM_ELEM_TAG) != 0); } -/* enable when the faces are added to the stack */ +/** Enable when the faces are added to the stack. */ BLI_INLINE void face_in_stack_enable(BMFace *f) { BM_elem_flag_disable(f, BM_ELEM_TAG); @@ -128,8 +138,11 @@ BLI_INLINE bool face_in_stack_test(BMFace *f) return (BM_elem_flag_test(f, BM_ELEM_TAG) == 0); } +/** \} */ + /* -------------------------------------------------------------------- */ -/* BMesh utils */ +/** \name BMesh Face Bisect + * \{ */ static int bm_vert_sortval_cb(const void *v_a_v, const void *v_b_v) { @@ -148,7 +161,7 @@ static int bm_vert_sortval_cb(const void *v_a_v, const void *v_b_v) static void bm_face_bisect_verts( BMesh *bm, BMFace *f, const float plane[4], const short oflag_center, const short oflag_new) { - /* unlikely more than 2 verts are needed */ + /* Unlikely more than 2 verts are needed. */ const uint f_len_orig = (uint)f->len; BMVert **vert_split_arr = BLI_array_alloca(vert_split_arr, f_len_orig); STACK_DECLARE(vert_split_arr); @@ -163,16 +176,14 @@ static void bm_face_bisect_verts( l_first = BM_FACE_FIRST_LOOP(f); - /* add plane-aligned verts to the stack - * and check we have verts from both sides in this face, - * ... that the face doesn't only have boundary verts on the plane for eg. */ + /* Add plane-aligned verts to the stack and check we have verts from both sides in this face + * (that the face doesn't only have boundary verts on the plane for eg). */ l_iter = l_first; do { if (vert_is_center_test(l_iter->v)) { BLI_assert(BM_VERT_DIR(l_iter->v) == 0); - /* if both are -1 or 1, or both are zero: - * don't flip 'inside' var while walking */ + /* If both are -1 or 1, or both are zero: don't flip 'inside' var while walking. */ BM_VERT_SKIP(l_iter->v) = (((BM_VERT_DIR(l_iter->prev->v) ^ BM_VERT_DIR(l_iter->next->v))) == 0); @@ -195,7 +206,7 @@ static void bm_face_bisect_verts( l_a = BM_face_vert_share_loop(f, vert_split_arr[0]); l_b = BM_face_vert_share_loop(f, vert_split_arr[1]); - /* common case, just cut the face once */ + /* Common case, just cut the face once. */ BM_face_split(bm, f, l_a, l_b, &l_new, NULL, true); if (l_new) { if (oflag_center | oflag_new) { @@ -207,6 +218,7 @@ static void bm_face_bisect_verts( } } else { + /* Less common case, _complicated_ we need to calculate how to do multiple cuts. */ uint i = 0; @@ -263,7 +275,6 @@ static void bm_face_bisect_verts( } while ((l_iter = l_iter->next) != l_first_non_center); } - /* less common case, _complicated_ we need to calculate how to do multiple cuts */ float(*face_verts_proj_2d)[2] = BLI_array_alloca(face_verts_proj_2d, f_len_orig); float axis_mat[3][3]; @@ -275,10 +286,8 @@ static void bm_face_bisect_verts( /* ---- */ /* Calculate the direction to sort verts in the face intersecting the plane */ - /* exact dir isn't so important, - * just need a dir for sorting verts across face, - * 'sort_dir' could be flipped either way, it not important, we only need to order the array - */ + /* The exact direction isn't important, vertices just need to be sorted across the face. + * (`sort_dir` could be flipped either way). */ cross_v3_v3v3(sort_dir, f->no, plane); if (UNLIKELY(normalize_v3(sort_dir) == 0.0f)) { /* find any 2 verts and get their direction */ @@ -289,8 +298,8 @@ static void bm_face_bisect_verts( } } if (UNLIKELY(i == STACK_SIZE(vert_split_arr))) { - /* ok, we can't do anything useful here, - * face has no area or so, bail out, this is highly unlikely but not impossible */ + /* Ok, we can't do anything useful here, + * face has no area or so, bail out, this is highly unlikely but not impossible. */ goto finally; } } @@ -298,7 +307,7 @@ static void bm_face_bisect_verts( /* ---- */ /* Calculate 2d coords to use for intersection checks */ - /* get the faces 2d coords */ + /* Get the faces 2d coords. */ BLI_assert(BM_face_is_normal_valid(f)); axis_dominant_v3_to_m3(axis_mat, f->no); @@ -310,7 +319,7 @@ static void bm_face_bisect_verts( } while ((l_iter = l_iter->next) != l_first); /* ---- */ - /* Sort the verts across the face from one side to another */ + /* Sort the verts across the face from one side to another. */ for (i = 0; i < STACK_SIZE(vert_split_arr); i++) { BMVert *v = vert_split_arr[i]; BM_VERT_SORTVAL(v) = dot_v3v3(sort_dir, v->co); @@ -320,9 +329,9 @@ static void bm_face_bisect_verts( vert_split_arr, STACK_SIZE(vert_split_arr), sizeof(*vert_split_arr), bm_vert_sortval_cb); /* ---- */ - /* Split the face across sorted splits */ + /* Split the face across sorted splits. */ - /* note: we don't know which face gets which splits, + /* NOTE: we don't know which face gets which splits, * so at the moment we have to search all faces for the vert pair, * while not all that nice, typically there are < 5 resulting faces, * so its not _that_ bad. */ @@ -350,8 +359,8 @@ static void bm_face_bisect_verts( uint j; for (j = 0; j < STACK_SIZE(face_split_arr); j++) { - /* would be nice to avoid loop lookup here, - * but we need to know which face the verts are in */ + /* It would be nice to avoid loop lookup here, + * but we need to know which face the verts are in. */ if ((l_a = BM_face_vert_share_loop(face_split_arr[j], v_a)) && (l_b = BM_face_vert_share_loop(face_split_arr[j], v_b))) { found = true; @@ -359,11 +368,10 @@ static void bm_face_bisect_verts( } } - /* ideally wont happen, but it can for self intersecting faces */ + /* Ideally wont happen, but it can for self intersecting faces. */ // BLI_assert(found == true); - /* in fact this simple test is good enough, - * test if the loops are adjacent */ + /* In fact this simple test is good enough, test if the loops are adjacent. */ if (found && !BM_loop_is_adjacent(l_a, l_b)) { BMLoop *l_new; BMFace *f_tmp; @@ -397,8 +405,11 @@ finally: (void)vert_split_arr; } +/** \} */ + /* -------------------------------------------------------------------- */ -/* Main logic */ +/** \name Public BMesh Bisect Function + * \{ */ /** * \param use_snap_center: Snap verts onto the plane. @@ -425,25 +436,25 @@ void BM_mesh_bisect_plane(BMesh *bm, BMIter iter; if (use_tag) { - /* build tagged edge array */ + /* Build tagged edge array. */ BMEdge *e; einput_len = 0; - /* flush edge tags to verts */ + /* Flush edge tags to verts. */ BM_mesh_elem_hflag_disable_all(bm, BM_VERT, BM_ELEM_TAG, false); - /* keep face tags as is */ + /* Keep face tags as is. */ BM_ITER_MESH_INDEX (e, &iter, bm, BM_EDGES_OF_MESH, i) { if (edge_is_cut_test(e)) { edges_arr[einput_len++] = e; - /* flush edge tags to verts */ + /* Flush edge tags to verts. */ BM_elem_flag_enable(e->v1, BM_ELEM_TAG); BM_elem_flag_enable(e->v2, BM_ELEM_TAG); } } - /* face tags are set by caller */ + /* Face tags are set by caller. */ } else { BMEdge *e; @@ -463,7 +474,7 @@ void BM_mesh_bisect_plane(BMesh *bm, if (use_tag && !BM_elem_flag_test(v, BM_ELEM_TAG)) { vert_is_center_disable(v); - /* these should never be accessed */ + /* These should never be accessed. */ BM_VERT_DIR(v) = 0; BM_VERT_DIST(v) = 0.0f; @@ -483,11 +494,11 @@ void BM_mesh_bisect_plane(BMesh *bm, } } - /* store a stack of faces to be evaluated for splitting */ + /* Store a stack of faces to be evaluated for splitting. */ BLI_LINKSTACK_INIT(face_stack); for (i = 0; i < einput_len; i++) { - /* we could check edge_is_cut_test(e) but there is no point */ + /* We could check `edge_is_cut_test(e)` but there is no point. */ BMEdge *e = edges_arr[i]; const int side[2] = {BM_VERT_DIR(e->v1), BM_VERT_DIR(e->v2)}; const float dist[2] = {BM_VERT_DIST(e->v1), BM_VERT_DIST(e->v2)}; @@ -524,8 +535,8 @@ void BM_mesh_bisect_plane(BMesh *bm, BM_VERT_DIST(v_new) = 0.0f; } else if (side[0] == 0 || side[1] == 0) { - /* check if either edge verts are aligned, - * if so - tag and push all faces that use it into the stack */ + /* Check if either edge verts are aligned, + * if so - tag and push all faces that use it into the stack. */ uint j; BM_ITER_ELEM_INDEX (v, &iter, e, BM_VERTS_OF_EDGE, j) { if (side[j] == 0) { @@ -545,7 +556,7 @@ void BM_mesh_bisect_plane(BMesh *bm, } } - /* if both verts are on the center - tag it */ + /* If both verts are on the center - tag it. */ if (oflag_center) { if (side[0] == 0 && side[1] == 0) { BMO_edge_flag_enable(bm, e, oflag_center); @@ -560,9 +571,11 @@ void BM_mesh_bisect_plane(BMesh *bm, bm_face_bisect_verts(bm, f, plane, oflag_center, oflag_new); } - /* Caused by access macros: BM_VERT_DIR, BM_VERT_SKIP. */ + /* Caused by access macros: #BM_VERT_DIR, #BM_VERT_SKIP. */ bm->elem_index_dirty |= BM_VERT; - /* now we have all faces to split in the stack */ + /* Now we have all faces to split in the stack. */ BLI_LINKSTACK_FREE(face_stack); } + +/** \} */ diff --git a/source/blender/compositor/COM_defines.h b/source/blender/compositor/COM_defines.h index c5ff2b3adc6..ef889807030 100644 --- a/source/blender/compositor/COM_defines.h +++ b/source/blender/compositor/COM_defines.h @@ -55,7 +55,6 @@ constexpr int COM_DATA_TYPE_COLOR_CHANNELS = COM_data_type_num_channels(DataType // configurable items // chunk size determination -// #define COM_DEBUG // chunk order /** diff --git a/source/blender/compositor/intern/COM_ChunkOrderHotspot.cc b/source/blender/compositor/intern/COM_ChunkOrderHotspot.cc index 79afcc9deea..b8e19fc2c34 100644 --- a/source/blender/compositor/intern/COM_ChunkOrderHotspot.cc +++ b/source/blender/compositor/intern/COM_ChunkOrderHotspot.cc @@ -30,4 +30,4 @@ double ChunkOrderHotspot::calc_distance(int x, int y) return result; } -} // namespace blender::compositor
\ No newline at end of file +} // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_Debug.cc b/source/blender/compositor/intern/COM_Debug.cc index 648ef7519d1..dfb4f53fee5 100644 --- a/source/blender/compositor/intern/COM_Debug.cc +++ b/source/blender/compositor/intern/COM_Debug.cc @@ -42,8 +42,6 @@ extern "C" { namespace blender::compositor { -#ifdef COM_DEBUG - int DebugInfo::m_file_index = 0; DebugInfo::NodeNameMap DebugInfo::m_node_names; DebugInfo::OpNameMap DebugInfo::m_op_names; @@ -69,50 +67,6 @@ std::string DebugInfo::operation_name(const NodeOperation *op) return ""; } -void DebugInfo::convert_started() -{ - m_op_names.clear(); -} - -void DebugInfo::execute_started(const ExecutionSystem *system) -{ - m_file_index = 1; - m_group_states.clear(); - for (ExecutionGroup *execution_group : system->m_groups) { - m_group_states[execution_group] = EG_WAIT; - } -} - -void DebugInfo::node_added(const Node *node) -{ - m_node_names[node] = std::string(node->getbNode() ? node->getbNode()->name : ""); -} - -void DebugInfo::node_to_operations(const Node *node) -{ - m_current_node_name = m_node_names[node]; -} - -void DebugInfo::operation_added(const NodeOperation *operation) -{ - m_op_names[operation] = m_current_node_name; -} - -void DebugInfo::operation_read_write_buffer(const NodeOperation *operation) -{ - m_current_op_name = m_op_names[operation]; -} - -void DebugInfo::execution_group_started(const ExecutionGroup *group) -{ - m_group_states[group] = EG_RUNNING; -} - -void DebugInfo::execution_group_finished(const ExecutionGroup *group) -{ - m_group_states[group] = EG_FINISHED; -} - int DebugInfo::graphviz_operation(const ExecutionSystem *system, NodeOperation *operation, const ExecutionGroup *group, @@ -442,6 +396,9 @@ bool DebugInfo::graphviz_system(const ExecutionSystem *system, char *str, int ma void DebugInfo::graphviz(const ExecutionSystem *system) { + if (!COM_EXPORT_GRAPHVIZ) { + return; + } char str[1000000]; if (graphviz_system(system, str, sizeof(str) - 1)) { char basename[FILE_MAX]; @@ -459,44 +416,4 @@ void DebugInfo::graphviz(const ExecutionSystem *system) } } -#else - -std::string DebugInfo::node_name(const Node * /*node*/) -{ - return ""; -} -std::string DebugInfo::operation_name(const NodeOperation * /*op*/) -{ - return ""; -} -void DebugInfo::convert_started() -{ -} -void DebugInfo::execute_started(const ExecutionSystem * /*system*/) -{ -} -void DebugInfo::node_added(const Node * /*node*/) -{ -} -void DebugInfo::node_to_operations(const Node * /*node*/) -{ -} -void DebugInfo::operation_added(const NodeOperation * /*operation*/) -{ -} -void DebugInfo::operation_read_write_buffer(const NodeOperation * /*operation*/) -{ -} -void DebugInfo::execution_group_started(const ExecutionGroup * /*group*/) -{ -} -void DebugInfo::execution_group_finished(const ExecutionGroup * /*group*/) -{ -} -void DebugInfo::graphviz(const ExecutionSystem * /*system*/) -{ -} - -#endif - } // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_Debug.h b/source/blender/compositor/intern/COM_Debug.h index bf7b981fbd5..e1aea69e481 100644 --- a/source/blender/compositor/intern/COM_Debug.h +++ b/source/blender/compositor/intern/COM_Debug.h @@ -21,11 +21,13 @@ #include <map> #include <string> +#include "COM_ExecutionSystem.h" #include "COM_NodeOperation.h" #include "COM_defines.h" namespace blender::compositor { +static constexpr bool COM_EXPORT_GRAPHVIZ = false; class Node; class ExecutionSystem; class ExecutionGroup; @@ -41,20 +43,81 @@ class DebugInfo { static std::string node_name(const Node *node); static std::string operation_name(const NodeOperation *op); - static void convert_started(); - static void execute_started(const ExecutionSystem *system); + private: + static int m_file_index; + /** Map nodes to usable names for debug output. */ + static NodeNameMap m_node_names; + /** Map operations to usable names for debug output. */ + static OpNameMap m_op_names; + /** Base name for all operations added by a node. */ + static std::string m_current_node_name; + /** Base name for automatic sub-operations. */ + static std::string m_current_op_name; + /** For visualizing group states. */ + static GroupStateMap m_group_states; + + public: + static void convert_started() + { + if (COM_EXPORT_GRAPHVIZ) { + m_op_names.clear(); + } + } + + static void execute_started(const ExecutionSystem *system) + { + if (COM_EXPORT_GRAPHVIZ) { + m_file_index = 1; + m_group_states.clear(); + for (ExecutionGroup *execution_group : system->m_groups) { + m_group_states[execution_group] = EG_WAIT; + } + } + }; + + static void node_added(const Node *node) + { + if (COM_EXPORT_GRAPHVIZ) { + m_node_names[node] = std::string(node->getbNode() ? node->getbNode()->name : ""); + } + } - static void node_added(const Node *node); - static void node_to_operations(const Node *node); - static void operation_added(const NodeOperation *operation); - static void operation_read_write_buffer(const NodeOperation *operation); + static void node_to_operations(const Node *node) + { + if (COM_EXPORT_GRAPHVIZ) { + m_current_node_name = m_node_names[node]; + } + } - static void execution_group_started(const ExecutionGroup *group); - static void execution_group_finished(const ExecutionGroup *group); + static void operation_added(const NodeOperation *operation) + { + if (COM_EXPORT_GRAPHVIZ) { + m_op_names[operation] = m_current_node_name; + } + }; + + static void operation_read_write_buffer(const NodeOperation *operation) + { + if (COM_EXPORT_GRAPHVIZ) { + m_current_op_name = m_op_names[operation]; + } + }; + + static void execution_group_started(const ExecutionGroup *group) + { + if (COM_EXPORT_GRAPHVIZ) { + m_group_states[group] = EG_RUNNING; + } + }; + static void execution_group_finished(const ExecutionGroup *group) + { + if (COM_EXPORT_GRAPHVIZ) { + m_group_states[group] = EG_FINISHED; + } + }; static void graphviz(const ExecutionSystem *system); -#ifdef COM_DEBUG protected: static int graphviz_operation(const ExecutionSystem *system, NodeOperation *operation, @@ -68,20 +131,6 @@ class DebugInfo { const char *name, const char *color, const char *style, char *str, int maxlen); static int graphviz_legend(char *str, int maxlen); static bool graphviz_system(const ExecutionSystem *system, char *str, int maxlen); - - private: - static int m_file_index; - /** Map nodes to usable names for debug output. */ - static NodeNameMap m_node_names; - /** Map operations to usable names for debug output. */ - static OpNameMap m_op_names; - /** Base name for all operations added by a node. */ - static std::string m_current_node_name; - /** Base name for automatic sub-operations. */ - static std::string m_current_op_name; - /** For visualizing group states. */ - static GroupStateMap m_group_states; -#endif }; } // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.cc b/source/blender/compositor/intern/COM_MemoryBuffer.cc index 68e39b19eaf..8c30d3215d7 100644 --- a/source/blender/compositor/intern/COM_MemoryBuffer.cc +++ b/source/blender/compositor/intern/COM_MemoryBuffer.cc @@ -25,29 +25,49 @@ namespace blender::compositor { MemoryBuffer::MemoryBuffer(MemoryProxy *memoryProxy, const rcti &rect, MemoryBufferState state) { m_rect = rect; + this->m_is_a_single_elem = false; this->m_memoryProxy = memoryProxy; this->m_num_channels = COM_data_type_num_channels(memoryProxy->getDataType()); this->m_buffer = (float *)MEM_mallocN_aligned( sizeof(float) * buffer_len() * this->m_num_channels, 16, "COM_MemoryBuffer"); this->m_state = state; this->m_datatype = memoryProxy->getDataType(); + + set_strides(); } -MemoryBuffer::MemoryBuffer(DataType dataType, const rcti &rect) +MemoryBuffer::MemoryBuffer(DataType dataType, const rcti &rect, bool is_a_single_elem) { m_rect = rect; + this->m_is_a_single_elem = is_a_single_elem; this->m_memoryProxy = nullptr; this->m_num_channels = COM_data_type_num_channels(dataType); this->m_buffer = (float *)MEM_mallocN_aligned( sizeof(float) * buffer_len() * this->m_num_channels, 16, "COM_MemoryBuffer"); this->m_state = MemoryBufferState::Temporary; this->m_datatype = dataType; + + set_strides(); } MemoryBuffer::MemoryBuffer(const MemoryBuffer &src) - : MemoryBuffer(src.m_memoryProxy, src.m_rect, MemoryBufferState::Temporary) + : MemoryBuffer(src.m_datatype, src.m_rect, false) +{ + m_memoryProxy = src.m_memoryProxy; + /* src may be single elem buffer */ + fill_from(src); +} + +void MemoryBuffer::set_strides() { - memcpy(m_buffer, src.m_buffer, buffer_len() * m_num_channels * sizeof(float)); + if (m_is_a_single_elem) { + this->elem_stride = 0; + this->row_stride = 0; + } + else { + this->elem_stride = m_num_channels; + this->row_stride = getWidth() * m_num_channels; + } } void MemoryBuffer::clear() @@ -100,6 +120,8 @@ MemoryBuffer::~MemoryBuffer() void MemoryBuffer::fill_from(const MemoryBuffer &src) { + BLI_assert(!this->is_a_single_elem()); + unsigned int otherY; unsigned int minX = MAX2(this->m_rect.xmin, src.m_rect.xmin); unsigned int maxX = MIN2(this->m_rect.xmax, src.m_rect.xmax); @@ -109,10 +131,8 @@ void MemoryBuffer::fill_from(const MemoryBuffer &src) int otherOffset; for (otherY = minY; otherY < maxY; otherY++) { - otherOffset = ((otherY - src.m_rect.ymin) * src.getWidth() + minX - src.m_rect.xmin) * - this->m_num_channels; - offset = ((otherY - this->m_rect.ymin) * getWidth() + minX - this->m_rect.xmin) * - this->m_num_channels; + otherOffset = src.get_coords_offset(minX, otherY); + offset = this->get_coords_offset(minX, otherY); memcpy(&this->m_buffer[offset], &src.m_buffer[otherOffset], (maxX - minX) * this->m_num_channels * sizeof(float)); @@ -123,8 +143,7 @@ void MemoryBuffer::writePixel(int x, int y, const float color[4]) { if (x >= this->m_rect.xmin && x < this->m_rect.xmax && y >= this->m_rect.ymin && y < this->m_rect.ymax) { - const int offset = (getWidth() * (y - this->m_rect.ymin) + x - this->m_rect.xmin) * - this->m_num_channels; + const int offset = get_coords_offset(x, y); memcpy(&this->m_buffer[offset], color, sizeof(float) * this->m_num_channels); } } @@ -133,8 +152,7 @@ void MemoryBuffer::addPixel(int x, int y, const float color[4]) { if (x >= this->m_rect.xmin && x < this->m_rect.xmax && y >= this->m_rect.ymin && y < this->m_rect.ymax) { - const int offset = (getWidth() * (y - this->m_rect.ymin) + x - this->m_rect.xmin) * - this->m_num_channels; + const int offset = get_coords_offset(x, y); float *dst = &this->m_buffer[offset]; const float *src = color; for (int i = 0; i < this->m_num_channels; i++, dst++, src++) { @@ -151,26 +169,31 @@ static void read_ewa_pixel_sampled(void *userdata, int x, int y, float result[4] void MemoryBuffer::readEWA(float *result, const float uv[2], const float derivatives[2][2]) { - BLI_assert(this->m_datatype == DataType::Color); - float inv_width = 1.0f / (float)this->getWidth(), inv_height = 1.0f / (float)this->getHeight(); - /* TODO(sergey): Render pipeline uses normalized coordinates and derivatives, - * but compositor uses pixel space. For now let's just divide the values and - * switch compositor to normalized space for EWA later. - */ - float uv_normal[2] = {uv[0] * inv_width, uv[1] * inv_height}; - float du_normal[2] = {derivatives[0][0] * inv_width, derivatives[0][1] * inv_height}; - float dv_normal[2] = {derivatives[1][0] * inv_width, derivatives[1][1] * inv_height}; - - BLI_ewa_filter(this->getWidth(), - this->getHeight(), - false, - true, - uv_normal, - du_normal, - dv_normal, - read_ewa_pixel_sampled, - this, - result); + if (m_is_a_single_elem) { + memcpy(result, m_buffer, sizeof(float) * this->m_num_channels); + } + else { + BLI_assert(this->m_datatype == DataType::Color); + float inv_width = 1.0f / (float)this->getWidth(), inv_height = 1.0f / (float)this->getHeight(); + /* TODO(sergey): Render pipeline uses normalized coordinates and derivatives, + * but compositor uses pixel space. For now let's just divide the values and + * switch compositor to normalized space for EWA later. + */ + float uv_normal[2] = {uv[0] * inv_width, uv[1] * inv_height}; + float du_normal[2] = {derivatives[0][0] * inv_width, derivatives[0][1] * inv_height}; + float dv_normal[2] = {derivatives[1][0] * inv_width, derivatives[1][1] * inv_height}; + + BLI_ewa_filter(this->getWidth(), + this->getHeight(), + false, + true, + uv_normal, + du_normal, + dv_normal, + read_ewa_pixel_sampled, + this, + result); + } } } // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.h b/source/blender/compositor/intern/COM_MemoryBuffer.h index 060a67f8797..97b220508e0 100644 --- a/source/blender/compositor/intern/COM_MemoryBuffer.h +++ b/source/blender/compositor/intern/COM_MemoryBuffer.h @@ -50,6 +50,25 @@ class MemoryProxy; * \brief a MemoryBuffer contains access to the data of a chunk */ class MemoryBuffer { + public: + /** + * Offset between elements. + * + * Should always be used for the x dimension when calculating buffer offsets. + * It will be 0 when is_a_single_elem=true. + * e.g: buffer_index = y * buffer.row_stride + x * buffer.elem_stride + */ + int elem_stride; + + /** + * Offset between rows. + * + * Should always be used for the y dimension when calculating buffer offsets. + * It will be 0 when is_a_single_elem=true. + * e.g: buffer_index = y * buffer.row_stride + x * buffer.elem_stride + */ + int row_stride; + private: /** * \brief proxy of the memory (same for all chunks in the same buffer) @@ -82,6 +101,11 @@ class MemoryBuffer { */ uint8_t m_num_channels; + /** + * Whether buffer is a single element in memory. + */ + bool m_is_a_single_elem; + public: /** * \brief construct new temporarily MemoryBuffer for an area @@ -91,7 +115,7 @@ class MemoryBuffer { /** * \brief construct new temporarily MemoryBuffer for an area */ - MemoryBuffer(DataType datatype, const rcti &rect); + MemoryBuffer(DataType datatype, const rcti &rect, bool is_a_single_elem = false); /** * Copy constructor @@ -103,6 +127,102 @@ class MemoryBuffer { */ ~MemoryBuffer(); + /** + * Whether buffer is a single element in memory independently of its resolution. True for set + * operations buffers. + */ + bool is_a_single_elem() const + { + return m_is_a_single_elem; + } + + float &operator[](int index) + { + BLI_assert(m_is_a_single_elem ? index < m_num_channels : + index < get_coords_offset(getWidth(), getHeight())); + return m_buffer[index]; + } + + const float &operator[](int index) const + { + BLI_assert(m_is_a_single_elem ? index < m_num_channels : + index < get_coords_offset(getWidth(), getHeight())); + return m_buffer[index]; + } + + /** + * Get offset needed to jump from buffer start to given coordinates. + */ + int get_coords_offset(int x, int y) const + { + return (y - m_rect.ymin) * row_stride + (x - m_rect.xmin) * elem_stride; + } + + /** + * Get buffer element at given coordinates. + */ + float *get_elem(int x, int y) + { + BLI_assert(x >= m_rect.xmin && x < m_rect.xmax && y >= m_rect.ymin && y < m_rect.ymax); + return m_buffer + get_coords_offset(x, y); + } + + /** + * Get buffer element at given coordinates. + */ + const float *get_elem(int x, int y) const + { + BLI_assert(x >= m_rect.xmin && x < m_rect.xmax && y >= m_rect.ymin && y < m_rect.ymax); + return m_buffer + get_coords_offset(x, y); + } + + /** + * Get channel value at given coordinates. + */ + float &get_value(int x, int y, int channel) + { + BLI_assert(x >= m_rect.xmin && x < m_rect.xmax && y >= m_rect.ymin && y < m_rect.ymax && + channel >= 0 && channel < m_num_channels); + return m_buffer[get_coords_offset(x, y) + channel]; + } + + /** + * Get channel value at given coordinates. + */ + const float &get_value(int x, int y, int channel) const + { + BLI_assert(x >= m_rect.xmin && x < m_rect.xmax && y >= m_rect.ymin && y < m_rect.ymax && + channel >= 0 && channel < m_num_channels); + return m_buffer[get_coords_offset(x, y) + channel]; + } + + /** + * Get the buffer row end. + */ + const float *get_row_end(int y) const + { + BLI_assert(y >= 0 && y < getHeight()); + return m_buffer + (is_a_single_elem() ? m_num_channels : get_coords_offset(getWidth(), y)); + } + + /** + * Get the number of elements in memory for a row. For single element buffers it will always + * be 1. + */ + int get_memory_width() const + { + return is_a_single_elem() ? 1 : getWidth(); + } + + /** + * Get number of elements in memory for a column. For single element buffers it will + * always be 1. + */ + int get_memory_height() const + { + return is_a_single_elem() ? 1 : getHeight(); + } + uint8_t get_num_channels() { return this->m_num_channels; @@ -216,7 +336,7 @@ class MemoryBuffer { int u = x; int v = y; this->wrap_pixel(u, v, extend_x, extend_y); - const int offset = (getWidth() * y + x) * this->m_num_channels; + const int offset = get_coords_offset(u, v); float *buffer = &this->m_buffer[offset]; memcpy(result, buffer, sizeof(float) * this->m_num_channels); } @@ -232,7 +352,7 @@ class MemoryBuffer { int v = y; this->wrap_pixel(u, v, extend_x, extend_y); - const int offset = (getWidth() * v + u) * this->m_num_channels; + const int offset = get_coords_offset(u, v); BLI_assert(offset >= 0); BLI_assert(offset < this->buffer_len() * this->m_num_channels); @@ -258,15 +378,20 @@ class MemoryBuffer { copy_vn_fl(result, this->m_num_channels, 0.0f); return; } - BLI_bilinear_interpolation_wrap_fl(this->m_buffer, - result, - getWidth(), - getHeight(), - this->m_num_channels, - u, - v, - extend_x == MemoryBufferExtend::Repeat, - extend_y == MemoryBufferExtend::Repeat); + if (m_is_a_single_elem) { + memcpy(result, m_buffer, sizeof(float) * this->m_num_channels); + } + else { + BLI_bilinear_interpolation_wrap_fl(this->m_buffer, + result, + getWidth(), + getHeight(), + this->m_num_channels, + u, + v, + extend_x == MemoryBufferExtend::Repeat, + extend_y == MemoryBufferExtend::Repeat); + } } void readEWA(float *result, const float uv[2], const float derivatives[2][2]); @@ -321,9 +446,10 @@ class MemoryBuffer { float get_max_value(const rcti &rect) const; private: + void set_strides(); const int buffer_len() const { - return getWidth() * getHeight(); + return get_memory_width() * get_memory_height(); } #ifdef WITH_CXX_GUARDEDALLOC diff --git a/source/blender/compositor/intern/COM_WorkScheduler.cc b/source/blender/compositor/intern/COM_WorkScheduler.cc index ee3a6dedd44..d578ac24a4a 100644 --- a/source/blender/compositor/intern/COM_WorkScheduler.cc +++ b/source/blender/compositor/intern/COM_WorkScheduler.cc @@ -263,10 +263,10 @@ static void opencl_initialize(const bool use_opencl) if (error2 != CL_SUCCESS) { printf("CLERROR[%d]: %s\n", error2, clewErrorString(error2)); } - g_work_scheduler.opencl.devices.append(OpenCLDevice(g_work_scheduler.opencl.context, - device, - g_work_scheduler.opencl.program, - vendorID)); + g_work_scheduler.opencl.devices.append_as(g_work_scheduler.opencl.context, + device, + g_work_scheduler.opencl.program, + vendorID); } } MEM_freeN(cldevices); @@ -368,7 +368,7 @@ static void threading_model_queue_initialize(const int num_cpu_threads) /* Initialize CPU threads. */ if (!g_work_scheduler.queue.initialized) { for (int index = 0; index < num_cpu_threads; index++) { - g_work_scheduler.queue.devices.append(CPUDevice(index)); + g_work_scheduler.queue.devices.append_as(index); } BLI_thread_local_create(g_thread_device); g_work_scheduler.queue.initialized = true; diff --git a/source/blender/compositor/operations/COM_ConvertOperation.cc b/source/blender/compositor/operations/COM_ConvertOperation.cc index 2ea15185c0f..384936533c7 100644 --- a/source/blender/compositor/operations/COM_ConvertOperation.cc +++ b/source/blender/compositor/operations/COM_ConvertOperation.cc @@ -18,6 +18,8 @@ #include "COM_ConvertOperation.h" +#include "BLI_color.hh" + #include "IMB_colormanagement.h" namespace blender::compositor { @@ -355,21 +357,10 @@ void ConvertPremulToStraightOperation::executePixelSampled(float output[4], float y, PixelSampler sampler) { - float inputValue[4]; - float alpha; - - this->m_inputOperation->readSampled(inputValue, x, y, sampler); - alpha = inputValue[3]; - - if (fabsf(alpha) < 1e-5f) { - zero_v3(output); - } - else { - mul_v3_v3fl(output, inputValue, 1.0f / alpha); - } - - /* never touches the alpha */ - output[3] = alpha; + ColorSceneLinear4f<eAlpha::Premultiplied> input; + this->m_inputOperation->readSampled(input, x, y, sampler); + ColorSceneLinear4f<eAlpha::Straight> converted = input.unpremultiply_alpha(); + copy_v4_v4(output, converted); } /* ******** Straight to Premul ******** */ @@ -385,16 +376,10 @@ void ConvertStraightToPremulOperation::executePixelSampled(float output[4], float y, PixelSampler sampler) { - float inputValue[4]; - float alpha; - - this->m_inputOperation->readSampled(inputValue, x, y, sampler); - alpha = inputValue[3]; - - mul_v3_v3fl(output, inputValue, alpha); - - /* never touches the alpha */ - output[3] = alpha; + ColorSceneLinear4f<eAlpha::Straight> input; + this->m_inputOperation->readSampled(input, x, y, sampler); + ColorSceneLinear4f<eAlpha::Premultiplied> converted = input.premultiply_alpha(); + copy_v4_v4(output, converted); } /* ******** Separate Channels ******** */ diff --git a/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc b/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc index 46ae00dee34..4edcc206f5b 100644 --- a/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc +++ b/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc @@ -31,6 +31,31 @@ namespace blender::compositor { +PlaneDistortBaseOperation::PlaneDistortBaseOperation() + : m_motion_blur_samples(1), m_motion_blur_shutter(0.5f) +{ +} + +void PlaneDistortBaseOperation::calculateCorners(const float corners[4][2], + bool normalized, + int sample) +{ + BLI_assert(sample < this->m_motion_blur_samples); + MotionSample *sample_data = &this->m_samples[sample]; + if (normalized) { + for (int i = 0; i < 4; i++) { + sample_data->frameSpaceCorners[i][0] = corners[i][0] * this->getWidth(); + sample_data->frameSpaceCorners[i][1] = corners[i][1] * this->getHeight(); + } + } + else { + for (int i = 0; i < 4; i++) { + sample_data->frameSpaceCorners[i][0] = corners[i][0]; + sample_data->frameSpaceCorners[i][1] = corners[i][1]; + } + } +} + /* ******** PlaneDistort WarpImage ******** */ BLI_INLINE void warpCoord(float x, float y, float matrix[3][3], float uv[2], float deriv[2][2]) @@ -46,13 +71,11 @@ BLI_INLINE void warpCoord(float x, float y, float matrix[3][3], float uv[2], flo deriv[1][1] = (matrix[1][1] - matrix[1][2] * uv[1]) / vec[2]; } -PlaneDistortWarpImageOperation::PlaneDistortWarpImageOperation() +PlaneDistortWarpImageOperation::PlaneDistortWarpImageOperation() : PlaneDistortBaseOperation() { this->addInputSocket(DataType::Color, ResizeMode::None); this->addOutputSocket(DataType::Color); this->m_pixelReader = nullptr; - this->m_motion_blur_samples = 1; - this->m_motion_blur_shutter = 0.5f; this->flags.complex = true; } @@ -60,24 +83,13 @@ void PlaneDistortWarpImageOperation::calculateCorners(const float corners[4][2], bool normalized, int sample) { - BLI_assert(sample < this->m_motion_blur_samples); + PlaneDistortBaseOperation::calculateCorners(corners, normalized, sample); + const int width = this->m_pixelReader->getWidth(); const int height = this->m_pixelReader->getHeight(); float frame_corners[4][2] = { {0.0f, 0.0f}, {(float)width, 0.0f}, {(float)width, (float)height}, {0.0f, (float)height}}; MotionSample *sample_data = &this->m_samples[sample]; - if (normalized) { - for (int i = 0; i < 4; i++) { - sample_data->frameSpaceCorners[i][0] = corners[i][0] * this->getWidth(); - sample_data->frameSpaceCorners[i][1] = corners[i][1] * this->getHeight(); - } - } - else { - for (int i = 0; i < 4; i++) { - sample_data->frameSpaceCorners[i][0] = corners[i][0]; - sample_data->frameSpaceCorners[i][1] = corners[i][1]; - } - } BKE_tracking_homography_between_two_quads( sample_data->frameSpaceCorners, frame_corners, sample_data->perspectiveMatrix); } @@ -147,34 +159,12 @@ bool PlaneDistortWarpImageOperation::determineDependingAreaOfInterest( /* ******** PlaneDistort Mask ******** */ -PlaneDistortMaskOperation::PlaneDistortMaskOperation() +PlaneDistortMaskOperation::PlaneDistortMaskOperation() : PlaneDistortBaseOperation() { addOutputSocket(DataType::Value); /* Currently hardcoded to 8 samples. */ m_osa = 8; - this->m_motion_blur_samples = 1; - this->m_motion_blur_shutter = 0.5f; -} - -void PlaneDistortMaskOperation::calculateCorners(const float corners[4][2], - bool normalized, - int sample) -{ - BLI_assert(sample < this->m_motion_blur_samples); - MotionSample *sample_data = &this->m_samples[sample]; - if (normalized) { - for (int i = 0; i < 4; i++) { - sample_data->frameSpaceCorners[i][0] = corners[i][0] * this->getWidth(); - sample_data->frameSpaceCorners[i][1] = corners[i][1] * this->getHeight(); - } - } - else { - for (int i = 0; i < 4; i++) { - sample_data->frameSpaceCorners[i][0] = corners[i][0]; - sample_data->frameSpaceCorners[i][1] = corners[i][1]; - } - } } void PlaneDistortMaskOperation::initExecution() diff --git a/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.h b/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.h index 95e5c86bd4d..cc6e4d00d71 100644 --- a/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.h +++ b/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.h @@ -32,21 +32,43 @@ namespace blender::compositor { #define PLANE_DISTORT_MAX_SAMPLES 64 -class PlaneDistortWarpImageOperation : public NodeOperation { +class PlaneDistortBaseOperation : public NodeOperation { protected: struct MotionSample { float frameSpaceCorners[4][2]; /* Corners coordinates in pixel space. */ float perspectiveMatrix[3][3]; }; - SocketReader *m_pixelReader; MotionSample m_samples[PLANE_DISTORT_MAX_SAMPLES]; int m_motion_blur_samples; float m_motion_blur_shutter; public: + PlaneDistortBaseOperation(); + + void setMotionBlurSamples(int samples) + { + BLI_assert(samples <= PLANE_DISTORT_MAX_SAMPLES); + this->m_motion_blur_samples = samples; + } + void setMotionBlurShutter(float shutter) + { + this->m_motion_blur_shutter = shutter; + } + + virtual void calculateCorners(const float corners[4][2], bool normalized, int sample); + + private: + friend class PlaneTrackCommon; +}; + +class PlaneDistortWarpImageOperation : public PlaneDistortBaseOperation { + protected: + SocketReader *m_pixelReader; + + public: PlaneDistortWarpImageOperation(); - void calculateCorners(const float corners[4][2], bool normalized, int sample); + void calculateCorners(const float corners[4][2], bool normalized, int sample) override; void initExecution() override; void deinitExecution() override; @@ -56,47 +78,19 @@ class PlaneDistortWarpImageOperation : public NodeOperation { bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; - - void setMotionBlurSamples(int samples) - { - BLI_assert(samples <= PLANE_DISTORT_MAX_SAMPLES); - this->m_motion_blur_samples = samples; - } - void setMotionBlurShutter(float shutter) - { - this->m_motion_blur_shutter = shutter; - } }; -class PlaneDistortMaskOperation : public NodeOperation { +class PlaneDistortMaskOperation : public PlaneDistortBaseOperation { protected: - struct MotionSample { - float frameSpaceCorners[4][2]; /* Corners coordinates in pixel space. */ - }; int m_osa; - MotionSample m_samples[PLANE_DISTORT_MAX_SAMPLES]; float m_jitter[32][2]; - int m_motion_blur_samples; - float m_motion_blur_shutter; public: PlaneDistortMaskOperation(); - void calculateCorners(const float corners[4][2], bool normalized, int sample); - void initExecution() override; void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; - - void setMotionBlurSamples(int samples) - { - BLI_assert(samples <= PLANE_DISTORT_MAX_SAMPLES); - this->m_motion_blur_samples = samples; - } - void setMotionBlurShutter(float shutter) - { - this->m_motion_blur_shutter = shutter; - } }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_PlaneTrackOperation.cc b/source/blender/compositor/operations/COM_PlaneTrackOperation.cc index 565bde6c945..0884f2ad979 100644 --- a/source/blender/compositor/operations/COM_PlaneTrackOperation.cc +++ b/source/blender/compositor/operations/COM_PlaneTrackOperation.cc @@ -41,6 +41,26 @@ PlaneTrackCommon::PlaneTrackCommon() this->m_planeTrackName[0] = '\0'; } +void PlaneTrackCommon::read_and_calculate_corners(PlaneDistortBaseOperation *distort_op) +{ + float corners[4][2]; + if (distort_op->m_motion_blur_samples == 1) { + readCornersFromTrack(corners, this->m_framenumber); + distort_op->calculateCorners(corners, true, 0); + } + else { + const float frame = (float)this->m_framenumber - distort_op->m_motion_blur_shutter; + const float frame_step = (distort_op->m_motion_blur_shutter * 2.0f) / + distort_op->m_motion_blur_samples; + float frame_iter = frame; + for (int sample = 0; sample < distort_op->m_motion_blur_samples; sample++) { + readCornersFromTrack(corners, frame_iter); + distort_op->calculateCorners(corners, true, sample); + frame_iter += frame_step; + } + } +} + void PlaneTrackCommon::readCornersFromTrack(float corners[4][2], float frame) { MovieTracking *tracking; @@ -84,21 +104,7 @@ void PlaneTrackCommon::determineResolution(unsigned int resolution[2], void PlaneTrackMaskOperation::initExecution() { PlaneDistortMaskOperation::initExecution(); - float corners[4][2]; - if (this->m_motion_blur_samples == 1) { - readCornersFromTrack(corners, this->m_framenumber); - calculateCorners(corners, true, 0); - } - else { - const float frame = (float)this->m_framenumber - this->m_motion_blur_shutter; - const float frame_step = (this->m_motion_blur_shutter * 2.0f) / this->m_motion_blur_samples; - float frame_iter = frame; - for (int sample = 0; sample < this->m_motion_blur_samples; sample++) { - readCornersFromTrack(corners, frame_iter); - calculateCorners(corners, true, sample); - frame_iter += frame_step; - } - } + PlaneTrackCommon::read_and_calculate_corners(this); } /* ******** PlaneTrackWarpImageOperation ******** */ @@ -106,22 +112,7 @@ void PlaneTrackMaskOperation::initExecution() void PlaneTrackWarpImageOperation::initExecution() { PlaneDistortWarpImageOperation::initExecution(); - /* TODO(sergey): De-duplicate with mask operation. */ - float corners[4][2]; - if (this->m_motion_blur_samples == 1) { - readCornersFromTrack(corners, this->m_framenumber); - calculateCorners(corners, true, 0); - } - else { - const float frame = (float)this->m_framenumber - this->m_motion_blur_shutter; - const float frame_step = (this->m_motion_blur_shutter * 2.0f) / this->m_motion_blur_samples; - float frame_iter = frame; - for (int sample = 0; sample < this->m_motion_blur_samples; sample++) { - readCornersFromTrack(corners, frame_iter); - calculateCorners(corners, true, sample); - frame_iter += frame_step; - } - } + PlaneTrackCommon::read_and_calculate_corners(this); } } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_PlaneTrackOperation.h b/source/blender/compositor/operations/COM_PlaneTrackOperation.h index 95a7d536742..d240c8b06e9 100644 --- a/source/blender/compositor/operations/COM_PlaneTrackOperation.h +++ b/source/blender/compositor/operations/COM_PlaneTrackOperation.h @@ -40,7 +40,7 @@ class PlaneTrackCommon { /* note: this class is not an operation itself (to prevent virtual inheritance issues) * implementation classes must make wrappers to use these methods, see below. */ - void readCornersFromTrack(float corners[4][2], float frame); + void read_and_calculate_corners(PlaneDistortBaseOperation *distort_op); void determineResolution(unsigned int resolution[2], unsigned int preferredResolution[2]); public: @@ -62,6 +62,9 @@ class PlaneTrackCommon { { this->m_framenumber = framenumber; } + + private: + void readCornersFromTrack(float corners[4][2], float frame); }; class PlaneTrackMaskOperation : public PlaneDistortMaskOperation, public PlaneTrackCommon { diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc index ec5037fb29c..ae530cc010e 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc @@ -84,6 +84,7 @@ #include "BKE_lattice.h" #include "BKE_layer.h" #include "BKE_lib_id.h" +#include "BKE_lib_query.h" #include "BKE_light.h" #include "BKE_mask.h" #include "BKE_material.h" @@ -114,6 +115,7 @@ #include "intern/builder/deg_builder.h" #include "intern/depsgraph.h" +#include "intern/depsgraph_tag.h" #include "intern/depsgraph_type.h" #include "intern/eval/deg_eval_copy_on_write.h" #include "intern/node/deg_node.h" @@ -360,7 +362,103 @@ void DepsgraphNodeBuilder::begin_build() graph_->entry_tags.clear(); } -void DepsgraphNodeBuilder::end_build() +/* Util callbacks for `BKE_library_foreach_ID_link`, used to detect when a COW ID is using ID + * pointers that are either: + * - COW ID pointers that do not exist anymore in current depsgraph. + * - Orig ID pointers that do have now a COW version in current depsgraph. + * In both cases, it means the COW ID user needs to be flushed, to ensure its pointers are properly + * remapped. + * + * NOTE: This is split in two, a static function and a public method of the node builder, to allow + * the code to access the builder's data more easily. */ + +/* `id_cow_self` is the user of `id_pointer`, see also `LibraryIDLinkCallbackData` struct + * definition. */ +int DepsgraphNodeBuilder::foreach_id_cow_detect_need_for_update_callback(ID *id_cow_self, + ID *id_pointer) +{ + if (id_pointer->orig_id == nullptr) { + /* `id_cow_self` uses a non-cow ID, if that ID has a COW copy in current depsgraph its owner + * needs to be remapped, i.e. COW-flushed. */ + IDNode *id_node = find_id_node(id_pointer); + if (id_node != nullptr && id_node->id_cow != nullptr) { + graph_id_tag_update(bmain_, + graph_, + id_cow_self->orig_id, + ID_RECALC_COPY_ON_WRITE, + DEG_UPDATE_SOURCE_RELATIONS); + return IDWALK_RET_STOP_ITER; + } + } + else { + /* `id_cow_self` uses a COW ID, if that COW copy is removed from current depsgraph its owner + * needs to be remapped, i.e. COW-flushed. */ + /* NOTE: at that stage, old existing COW copies that are to be removed from current state of + * evaluated depsgraph are still valid pointers, they are freed later (typically during + * destruction of the builder itself). */ + IDNode *id_node = find_id_node(id_pointer->orig_id); + if (id_node == nullptr) { + graph_id_tag_update(bmain_, + graph_, + id_cow_self->orig_id, + ID_RECALC_COPY_ON_WRITE, + DEG_UPDATE_SOURCE_RELATIONS); + return IDWALK_RET_STOP_ITER; + } + } + return IDWALK_RET_NOP; +} + +static int foreach_id_cow_detect_need_for_update_callback(LibraryIDLinkCallbackData *cb_data) +{ + ID *id = *cb_data->id_pointer; + if (id == nullptr) { + return IDWALK_RET_NOP; + } + + DepsgraphNodeBuilder *builder = static_cast<DepsgraphNodeBuilder *>(cb_data->user_data); + ID *id_cow_self = cb_data->id_self; + + return builder->foreach_id_cow_detect_need_for_update_callback(id_cow_self, id); +} + +/* Check for IDs that need to be flushed (COW-updated) because the depsgraph itself created or + * removed some of their evaluated dependencies. + * + * NOTE: Currently the only ID types that depsgraph may decide to not evaluate/generate COW + * copies for, even though they are referenced by other data-blocks, are Collections and Objects + * (through their various visibility flags, and the ones from LayerCollections too). However, this + * code is kept generic as it makes it more future-proof, and optimization here would give + * negligible performance improvements in typical cases. + * + * NOTE: This mechanism may also 'fix' some missing update tagging from non-depsgraph code in + * some cases. This is slightly unfortunate (as it may hide issues in other parts of Blender + * code), but cannot really be avoided currently. + */ +void DepsgraphNodeBuilder::update_invalid_cow_pointers() +{ + for (const IDNode *id_node : graph_->id_nodes) { + if (id_node->previously_visible_components_mask == 0) { + /* Newly added node/ID, no need to check it. */ + continue; + } + if (ELEM(id_node->id_cow, id_node->id_orig, nullptr)) { + /* Node/ID with no COW data, no need to check it. */ + continue; + } + if ((id_node->id_cow->recalc & ID_RECALC_COPY_ON_WRITE) != 0) { + /* Node/ID already tagged for COW flush, no need to check it. */ + continue; + } + BKE_library_foreach_ID_link(nullptr, + id_node->id_cow, + deg::foreach_id_cow_detect_need_for_update_callback, + this, + IDWALK_IGNORE_EMBEDDED_ID | IDWALK_READONLY); + } +} + +void DepsgraphNodeBuilder::tag_previously_tagged_nodes() { for (const SavedEntryTag &entry_tag : saved_entry_tags_) { IDNode *id_node = find_id_node(entry_tag.id_orig); @@ -382,6 +480,12 @@ void DepsgraphNodeBuilder::end_build() } } +void DepsgraphNodeBuilder::end_build() +{ + tag_previously_tagged_nodes(); + update_invalid_cow_pointers(); +} + void DepsgraphNodeBuilder::build_id(ID *id) { if (id == nullptr) { @@ -1557,6 +1661,12 @@ void DepsgraphNodeBuilder::build_nodetree_socket(bNodeSocket *socket) else if (socket->type == SOCK_COLLECTION) { build_id((ID *)((bNodeSocketValueCollection *)socket->default_value)->value); } + else if (socket->type == SOCK_TEXTURE) { + build_id((ID *)((bNodeSocketValueTexture *)socket->default_value)->value); + } + else if (socket->type == SOCK_MATERIAL) { + build_id((ID *)((bNodeSocketValueMaterial *)socket->default_value)->value); + } } void DepsgraphNodeBuilder::build_nodetree(bNodeTree *ntree) diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.h b/source/blender/depsgraph/intern/builder/deg_builder_nodes.h index a7033c8c8f3..151e0d844b6 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.h @@ -101,6 +101,8 @@ class DepsgraphNodeBuilder : public DepsgraphBuilder { virtual void begin_build(); virtual void end_build(); + int foreach_id_cow_detect_need_for_update_callback(ID *id_cow_self, ID *id_pointer); + IDNode *add_id_node(ID *id); IDNode *find_id_node(ID *id); TimeSourceNode *add_time_source(); @@ -276,6 +278,9 @@ class DepsgraphNodeBuilder : public DepsgraphBuilder { bool is_reference, void *user_data); + void tag_previously_tagged_nodes(); + void update_invalid_cow_pointers(); + /* State which demotes currently built entities. */ Scene *scene_; ViewLayer *view_layer_; diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes_rig.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes_rig.cc index 13caba67713..00c78b8edce 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes_rig.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes_rig.cc @@ -169,7 +169,7 @@ void DepsgraphNodeBuilder::build_rig(Object *object, bool is_object_visible) } /* Speed optimization for animation lookups. */ if (object->pose != nullptr) { - BKE_pose_channels_hash_make(object->pose); + BKE_pose_channels_hash_ensure(object->pose); if (object->pose->flag & POSE_CONSTRAINTS_NEED_UPDATE_FLAGS) { BKE_pose_update_constraint_flags(object->pose); } @@ -318,7 +318,7 @@ void DepsgraphNodeBuilder::build_proxy_rig(Object *object, bool is_object_visibl /* Armature. */ build_armature(armature); /* speed optimization for animation lookups */ - BKE_pose_channels_hash_make(object->pose); + BKE_pose_channels_hash_ensure(object->pose); if (object->pose->flag & POSE_CONSTRAINTS_NEED_UPDATE_FLAGS) { BKE_pose_update_constraint_flags(object->pose); } diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index 0f8b613f7ac..8a02228146a 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -2401,6 +2401,18 @@ void DepsgraphRelationBuilder::build_nodetree_socket(bNodeSocket *socket) build_collection(nullptr, nullptr, collection); } } + else if (socket->type == SOCK_TEXTURE) { + Tex *texture = ((bNodeSocketValueTexture *)socket->default_value)->value; + if (texture != nullptr) { + build_texture(texture); + } + } + else if (socket->type == SOCK_MATERIAL) { + Material *material = ((bNodeSocketValueMaterial *)socket->default_value)->value; + if (material != nullptr) { + build_material(material); + } + } } void DepsgraphRelationBuilder::build_nodetree(bNodeTree *ntree) diff --git a/source/blender/depsgraph/intern/depsgraph_query_iter.cc b/source/blender/depsgraph/intern/depsgraph_query_iter.cc index ed002321729..df1cf8cc771 100644 --- a/source/blender/depsgraph/intern/depsgraph_query_iter.cc +++ b/source/blender/depsgraph/intern/depsgraph_query_iter.cc @@ -219,6 +219,29 @@ bool deg_iterator_components_step(BLI_Iterator *iter) } } + /* The curve component. */ + if (data->geometry_component_id == 3) { + data->geometry_component_id++; + + const CurveComponent *component = geometry_set->get_component_for_read<CurveComponent>(); + if (component != nullptr) { + const Curve *curve = component->get_curve_for_render(); + + if (curve != nullptr) { + Object *temp_object = &data->temp_geometry_component_object; + *temp_object = *data->geometry_component_owner; + temp_object->type = OB_CURVE; + temp_object->data = (void *)curve; + /* Assign data_eval here too, because curve rendering code tries + * to use a mesh if it can find one in this pointer. */ + temp_object->runtime.data_eval = (ID *)curve; + temp_object->runtime.select_id = data->geometry_component_owner->runtime.select_id; + iter->current = temp_object; + return true; + } + } + } + data->geometry_component_owner = nullptr; return false; } diff --git a/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc b/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc index 43bcb23a38a..e5d7bd70214 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc @@ -606,20 +606,12 @@ void update_lattice_edit_mode_pointers(const Depsgraph * /*depsgraph*/, void update_mesh_edit_mode_pointers(const ID *id_orig, ID *id_cow) { - /* For meshes we need to update edit_mesh to make it to point - * to the CoW version of object. - * - * This is kind of confusing, because actual bmesh is not owned by - * the CoW object, so need to be accurate about using link from - * edit_mesh to object. */ const Mesh *mesh_orig = (const Mesh *)id_orig; Mesh *mesh_cow = (Mesh *)id_cow; if (mesh_orig->edit_mesh == nullptr) { return; } - mesh_cow->edit_mesh = (BMEditMesh *)MEM_dupallocN(mesh_orig->edit_mesh); - mesh_cow->edit_mesh->mesh_eval_cage = nullptr; - mesh_cow->edit_mesh->mesh_eval_final = nullptr; + mesh_cow->edit_mesh = mesh_orig->edit_mesh; } /* Edit data is stored and owned by original datablocks, copied ones @@ -1001,11 +993,6 @@ void discard_lattice_edit_mode_pointers(ID *id_cow) void discard_mesh_edit_mode_pointers(ID *id_cow) { Mesh *mesh_cow = (Mesh *)id_cow; - if (mesh_cow->edit_mesh == nullptr) { - return; - } - BKE_editmesh_free_derivedmesh(mesh_cow->edit_mesh); - MEM_freeN(mesh_cow->edit_mesh); mesh_cow->edit_mesh = nullptr; } diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index 196b46953c4..afb0f613290 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -31,6 +31,7 @@ set(INC ../depsgraph ../editors/include ../editors/space_view3d + ../functions ../gpu ../imbuf ../makesdna @@ -51,7 +52,7 @@ set(INC set(SRC intern/draw_cache.c intern/draw_cache_extract_mesh.c - intern/draw_cache_impl_curve.c + intern/draw_cache_impl_curve.cc intern/draw_cache_impl_displist.c intern/draw_cache_impl_gpencil.c intern/draw_cache_impl_hair.c @@ -478,6 +479,7 @@ add_dependencies(bf_draw bf_dna) if(WITH_GTESTS) if(WITH_OPENGL_DRAW_TESTS) set(TEST_SRC + tests/draw_testing.cc tests/shaders_test.cc ) set(TEST_INC diff --git a/source/blender/draw/engines/eevee/eevee_cryptomatte.c b/source/blender/draw/engines/eevee/eevee_cryptomatte.c index 70d48ea6040..7fe984b4397 100644 --- a/source/blender/draw/engines/eevee/eevee_cryptomatte.c +++ b/source/blender/draw/engines/eevee/eevee_cryptomatte.c @@ -265,8 +265,7 @@ void EEVEE_cryptomatte_object_hair_cache_populate(EEVEE_Data *vedata, Object *ob) { BLI_assert(ob->type == OB_HAIR); - Hair *hair = ob->data; - Material *material = hair->mat ? hair->mat[HAIR_MATERIAL_NR - 1] : NULL; + Material *material = BKE_object_material_get_eval(ob, HAIR_MATERIAL_NR); eevee_cryptomatte_hair_cache_populate(vedata, sldata, ob, NULL, NULL, material); } @@ -291,8 +290,7 @@ void EEVEE_cryptomatte_particle_hair_cache_populate(EEVEE_Data *vedata, if (draw_as != PART_DRAW_PATH) { continue; } - Mesh *mesh = ob->data; - Material *material = part->omat - 1 < mesh->totcol ? NULL : mesh->mat[part->omat - 1]; + Material *material = BKE_object_material_get_eval(ob, part->omat); eevee_cryptomatte_hair_cache_populate(vedata, sldata, ob, psys, md, material); } } @@ -318,7 +316,7 @@ void EEVEE_cryptomatte_cache_populate(EEVEE_Data *vedata, EEVEE_ViewLayerData *s if (geom == NULL) { continue; } - Material *material = BKE_object_material_get(ob, i + 1); + Material *material = BKE_object_material_get_eval(ob, i + 1); DRWShadingGroup *grp = eevee_cryptomatte_shading_group_create( vedata, sldata, ob, material, false); DRW_shgroup_call(grp, geom, ob); diff --git a/source/blender/draw/engines/eevee/eevee_materials.c b/source/blender/draw/engines/eevee/eevee_materials.c index a4325675ea9..d2e0c8308c5 100644 --- a/source/blender/draw/engines/eevee/eevee_materials.c +++ b/source/blender/draw/engines/eevee/eevee_materials.c @@ -723,7 +723,7 @@ BLI_INLINE Material *eevee_object_material_get(Object *ob, int slot, bool holdou if (holdout) { return BKE_material_default_holdout(); } - Material *ma = BKE_object_material_get(ob, slot + 1); + Material *ma = BKE_object_material_get_eval(ob, slot + 1); if (ma == NULL) { if (ob->type == OB_VOLUME) { ma = BKE_material_default_volume(); diff --git a/source/blender/draw/engines/eevee/eevee_volumes.c b/source/blender/draw/engines/eevee/eevee_volumes.c index 809d6010f10..eed36221fcb 100644 --- a/source/blender/draw/engines/eevee/eevee_volumes.c +++ b/source/blender/draw/engines/eevee/eevee_volumes.c @@ -501,7 +501,7 @@ void EEVEE_volumes_cache_object_add(EEVEE_ViewLayerData *sldata, Scene *scene, Object *ob) { - Material *ma = BKE_object_material_get(ob, 1); + Material *ma = BKE_object_material_get_eval(ob, 1); if (ma == NULL) { if (ob->type == OB_VOLUME) { diff --git a/source/blender/draw/engines/gpencil/gpencil_cache_utils.c b/source/blender/draw/engines/gpencil/gpencil_cache_utils.c index ee51b751187..af8b029a08e 100644 --- a/source/blender/draw/engines/gpencil/gpencil_cache_utils.c +++ b/source/blender/draw/engines/gpencil/gpencil_cache_utils.c @@ -60,7 +60,8 @@ GPENCIL_tObject *gpencil_object_cache_add(GPENCIL_PrivateData *pd, Object *ob) /* Check if any material with holdout flag enabled. */ tgp_ob->do_mat_holdout = false; - for (int i = 0; i < ob->totcol; i++) { + const int tot_materials = BKE_object_material_count_eval(ob); + for (int i = 0; i < tot_materials; i++) { MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, i + 1); if (((gp_style != NULL) && (gp_style->flag & GP_MATERIAL_IS_STROKE_HOLDOUT)) || ((gp_style->flag & GP_MATERIAL_IS_FILL_HOLDOUT))) { @@ -273,7 +274,13 @@ GPENCIL_tLayer *gpencil_layer_cache_add(GPENCIL_PrivateData *pd, const bool override_vertcol = (pd->v3d_color_type != -1); const bool is_vert_col_mode = (pd->v3d_color_type == V3D_SHADING_VERTEX_COLOR) || GPENCIL_VERTEX_MODE(gpd) || pd->is_render; - bool is_masked = (gpl->flag & GP_LAYER_USE_MASK) && !BLI_listbase_is_empty(&gpl->mask_layers); + const bool is_viewlayer_render = pd->is_render && (gpl->viewlayername[0] != '\0') && + STREQ(pd->view_layer->name, gpl->viewlayername); + const bool disable_masks_render = is_viewlayer_render && + (gpl->flag & GP_LAYER_DISABLE_MASKS_IN_VIEWLAYER); + bool is_masked = disable_masks_render ? false : + (gpl->flag & GP_LAYER_USE_MASK) && + !BLI_listbase_is_empty(&gpl->mask_layers); float vert_col_opacity = (override_vertcol) ? (is_vert_col_mode ? pd->vertex_paint_opacity : 0.0f) : diff --git a/source/blender/draw/engines/gpencil/gpencil_draw_data.c b/source/blender/draw/engines/gpencil/gpencil_draw_data.c index 526f553329e..e3e84dd4c8c 100644 --- a/source/blender/draw/engines/gpencil/gpencil_draw_data.c +++ b/source/blender/draw/engines/gpencil/gpencil_draw_data.c @@ -186,7 +186,7 @@ GPENCIL_MaterialPool *gpencil_material_pool_create(GPENCIL_PrivateData *pd, Obje { GPENCIL_MaterialPool *matpool = pd->last_material_pool; - int mat_len = max_ii(1, ob->totcol); + int mat_len = max_ii(1, BKE_object_material_count_eval(ob)); bool reuse_matpool = matpool && ((matpool->used_count + mat_len) <= GP_MATERIAL_BUFFER_LEN); diff --git a/source/blender/draw/engines/gpencil/gpencil_engine.h b/source/blender/draw/engines/gpencil/gpencil_engine.h index 5ceb909bc88..34fe29055d6 100644 --- a/source/blender/draw/engines/gpencil/gpencil_engine.h +++ b/source/blender/draw/engines/gpencil/gpencil_engine.h @@ -297,7 +297,9 @@ typedef struct GPENCIL_PrivateData { int v3d_color_type; /* Current frame */ int cfra; - /* If we are rendering for final render (F12). */ + /* If we are rendering for final render (F12). + * NOTE: set to false for viewport and opengl rendering (including VSE scene rendering), but set + * to true when rendering in `OB_RENDER` shading mode (viewport or opengl rendering) */ bool is_render; /* If we are in viewport display (used for VFX). */ bool is_viewport; diff --git a/source/blender/draw/engines/gpencil/gpencil_shader_fx.c b/source/blender/draw/engines/gpencil/gpencil_shader_fx.c index 0b66141e51a..21d55357a2a 100644 --- a/source/blender/draw/engines/gpencil/gpencil_shader_fx.c +++ b/source/blender/draw/engines/gpencil/gpencil_shader_fx.c @@ -424,22 +424,26 @@ static void gpencil_vfx_glow(GlowShaderFxData *fx, Object *UNUSED(ob), gpIterVfx GPUShader *sh = GPENCIL_shader_fx_glow_get(); - float ref_col[3]; + float ref_col[4]; if (fx->mode == eShaderFxGlowMode_Luminance) { + /* Only pass in the first value for luminance. */ ref_col[0] = fx->threshold; ref_col[1] = -1.0f; ref_col[2] = -1.0f; + ref_col[3] = -1.0f; } else { + /* First three values are the RGB for the selected color, last value the threshold. */ copy_v3_v3(ref_col, fx->select_color); + ref_col[3] = fx->threshold; } DRWState state = DRW_STATE_WRITE_COLOR; grp = gpencil_vfx_pass_create("Fx Glow H", state, iter, sh); DRW_shgroup_uniform_vec2_copy(grp, "offset", (float[2]){fx->blur[0] * c, fx->blur[0] * s}); DRW_shgroup_uniform_int_copy(grp, "sampCount", max_ii(1, min_ii(fx->samples, fx->blur[0]))); - DRW_shgroup_uniform_vec3_copy(grp, "threshold", ref_col); + DRW_shgroup_uniform_vec4_copy(grp, "threshold", ref_col); DRW_shgroup_uniform_vec4_copy(grp, "glowColor", fx->glow_color); DRW_shgroup_uniform_bool_copy(grp, "glowUnder", use_glow_under); DRW_shgroup_uniform_bool_copy(grp, "firstPass", true); @@ -473,7 +477,7 @@ static void gpencil_vfx_glow(GlowShaderFxData *fx, Object *UNUSED(ob), gpIterVfx grp = gpencil_vfx_pass_create("Fx Glow V", state, iter, sh); DRW_shgroup_uniform_vec2_copy(grp, "offset", (float[2]){-fx->blur[1] * s, fx->blur[1] * c}); DRW_shgroup_uniform_int_copy(grp, "sampCount", max_ii(1, min_ii(fx->samples, fx->blur[0]))); - DRW_shgroup_uniform_vec3_copy(grp, "threshold", (float[3]){-1.0f, -1.0f, -1.0f}); + DRW_shgroup_uniform_vec4_copy(grp, "threshold", (float[4]){-1.0f, -1.0f, -1.0f, -1.0}); DRW_shgroup_uniform_vec4_copy(grp, "glowColor", (float[4]){1.0f, 1.0f, 1.0f, fx->glow_color[3]}); DRW_shgroup_uniform_bool_copy(grp, "firstPass", false); DRW_shgroup_uniform_int_copy(grp, "blendMode", fx->blend_mode); diff --git a/source/blender/draw/engines/gpencil/shaders/gpencil_vfx_frag.glsl b/source/blender/draw/engines/gpencil/shaders/gpencil_vfx_frag.glsl index bb905f8694b..269ed49c4d0 100644 --- a/source/blender/draw/engines/gpencil/shaders/gpencil_vfx_frag.glsl +++ b/source/blender/draw/engines/gpencil/shaders/gpencil_vfx_frag.glsl @@ -145,7 +145,7 @@ void main() uniform vec4 glowColor; uniform vec2 offset; uniform int sampCount; -uniform vec3 threshold; +uniform vec4 threshold; uniform bool firstPass; uniform bool glowUnder; uniform int blendMode; @@ -168,7 +168,7 @@ void main() vec3 rev = texture(revealBuf, uv).rgb; if (threshold.x > -1.0) { if (threshold.y > -1.0) { - if (any(greaterThan(abs(col - threshold), vec3(0.05)))) { + if (any(greaterThan(abs(col - vec3(threshold)), vec3(threshold.w)))) { weight = 0.0; } } diff --git a/source/blender/draw/engines/overlay/overlay_armature.c b/source/blender/draw/engines/overlay/overlay_armature.c index 54224071d23..fbad60ff4ab 100644 --- a/source/blender/draw/engines/overlay/overlay_armature.c +++ b/source/blender/draw/engines/overlay/overlay_armature.c @@ -1031,7 +1031,7 @@ static void pchan_draw_data_init(bPoseChannel *pchan) static void draw_bone_update_disp_matrix_default(EditBone *eBone, bPoseChannel *pchan) { float ebmat[4][4]; - float length; + float bone_scale[3]; float(*bone_mat)[4]; float(*disp_mat)[4]; float(*disp_tail_mat)[4]; @@ -1040,23 +1040,23 @@ static void draw_bone_update_disp_matrix_default(EditBone *eBone, bPoseChannel * * and not be tight to the draw pass creation. * This would refresh armature without invalidating the draw cache */ if (pchan) { - length = pchan->bone->length; bone_mat = pchan->pose_mat; disp_mat = pchan->disp_mat; disp_tail_mat = pchan->disp_tail_mat; + copy_v3_fl(bone_scale, pchan->bone->length); } else { eBone->length = len_v3v3(eBone->tail, eBone->head); ED_armature_ebone_to_mat4(eBone, ebmat); - length = eBone->length; + copy_v3_fl(bone_scale, eBone->length); bone_mat = ebmat; disp_mat = eBone->disp_mat; disp_tail_mat = eBone->disp_tail_mat; } copy_m4_m4(disp_mat, bone_mat); - rescale_m4(disp_mat, (float[3]){length, length, length}); + rescale_m4(disp_mat, bone_scale); copy_m4_m4(disp_tail_mat, disp_mat); translate_m4(disp_tail_mat, 0.0f, 1.0f, 0.0f); } @@ -1255,19 +1255,27 @@ static void draw_bone_update_disp_matrix_bbone(EditBone *eBone, bPoseChannel *pc static void draw_bone_update_disp_matrix_custom(bPoseChannel *pchan) { - float length; + float bone_scale[3]; float(*bone_mat)[4]; float(*disp_mat)[4]; float(*disp_tail_mat)[4]; + float rot_mat[3][3]; /* See TODO above */ - length = PCHAN_CUSTOM_DRAW_SIZE(pchan); + mul_v3_v3fl(bone_scale, pchan->custom_scale_xyz, PCHAN_CUSTOM_BONE_LENGTH(pchan)); bone_mat = pchan->custom_tx ? pchan->custom_tx->pose_mat : pchan->pose_mat; disp_mat = pchan->disp_mat; disp_tail_mat = pchan->disp_tail_mat; + eulO_to_mat3(rot_mat, pchan->custom_rotation_euler, ROT_MODE_XYZ); + copy_m4_m4(disp_mat, bone_mat); - rescale_m4(disp_mat, (float[3]){length, length, length}); + translate_m4(disp_mat, + pchan->custom_translation[0], + pchan->custom_translation[1], + pchan->custom_translation[2]); + mul_m4_m4m3(disp_mat, disp_mat, rot_mat); + rescale_m4(disp_mat, bone_scale); copy_m4_m4(disp_tail_mat, disp_mat); translate_m4(disp_tail_mat, 0.0f, 1.0f, 0.0f); } diff --git a/source/blender/draw/engines/overlay/overlay_gpencil.c b/source/blender/draw/engines/overlay/overlay_gpencil.c index 891142fe0a2..aa26aa47faa 100644 --- a/source/blender/draw/engines/overlay/overlay_gpencil.c +++ b/source/blender/draw/engines/overlay/overlay_gpencil.c @@ -382,7 +382,7 @@ static void overlay_gpencil_draw_stroke_color_name(bGPDlayer *UNUSED(gpl), void *thunk) { Object *ob = (Object *)thunk; - Material *ma = BKE_object_material_get(ob, gps->mat_nr + 1); + Material *ma = BKE_object_material_get_eval(ob, gps->mat_nr + 1); if (ma == NULL) { return; } diff --git a/source/blender/draw/engines/overlay/overlay_motion_path.c b/source/blender/draw/engines/overlay/overlay_motion_path.c index 48b7b53a5ba..a92f11aca38 100644 --- a/source/blender/draw/engines/overlay/overlay_motion_path.c +++ b/source/blender/draw/engines/overlay/overlay_motion_path.c @@ -190,7 +190,7 @@ static void motion_path_cache(OVERLAY_Data *vedata, bool is_keyframe = (mpv->flag & MOTIONPATH_VERT_KEY) != 0; if ((show_keyframes && show_keyframes_no && is_keyframe) || (show_frame_no && (i == 0))) { - numstr_len = BLI_snprintf(numstr, sizeof(numstr), " %d", frame); + numstr_len = BLI_snprintf_rlen(numstr, sizeof(numstr), " %d", frame); DRW_text_cache_add( dt, mpv->co, numstr, numstr_len, 0, 0, txt_flag, (is_keyframe) ? col_kf : col); } @@ -200,7 +200,7 @@ static void motion_path_cache(OVERLAY_Data *vedata, /* Only draw frame number if several consecutive highlighted points * don't occur on same point. */ if ((equals_v3v3(mpv->co, mpvP->co) == 0) || (equals_v3v3(mpv->co, mpvN->co) == 0)) { - numstr_len = BLI_snprintf(numstr, sizeof(numstr), " %d", frame); + numstr_len = BLI_snprintf_rlen(numstr, sizeof(numstr), " %d", frame); DRW_text_cache_add(dt, mpv->co, numstr, numstr_len, 0, 0, txt_flag, col); } } diff --git a/source/blender/draw/engines/overlay/overlay_paint.c b/source/blender/draw/engines/overlay/overlay_paint.c index 89e724bcfcc..d52640ed174 100644 --- a/source/blender/draw/engines/overlay/overlay_paint.c +++ b/source/blender/draw/engines/overlay/overlay_paint.c @@ -48,7 +48,7 @@ static bool paint_object_is_rendered_transparent(View3D *v3d, Object *ob) v3d->shading.color_type == V3D_SHADING_MATERIAL_COLOR) { Mesh *me = ob->data; for (int i = 0; i < me->totcol; i++) { - Material *mat = me->mat[i]; + Material *mat = BKE_object_material_get_eval(ob, i + 1); if (mat && mat->a < 1.0f) { return true; } diff --git a/source/blender/draw/engines/overlay/overlay_particle.c b/source/blender/draw/engines/overlay/overlay_particle.c index 71064e7ff47..5fa74a8c3a6 100644 --- a/source/blender/draw/engines/overlay/overlay_particle.c +++ b/source/blender/draw/engines/overlay/overlay_particle.c @@ -186,7 +186,7 @@ void OVERLAY_particle_cache_populate(OVERLAY_Data *vedata, Object *ob) /* TODO(fclem): Here would be a good place for preemptive culling. */ /* NOTE(fclem): Is color even useful in our modern context? */ - Material *ma = BKE_object_material_get(ob, part->omat); + Material *ma = BKE_object_material_get_eval(ob, part->omat); float color[4] = {0.6f, 0.6f, 0.6f, part->draw_size}; if (ma != NULL) { copy_v3_v3(color, &ma->r); diff --git a/source/blender/draw/engines/workbench/workbench_materials.c b/source/blender/draw/engines/workbench/workbench_materials.c index 6e9118bfe46..800d1085505 100644 --- a/source/blender/draw/engines/workbench/workbench_materials.c +++ b/source/blender/draw/engines/workbench/workbench_materials.c @@ -93,7 +93,7 @@ void workbench_material_ubo_data(WORKBENCH_PrivateData *wpd, /* Return correct material or empty default material if slot is empty. */ BLI_INLINE Material *workbench_object_material_get(Object *ob, int mat_nr) { - Material *ma = BKE_object_material_get(ob, mat_nr); + Material *ma = BKE_object_material_get_eval(ob, mat_nr); if (ma == NULL) { ma = BKE_material_default_empty(); } diff --git a/source/blender/draw/engines/workbench/workbench_volume.c b/source/blender/draw/engines/workbench/workbench_volume.c index 525a81b5581..ddda6d7b58e 100644 --- a/source/blender/draw/engines/workbench/workbench_volume.c +++ b/source/blender/draw/engines/workbench/workbench_volume.c @@ -202,7 +202,7 @@ static void workbench_volume_material_color(WORKBENCH_PrivateData *wpd, eV3DShadingColorType color_type, float color[3]) { - Material *ma = BKE_object_material_get(ob, VOLUME_MATERIAL_NR); + Material *ma = BKE_object_material_get_eval(ob, VOLUME_MATERIAL_NR); WORKBENCH_UBO_Material ubo_data; workbench_material_ubo_data(wpd, ob, ma, &ubo_data, color_type); copy_v3_v3(color, ubo_data.base_color); diff --git a/source/blender/draw/intern/draw_cache_extract.h b/source/blender/draw/intern/draw_cache_extract.h index c929fe7dfd3..a3e43ff2ec5 100644 --- a/source/blender/draw/intern/draw_cache_extract.h +++ b/source/blender/draw/intern/draw_cache_extract.h @@ -88,6 +88,10 @@ typedef enum eMRExtractType { BLI_INLINE int mesh_render_mat_len_get(Mesh *me) { + /* In edit mode, the displayed mesh is stored in the edit-mesh. */ + if (me->edit_mesh && me->edit_mesh->mesh_eval_final) { + return MAX2(1, me->edit_mesh->mesh_eval_final->totcol); + } return MAX2(1, me->totcol); } @@ -260,6 +264,10 @@ typedef struct MeshBatchCache { bool no_loose_wire; } MeshBatchCache; +#define MBC_BATCH_LEN (sizeof(((MeshBatchCache){0}).batch) / sizeof(void *)) +#define MBC_VBO_LEN (sizeof(((MeshBufferCache){0}).vbo) / sizeof(void *)) +#define MBC_IBO_LEN (sizeof(((MeshBufferCache){0}).ibo) / sizeof(void *)) + void mesh_buffer_cache_create_requested(struct TaskGraph *task_graph, MeshBatchCache *cache, MeshBufferCache mbc, diff --git a/source/blender/draw/intern/draw_cache_impl.h b/source/blender/draw/intern/draw_cache_impl.h index 7b97ce43558..5743f39f7da 100644 --- a/source/blender/draw/intern/draw_cache_impl.h +++ b/source/blender/draw/intern/draw_cache_impl.h @@ -43,6 +43,10 @@ struct bGPdata; #include "BKE_mesh_types.h" +#ifdef __cplusplus +extern "C" { +#endif + /* Expose via BKE callbacks */ void DRW_mball_batch_cache_dirty_tag(struct MetaBall *mb, int mode); void DRW_mball_batch_cache_validate(struct MetaBall *mb); @@ -262,3 +266,6 @@ struct GPUBatch *DRW_particles_batch_cache_get_edit_inner_points(struct Object * struct GPUBatch *DRW_particles_batch_cache_get_edit_tip_points(struct Object *object, struct ParticleSystem *psys, struct PTCacheEdit *edit); +#ifdef __cplusplus +} +#endif diff --git a/source/blender/draw/intern/draw_cache_impl_curve.c b/source/blender/draw/intern/draw_cache_impl_curve.cc index e9558fb320c..5cf99db5485 100644 --- a/source/blender/draw/intern/draw_cache_impl_curve.c +++ b/source/blender/draw/intern/draw_cache_impl_curve.cc @@ -25,8 +25,11 @@ #include "MEM_guardedalloc.h" +#include "BLI_array.hh" +#include "BLI_float3.hh" #include "BLI_listbase.h" #include "BLI_math_vector.h" +#include "BLI_span.hh" #include "BLI_utildefines.h" #include "DNA_curve_types.h" @@ -34,6 +37,8 @@ #include "BKE_curve.h" #include "BKE_displist.h" #include "BKE_font.h" +#include "BKE_geometry_set.hh" +#include "BKE_spline.hh" #include "GPU_batch.h" #include "GPU_capabilities.h" @@ -48,6 +53,11 @@ #include "draw_cache_impl.h" /* own include */ +using blender::Array; +using blender::float3; +using blender::IndexRange; +using blender::Span; + /* See: edit_curve_point_vert.glsl for duplicate includes. */ #define SELECT 1 #define ACTIVE_NURB (1 << 2) @@ -139,12 +149,28 @@ static void curve_render_wire_verts_edges_len_get(const CurveCache *ob_curve_cac } } +static void curve_eval_render_wire_verts_edges_len_get(const CurveEval &curve_eval, + int *r_curve_len, + int *r_vert_len, + int *r_edge_len) +{ + Span<SplinePtr> splines = curve_eval.splines(); + *r_curve_len = splines.size(); + *r_vert_len = 0; + *r_edge_len = 0; + for (const SplinePtr &spline : splines) { + *r_vert_len += spline->evaluated_points_size(); + *r_edge_len += spline->evaluated_edges_size(); + } +} + static int curve_render_normal_len_get(const ListBase *lb, const CurveCache *ob_curve_cache) { int normal_len = 0; const BevList *bl; const Nurb *nu; - for (bl = ob_curve_cache->bev.first, nu = lb->first; nu && bl; bl = bl->next, nu = nu->next) { + for (bl = (const BevList *)ob_curve_cache->bev.first, nu = (const Nurb *)lb->first; nu && bl; + bl = bl->next, nu = nu->next) { int nr = bl->nr; int skip = nu->resolu / 16; #if 0 @@ -163,7 +189,7 @@ static int curve_render_normal_len_get(const ListBase *lb, const CurveCache *ob_ /* ---------------------------------------------------------------------- */ /* Curve Interface, indirect, partially cached access to complex data. */ -typedef struct CurveRenderData { +struct CurveRenderData { int types; struct { @@ -191,6 +217,9 @@ typedef struct CurveRenderData { /* borrow from 'Object' */ CurveCache *ob_curve_cache; + /* Owned by the evaluated object's geometry set (#geometry_set_eval). */ + const CurveEval *curve_eval; + /* borrow from 'Curve' */ ListBase *nurbs; @@ -198,7 +227,7 @@ typedef struct CurveRenderData { int actnu; /* edit, index in active nurb (BPoint or BezTriple) */ int actvert; -} CurveRenderData; +}; enum { /* Wire center-line */ @@ -220,7 +249,7 @@ static CurveRenderData *curve_render_data_create(Curve *cu, CurveCache *ob_curve_cache, const int types) { - CurveRenderData *rdata = MEM_callocN(sizeof(*rdata), __func__); + CurveRenderData *rdata = (CurveRenderData *)MEM_callocN(sizeof(*rdata), __func__); rdata->types = types; ListBase *nurbs; @@ -229,11 +258,21 @@ static CurveRenderData *curve_render_data_create(Curve *cu, rdata->ob_curve_cache = ob_curve_cache; + rdata->curve_eval = cu->curve_eval; + if (types & CU_DATATYPE_WIRE) { - curve_render_wire_verts_edges_len_get(rdata->ob_curve_cache, - &rdata->wire.curve_len, - &rdata->wire.vert_len, - &rdata->wire.edge_len); + if (rdata->curve_eval != nullptr) { + curve_eval_render_wire_verts_edges_len_get(*rdata->curve_eval, + &rdata->wire.curve_len, + &rdata->wire.vert_len, + &rdata->wire.edge_len); + } + else { + curve_render_wire_verts_edges_len_get(rdata->ob_curve_cache, + &rdata->wire.curve_len, + &rdata->wire.vert_len, + &rdata->wire.edge_len); + } } if (cu->editnurb) { @@ -314,7 +353,7 @@ static void curve_cd_calc_used_gpu_layers(CustomDataMask *cd_layers, { for (int i = 0; i < gpumat_array_len; i++) { struct GPUMaterial *gpumat = gpumat_array[i]; - if (gpumat == NULL) { + if (gpumat == nullptr) { continue; } @@ -354,7 +393,7 @@ static void curve_cd_calc_used_gpu_layers(CustomDataMask *cd_layers, /* ---------------------------------------------------------------------- */ /* Curve GPUBatch Cache */ -typedef struct CurveBatchCache { +struct CurveBatchCache { struct { GPUVertBuf *pos_nor; GPUVertBuf *edge_fac; @@ -406,15 +445,15 @@ typedef struct CurveBatchCache { /* Valid only if edge_detection is up to date. */ bool is_manifold; -} CurveBatchCache; +}; /* GPUBatch cache management. */ static bool curve_batch_cache_valid(Curve *cu) { - CurveBatchCache *cache = cu->batch_cache; + CurveBatchCache *cache = (CurveBatchCache *)cu->batch_cache; - if (cache == NULL) { + if (cache == nullptr) { return false; } @@ -426,7 +465,7 @@ static bool curve_batch_cache_valid(Curve *cu) return false; } - if (cache->is_editmode != ((cu->editnurb != NULL) || (cu->editfont != NULL))) { + if (cache->is_editmode != ((cu->editnurb != nullptr) || (cu->editfont != nullptr))) { return false; } @@ -441,10 +480,11 @@ static bool curve_batch_cache_valid(Curve *cu) static void curve_batch_cache_init(Curve *cu) { - CurveBatchCache *cache = cu->batch_cache; + CurveBatchCache *cache = (CurveBatchCache *)cu->batch_cache; if (!cache) { - cache = cu->batch_cache = MEM_callocN(sizeof(*cache), __func__); + cache = (CurveBatchCache *)MEM_callocN(sizeof(*cache), __func__); + cu->batch_cache = cache; } else { memset(cache, 0, sizeof(*cache)); @@ -463,11 +503,12 @@ static void curve_batch_cache_init(Curve *cu) cache->cd_used = 0; cache->mat_len = DRW_curve_material_count_get(cu); - cache->surf_per_mat_tris = MEM_callocN(sizeof(*cache->surf_per_mat_tris) * cache->mat_len, - __func__); - cache->surf_per_mat = MEM_callocN(sizeof(*cache->surf_per_mat) * cache->mat_len, __func__); + cache->surf_per_mat_tris = (GPUIndexBuf **)MEM_callocN( + sizeof(*cache->surf_per_mat_tris) * cache->mat_len, __func__); + cache->surf_per_mat = (GPUBatch **)MEM_callocN(sizeof(*cache->surf_per_mat) * cache->mat_len, + __func__); - cache->is_editmode = (cu->editnurb != NULL) || (cu->editfont != NULL); + cache->is_editmode = (cu->editnurb != nullptr) || (cu->editfont != nullptr); cache->is_dirty = false; } @@ -482,13 +523,13 @@ void DRW_curve_batch_cache_validate(Curve *cu) static CurveBatchCache *curve_batch_cache_get(Curve *cu) { - return cu->batch_cache; + return (CurveBatchCache *)cu->batch_cache; } void DRW_curve_batch_cache_dirty_tag(Curve *cu, int mode) { - CurveBatchCache *cache = cu->batch_cache; - if (cache == NULL) { + CurveBatchCache *cache = (CurveBatchCache *)cu->batch_cache; + if (cache == nullptr) { return; } switch (mode) { @@ -508,7 +549,7 @@ void DRW_curve_batch_cache_dirty_tag(Curve *cu, int mode) static void curve_batch_cache_clear(Curve *cu) { - CurveBatchCache *cache = cu->batch_cache; + CurveBatchCache *cache = (CurveBatchCache *)cu->batch_cache; if (!cache) { return; } @@ -553,8 +594,6 @@ void DRW_curve_batch_cache_free(Curve *cu) /* GPUBatch cache usage. */ static void curve_create_curves_pos(CurveRenderData *rdata, GPUVertBuf *vbo_curves_pos) { - BLI_assert(rdata->ob_curve_cache != NULL); - static GPUVertFormat format = {0}; static struct { uint pos; @@ -567,30 +606,46 @@ static void curve_create_curves_pos(CurveRenderData *rdata, GPUVertBuf *vbo_curv GPU_vertbuf_init_with_format(vbo_curves_pos, &format); GPU_vertbuf_data_alloc(vbo_curves_pos, vert_len); - int v_idx = 0; - LISTBASE_FOREACH (const BevList *, bl, &rdata->ob_curve_cache->bev) { - if (bl->nr <= 0) { - continue; - } - const int i_end = v_idx + bl->nr; - for (const BevPoint *bevp = bl->bevpoints; v_idx < i_end; v_idx++, bevp++) { - GPU_vertbuf_attr_set(vbo_curves_pos, attr_id.pos, v_idx, bevp->vec); + if (rdata->curve_eval != nullptr) { + const CurveEval &curve_eval = *rdata->curve_eval; + Span<SplinePtr> splines = curve_eval.splines(); + Array<int> offsets = curve_eval.evaluated_point_offsets(); + BLI_assert(offsets.last() == vert_len); + + for (const int i_spline : splines.index_range()) { + Span<float3> positions = splines[i_spline]->evaluated_positions(); + for (const int i_point : positions.index_range()) { + GPU_vertbuf_attr_set( + vbo_curves_pos, attr_id.pos, offsets[i_spline] + i_point, positions[i_point]); + } } } - LISTBASE_FOREACH (const DispList *, dl, &rdata->ob_curve_cache->disp) { - if (ELEM(dl->type, DL_SEGM, DL_POLY)) { - for (int i = 0; i < dl->nr; v_idx++, i++) { - GPU_vertbuf_attr_set(vbo_curves_pos, attr_id.pos, v_idx, &((float(*)[3])dl->verts)[i]); + else { + BLI_assert(rdata->ob_curve_cache != nullptr); + + int v_idx = 0; + LISTBASE_FOREACH (const BevList *, bl, &rdata->ob_curve_cache->bev) { + if (bl->nr <= 0) { + continue; + } + const int i_end = v_idx + bl->nr; + for (const BevPoint *bevp = bl->bevpoints; v_idx < i_end; v_idx++, bevp++) { + GPU_vertbuf_attr_set(vbo_curves_pos, attr_id.pos, v_idx, bevp->vec); + } + } + LISTBASE_FOREACH (const DispList *, dl, &rdata->ob_curve_cache->disp) { + if (ELEM(dl->type, DL_SEGM, DL_POLY)) { + for (int i = 0; i < dl->nr; v_idx++, i++) { + GPU_vertbuf_attr_set(vbo_curves_pos, attr_id.pos, v_idx, &((float(*)[3])dl->verts)[i]); + } } } + BLI_assert(v_idx == vert_len); } - BLI_assert(v_idx == vert_len); } static void curve_create_curves_lines(CurveRenderData *rdata, GPUIndexBuf *ibo_curve_lines) { - BLI_assert(rdata->ob_curve_cache != NULL); - const int vert_len = curve_render_data_wire_verts_len_get(rdata); const int edge_len = curve_render_data_wire_edges_len_get(rdata); const int curve_len = curve_render_data_wire_curve_len_get(rdata); @@ -600,34 +655,56 @@ static void curve_create_curves_lines(CurveRenderData *rdata, GPUIndexBuf *ibo_c GPUIndexBufBuilder elb; GPU_indexbuf_init_ex(&elb, GPU_PRIM_LINE_STRIP, index_len, vert_len); - int v_idx = 0; - LISTBASE_FOREACH (const BevList *, bl, &rdata->ob_curve_cache->bev) { - if (bl->nr <= 0) { - continue; - } - const bool is_cyclic = bl->poly != -1; - if (is_cyclic) { - GPU_indexbuf_add_generic_vert(&elb, v_idx + (bl->nr - 1)); - } - for (int i = 0; i < bl->nr; i++) { - GPU_indexbuf_add_generic_vert(&elb, v_idx + i); + if (rdata->curve_eval != nullptr) { + const CurveEval &curve_eval = *rdata->curve_eval; + Span<SplinePtr> splines = curve_eval.splines(); + Array<int> offsets = curve_eval.evaluated_point_offsets(); + BLI_assert(offsets.last() == vert_len); + + for (const int i_spline : splines.index_range()) { + const int eval_size = splines[i_spline]->evaluated_points_size(); + if (splines[i_spline]->is_cyclic()) { + GPU_indexbuf_add_generic_vert(&elb, offsets[i_spline] + eval_size - 1); + } + for (const int i_point : IndexRange(eval_size)) { + GPU_indexbuf_add_generic_vert(&elb, offsets[i_spline] + i_point); + } + GPU_indexbuf_add_primitive_restart(&elb); } - GPU_indexbuf_add_primitive_restart(&elb); - v_idx += bl->nr; } - LISTBASE_FOREACH (const DispList *, dl, &rdata->ob_curve_cache->disp) { - if (ELEM(dl->type, DL_SEGM, DL_POLY)) { - const bool is_cyclic = dl->type == DL_POLY; + else { + BLI_assert(rdata->ob_curve_cache != nullptr); + + int v_idx = 0; + LISTBASE_FOREACH (const BevList *, bl, &rdata->ob_curve_cache->bev) { + if (bl->nr <= 0) { + continue; + } + const bool is_cyclic = bl->poly != -1; if (is_cyclic) { - GPU_indexbuf_add_generic_vert(&elb, v_idx + (dl->nr - 1)); + GPU_indexbuf_add_generic_vert(&elb, v_idx + (bl->nr - 1)); } - for (int i = 0; i < dl->nr; i++) { + for (int i = 0; i < bl->nr; i++) { GPU_indexbuf_add_generic_vert(&elb, v_idx + i); } GPU_indexbuf_add_primitive_restart(&elb); - v_idx += dl->nr; + v_idx += bl->nr; + } + LISTBASE_FOREACH (const DispList *, dl, &rdata->ob_curve_cache->disp) { + if (ELEM(dl->type, DL_SEGM, DL_POLY)) { + const bool is_cyclic = dl->type == DL_POLY; + if (is_cyclic) { + GPU_indexbuf_add_generic_vert(&elb, v_idx + (dl->nr - 1)); + } + for (int i = 0; i < dl->nr; i++) { + GPU_indexbuf_add_generic_vert(&elb, v_idx + i); + } + GPU_indexbuf_add_primitive_restart(&elb); + v_idx += dl->nr; + } } } + GPU_indexbuf_build_in_place(&elb, ibo_curve_lines); } @@ -677,7 +754,9 @@ static void curve_create_edit_curves_nor(CurveRenderData *rdata, const uint tan_id = do_hq_normals ? attr_id.tan_hq : attr_id.tan; const uint rad_id = do_hq_normals ? attr_id.rad_hq : attr_id.rad; - for (bl = rdata->ob_curve_cache->bev.first, nu = rdata->nurbs->first; nu && bl; + for (bl = (const BevList *)rdata->ob_curve_cache->bev.first, + nu = (const Nurb *)rdata->nurbs->first; + nu && bl; bl = bl->next, nu = nu->next) { const BevPoint *bevp = bl->bevpoints; int nr = bl->nr; @@ -773,8 +852,8 @@ static void curve_create_edit_data_and_handles(CurveRenderData *rdata, GPU_vertbuf_data_alloc(vbo_data, verts_len_capacity); } - GPUIndexBufBuilder elb_verts, *elbp_verts = NULL; - GPUIndexBufBuilder elb_lines, *elbp_lines = NULL; + GPUIndexBufBuilder elb_verts, *elbp_verts = nullptr; + GPUIndexBufBuilder elb_lines, *elbp_lines = nullptr; if (DRW_TEST_ASSIGN_IBO(ibo_edit_verts_points)) { elbp_verts = &elb_verts; GPU_indexbuf_init(elbp_verts, GPU_PRIM_POINTS, verts_len_capacity, verts_len_capacity); @@ -785,13 +864,13 @@ static void curve_create_edit_data_and_handles(CurveRenderData *rdata, } int nu_id = 0; - for (Nurb *nu = rdata->nurbs->first; nu; nu = nu->next, nu_id++) { + for (Nurb *nu = (Nurb *)rdata->nurbs->first; nu; nu = nu->next, nu_id++) { const BezTriple *bezt = nu->bezt; const BPoint *bp = nu->bp; if (bezt) { for (int a = 0; a < nu->pntsu; a++, bezt++) { - if (bezt->hide == true) { + if (bezt->hide != 0) { continue; } const bool handle_selected = BEZT_ISSEL_ANY(bezt); @@ -826,7 +905,7 @@ static void curve_create_edit_data_and_handles(CurveRenderData *rdata, else if (bp) { int pt_len = nu->pntsu * nu->pntsv; for (int a = 0; a < pt_len; a++, bp++, vbo_len_used += 1) { - if (bp->hide == true) { + if (bp->hide != 0) { continue; } int u = (a % nu->pntsu); @@ -837,8 +916,8 @@ static void curve_create_edit_data_and_handles(CurveRenderData *rdata, GPU_indexbuf_add_point_vert(elbp_verts, vbo_len_used); } if (elbp_lines) { - const BPoint *bp_next_u = (u < (nu->pntsu - 1)) ? &nu->bp[a + 1] : NULL; - const BPoint *bp_next_v = (v < (nu->pntsv - 1)) ? &nu->bp[a + nu->pntsu] : NULL; + const BPoint *bp_next_u = (u < (nu->pntsu - 1)) ? &nu->bp[a + 1] : nullptr; + const BPoint *bp_next_v = (v < (nu->pntsv - 1)) ? &nu->bp[a + nu->pntsu] : nullptr; if (bp_next_u && (bp_next_u->hide == false)) { GPU_indexbuf_add_line_verts(elbp_lines, vbo_len_used, vbo_len_used + 1); } @@ -858,17 +937,17 @@ static void curve_create_edit_data_and_handles(CurveRenderData *rdata, } /* Resize & Finish */ - if (elbp_verts != NULL) { + if (elbp_verts != nullptr) { GPU_indexbuf_build_in_place(elbp_verts, ibo_edit_verts_points); } - if (elbp_lines != NULL) { + if (elbp_lines != nullptr) { GPU_indexbuf_build_in_place(elbp_lines, ibo_edit_lines); } if (vbo_len_used != verts_len_capacity) { - if (vbo_pos != NULL) { + if (vbo_pos != nullptr) { GPU_vertbuf_data_resize(vbo_pos, vbo_len_used); } - if (vbo_data != NULL) { + if (vbo_data != nullptr) { GPU_vertbuf_data_resize(vbo_data, vbo_len_used); } } @@ -932,7 +1011,7 @@ GPUVertBuf *DRW_curve_batch_cache_pos_vertbuf_get(struct Curve *cu) /* Request surface to trigger the vbo filling. Otherwise it may do nothing. */ DRW_batch_request(&cache->batch.surfaces); - DRW_vbo_request(NULL, &cache->ordered.loop_pos_nor); + DRW_vbo_request(nullptr, &cache->ordered.loop_pos_nor); return cache->ordered.loop_pos_nor; } @@ -968,7 +1047,7 @@ void DRW_curve_batch_cache_create_requested(Object *ob, const struct Scene *scen { BLI_assert(ELEM(ob->type, OB_CURVE, OB_SURF, OB_FONT)); - Curve *cu = ob->data; + Curve *cu = (Curve *)ob->data; CurveBatchCache *cache = curve_batch_cache_get(cu); /* Verify that all surface batches have needed attribute layers. */ @@ -1065,8 +1144,11 @@ void DRW_curve_batch_cache_create_requested(Object *ob, const struct Scene *scen CurveRenderData *rdata = curve_render_data_create(cu, ob->runtime.curve_cache, mr_flag); - /* DispLists */ - ListBase *lb = &rdata->ob_curve_cache->disp; + /* The object's curve cache can be empty (in one case because we use #CurveEval's cache instead), + * If so, point to an empty DispList list to avoid the need to check for null in the following + * functions. */ + ListBase empty_lb = {nullptr, nullptr}; + ListBase *lb = rdata->ob_curve_cache == nullptr ? &empty_lb : &rdata->ob_curve_cache->disp; /* Generate VBOs */ if (DRW_vbo_requested(cache->ordered.pos_nor)) { @@ -1118,7 +1200,7 @@ void DRW_curve_batch_cache_create_requested(Object *ob, const struct Scene *scen #ifdef DEBUG /* Make sure all requested batches have been setup. */ for (int i = 0; i < sizeof(cache->batch) / sizeof(void *); i++) { - BLI_assert(!DRW_batch_requested(((GPUBatch **)&cache->batch)[i], 0)); + BLI_assert(!DRW_batch_requested(((GPUBatch **)&cache->batch)[i], (GPUPrimType)0)); } #endif } diff --git a/source/blender/draw/intern/draw_cache_impl_gpencil.c b/source/blender/draw/intern/draw_cache_impl_gpencil.c index c07271a0d33..49b5e0fecd3 100644 --- a/source/blender/draw/intern/draw_cache_impl_gpencil.c +++ b/source/blender/draw/intern/draw_cache_impl_gpencil.c @@ -348,7 +348,14 @@ static void gpencil_buffer_add_stroke(gpStrokeVert *verts, } /* Draw line to first point to complete the loop for cyclic strokes. */ if (is_cyclic) { - gpencil_buffer_add_point(verts, cols, gps, &pts[0], v++, false); + gpencil_buffer_add_point(verts, cols, gps, &pts[0], v, false); + /* UV factor needs to be adjusted for the last point to not be equal to the UV factor of the + * first point. It should be the factor of the last point plus the distance from the last point + * to the first. + */ + gpStrokeVert *vert = &verts[v]; + vert->u_stroke = verts[v - 1].u_stroke + len_v3v3(&pts[pts_len - 1].x, &pts[0].x); + v++; } /* Last adjacency point (not drawn). */ adj_idx = (is_cyclic) ? 1 : max_ii(0, pts_len - 2); diff --git a/source/blender/draw/intern/draw_cache_impl_mesh.c b/source/blender/draw/intern/draw_cache_impl_mesh.c index af54b57b162..8d5fdcc5276 100644 --- a/source/blender/draw/intern/draw_cache_impl_mesh.c +++ b/source/blender/draw/intern/draw_cache_impl_mesh.c @@ -1160,25 +1160,25 @@ static void drw_mesh_batch_cache_check_available(struct TaskGraph *task_graph, M * some issues (See T77867 where we needed to disable this function in order to debug what was * happening in release builds). */ BLI_task_graph_work_and_wait(task_graph); - for (int i = 0; i < sizeof(cache->batch) / sizeof(void *); i++) { + for (int i = 0; i < MBC_BATCH_LEN; i++) { BLI_assert(!DRW_batch_requested(((GPUBatch **)&cache->batch)[i], 0)); } - for (int i = 0; i < sizeof(cache->final.vbo) / sizeof(void *); i++) { + for (int i = 0; i < MBC_VBO_LEN; i++) { BLI_assert(!DRW_vbo_requested(((GPUVertBuf **)&cache->final.vbo)[i])); } - for (int i = 0; i < sizeof(cache->final.ibo) / sizeof(void *); i++) { + for (int i = 0; i < MBC_IBO_LEN; i++) { BLI_assert(!DRW_ibo_requested(((GPUIndexBuf **)&cache->final.ibo)[i])); } - for (int i = 0; i < sizeof(cache->cage.vbo) / sizeof(void *); i++) { + for (int i = 0; i < MBC_VBO_LEN; i++) { BLI_assert(!DRW_vbo_requested(((GPUVertBuf **)&cache->cage.vbo)[i])); } - for (int i = 0; i < sizeof(cache->cage.ibo) / sizeof(void *); i++) { + for (int i = 0; i < MBC_IBO_LEN; i++) { BLI_assert(!DRW_ibo_requested(((GPUIndexBuf **)&cache->cage.ibo)[i])); } - for (int i = 0; i < sizeof(cache->uv_cage.vbo) / sizeof(void *); i++) { + for (int i = 0; i < MBC_VBO_LEN; i++) { BLI_assert(!DRW_vbo_requested(((GPUVertBuf **)&cache->uv_cage.vbo)[i])); } - for (int i = 0; i < sizeof(cache->uv_cage.ibo) / sizeof(void *); i++) { + for (int i = 0; i < MBC_IBO_LEN; i++) { BLI_assert(!DRW_ibo_requested(((GPUIndexBuf **)&cache->uv_cage.ibo)[i])); } } diff --git a/source/blender/draw/intern/draw_cache_inline.h b/source/blender/draw/intern/draw_cache_inline.h index ebe97b4e7c4..bfc714e5d6a 100644 --- a/source/blender/draw/intern/draw_cache_inline.h +++ b/source/blender/draw/intern/draw_cache_inline.h @@ -53,13 +53,13 @@ BLI_INLINE GPUBatch *DRW_batch_request(GPUBatch **batch) return *batch; } -BLI_INLINE bool DRW_batch_requested(GPUBatch *batch, int prim_type) +BLI_INLINE bool DRW_batch_requested(GPUBatch *batch, GPUPrimType prim_type) { /* Batch has been requested if it has been created but not initialized. */ if (batch != NULL && batch->verts[0] == NULL) { /* HACK. We init without a valid VBO and let the first vbo binding * fill verts[0]. */ - GPU_batch_init_ex(batch, prim_type, (GPUVertBuf *)1, NULL, 0); + GPU_batch_init_ex(batch, prim_type, (GPUVertBuf *)1, NULL, (eGPUBatchFlag)0); batch->verts[0] = NULL; return true; } diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c index c09b4719f3a..37f6bbf52b5 100644 --- a/source/blender/draw/intern/draw_manager.c +++ b/source/blender/draw/intern/draw_manager.c @@ -1473,6 +1473,14 @@ void DRW_draw_callbacks_post_scene(void) GPU_depth_test(GPU_DEPTH_LESS_EQUAL); } + else { + if (v3d && ((v3d->flag2 & V3D_SHOW_ANNOTATION) != 0)) { + GPU_depth_test(GPU_DEPTH_NONE); + /* XXX: as scene->gpd is not copied for COW yet */ + ED_annotation_draw_view3d(DEG_get_input_scene(depsgraph), depsgraph, v3d, region, true); + GPU_depth_test(GPU_DEPTH_LESS_EQUAL); + } + } } struct DRWTextStore *DRW_text_cache_ensure(void) diff --git a/source/blender/draw/intern/draw_manager_shader.c b/source/blender/draw/intern/draw_manager_shader.c index 2aad1f10154..83d0030f89b 100644 --- a/source/blender/draw/intern/draw_manager_shader.c +++ b/source/blender/draw/intern/draw_manager_shader.c @@ -396,6 +396,7 @@ GPUShader *DRW_shader_create_with_transform_feedback(const char *vert, datatoc_gpu_shader_depth_only_frag_glsl, geom, NULL, + NULL, defines, prim_type, varying_names, diff --git a/source/blender/draw/tests/draw_testing.cc b/source/blender/draw/tests/draw_testing.cc new file mode 100644 index 00000000000..0104437e921 --- /dev/null +++ b/source/blender/draw/tests/draw_testing.cc @@ -0,0 +1,18 @@ +/* Apache License, Version 2.0 */ + +#include "draw_testing.hh" + +#include "GPU_shader.h" + +#include "draw_manager_testing.h" + +namespace blender::draw { + +/* Base class for draw test cases. It will setup and tear down the GPU part around each test. */ +void DrawTest::SetUp() +{ + GPUTest::SetUp(); + DRW_draw_state_init_gtests(GPU_SHADER_CFG_DEFAULT); +} + +} // namespace blender::draw diff --git a/source/blender/draw/tests/draw_testing.hh b/source/blender/draw/tests/draw_testing.hh new file mode 100644 index 00000000000..ec0b15b611e --- /dev/null +++ b/source/blender/draw/tests/draw_testing.hh @@ -0,0 +1,13 @@ +/* Apache License, Version 2.0 */ + +#include "gpu_testing.hh" + +namespace blender::draw { + +/* Base class for draw test cases. It will setup and tear down the GPU part around each test. */ +class DrawTest : public blender::gpu::GPUTest { + public: + void SetUp() override; +}; + +} // namespace blender::draw diff --git a/source/blender/draw/tests/shaders_test.cc b/source/blender/draw/tests/shaders_test.cc index 96d544fd855..c96f22859ca 100644 --- a/source/blender/draw/tests/shaders_test.cc +++ b/source/blender/draw/tests/shaders_test.cc @@ -2,12 +2,12 @@ #include "testing/testing.h" +#include "draw_testing.hh" #include "intern/draw_manager_testing.h" #include "GPU_context.h" #include "GPU_init_exit.h" #include "GPU_shader.h" -#include "gpu_testing.hh" #include "engines/eevee/eevee_private.h" #include "engines/gpencil/gpencil_engine.h" @@ -17,19 +17,9 @@ namespace blender::draw { -/* Base class for draw test cases. It will setup and tear down the GPU part around each test. */ -class DrawTest : public blender::gpu::GPUTest { - void SetUp() override - { - GPUTest::SetUp(); - DRW_draw_state_init_gtests(GPU_SHADER_CFG_DEFAULT); - } -}; - TEST_F(DrawTest, workbench_glsl_shaders) { workbench_shader_library_ensure(); - DRW_draw_state_init_gtests(GPU_SHADER_CFG_DEFAULT); const int MAX_WPD = 6; WORKBENCH_PrivateData wpds[MAX_WPD]; diff --git a/source/blender/editors/animation/anim_channels_defines.c b/source/blender/editors/animation/anim_channels_defines.c index 6dd1fe75163..d4941950029 100644 --- a/source/blender/editors/animation/anim_channels_defines.c +++ b/source/blender/editors/animation/anim_channels_defines.c @@ -4674,7 +4674,7 @@ static void achannel_setting_flush_widget_cb(bContext *C, void *ale_npoin, void DEG_id_tag_update(ale_setting->id, ID_RECALC_ANIMATION); } if (ale_setting->adt && ale_setting->adt->action) { - /* Action is it's own datablock, so has to be tagged specifically. */ + /* Action is its own datablock, so has to be tagged specifically. */ DEG_id_tag_update(&ale_setting->adt->action->id, ID_RECALC_ANIMATION); } diff --git a/source/blender/editors/armature/CMakeLists.txt b/source/blender/editors/armature/CMakeLists.txt index 98c050950be..0030e78002b 100644 --- a/source/blender/editors/armature/CMakeLists.txt +++ b/source/blender/editors/armature/CMakeLists.txt @@ -17,6 +17,7 @@ set(INC ../include + ../../blenfont ../../blenkernel ../../blenlib ../../blentranslation diff --git a/source/blender/editors/armature/armature_add.c b/source/blender/editors/armature/armature_add.c index 68fff1091af..3902f6613a1 100644 --- a/source/blender/editors/armature/armature_add.c +++ b/source/blender/editors/armature/armature_add.c @@ -339,7 +339,7 @@ void postEditBoneDuplicate(struct ListBase *editbones, Object *ob) } BKE_pose_channels_hash_free(ob->pose); - BKE_pose_channels_hash_make(ob->pose); + BKE_pose_channels_hash_ensure(ob->pose); GHash *name_map = BLI_ghash_str_new(__func__); @@ -390,7 +390,7 @@ static void updateDuplicateSubtarget(EditBone *dup_bone, bConstraint *curcon; ListBase *conlist; - if ((pchan = BKE_pose_channel_verify(ob->pose, dup_bone->name))) { + if ((pchan = BKE_pose_channel_ensure(ob->pose, dup_bone->name))) { if ((conlist = &pchan->constraints)) { for (curcon = conlist->first; curcon; curcon = curcon->next) { /* does this constraint have a subtarget in @@ -825,7 +825,7 @@ static void updateDuplicateConstraintSettings(EditBone *dup_bone, EditBone *orig bConstraint *curcon; ListBase *conlist; - if ((pchan = BKE_pose_channel_verify(ob->pose, dup_bone->name)) == NULL || + if ((pchan = BKE_pose_channel_ensure(ob->pose, dup_bone->name)) == NULL || (conlist = &pchan->constraints) == NULL) { return; } @@ -855,7 +855,7 @@ static void updateDuplicateCustomBoneShapes(bContext *C, EditBone *dup_bone, Obj return; } bPoseChannel *pchan; - pchan = BKE_pose_channel_verify(ob->pose, dup_bone->name); + pchan = BKE_pose_channel_ensure(ob->pose, dup_bone->name); if (pchan->custom != NULL) { Main *bmain = CTX_data_main(C); @@ -885,12 +885,12 @@ static void copy_pchan(EditBone *src_bone, EditBone *dst_bone, Object *src_ob, O if (src_ob->pose) { bPoseChannel *chanold, *channew; - chanold = BKE_pose_channel_verify(src_ob->pose, src_bone->name); + chanold = BKE_pose_channel_ensure(src_ob->pose, src_bone->name); if (chanold) { /* WARNING: this creates a new posechannel, but there will not be an attached bone * yet as the new bones created here are still 'EditBones' not 'Bones'. */ - channew = BKE_pose_channel_verify(dst_ob->pose, dst_bone->name); + channew = BKE_pose_channel_ensure(dst_ob->pose, dst_bone->name); if (channew) { BKE_pose_channel_copy_data(channew, chanold); @@ -1193,7 +1193,7 @@ static int armature_symmetrize_exec(bContext *C, wmOperator *op) * is synchronized. */ bPoseChannel *pchan; /* Make sure we clean up the old data before overwriting it */ - pchan = BKE_pose_channel_verify(obedit->pose, ebone_iter->temp.ebone->name); + pchan = BKE_pose_channel_ensure(obedit->pose, ebone_iter->temp.ebone->name); BKE_pose_channel_free(pchan); /* Sync pchan data */ copy_pchan(ebone_iter, ebone_iter->temp.ebone, obedit, obedit); diff --git a/source/blender/editors/armature/pose_slide.c b/source/blender/editors/armature/pose_slide.c index 93d36abe792..f7b54b79601 100644 --- a/source/blender/editors/armature/pose_slide.c +++ b/source/blender/editors/armature/pose_slide.c @@ -33,6 +33,7 @@ #include "DNA_armature_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" +#include "DNA_vec_types.h" #include "BKE_fcurve.h" #include "BKE_nla.h" @@ -41,6 +42,7 @@ #include "BKE_layer.h" #include "BKE_object.h" #include "BKE_report.h" +#include "BKE_screen.h" #include "BKE_unit.h" #include "RNA_access.h" @@ -50,15 +52,28 @@ #include "WM_types.h" #include "UI_interface.h" +#include "UI_resources.h" #include "ED_armature.h" #include "ED_keyframes_draw.h" #include "ED_markers.h" #include "ED_numinput.h" #include "ED_screen.h" +#include "ED_space_api.h" + +#include "GPU_immediate.h" +#include "GPU_immediate_util.h" +#include "GPU_matrix.h" +#include "GPU_state.h" #include "armature_intern.h" +#include "BLF_api.h" + +/* Pixel distance from 0% to 100%. */ +#define SLIDE_PIXEL_DISTANCE (300 * U.pixelsize) +#define OVERSHOOT_RANGE_DELTA 0.2f + /* **************************************************** */ /* == POSE 'SLIDING' TOOLS == * @@ -110,15 +125,36 @@ typedef struct tPoseSlideOp { /** unused for now, but can later get used for storing runtime settings.... */ short flag; + /* Store overlay settings when invoking the operator. Bones will be temporarily hidden. */ + int overlay_flag; + /** which transforms/channels are affected (ePoseSlide_Channels) */ short channels; /** axis-limits for transforms (ePoseSlide_AxisLock) */ short axislock; - /** 0-1 value for determining the influence of whatever is relevant */ + /* Allow overshoot or clamp between 0% and 100%. */ + bool overshoot; + + /* Reduces percentage delta from mouse movement. */ + bool precision; + + /* Move percentage in 10% steps. */ + bool increments; + + /* Draw callback handler. */ + void *draw_handle; + + /* Accumulative, unclamped and unrounded percentage. */ + float raw_percentage; + + /* 0-1 value for determining the influence of whatever is relevant. */ float percentage; - /** numeric input */ + /* Last cursor position in screen space used for mouse movement delta calculation. */ + int last_cursor_x; + + /* Numeric input. */ NumInput num; struct tPoseSlideObject *ob_data_array; @@ -187,6 +223,240 @@ static const EnumPropertyItem prop_axis_lock_types[] = { /* ------------------------------------ */ +static void draw_overshoot_triangle(const uint8_t color[4], + const bool facing_right, + const float x, + const float y) +{ + const uint shdr_pos_2d = GPU_vertformat_attr_add( + immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + GPU_blend(GPU_BLEND_ALPHA); + GPU_polygon_smooth(true); + immUniformColor3ubvAlpha(color, 225); + const float triangle_side_length = facing_right ? 6 * U.pixelsize : -6 * U.pixelsize; + const float triangle_offset = facing_right ? 2 * U.pixelsize : -2 * U.pixelsize; + + immBegin(GPU_PRIM_TRIS, 3); + immVertex2f(shdr_pos_2d, x + triangle_offset + triangle_side_length, y); + immVertex2f(shdr_pos_2d, x + triangle_offset, y + triangle_side_length / 2); + immVertex2f(shdr_pos_2d, x + triangle_offset, y - triangle_side_length / 2); + immEnd(); + + GPU_polygon_smooth(false); + GPU_blend(GPU_BLEND_NONE); + immUnbindProgram(); +} + +static void draw_ticks(const float start_percentage, + const float end_percentage, + const struct vec2f line_start, + const float base_tick_height, + const float line_width, + const uint8_t color_overshoot[4], + const uint8_t color_line[4]) +{ + /* Use percentage represented as 0-100 int to avoid floating point precision problems. */ + const int tick_increment = 10; + + /* Round initial_tick_percentage up to the next tick_increment. */ + int tick_percentage = ceil((start_percentage * 100) / tick_increment) * tick_increment; + float tick_height = base_tick_height; + + while (tick_percentage <= (int)(end_percentage * 100)) { + /* Different ticks have different heights. Multiples of 100% are the tallest, 50% is a bit + * smaller and the rest is the minimum size. */ + if (tick_percentage % 100 == 0) { + tick_height = base_tick_height; + } + else if (tick_percentage % 50 == 0) { + tick_height = base_tick_height * 0.8; + } + else { + tick_height = base_tick_height * 0.5; + } + + const float x = line_start.x + + (((float)tick_percentage / 100) - start_percentage) * SLIDE_PIXEL_DISTANCE; + const struct rctf tick_rect = {.xmin = x - (line_width / 2), + .xmax = x + (line_width / 2), + .ymin = line_start.y - (tick_height / 2), + .ymax = line_start.y + (tick_height / 2)}; + + if (tick_percentage < 0 || tick_percentage > 100) { + UI_draw_roundbox_3ub_alpha(&tick_rect, true, 1, color_overshoot, 255); + } + else { + UI_draw_roundbox_3ub_alpha(&tick_rect, true, 1, color_line, 255); + } + tick_percentage += tick_increment; + } +} + +static void draw_main_line(const struct rctf main_line_rect, + const float percentage, + const bool overshoot, + const uint8_t color_overshoot[4], + const uint8_t color_line[4]) +{ + if (overshoot) { + /* In overshoot mode, draw the 0-100% range differently to provide a visual reference. */ + const float line_zero_percent = main_line_rect.xmin - + ((percentage - 0.5f - OVERSHOOT_RANGE_DELTA) * + SLIDE_PIXEL_DISTANCE); + + const float clamped_line_zero_percent = clamp_f( + line_zero_percent, main_line_rect.xmin, main_line_rect.xmax); + const float clamped_line_hundred_percent = clamp_f( + line_zero_percent + SLIDE_PIXEL_DISTANCE, main_line_rect.xmin, main_line_rect.xmax); + + const struct rctf left_overshoot_line_rect = {.xmin = main_line_rect.xmin, + .xmax = clamped_line_zero_percent, + .ymin = main_line_rect.ymin, + .ymax = main_line_rect.ymax}; + const struct rctf right_overshoot_line_rect = {.xmin = clamped_line_hundred_percent, + .xmax = main_line_rect.xmax, + .ymin = main_line_rect.ymin, + .ymax = main_line_rect.ymax}; + UI_draw_roundbox_3ub_alpha(&left_overshoot_line_rect, true, 0, color_overshoot, 255); + UI_draw_roundbox_3ub_alpha(&right_overshoot_line_rect, true, 0, color_overshoot, 255); + + const struct rctf non_overshoot_line_rect = {.xmin = clamped_line_zero_percent, + .xmax = clamped_line_hundred_percent, + .ymin = main_line_rect.ymin, + .ymax = main_line_rect.ymax}; + UI_draw_roundbox_3ub_alpha(&non_overshoot_line_rect, true, 0, color_line, 255); + } + else { + UI_draw_roundbox_3ub_alpha(&main_line_rect, true, 0, color_line, 255); + } +} + +static void draw_backdrop(const int fontid, + const struct rctf main_line_rect, + const float color_bg[4], + const short region_y_size, + const float base_tick_height) +{ + float string_pixel_size[2]; + const char *percentage_placeholder = "000%%"; + BLF_width_and_height(fontid, + percentage_placeholder, + sizeof(percentage_placeholder), + &string_pixel_size[0], + &string_pixel_size[1]); + const struct vec2f pad = {.x = (region_y_size - base_tick_height) / 2, .y = 2.0f * U.pixelsize}; + const struct rctf backdrop_rect = {.xmin = main_line_rect.xmin - string_pixel_size[0] - pad.x, + .xmax = main_line_rect.xmax + pad.x, + .ymin = pad.y, + .ymax = region_y_size - pad.y}; + UI_draw_roundbox_aa(&backdrop_rect, true, 4.0f, color_bg); +} + +/* Draw an on screen Slider for a Pose Slide Operator. */ +static void pose_slide_draw_2d_slider(const struct bContext *UNUSED(C), ARegion *region, void *arg) +{ + tPoseSlideOp *pso = arg; + + /* Only draw in region from which the Operator was started. */ + if (region != pso->region) { + return; + } + + uint8_t color_text[4]; + uint8_t color_line[4]; + uint8_t color_handle[4]; + uint8_t color_overshoot[4]; + float color_bg[4]; + + /* Get theme colors. */ + UI_GetThemeColor4ubv(TH_TEXT, color_text); + UI_GetThemeColor4ubv(TH_TEXT, color_line); + UI_GetThemeColor4ubv(TH_TEXT, color_overshoot); + UI_GetThemeColor4ubv(TH_ACTIVE, color_handle); + UI_GetThemeColor3fv(TH_BACK, color_bg); + + color_bg[3] = 0.5f; + color_overshoot[0] = color_overshoot[0] * 0.7; + color_overshoot[1] = color_overshoot[1] * 0.7; + color_overshoot[2] = color_overshoot[2] * 0.7; + + /* Get the default font. */ + const uiStyle *style = UI_style_get(); + const uiFontStyle *fstyle = &style->widget; + const int fontid = fstyle->uifont_id; + BLF_color3ubv(fontid, color_text); + BLF_rotation(fontid, 0.0f); + + const float line_width = 1.5 * U.pixelsize; + const float base_tick_height = 12.0 * U.pixelsize; + const float line_y = region->winy / 2; + + struct rctf main_line_rect = {.xmin = (region->winx / 2) - (SLIDE_PIXEL_DISTANCE / 2), + .xmax = (region->winx / 2) + (SLIDE_PIXEL_DISTANCE / 2), + .ymin = line_y - line_width / 2, + .ymax = line_y + line_width / 2}; + float line_start_percentage = 0; + int handle_pos_x = main_line_rect.xmin + SLIDE_PIXEL_DISTANCE * pso->percentage; + + if (pso->overshoot) { + main_line_rect.xmin = main_line_rect.xmin - SLIDE_PIXEL_DISTANCE * OVERSHOOT_RANGE_DELTA; + main_line_rect.xmax = main_line_rect.xmax + SLIDE_PIXEL_DISTANCE * OVERSHOOT_RANGE_DELTA; + line_start_percentage = pso->percentage - 0.5f - OVERSHOOT_RANGE_DELTA; + handle_pos_x = region->winx / 2; + } + + draw_backdrop(fontid, main_line_rect, color_bg, pso->region->winy, base_tick_height); + + draw_main_line(main_line_rect, pso->percentage, pso->overshoot, color_overshoot, color_line); + + const float percentage_range = pso->overshoot ? 1 + OVERSHOOT_RANGE_DELTA * 2 : 1; + const struct vec2f line_start_position = {.x = main_line_rect.xmin, .y = line_y}; + draw_ticks(line_start_percentage, + line_start_percentage + percentage_range, + line_start_position, + base_tick_height, + line_width, + color_overshoot, + color_line); + + /* Draw triangles at the ends of the line in overshoot mode to indicate direction of 0-100% + * range.*/ + if (pso->overshoot) { + if (pso->percentage > 1 + OVERSHOOT_RANGE_DELTA + 0.5) { + draw_overshoot_triangle(color_line, false, main_line_rect.xmin, line_y); + } + if (pso->percentage < 0 - OVERSHOOT_RANGE_DELTA - 0.5) { + draw_overshoot_triangle(color_line, true, main_line_rect.xmax, line_y); + } + } + + char percentage_string[256]; + + /* Draw handle indicating current percentage. */ + const struct rctf handle_rect = {.xmin = handle_pos_x - (line_width), + .xmax = handle_pos_x + (line_width), + .ymin = line_y - (base_tick_height / 2), + .ymax = line_y + (base_tick_height / 2)}; + + UI_draw_roundbox_3ub_alpha(&handle_rect, true, 1, color_handle, 255); + BLI_snprintf(percentage_string, sizeof(percentage_string), "%.0f%%", pso->percentage * 100); + + /* Draw percentage string. */ + float percentage_pixel_size[2]; + BLF_width_and_height(fontid, + percentage_string, + sizeof(percentage_string), + &percentage_pixel_size[0], + &percentage_pixel_size[1]); + + BLF_position(fontid, + main_line_rect.xmin - 24.0 * U.pixelsize - percentage_pixel_size[0] / 2, + (region->winy / 2) - percentage_pixel_size[1] / 2, + 0.0f); + BLF_draw(fontid, percentage_string, sizeof(percentage_string)); +} + /* operator init */ static int pose_slide_init(bContext *C, wmOperator *op, ePoseSlide_Modes mode) { @@ -205,6 +475,7 @@ static int pose_slide_init(bContext *C, wmOperator *op, ePoseSlide_Modes mode) /* set range info from property values - these may get overridden for the invoke() */ pso->percentage = RNA_float_get(op->ptr, "percentage"); + pso->raw_percentage = pso->percentage; pso->prevFrame = RNA_int_get(op->ptr, "prev_frame"); pso->nextFrame = RNA_int_get(op->ptr, "next_frame"); @@ -257,6 +528,14 @@ static int pose_slide_init(bContext *C, wmOperator *op, ePoseSlide_Modes mode) pso->num.val_flag[0] |= NUM_NO_NEGATIVE; pso->num.unit_type[0] = B_UNIT_NONE; /* percentages don't have any units... */ + /* Register UI drawing callback. */ + ARegion *region_header = BKE_area_find_region_type(pso->area, RGN_TYPE_HEADER); + if (region_header != NULL) { + pso->region = region_header; + pso->draw_handle = ED_region_draw_cb_activate( + region_header->type, pose_slide_draw_2d_slider, pso, REGION_DRAW_POST_PIXEL); + } + /* return status is whether we've got all the data we were requested to get */ return 1; } @@ -266,6 +545,13 @@ static void pose_slide_exit(wmOperator *op) { tPoseSlideOp *pso = op->customdata; + /* Hide Bone Overlay. */ + View3D *v3d = pso->area->spacedata.first; + v3d->overlay.flag = pso->overlay_flag; + + /* Remove UI drawing callback. */ + ED_region_draw_cb_exit(pso->region->type, pso->draw_handle); + /* if data exists, clear its data and exit */ if (pso) { /* free the temp pchan links and their data */ @@ -602,7 +888,7 @@ static void pose_slide_apply_quat(tPoseSlideOp *pso, tPChanFCurveLink *pfl) static void pose_slide_rest_pose_apply_vec3(tPoseSlideOp *pso, float vec[3], float default_value) { - /* We only slide to the rest pose. So only use the default rest pose value */ + /* We only slide to the rest pose. So only use the default rest pose value. */ const int lock = pso->axislock; for (int idx = 0; idx < 3; idx++) { if ((lock == 0) || ((lock & PS_LOCK_X) && (idx == 0)) || ((lock & PS_LOCK_Y) && (idx == 1)) || @@ -621,7 +907,7 @@ static void pose_slide_rest_pose_apply_vec3(tPoseSlideOp *pso, float vec[3], flo static void pose_slide_rest_pose_apply_other_rot(tPoseSlideOp *pso, float vec[4], bool quat) { - /* We only slide to the rest pose. So only use the default rest pose value */ + /* We only slide to the rest pose. So only use the default rest pose value. */ float default_values[] = {1.0f, 0.0f, 0.0f, 0.0f}; if (!quat) { /* Axis Angle */ @@ -789,14 +1075,18 @@ static void pose_slide_reset(tPoseSlideOp *pso) /* ------------------------------------ */ -/* draw percentage indicator in header */ +/* Draw percentage indicator in workspace footer. */ /* TODO: Include hints about locks here... */ -static void pose_slide_draw_status(tPoseSlideOp *pso) +static void pose_slide_draw_status(bContext *C, tPoseSlideOp *pso) { char status_str[UI_MAX_DRAW_STR]; char limits_str[UI_MAX_DRAW_STR]; char axis_str[50]; char mode_str[32]; + char overshoot_str[50]; + char precision_str[50]; + char increments_str[50]; + char bone_vis_str[50]; switch (pso->mode) { case POSESLIDE_PUSH: @@ -871,25 +1161,51 @@ static void pose_slide_draw_status(tPoseSlideOp *pso) break; } + if (pso->overshoot) { + BLI_strncpy(overshoot_str, TIP_("[E] - Disable overshoot"), sizeof(overshoot_str)); + } + else { + BLI_strncpy(overshoot_str, TIP_("E - Enable overshoot"), sizeof(overshoot_str)); + } + + if (pso->precision) { + BLI_strncpy(precision_str, TIP_("[Shift] - Precision active"), sizeof(precision_str)); + } + else { + BLI_strncpy(precision_str, TIP_("Shift - Hold for precision"), sizeof(precision_str)); + } + + if (pso->increments) { + BLI_strncpy(increments_str, TIP_("[Ctrl] - Increments active"), sizeof(increments_str)); + } + else { + BLI_strncpy(increments_str, TIP_("Ctrl - Hold for 10% increments"), sizeof(increments_str)); + } + + BLI_strncpy(bone_vis_str, TIP_("[H] - Toggle bone visibility"), sizeof(increments_str)); + if (hasNumInput(&pso->num)) { Scene *scene = pso->scene; - char str_ofs[NUM_STR_REP_LEN]; + char str_offs[NUM_STR_REP_LEN]; - outputNumInput(&pso->num, str_ofs, &scene->unit); + outputNumInput(&pso->num, str_offs, &scene->unit); - BLI_snprintf( - status_str, sizeof(status_str), "%s: %s | %s", mode_str, str_ofs, limits_str); + BLI_snprintf(status_str, sizeof(status_str), "%s: %s | %s", mode_str, str_offs, limits_str); } else { BLI_snprintf(status_str, sizeof(status_str), - "%s: %d %% | %s", + "%s: %s | %s | %s | %s | %s", mode_str, - (int)(pso->percentage * 100.0f), - limits_str); + limits_str, + overshoot_str, + precision_str, + increments_str, + bone_vis_str); } - ED_area_status_text(pso->area, status_str); + ED_workspace_status_text(C, status_str); + ED_area_status_text(pso->area, ""); } /* common code for invoke() methods */ @@ -975,21 +1291,40 @@ static int pose_slide_invoke_common(bContext *C, wmOperator *op, tPoseSlideOp *p WM_cursor_modal_set(win, WM_CURSOR_EW_SCROLL); /* header print */ - pose_slide_draw_status(pso); + pose_slide_draw_status(C, pso); /* add a modal handler for this operator */ WM_event_add_modal_handler(C, op); + + /* Hide Bone Overlay. */ + View3D *v3d = pso->area->spacedata.first; + pso->overlay_flag = v3d->overlay.flag; + v3d->overlay.flag |= V3D_OVERLAY_HIDE_BONES; + return OPERATOR_RUNNING_MODAL; } -/* calculate percentage based on position of mouse (we only use x-axis for now. - * since this is more convenient for users to do), and store new percentage value +/* Calculate percentage based on mouse movement, clamp or round to increments if + * enabled by the user. Store the new percentage value. */ static void pose_slide_mouse_update_percentage(tPoseSlideOp *pso, wmOperator *op, const wmEvent *event) { - pso->percentage = (event->x - pso->region->winrct.xmin) / ((float)pso->region->winx); + const float percentage_delta = (event->x - pso->last_cursor_x) / ((float)(SLIDE_PIXEL_DISTANCE)); + /* Reduced percentage delta in precision mode (shift held). */ + pso->raw_percentage += pso->precision ? (percentage_delta / 8) : percentage_delta; + pso->percentage = pso->raw_percentage; + pso->last_cursor_x = event->x; + + if (!pso->overshoot) { + pso->percentage = clamp_f(pso->percentage, 0, 1); + } + + if (pso->increments) { + pso->percentage = round(pso->percentage * 10) / 10; + } + RNA_float_set(op->ptr, "percentage", pso->percentage); } @@ -1056,9 +1391,13 @@ static int pose_slide_modal(bContext *C, wmOperator *op, const wmEvent *event) case EVT_PADENTER: { if (event->val == KM_PRESS) { /* return to normal cursor and header status */ + ED_workspace_status_text(C, NULL); ED_area_status_text(pso->area, NULL); WM_cursor_modal_restore(win); + /* Depsgraph updates + redraws. Redraw needed to remove UI. */ + pose_slide_refresh(C, pso); + /* insert keyframes as required... */ pose_slide_autoKeyframe(C, pso); pose_slide_exit(op); @@ -1073,13 +1412,14 @@ static int pose_slide_modal(bContext *C, wmOperator *op, const wmEvent *event) case RIGHTMOUSE: { if (event->val == KM_PRESS) { /* return to normal cursor and header status */ + ED_workspace_status_text(C, NULL); ED_area_status_text(pso->area, NULL); WM_cursor_modal_restore(win); /* reset transforms back to original state */ pose_slide_reset(pso); - /* depsgraph updates + redraws */ + /* Depsgraph updates + redraws.*/ pose_slide_refresh(C, pso); /* clean up temp data */ @@ -1178,10 +1518,58 @@ static int pose_slide_modal(bContext *C, wmOperator *op, const wmEvent *event) break; } + /* Overshoot. */ + case EVT_EKEY: { + pso->overshoot = !pso->overshoot; + do_pose_update = true; + break; + } + + /* Precision mode. */ + case EVT_LEFTSHIFTKEY: + case EVT_RIGHTSHIFTKEY: { + pso->precision = true; + do_pose_update = true; + break; + } + + /* Increments mode. */ + case EVT_LEFTCTRLKEY: + case EVT_RIGHTCTRLKEY: { + pso->increments = true; + do_pose_update = true; + break; + } + + /* Toggle Bone visibility. */ + case EVT_HKEY: { + View3D *v3d = pso->area->spacedata.first; + v3d->overlay.flag ^= V3D_OVERLAY_HIDE_BONES; + } + default: /* Some other unhandled key... */ break; } } + /* Precision and stepping only active while button is held. */ + else if (event->val == KM_RELEASE) { + switch (event->type) { + case EVT_LEFTSHIFTKEY: + case EVT_RIGHTSHIFTKEY: { + pso->precision = false; + do_pose_update = true; + break; + } + case EVT_LEFTCTRLKEY: + case EVT_RIGHTCTRLKEY: { + pso->increments = false; + do_pose_update = true; + break; + } + default: + break; + } + } else { /* unhandled event - maybe it was some view manipulation? */ /* allow to pass through */ @@ -1193,8 +1581,10 @@ static int pose_slide_modal(bContext *C, wmOperator *op, const wmEvent *event) /* Perform pose updates - in response to some user action * (e.g. pressing a key or moving the mouse). */ if (do_pose_update) { + pose_slide_mouse_update_percentage(pso, op, event); + /* update percentage indicator in header */ - pose_slide_draw_status(pso); + pose_slide_draw_status(C, pso); /* reset transforms (to avoid accumulation errors) */ pose_slide_reset(pso); @@ -1247,11 +1637,11 @@ static void pose_slide_opdef_properties(wmOperatorType *ot) PropertyRNA *prop; prop = RNA_def_float_factor(ot->srna, - "factor", + "percentage", 0.5f, 0.0f, 1.0f, - "Factor", + "Percentage", "Weighting factor for which keyframe is favored more", 0.0, 1.0); @@ -1310,6 +1700,8 @@ static int pose_slide_push_invoke(bContext *C, wmOperator *op, const wmEvent *ev pso = op->customdata; + pso->last_cursor_x = event->x; + /* Initialize percentage so that it won't pop on first mouse move. */ pose_slide_mouse_update_percentage(pso, op, event); @@ -1349,7 +1741,7 @@ void POSE_OT_push(wmOperatorType *ot) ot->poll = ED_operator_posemode; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X; /* Properties */ pose_slide_opdef_properties(ot); @@ -1370,6 +1762,8 @@ static int pose_slide_relax_invoke(bContext *C, wmOperator *op, const wmEvent *e pso = op->customdata; + pso->last_cursor_x = event->x; + /* Initialize percentage so that it won't pop on first mouse move. */ pose_slide_mouse_update_percentage(pso, op, event); @@ -1409,7 +1803,7 @@ void POSE_OT_relax(wmOperatorType *ot) ot->poll = ED_operator_posemode; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X; /* Properties */ pose_slide_opdef_properties(ot); @@ -1429,6 +1823,8 @@ static int pose_slide_push_rest_invoke(bContext *C, wmOperator *op, const wmEven pso = op->customdata; + pso->last_cursor_x = event->x; + /* Initialize percentage so that it won't pop on first mouse move. */ pose_slide_mouse_update_percentage(pso, op, event); @@ -1468,7 +1864,7 @@ void POSE_OT_push_rest(wmOperatorType *ot) ot->poll = ED_operator_posemode; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X; /* Properties */ pose_slide_opdef_properties(ot); @@ -1489,6 +1885,8 @@ static int pose_slide_relax_rest_invoke(bContext *C, wmOperator *op, const wmEve pso = op->customdata; + pso->last_cursor_x = event->x; + /* Initialize percentage so that it won't pop on first mouse move. */ pose_slide_mouse_update_percentage(pso, op, event); @@ -1528,7 +1926,7 @@ void POSE_OT_relax_rest(wmOperatorType *ot) ot->poll = ED_operator_posemode; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X; /* Properties */ pose_slide_opdef_properties(ot); @@ -1549,6 +1947,8 @@ static int pose_slide_breakdown_invoke(bContext *C, wmOperator *op, const wmEven pso = op->customdata; + pso->last_cursor_x = event->x; + /* Initialize percentage so that it won't pop on first mouse move. */ pose_slide_mouse_update_percentage(pso, op, event); @@ -1588,7 +1988,7 @@ void POSE_OT_breakdown(wmOperatorType *ot) ot->poll = ED_operator_posemode; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X; /* Properties */ pose_slide_opdef_properties(ot); diff --git a/source/blender/editors/curve/editcurve.c b/source/blender/editors/curve/editcurve.c index 767d822aa39..535ccaa06fd 100644 --- a/source/blender/editors/curve/editcurve.c +++ b/source/blender/editors/curve/editcurve.c @@ -5576,7 +5576,7 @@ static int add_vertex_invoke(bContext *C, wmOperator *op, const wmEvent *event) SCE_SNAP_MODE_FACE, &(const struct SnapObjectParams){ .snap_select = (vc.obedit != NULL) ? SNAP_NOT_ACTIVE : SNAP_ALL, - .use_object_edit_cage = false, + .edit_mode_type = SNAP_GEOM_FINAL, }, mval, NULL, diff --git a/source/blender/editors/gizmo_library/gizmo_types/move3d_gizmo.c b/source/blender/editors/gizmo_library/gizmo_types/move3d_gizmo.c index 364444f99ae..68322ed56af 100644 --- a/source/blender/editors/gizmo_library/gizmo_types/move3d_gizmo.c +++ b/source/blender/editors/gizmo_library/gizmo_types/move3d_gizmo.c @@ -292,7 +292,7 @@ static int gizmo_move_modal(bContext *C, (SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE), &(const struct SnapObjectParams){ .snap_select = SNAP_ALL, - .use_object_edit_cage = true, + .edit_mode_type = SNAP_GEOM_EDIT, .use_occlusion_test = true, }, mval_fl, diff --git a/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.c b/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.c index 3cff07dde3f..b2d3a2e1576 100644 --- a/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.c +++ b/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.c @@ -68,7 +68,9 @@ typedef struct SnapGizmo3D { struct { int x; int y; +#ifdef USE_SNAP_DETECT_FROM_KEYMAP_HACK short shift, ctrl, alt, oskey; +#endif } last_eventstate; #ifdef USE_SNAP_DETECT_FROM_KEYMAP_HACK @@ -99,28 +101,30 @@ static bool eventstate_has_changed(SnapGizmo3D *snap_gizmo, const wmWindowManage if (wm && wm->winactive) { const wmEvent *event = wm->winactive->eventstate; if ((event->x != snap_gizmo->last_eventstate.x) || - (event->y != snap_gizmo->last_eventstate.y) || - (event->ctrl != snap_gizmo->last_eventstate.ctrl) || - (event->shift != snap_gizmo->last_eventstate.shift) || - (event->alt != snap_gizmo->last_eventstate.alt) || - (event->oskey != snap_gizmo->last_eventstate.oskey)) { + (event->y != snap_gizmo->last_eventstate.y)) { return true; } +#ifdef USE_SNAP_DETECT_FROM_KEYMAP_HACK + if (!(snap_gizmo->flag & ED_SNAPGIZMO_TOGGLE_ALWAYS_TRUE)) { + if ((event->ctrl != snap_gizmo->last_eventstate.ctrl) || + (event->shift != snap_gizmo->last_eventstate.shift) || + (event->alt != snap_gizmo->last_eventstate.alt) || + (event->oskey != snap_gizmo->last_eventstate.oskey)) { + return true; + } + } +#endif } return false; } /* Copies the current eventstate. */ -static void eventstate_save(SnapGizmo3D *snap_gizmo, const wmWindowManager *wm) +static void eventstate_save_xy(SnapGizmo3D *snap_gizmo, const wmWindowManager *wm) { if (wm && wm->winactive) { const wmEvent *event = wm->winactive->eventstate; snap_gizmo->last_eventstate.x = event->x; snap_gizmo->last_eventstate.y = event->y; - snap_gizmo->last_eventstate.ctrl = event->ctrl; - snap_gizmo->last_eventstate.shift = event->shift; - snap_gizmo->last_eventstate.alt = event->alt; - snap_gizmo->last_eventstate.oskey = event->oskey; } } @@ -140,6 +144,12 @@ static bool invert_snap(SnapGizmo3D *snap_gizmo, const wmWindowManager *wm) return snap_gizmo->invert_snap; } + /* Save new eventstate. */ + snap_gizmo->last_eventstate.ctrl = event->ctrl; + snap_gizmo->last_eventstate.shift = event->shift; + snap_gizmo->last_eventstate.alt = event->alt; + snap_gizmo->last_eventstate.oskey = event->oskey; + const int snap_on = snap_gizmo->snap_on; wmKeyMap *keymap = WM_keymap_active(wm, snap_gizmo->keymap); @@ -328,23 +338,17 @@ short ED_gizmotypes_snap_3d_update(wmGizmo *gz, Scene *scene = DEG_get_input_scene(depsgraph); #ifdef USE_SNAP_DETECT_FROM_KEYMAP_HACK - if ((snap_gizmo->flag & ED_SNAPGIZMO_TOGGLE_ALWAYS_TRUE) == 0) { - bool invert_snap_toggle = invert_snap(snap_gizmo, wm); - if (invert_snap_toggle != snap_gizmo->invert_snap) { - snap_gizmo->invert_snap = invert_snap_toggle; - - /* Status has changed, be sure to save before early return. */ - eventstate_save(snap_gizmo, wm); - } + if (!(snap_gizmo->flag & ED_SNAPGIZMO_TOGGLE_ALWAYS_TRUE)) { + snap_gizmo->invert_snap = invert_snap(snap_gizmo, wm); const ToolSettings *ts = scene->toolsettings; - if (invert_snap_toggle != !(ts->snap_flag & SCE_SNAP)) { + if (snap_gizmo->invert_snap != !(ts->snap_flag & SCE_SNAP)) { snap_gizmo->snap_elem = 0; return 0; } } #endif - eventstate_save(snap_gizmo, wm); + eventstate_save_xy(snap_gizmo, wm); snap_gizmo->is_enabled = true; @@ -364,25 +368,39 @@ short ED_gizmotypes_snap_3d_update(wmGizmo *gz, snap_elements &= ~SCE_SNAP_MODE_EDGE_PERPENDICULAR; } + eSnapSelect snap_select = (snap_gizmo->flag & ED_SNAPGIZMO_SNAP_ONLY_ACTIVE) ? + SNAP_ONLY_ACTIVE : + SNAP_ALL; + + eSnapEditType edit_mode_type = (snap_gizmo->flag & ED_SNAPGIZMO_SNAP_EDIT_GEOM_FINAL) ? + SNAP_GEOM_FINAL : + (snap_gizmo->flag & ED_SNAPGIZMO_SNAP_EDIT_GEOM_CAGE) ? + SNAP_GEOM_CAGE : + SNAP_GEOM_EDIT; + + bool use_occlusion_test = (snap_gizmo->flag & ED_SNAPGIZMO_OCCLUSION_ALWAYS_TRUE) ? false : + true; + float dist_px = 12.0f * U.pixelsize; ED_gizmotypes_snap_3d_context_ensure(scene, region, v3d, gz); - snap_elem = ED_transform_snap_object_project_view3d_ex(snap_gizmo->snap_context_v3d, - depsgraph, - snap_elements, - &(const struct SnapObjectParams){ - .snap_select = SNAP_ALL, - .use_object_edit_cage = true, - .use_occlusion_test = true, - }, - mval_fl, - prev_co, - &dist_px, - co, - no, - &index, - NULL, - NULL); + snap_elem = ED_transform_snap_object_project_view3d_ex( + snap_gizmo->snap_context_v3d, + depsgraph, + snap_elements, + &(const struct SnapObjectParams){ + .snap_select = snap_select, + .edit_mode_type = edit_mode_type, + .use_occlusion_test = use_occlusion_test, + }, + mval_fl, + prev_co, + &dist_px, + co, + no, + &index, + NULL, + NULL); } if (snap_elem == 0) { @@ -596,8 +614,8 @@ static void snap_gizmo_draw(const bContext *C, wmGizmo *gz) GPU_line_width(1.0f); - const float *prev_point = snap_gizmo_snap_elements(snap_gizmo) & - SCE_SNAP_MODE_EDGE_PERPENDICULAR ? + const float *prev_point = (snap_gizmo_snap_elements(snap_gizmo) & + SCE_SNAP_MODE_EDGE_PERPENDICULAR) ? snap_gizmo->prevpoint : NULL; diff --git a/source/blender/editors/gpencil/CMakeLists.txt b/source/blender/editors/gpencil/CMakeLists.txt index 47ae90acb74..bff7310e9f7 100644 --- a/source/blender/editors/gpencil/CMakeLists.txt +++ b/source/blender/editors/gpencil/CMakeLists.txt @@ -36,10 +36,12 @@ set(SRC annotate_paint.c drawgpencil.c editaction_gpencil.c + gpencil_add_blank.c gpencil_add_lineart.c gpencil_add_monkey.c gpencil_add_stroke.c gpencil_armature.c + gpencil_bake_animation.c gpencil_convert.c gpencil_data.c gpencil_edit.c diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c index 5c40bc8e418..c155587e95a 100644 --- a/source/blender/editors/gpencil/annotate_paint.c +++ b/source/blender/editors/gpencil/annotate_paint.c @@ -1548,7 +1548,7 @@ static void annotation_paint_initstroke(tGPsdata *p, if (p->gpl == NULL) { /* tag for annotations */ p->gpd->flag |= GP_DATA_ANNOTATIONS; - p->gpl = BKE_gpencil_layer_addnew(p->gpd, DATA_("Note"), true); + p->gpl = BKE_gpencil_layer_addnew(p->gpd, DATA_("Note"), true, false); if (p->custom_color[3]) { copy_v3_v3(p->gpl->color, p->custom_color); diff --git a/source/blender/editors/gpencil/gpencil_add_blank.c b/source/blender/editors/gpencil/gpencil_add_blank.c new file mode 100644 index 00000000000..3aa16e54597 --- /dev/null +++ b/source/blender/editors/gpencil/gpencil_add_blank.c @@ -0,0 +1,101 @@ +/* + * 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) 2017 Blender Foundation + * This is a new part of Blender + */ + +/** \file + * \ingroup edgpencil + */ + +#include "BLI_math.h" +#include "BLI_utildefines.h" + +#include "DNA_gpencil_types.h" +#include "DNA_material_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BKE_context.h" +#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" +#include "BKE_main.h" +#include "BKE_material.h" + +#include "DEG_depsgraph.h" + +#include "ED_gpencil.h" + +/* Definition of the most important info from a color */ +typedef struct ColorTemplate { + const char *name; + float line[4]; + float fill[4]; +} ColorTemplate; + +/* Add color an ensure duplications (matched by name) */ +static int gpencil_stroke_material(Main *bmain, Object *ob, const ColorTemplate *pct) +{ + int index; + Material *ma = BKE_gpencil_object_material_ensure_by_name(bmain, ob, pct->name, &index); + + copy_v4_v4(ma->gp_style->stroke_rgba, pct->line); + srgb_to_linearrgb_v4(ma->gp_style->stroke_rgba, ma->gp_style->stroke_rgba); + + copy_v4_v4(ma->gp_style->fill_rgba, pct->fill); + srgb_to_linearrgb_v4(ma->gp_style->fill_rgba, ma->gp_style->fill_rgba); + + return index; +} + +/* ***************************************************************** */ +/* Stroke Geometry */ + +/* ***************************************************************** */ +/* Color Data */ + +static const ColorTemplate gp_stroke_material_black = { + "Black", + {0.0f, 0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 0.0f, 0.0f}, +}; + +/* ***************************************************************** */ +/* Blank API */ + +/* Add a Simple empty object with one layer and one color. */ +void ED_gpencil_create_blank(bContext *C, Object *ob, float UNUSED(mat[4][4])) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + bGPdata *gpd = (bGPdata *)ob->data; + + /* create colors */ + int color_black = gpencil_stroke_material(bmain, ob, &gp_stroke_material_black); + + /* set first color as active and in brushes */ + ob->actcol = color_black + 1; + + /* layers */ + bGPDlayer *layer = BKE_gpencil_layer_addnew(gpd, "GP_Layer", true, false); + + /* frames */ + BKE_gpencil_frame_addnew(layer, CFRA); + + /* update depsgraph */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + gpd->flag |= GP_DATA_CACHE_IS_DIRTY; +} diff --git a/source/blender/editors/gpencil/gpencil_add_lineart.c b/source/blender/editors/gpencil/gpencil_add_lineart.c index 6b28c6ec13e..ac0da0ad1db 100644 --- a/source/blender/editors/gpencil/gpencil_add_lineart.c +++ b/source/blender/editors/gpencil/gpencil_add_lineart.c @@ -96,7 +96,7 @@ void ED_gpencil_create_lineart(bContext *C, Object *ob) ob->actcol = color_black + 1; /* layers */ - bGPDlayer *lines = BKE_gpencil_layer_addnew(gpd, "Lines", true); + bGPDlayer *lines = BKE_gpencil_layer_addnew(gpd, "Lines", true, false); /* frames */ BKE_gpencil_frame_addnew(lines, 0); diff --git a/source/blender/editors/gpencil/gpencil_add_monkey.c b/source/blender/editors/gpencil/gpencil_add_monkey.c index 4497d963c6d..d8734c4ae6b 100644 --- a/source/blender/editors/gpencil/gpencil_add_monkey.c +++ b/source/blender/editors/gpencil/gpencil_add_monkey.c @@ -837,8 +837,8 @@ void ED_gpencil_create_monkey(bContext *C, Object *ob, float mat[4][4]) /* layers */ /* NOTE: For now, we just add new layers, to make it easier to separate out old/new instances */ - bGPDlayer *Fills = BKE_gpencil_layer_addnew(gpd, "Fills", false); - bGPDlayer *Lines = BKE_gpencil_layer_addnew(gpd, "Lines", true); + bGPDlayer *Fills = BKE_gpencil_layer_addnew(gpd, "Fills", false, false); + bGPDlayer *Lines = BKE_gpencil_layer_addnew(gpd, "Lines", true, false); /* frames */ /* NOTE: No need to check for existing, as this will take care of it for us */ diff --git a/source/blender/editors/gpencil/gpencil_add_stroke.c b/source/blender/editors/gpencil/gpencil_add_stroke.c index 26237636526..e95496b51ee 100644 --- a/source/blender/editors/gpencil/gpencil_add_stroke.c +++ b/source/blender/editors/gpencil/gpencil_add_stroke.c @@ -225,8 +225,8 @@ void ED_gpencil_create_stroke(bContext *C, Object *ob, float mat[4][4]) ob->actcol = color_black + 1; /* layers */ - bGPDlayer *colors = BKE_gpencil_layer_addnew(gpd, "Colors", false); - bGPDlayer *lines = BKE_gpencil_layer_addnew(gpd, "Lines", true); + bGPDlayer *colors = BKE_gpencil_layer_addnew(gpd, "Colors", false, false); + bGPDlayer *lines = BKE_gpencil_layer_addnew(gpd, "Lines", true, false); /* frames */ bGPDframe *frame_color = BKE_gpencil_frame_addnew(colors, CFRA); diff --git a/source/blender/editors/gpencil/gpencil_bake_animation.c b/source/blender/editors/gpencil/gpencil_bake_animation.c new file mode 100644 index 00000000000..30ebc9189c5 --- /dev/null +++ b/source/blender/editors/gpencil/gpencil_bake_animation.c @@ -0,0 +1,448 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 Blender Foundation + * This is a new part of Blender + */ + +/** \file + * \ingroup edgpencil + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_ghash.h" +#include "BLI_math.h" + +#include "DNA_anim_types.h" +#include "DNA_gpencil_types.h" +#include "DNA_material_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" + +#include "BKE_anim_data.h" +#include "BKE_context.h" +#include "BKE_duplilist.h" +#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" +#include "BKE_layer.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_object.h" +#include "BKE_report.h" +#include "BKE_scene.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "ED_gpencil.h" +#include "ED_transform_snap_object_context.h" + +#include "gpencil_intern.h" + +const EnumPropertyItem rna_gpencil_reproject_type_items[] = { + {GP_REPROJECT_KEEP, "KEEP", 0, "No Reproject", ""}, + {GP_REPROJECT_FRONT, "FRONT", 0, "Front", "Reproject the strokes using the X-Z plane"}, + {GP_REPROJECT_SIDE, "SIDE", 0, "Side", "Reproject the strokes using the Y-Z plane"}, + {GP_REPROJECT_TOP, "TOP", 0, "Top", "Reproject the strokes using the X-Y plane"}, + {GP_REPROJECT_VIEW, + "VIEW", + 0, + "View", + "Reproject the strokes to end up on the same plane, as if drawn from the current " + "viewpoint " + "using 'Cursor' Stroke Placement"}, + {GP_REPROJECT_CURSOR, + "CURSOR", + 0, + "Cursor", + "Reproject the strokes using the orientation of 3D cursor"}, + {0, NULL, 0, NULL, NULL}, +}; + +/* Check frame_end is always > start frame! */ +static void gpencil_bake_set_frame_end(struct Main *UNUSED(main), + struct Scene *UNUSED(scene), + struct PointerRNA *ptr) +{ + int frame_start = RNA_int_get(ptr, "frame_start"); + int frame_end = RNA_int_get(ptr, "frame_end"); + + if (frame_end <= frame_start) { + RNA_int_set(ptr, "frame_end", frame_start + 1); + } +} + +/* Extract mesh animation to Grease Pencil. */ +static bool gpencil_bake_grease_pencil_animation_poll(bContext *C) +{ + Object *obact = CTX_data_active_object(C); + if (CTX_data_mode_enum(C) != CTX_MODE_OBJECT) { + return false; + } + + /* Check if grease pencil or empty for dupli groups. */ + if ((obact == NULL) || ((obact->type != OB_GPENCIL) && (obact->type != OB_EMPTY))) { + return false; + } + + /* Only if the current view is 3D View. */ + ScrArea *area = CTX_wm_area(C); + return (area && area->spacetype); +} + +typedef struct GpBakeOb { + struct GpBakeOb *next, *prev; + Object *ob; +} GpBakeOb; + +/* Get list of keyframes used by selected objects. */ +static void animdata_keyframe_list_get(ListBase *ob_list, + const bool only_selected, + GHash *r_keyframes) +{ + /* Loop all objects to get the list of keyframes used. */ + LISTBASE_FOREACH (GpBakeOb *, elem, ob_list) { + Object *ob = elem->ob; + AnimData *adt = BKE_animdata_from_id(&ob->id); + if ((adt == NULL) || (adt->action == NULL)) { + continue; + } + LISTBASE_FOREACH (FCurve *, fcurve, &adt->action->curves) { + int i; + BezTriple *bezt; + for (i = 0, bezt = fcurve->bezt; i < fcurve->totvert; i++, bezt++) { + /* Keyframe number is x value of point. */ + if ((bezt->f2 & SELECT) || (!only_selected)) { + /* Insert only one key for each keyframe number. */ + int key = (int)bezt->vec[1][0]; + if (!BLI_ghash_haskey(r_keyframes, POINTER_FROM_INT(key))) { + BLI_ghash_insert(r_keyframes, POINTER_FROM_INT(key), POINTER_FROM_INT(key)); + } + } + } + } + } +} + +static void gpencil_bake_duplilist(Depsgraph *depsgraph, Scene *scene, Object *ob, ListBase *list) +{ + GpBakeOb *elem = NULL; + ListBase *lb; + DupliObject *dob; + lb = object_duplilist(depsgraph, scene, ob); + for (dob = lb->first; dob; dob = dob->next) { + if (dob->ob->type != OB_GPENCIL) { + continue; + } + + elem = MEM_callocN(sizeof(GpBakeOb), __func__); + elem->ob = dob->ob; + BLI_addtail(list, elem); + } + + free_object_duplilist(lb); +} + +static void gpencil_bake_ob_list(bContext *C, Depsgraph *depsgraph, Scene *scene, ListBase *list) +{ + GpBakeOb *elem = NULL; + + /* Add active object. In some files this could not be in selected array. */ + Object *obact = CTX_data_active_object(C); + + if (obact->type == OB_GPENCIL) { + elem = MEM_callocN(sizeof(GpBakeOb), __func__); + elem->ob = obact; + BLI_addtail(list, elem); + } + /* Add duplilist. */ + else if (obact->type == OB_EMPTY) { + gpencil_bake_duplilist(depsgraph, scene, obact, list); + } + + /* Add other selected objects. */ + CTX_DATA_BEGIN (C, Object *, ob, selected_objects) { + if (ob == obact) { + continue; + } + /* Add selected objects.*/ + if (ob->type == OB_GPENCIL) { + elem = MEM_callocN(sizeof(GpBakeOb), __func__); + elem->ob = ob; + BLI_addtail(list, elem); + } + + /* Add duplilist. */ + if (ob->type == OB_EMPTY) { + gpencil_bake_duplilist(depsgraph, scene, ob, list); + } + } + CTX_DATA_END; +} + +static void gpencil_bake_free_ob_list(ListBase *list) +{ + LISTBASE_FOREACH_MUTABLE (GpBakeOb *, elem, list) { + MEM_SAFE_FREE(elem); + } +} + +static int gpencil_bake_grease_pencil_animation_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Scene *scene = CTX_data_scene(C); + ARegion *region = CTX_wm_region(C); + View3D *v3d = CTX_wm_view3d(C); + + ListBase ob_selected_list = {NULL, NULL}; + gpencil_bake_ob_list(C, depsgraph, scene, &ob_selected_list); + + /* Grab all relevant settings. */ + const int step = RNA_int_get(op->ptr, "step"); + + const int frame_start = (scene->r.sfra > RNA_int_get(op->ptr, "frame_start")) ? + scene->r.sfra : + RNA_int_get(op->ptr, "frame_start"); + + const int frame_end = (scene->r.efra < RNA_int_get(op->ptr, "frame_end")) ? + scene->r.efra : + RNA_int_get(op->ptr, "frame_end"); + + const bool only_selected = RNA_boolean_get(op->ptr, "only_selected"); + const int frame_offset = RNA_int_get(op->ptr, "frame_target") - frame_start; + const int project_type = RNA_enum_get(op->ptr, "project_type"); + + /* Create a new grease pencil object. */ + Object *ob_gpencil = NULL; + ushort local_view_bits = (v3d && v3d->localvd) ? v3d->local_view_uuid : 0; + ob_gpencil = ED_gpencil_add_object(C, scene->cursor.location, local_view_bits); + float invmat[4][4]; + invert_m4_m4(invmat, ob_gpencil->obmat); + + bGPdata *gpd_dst = (bGPdata *)ob_gpencil->data; + gpd_dst->draw_mode = GP_DRAWMODE_2D; + + /* Set cursor to indicate working. */ + WM_cursor_wait(true); + + GP_SpaceConversion gsc = {NULL}; + SnapObjectContext *sctx = NULL; + if (project_type != GP_REPROJECT_KEEP) { + /* Init space conversion stuff. */ + gpencil_point_conversion_init(C, &gsc); + /* Move the grease pencil object to conversion data. */ + gsc.ob = ob_gpencil; + + /* Init snap context for geometry projection. */ + sctx = ED_transform_snap_object_context_create_view3d(scene, 0, region, CTX_wm_view3d(C)); + } + + /* Loop all frame range. */ + int oldframe = (int)DEG_get_ctime(depsgraph); + int key = -1; + + /* Get list of keyframes. */ + GHash *keyframe_list = BLI_ghash_int_new(__func__); + if (only_selected) { + animdata_keyframe_list_get(&ob_selected_list, only_selected, keyframe_list); + } + + for (int i = frame_start; i < frame_end + 1; i++) { + key++; + /* Jump if not step limit but include last frame always. */ + if ((key % step != 0) && (i != frame_end)) { + continue; + } + + /* Check if frame is in the list of frames to be exported. */ + if ((only_selected) && (!BLI_ghash_haskey(keyframe_list, POINTER_FROM_INT(i)))) { + continue; + } + + /* Move scene to new frame. */ + CFRA = i; + BKE_scene_graph_update_for_newframe(depsgraph); + + /* Loop all objects in the list. */ + LISTBASE_FOREACH (GpBakeOb *, elem, &ob_selected_list) { + Object *ob_eval = (Object *)DEG_get_evaluated_object(depsgraph, elem->ob); + bGPdata *gpd_src = ob_eval->data; + + LISTBASE_FOREACH (bGPDlayer *, gpl_src, &gpd_src->layers) { + /* Create destination layer. */ + char *layer_name; + layer_name = BLI_sprintfN("%s_%s", elem->ob->id.name + 2, gpl_src->info); + bGPDlayer *gpl_dst = BKE_gpencil_layer_named_get(gpd_dst, layer_name); + if (gpl_dst == NULL) { + gpl_dst = BKE_gpencil_layer_addnew(gpd_dst, layer_name, true, false); + } + MEM_freeN(layer_name); + + /* Layer Transform matrix. */ + float matrix[4][4]; + BKE_gpencil_layer_transform_matrix_get(depsgraph, elem->ob, gpl_src, matrix); + + /* Duplicate frame. */ + bGPDframe *gpf_src = BKE_gpencil_layer_frame_get(gpl_src, CFRA, GP_GETFRAME_USE_PREV); + if (gpf_src == NULL) { + continue; + } + bGPDframe *gpf_dst = BKE_gpencil_frame_duplicate(gpf_src, true); + gpf_dst->framenum = CFRA + frame_offset; + gpf_dst->flag &= ~GP_FRAME_SELECT; + BLI_addtail(&gpl_dst->frames, gpf_dst); + + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf_dst->strokes) { + /* Create material of the stroke. */ + Material *ma_src = BKE_object_material_get(elem->ob, gps->mat_nr + 1); + bool found = false; + for (int index = 0; index < ob_gpencil->totcol; index++) { + Material *ma_dst = BKE_object_material_get(ob_gpencil, index + 1); + if (ma_src == ma_dst) { + found = true; + break; + } + } + if (!found) { + BKE_object_material_slot_add(bmain, ob_gpencil); + BKE_object_material_assign( + bmain, ob_gpencil, ma_src, ob_gpencil->totcol, BKE_MAT_ASSIGN_USERPREF); + } + + /* Set new material index. */ + gps->mat_nr = BKE_gpencil_object_material_index_get(ob_gpencil, ma_src); + + /* Update point location to new object space. */ + for (int j = 0; j < gps->totpoints; j++) { + bGPDspoint *pt = &gps->points[j]; + mul_m4_v3(matrix, &pt->x); + mul_m4_v3(invmat, &pt->x); + } + + /* Reproject stroke. */ + if (project_type != GP_REPROJECT_KEEP) { + ED_gpencil_stroke_reproject( + depsgraph, &gsc, sctx, gpl_dst, gpf_dst, gps, project_type, false); + } + else { + BKE_gpencil_stroke_geometry_update(gpd_dst, gps); + } + } + } + } + } + /* Return scene frame state and DB to original state. */ + CFRA = oldframe; + BKE_scene_graph_update_for_newframe(depsgraph); + + /* Free memory. */ + gpencil_bake_free_ob_list(&ob_selected_list); + if (sctx != NULL) { + ED_transform_snap_object_context_destroy(sctx); + } + /* Free temp hash table. */ + if (keyframe_list != NULL) { + BLI_ghash_free(keyframe_list, NULL, NULL); + } + + /* Notifiers. */ + DEG_relations_tag_update(bmain); + DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); + DEG_id_tag_update(&gpd_dst->id, ID_RECALC_COPY_ON_WRITE); + WM_event_add_notifier(C, NC_OBJECT | NA_ADDED, NULL); + WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene); + + /* Reset cursor. */ + WM_cursor_wait(false); + + /* done */ + return OPERATOR_FINISHED; +} + +static int gpencil_bake_grease_pencil_animation_invoke(bContext *C, + wmOperator *op, + const wmEvent *UNUSED(event)) +{ + PropertyRNA *prop; + Scene *scene = CTX_data_scene(C); + + prop = RNA_struct_find_property(op->ptr, "frame_start"); + if (!RNA_property_is_set(op->ptr, prop)) { + const int frame_start = RNA_property_int_get(op->ptr, prop); + if (frame_start < scene->r.sfra) { + RNA_property_int_set(op->ptr, prop, scene->r.sfra); + } + } + + prop = RNA_struct_find_property(op->ptr, "frame_end"); + if (!RNA_property_is_set(op->ptr, prop)) { + const int frame_end = RNA_property_int_get(op->ptr, prop); + if (frame_end > scene->r.efra) { + RNA_property_int_set(op->ptr, prop, scene->r.efra); + } + } + + /* Show popup dialog to allow editing. */ + return WM_operator_props_dialog_popup(C, op, 250); +} + +void GPENCIL_OT_bake_grease_pencil_animation(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Bake Object Transform to Grease Pencil"; + ot->idname = "GPENCIL_OT_bake_grease_pencil_animation"; + ot->description = "Bake grease pencil object transform to grease pencil keyframes"; + + /* callbacks */ + ot->invoke = gpencil_bake_grease_pencil_animation_invoke; + ot->exec = gpencil_bake_grease_pencil_animation_exec; + ot->poll = gpencil_bake_grease_pencil_animation_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + prop = RNA_def_int( + ot->srna, "frame_start", 1, 1, 100000, "Start Frame", "The start frame", 1, 100000); + + prop = RNA_def_int( + ot->srna, "frame_end", 250, 1, 100000, "End Frame", "The end frame of animation", 1, 100000); + RNA_def_property_update_runtime(prop, gpencil_bake_set_frame_end); + + prop = RNA_def_int(ot->srna, "step", 1, 1, 100, "Step", "Step between generated frames", 1, 100); + + RNA_def_boolean( + ot->srna, "only_selected", 0, "Only Selected Keyframes", "Convert only selected keyframes"); + RNA_def_int( + ot->srna, "frame_target", 1, 1, 100000, "Target Frame", "Destination frame", 1, 100000); + + RNA_def_enum(ot->srna, + "project_type", + rna_gpencil_reproject_type_items, + GP_REPROJECT_KEEP, + "Projection Type", + ""); +} diff --git a/source/blender/editors/gpencil/gpencil_convert.c b/source/blender/editors/gpencil/gpencil_convert.c index ac75ae44c8a..8ab413e907c 100644 --- a/source/blender/editors/gpencil/gpencil_convert.c +++ b/source/blender/editors/gpencil/gpencil_convert.c @@ -1857,7 +1857,7 @@ static int image_to_gpencil_exec(bContext *C, wmOperator *op) /* Add layer and frame. */ bGPdata *gpd = (bGPdata *)ob->data; - bGPDlayer *gpl = BKE_gpencil_layer_addnew(gpd, "Image Layer", true); + bGPDlayer *gpl = BKE_gpencil_layer_addnew(gpd, "Image Layer", true, false); bGPDframe *gpf = BKE_gpencil_frame_addnew(gpl, CFRA); done = BKE_gpencil_from_image(sima, gpd, gpf, size, is_mask); diff --git a/source/blender/editors/gpencil/gpencil_data.c b/source/blender/editors/gpencil/gpencil_data.c index b269fd84d5f..d9a807d17ab 100644 --- a/source/blender/editors/gpencil/gpencil_data.c +++ b/source/blender/editors/gpencil/gpencil_data.c @@ -129,7 +129,7 @@ static int gpencil_data_add_exec(bContext *C, wmOperator *op) gpd->flag |= GP_DATA_ANNOTATIONS; /* add new layer (i.e. a "note") */ - BKE_gpencil_layer_addnew(*gpd_ptr, DATA_("Note"), true); + BKE_gpencil_layer_addnew(*gpd_ptr, DATA_("Note"), true, false); /* notifiers */ WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); @@ -231,7 +231,7 @@ static int gpencil_layer_add_exec(bContext *C, wmOperator *op) /* mark as annotation */ (*gpd_ptr)->flag |= GP_DATA_ANNOTATIONS; - BKE_gpencil_layer_addnew(*gpd_ptr, DATA_("Note"), true); + BKE_gpencil_layer_addnew(*gpd_ptr, DATA_("Note"), true, false); gpd = *gpd_ptr; } else { @@ -239,7 +239,7 @@ static int gpencil_layer_add_exec(bContext *C, wmOperator *op) Object *ob = CTX_data_active_object(C); if ((ob != NULL) && (ob->type == OB_GPENCIL)) { gpd = (bGPdata *)ob->data; - bGPDlayer *gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true); + bGPDlayer *gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true, false); /* Add a new frame to make it visible in Dopesheet. */ if (gpl != NULL) { gpl->actframe = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_ADD_NEW); @@ -524,7 +524,6 @@ enum { static bool gpencil_layer_duplicate_object_poll(bContext *C) { - ViewLayer *view_layer = CTX_data_view_layer(C); Object *ob = CTX_data_active_object(C); if ((ob == NULL) || (ob->type != OB_GPENCIL)) { return false; @@ -537,90 +536,75 @@ static bool gpencil_layer_duplicate_object_poll(bContext *C) return false; } - /* check there are more grease pencil objects */ - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { - if ((base->object != ob) && (base->object->type == OB_GPENCIL)) { - return true; - } - } - - return false; + return true; } static int gpencil_layer_duplicate_object_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); - Scene *scene = CTX_data_scene(C); - char name[MAX_ID_NAME - 2]; - RNA_string_get(op->ptr, "object", name); - - if (name[0] == '\0') { - return OPERATOR_CANCELLED; - } - - Object *ob_dst = (Object *)BKE_scene_object_find_by_name(scene, name); - - int mode = RNA_enum_get(op->ptr, "mode"); + const bool only_active = RNA_boolean_get(op->ptr, "only_active"); + const int mode = RNA_enum_get(op->ptr, "mode"); Object *ob_src = CTX_data_active_object(C); bGPdata *gpd_src = (bGPdata *)ob_src->data; - bGPDlayer *gpl_src = BKE_gpencil_layer_active_get(gpd_src); + bGPDlayer *gpl_active = BKE_gpencil_layer_active_get(gpd_src); - /* Sanity checks. */ - if (ELEM(NULL, gpd_src, gpl_src, ob_dst)) { - return OPERATOR_CANCELLED; - } - /* Cannot copy itself and check destination type. */ - if ((ob_src == ob_dst) || (ob_dst->type != OB_GPENCIL)) { - return OPERATOR_CANCELLED; - } - - bGPdata *gpd_dst = (bGPdata *)ob_dst->data; - - /* Create new layer. */ - bGPDlayer *gpl_dst = BKE_gpencil_layer_addnew(gpd_dst, gpl_src->info, true); - /* Need to copy some variables (not all). */ - gpl_dst->onion_flag = gpl_src->onion_flag; - gpl_dst->thickness = gpl_src->thickness; - gpl_dst->line_change = gpl_src->line_change; - copy_v4_v4(gpl_dst->tintcolor, gpl_src->tintcolor); - gpl_dst->opacity = gpl_src->opacity; - - /* Create all frames. */ - LISTBASE_FOREACH (bGPDframe *, gpf_src, &gpl_src->frames) { - - if ((mode == GP_LAYER_COPY_OBJECT_ACT_FRAME) && (gpf_src != gpl_src->actframe)) { + CTX_DATA_BEGIN (C, Object *, ob, selected_objects) { + if ((ob == ob_src) || (ob->type != OB_GPENCIL)) { continue; } + bGPdata *gpd_dst = (bGPdata *)ob->data; + LISTBASE_FOREACH_BACKWARD (bGPDlayer *, gpl_src, &gpd_src->layers) { + if ((only_active) && (gpl_src != gpl_active)) { + continue; + } + /* Create new layer (adding at head of the list). */ + bGPDlayer *gpl_dst = BKE_gpencil_layer_addnew(gpd_dst, gpl_src->info, true, true); + /* Need to copy some variables (not all). */ + gpl_dst->onion_flag = gpl_src->onion_flag; + gpl_dst->thickness = gpl_src->thickness; + gpl_dst->line_change = gpl_src->line_change; + copy_v4_v4(gpl_dst->tintcolor, gpl_src->tintcolor); + gpl_dst->opacity = gpl_src->opacity; + + /* Create all frames. */ + LISTBASE_FOREACH (bGPDframe *, gpf_src, &gpl_src->frames) { + + if ((mode == GP_LAYER_COPY_OBJECT_ACT_FRAME) && (gpf_src != gpl_src->actframe)) { + continue; + } - /* Create new frame. */ - bGPDframe *gpf_dst = BKE_gpencil_frame_addnew(gpl_dst, gpf_src->framenum); + /* Create new frame. */ + bGPDframe *gpf_dst = BKE_gpencil_frame_addnew(gpl_dst, gpf_src->framenum); - /* Copy strokes. */ - LISTBASE_FOREACH (bGPDstroke *, gps_src, &gpf_src->strokes) { + /* Copy strokes. */ + LISTBASE_FOREACH (bGPDstroke *, gps_src, &gpf_src->strokes) { - /* Make copy of source stroke. */ - bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true, true); + /* Make copy of source stroke. */ + bGPDstroke *gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true, true); - /* Check if material is in destination object, - * otherwise add the slot with the material. */ - Material *ma_src = BKE_object_material_get(ob_src, gps_src->mat_nr + 1); - if (ma_src != NULL) { - int idx = BKE_gpencil_object_material_ensure(bmain, ob_dst, ma_src); + /* Check if material is in destination object, + * otherwise add the slot with the material. */ + Material *ma_src = BKE_object_material_get(ob_src, gps_src->mat_nr + 1); + if (ma_src != NULL) { + int idx = BKE_gpencil_object_material_ensure(bmain, ob, ma_src); - /* Reassign the stroke material to the right slot in destination object. */ - gps_dst->mat_nr = idx; - } + /* Reassign the stroke material to the right slot in destination object. */ + gps_dst->mat_nr = idx; + } - /* Add new stroke to frame. */ - BLI_addtail(&gpf_dst->strokes, gps_dst); + /* Add new stroke to frame. */ + BLI_addtail(&gpf_dst->strokes, gps_dst); + } + } } + /* notifiers */ + DEG_id_tag_update(&gpd_dst->id, + ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); + DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE); } + CTX_DATA_END; - /* notifiers */ - DEG_id_tag_update(&gpd_dst->id, - ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); - DEG_id_tag_update(&ob_dst->id, ID_RECALC_COPY_ON_WRITE); WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); return OPERATOR_FINISHED; @@ -628,6 +612,8 @@ static int gpencil_layer_duplicate_object_exec(bContext *C, wmOperator *op) void GPENCIL_OT_layer_duplicate_object(wmOperatorType *ot) { + PropertyRNA *prop; + static const EnumPropertyItem copy_mode[] = { {GP_LAYER_COPY_OBJECT_ALL_FRAME, "ALL", 0, "All Frames", ""}, {GP_LAYER_COPY_OBJECT_ACT_FRAME, "ACTIVE", 0, "Active Frame", ""}, @@ -637,7 +623,7 @@ void GPENCIL_OT_layer_duplicate_object(wmOperatorType *ot) /* identifiers */ ot->name = "Duplicate Layer to New Object"; ot->idname = "GPENCIL_OT_layer_duplicate_object"; - ot->description = "Make a copy of the active Grease Pencil layer to new object"; + ot->description = "Make a copy of the active Grease Pencil layer to selected object"; /* callbacks */ ot->exec = gpencil_layer_duplicate_object_exec; @@ -646,11 +632,14 @@ void GPENCIL_OT_layer_duplicate_object(wmOperatorType *ot) /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - ot->prop = RNA_def_string( - ot->srna, "object", NULL, MAX_ID_NAME - 2, "Object", "Name of the destination object"); - RNA_def_property_flag(ot->prop, PROP_HIDDEN | PROP_SKIP_SAVE); + ot->prop = RNA_def_enum(ot->srna, "mode", copy_mode, GP_LAYER_COPY_OBJECT_ALL_FRAME, "Mode", ""); - RNA_def_enum(ot->srna, "mode", copy_mode, GP_LAYER_COPY_OBJECT_ALL_FRAME, "Mode", ""); + prop = RNA_def_boolean(ot->srna, + "only_active", + true, + "Only Active", + "Copy only active Layer, uncheck to append all layers"); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); } /* ********************* Duplicate Frame ************************** */ @@ -1443,7 +1432,7 @@ static int gpencil_layer_change_exec(bContext *C, wmOperator *op) /* Get layer or create new one */ if (layer_num == -1) { /* Create layer */ - gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true); + gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true, false); } else { /* Try to get layer */ @@ -3589,6 +3578,79 @@ void GPENCIL_OT_set_active_material(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } +/* ********************* Append Materials in a new object ************************** */ +static bool gpencil_materials_copy_to_object_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + if ((ob == NULL) || (ob->type != OB_GPENCIL)) { + return false; + } + short *totcolp = BKE_object_material_len_p(ob); + if (*totcolp == 0) { + return false; + } + + return true; +} + +static int gpencil_materials_copy_to_object_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + const bool only_active = RNA_boolean_get(op->ptr, "only_active"); + Object *ob_src = CTX_data_active_object(C); + Material *ma_active = BKE_gpencil_material(ob_src, ob_src->actcol); + + CTX_DATA_BEGIN (C, Object *, ob, selected_objects) { + if ((ob == ob_src) || (ob->type != OB_GPENCIL)) { + continue; + } + /* Duplicate materials. */ + for (int i = 0; i < ob_src->totcol; i++) { + Material *ma_src = BKE_object_material_get(ob_src, i + 1); + if (only_active && ma_src != ma_active) { + continue; + } + + if (ma_src != NULL) { + BKE_gpencil_object_material_ensure(bmain, ob, ma_src); + } + } + + /* notifiers */ + DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE); + } + CTX_DATA_END; + + /* notifiers */ + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_materials_copy_to_object(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Copy Materials to Selected Object"; + ot->idname = "GPENCIL_OT_materials_copy_to_object"; + ot->description = "Append Materials of the active Grease Pencil to other object"; + + /* callbacks */ + ot->exec = gpencil_materials_copy_to_object_exec; + ot->poll = gpencil_materials_copy_to_object_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + prop = RNA_def_boolean(ot->srna, + "only_active", + true, + "Only Active", + "Append only active material, uncheck to append all materials"); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); +} + /* Parent GPencil object to Lattice */ bool ED_gpencil_add_lattice_modifier(const bContext *C, ReportList *reports, @@ -3745,3 +3807,51 @@ void GPENCIL_OT_layer_mask_remove(wmOperatorType *ot) ot->exec = gpencil_layer_mask_remove_exec; ot->poll = gpencil_active_layer_poll; } + +static int gpencil_layer_mask_move_exec(bContext *C, wmOperator *op) +{ + bGPdata *gpd = ED_gpencil_data_get_active(C); + bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); + const int direction = RNA_enum_get(op->ptr, "type"); + + /* sanity checks */ + if (ELEM(NULL, gpd, gpl)) { + return OPERATOR_CANCELLED; + } + if (gpl->act_mask > 0) { + bGPDlayer_Mask *mask = BLI_findlink(&gpl->mask_layers, gpl->act_mask - 1); + if (mask != NULL) { + BLI_assert(ELEM(direction, -1, 0, 1)); /* we use value below */ + if (BLI_listbase_link_move(&gpl->mask_layers, mask, direction)) { + gpl->act_mask += direction; + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL); + } + } + } + + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_layer_mask_move(wmOperatorType *ot) +{ + static const EnumPropertyItem slot_move[] = { + {GP_LAYER_MOVE_UP, "UP", 0, "Up", ""}, + {GP_LAYER_MOVE_DOWN, "DOWN", 0, "Down", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + /* identifiers */ + ot->name = "Move Grease Pencil Layer Mask"; + ot->idname = "GPENCIL_OT_layer_mask_move"; + ot->description = "Move the active Grease Pencil mask layer up/down in the list"; + + /* api callbacks */ + ot->exec = gpencil_layer_mask_move_exec; + ot->poll = gpencil_active_layer_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_enum(ot->srna, "type", slot_move, 0, "Type", ""); +} diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index 66d50e2fd12..e65afa1abff 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -1677,7 +1677,7 @@ static int gpencil_strokes_paste_exec(bContext *C, wmOperator *op) if (gpl == NULL) { /* no active layer - let's just create one */ - gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true); + gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true, false); } else if ((BKE_gpencil_layer_is_editable(gpl) == false) && (type == GP_COPY_TO_ACTIVE)) { BKE_report( @@ -1818,18 +1818,13 @@ static int gpencil_move_to_layer_exec(bContext *C, wmOperator *op) { Object *ob = CTX_data_active_object(C); bGPdata *gpd = (bGPdata *)ob->data; - Scene *scene = CTX_data_scene(C); bGPDlayer *target_layer = NULL; ListBase strokes = {NULL, NULL}; int layer_num = RNA_int_get(op->ptr, "layer"); const bool use_autolock = (bool)(gpd->flag & GP_DATA_AUTOLOCK_LAYERS); + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); - if (GPENCIL_MULTIEDIT_SESSIONS_ON(gpd)) { - BKE_report(op->reports, RPT_ERROR, "Operator not supported in multiframe edition"); - return OPERATOR_CANCELLED; - } - - /* if autolock enabled, disabled now */ + /* If autolock enabled, disabled now. */ if (use_autolock) { gpd->flag &= ~GP_DATA_AUTOLOCK_LAYERS; } @@ -1840,7 +1835,7 @@ static int gpencil_move_to_layer_exec(bContext *C, wmOperator *op) } else { /* Create a new layer. */ - target_layer = BKE_gpencil_layer_addnew(gpd, "GP_Layer", true); + target_layer = BKE_gpencil_layer_addnew(gpd, "GP_Layer", true, false); } if (target_layer == NULL) { @@ -1852,53 +1847,59 @@ static int gpencil_move_to_layer_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - /* Extract all strokes to move to this layer - * NOTE: We need to do this in a two-pass system to avoid conflicts with strokes - * getting repeatedly moved - */ - CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { - bGPDframe *gpf = gpl->actframe; - - /* skip if no frame with strokes, or if this is the layer we're moving strokes to */ - if ((gpl == target_layer) || (gpf == NULL)) { + /* Extract all strokes to move to this layer. */ + CTX_DATA_BEGIN (C, bGPDlayer *, gpl_src, editable_gpencil_layers) { + /* Skip if this is the layer we're moving strokes to. */ + if (gpl_src == target_layer) { continue; } + bGPDframe *init_gpf = (is_multiedit) ? gpl_src->frames.first : gpl_src->actframe; + for (bGPDframe *gpf_src = init_gpf; gpf_src; gpf_src = gpf_src->next) { + if ((gpf_src == gpl_src->actframe) || + ((gpf_src->flag & GP_FRAME_SELECT) && (is_multiedit))) { + if (gpf_src == NULL) { + continue; + } - /* make copies of selected strokes, and deselect these once we're done */ - LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) { + bGPDstroke *gpsn = NULL; + BLI_listbase_clear(&strokes); + for (bGPDstroke *gps = gpf_src->strokes.first; gps; gps = gpsn) { + gpsn = gps->next; + /* Skip strokes that are invalid for current view. */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + /* Check if the color is editable. */ + if (ED_gpencil_stroke_material_editable(ob, gpl_src, gps) == false) { + continue; + } - /* skip strokes that are invalid for current view */ - if (ED_gpencil_stroke_can_use(C, gps) == false) { - continue; - } + if (gps->flag & GP_STROKE_SELECT) { + BLI_remlink(&gpf_src->strokes, gps); + BLI_addtail(&strokes, gps); + } + } + /* Paste them all in one go. */ + if (strokes.first) { + bGPDframe *gpf_dst = BKE_gpencil_layer_frame_get( + target_layer, gpf_src->framenum, GP_GETFRAME_ADD_NEW); - /* Check if the color is editable. */ - if (ED_gpencil_stroke_material_editable(ob, gpl, gps) == false) { - continue; + BLI_movelisttolist(&gpf_dst->strokes, &strokes); + BLI_assert((strokes.first == strokes.last) && (strokes.first == NULL)); + } } - - /* TODO: Don't just move entire strokes - instead, only copy the selected portions... */ - if (gps->flag & GP_STROKE_SELECT) { - BLI_remlink(&gpf->strokes, gps); - BLI_addtail(&strokes, gps); + /* If not multi-edit, exit loop. */ + if (!is_multiedit) { + break; } } - - /* if new layer and autolock, lock old layer */ + /* If new layer and autolock, lock old layer. */ if ((layer_num == -1) && (use_autolock)) { - gpl->flag |= GP_LAYER_LOCKED; + gpl_src->flag |= GP_LAYER_LOCKED; } } CTX_DATA_END; - /* Paste them all in one go */ - if (strokes.first) { - bGPDframe *gpf = BKE_gpencil_layer_frame_get(target_layer, CFRA, GP_GETFRAME_ADD_NEW); - - BLI_movelisttolist(&gpf->strokes, &strokes); - BLI_assert((strokes.first == strokes.last) && (strokes.first == NULL)); - } - /* back autolock status */ if (use_autolock) { gpd->flag |= GP_DATA_AUTOLOCK_LAYERS; @@ -3762,6 +3763,7 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op) const eGP_ReprojectModes mode = RNA_enum_get(op->ptr, "type"); const bool keep_original = RNA_boolean_get(op->ptr, "keep_original"); const bool is_curve_edit = (bool)GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); + const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); /* Init snap context for geometry projection. */ SnapObjectContext *sctx = NULL; @@ -3774,36 +3776,55 @@ static int gpencil_strokes_reproject_exec(bContext *C, wmOperator *op) int cfra_prv = INT_MIN; /* Go through each editable + selected stroke, adjusting each of its points one by one... */ - GP_EDITABLE_STROKES_BEGIN (gpstroke_iter, C, gpl, gps) { - bool curve_select = false; - if (is_curve_edit && gps->editcurve != NULL) { - curve_select = gps->editcurve->flag & GP_CURVE_SELECT; - } + CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) { + bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe; - if (gps->flag & GP_STROKE_SELECT || curve_select) { + for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) { + if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) { + if (gpf == NULL) { + continue; + } + for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) { + /* skip strokes that are invalid for current view */ + if (ED_gpencil_stroke_can_use(C, gps) == false) { + continue; + } + bool curve_select = false; + if (is_curve_edit && gps->editcurve != NULL) { + curve_select = gps->editcurve->flag & GP_CURVE_SELECT; + } - /* update frame to get the new location of objects */ - if ((mode == GP_REPROJECT_SURFACE) && (cfra_prv != gpf_->framenum)) { - cfra_prv = gpf_->framenum; - CFRA = gpf_->framenum; - BKE_scene_graph_update_for_newframe(depsgraph); - } + if (gps->flag & GP_STROKE_SELECT || curve_select) { - ED_gpencil_stroke_reproject(depsgraph, &gsc, sctx, gpl, gpf_, gps, mode, keep_original); + /* update frame to get the new location of objects */ + if ((mode == GP_REPROJECT_SURFACE) && (cfra_prv != gpf->framenum)) { + cfra_prv = gpf->framenum; + CFRA = gpf->framenum; + BKE_scene_graph_update_for_newframe(depsgraph); + } - if (is_curve_edit && gps->editcurve != NULL) { - BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps); - /* Update the selection from the stroke to the curve. */ - BKE_gpencil_editcurve_stroke_sync_selection(gpd, gps, gps->editcurve); + ED_gpencil_stroke_reproject(depsgraph, &gsc, sctx, gpl, gpf, gps, mode, keep_original); - gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; - BKE_gpencil_stroke_geometry_update(gpd, gps); - } + if (is_curve_edit && gps->editcurve != NULL) { + BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps); + /* Update the selection from the stroke to the curve. */ + BKE_gpencil_editcurve_stroke_sync_selection(gpd, gps, gps->editcurve); - changed = true; + gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE; + BKE_gpencil_stroke_geometry_update(gpd, gps); + } + + changed = true; + } + } + } + /* If not multi-edit, exit loop. */ + if (!is_multiedit) { + break; + } } } - GP_EDITABLE_STROKES_END(gpstroke_iter); + CTX_DATA_END; /* return frame state and DB to original state */ CFRA = oldframe; @@ -3832,7 +3853,8 @@ void GPENCIL_OT_reproject(wmOperatorType *ot) "VIEW", 0, "View", - "Reproject the strokes to end up on the same plane, as if drawn from the current viewpoint " + "Reproject the strokes to end up on the same plane, as if drawn from the current " + "viewpoint " "using 'Cursor' Stroke Placement"}, {GP_REPROJECT_SURFACE, "SURFACE", @@ -3851,7 +3873,8 @@ void GPENCIL_OT_reproject(wmOperatorType *ot) ot->name = "Reproject Strokes"; ot->idname = "GPENCIL_OT_reproject"; ot->description = - "Reproject the selected strokes from the current viewpoint as if they had been newly drawn " + "Reproject the selected strokes from the current viewpoint as if they had been newly " + "drawn " "(e.g. to fix problems from accidental 3D cursor movement or accidental viewport changes, " "or for matching deforming geometry)"; @@ -4208,7 +4231,8 @@ void GPENCIL_OT_stroke_subdivide(wmOperatorType *ot) ot->name = "Subdivide Stroke"; ot->idname = "GPENCIL_OT_stroke_subdivide"; ot->description = - "Subdivide between continuous selected points of the stroke adding a point half way between " + "Subdivide between continuous selected points of the stroke adding a point half way " + "between " "them"; /* api callbacks */ @@ -4593,7 +4617,7 @@ static int gpencil_stroke_separate_exec(bContext *C, wmOperator *op) if (gps->flag & GP_STROKE_SELECT) { /* add layer if not created before */ if (gpl_dst == NULL) { - gpl_dst = BKE_gpencil_layer_addnew(gpd_dst, gpl->info, false); + gpl_dst = BKE_gpencil_layer_addnew(gpd_dst, gpl->info, false, false); } /* add frame if not created before */ diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c index bb0a2916d2d..f74e211dd65 100644 --- a/source/blender/editors/gpencil/gpencil_fill.c +++ b/source/blender/editors/gpencil/gpencil_fill.c @@ -1247,6 +1247,7 @@ static bool dilate_shape(ImBuf *ibuf) static void gpencil_get_outline_points(tGPDfill *tgpf, const bool dilate) { ImBuf *ibuf; + Brush *brush = tgpf->brush; float rgba[4]; void *lock; int v[2]; @@ -1279,7 +1280,9 @@ static void gpencil_get_outline_points(tGPDfill *tgpf, const bool dilate) /* Dilate. */ if (dilate) { - dilate_shape(ibuf); + for (int i = 0; i < brush->gpencil_settings->dilate_pixels; i++) { + dilate_shape(ibuf); + } } for (int idx = imagesize - 1; idx != 0; idx--) { @@ -1686,7 +1689,7 @@ static tGPDfill *gpencil_session_init_fill(bContext *C, wmOperator *op) tgpf->gpd = gpd; tgpf->gpl = BKE_gpencil_layer_active_get(gpd); if (tgpf->gpl == NULL) { - tgpf->gpl = BKE_gpencil_layer_addnew(tgpf->gpd, DATA_("GP_Layer"), true); + tgpf->gpl = BKE_gpencil_layer_addnew(tgpf->gpd, DATA_("GP_Layer"), true, false); } tgpf->lock_axis = ts->gp_sculpt.lock_axis; diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h index c6f74c39beb..0e8fdc3c375 100644 --- a/source/blender/editors/gpencil/gpencil_intern.h +++ b/source/blender/editors/gpencil/gpencil_intern.h @@ -421,6 +421,7 @@ void GPENCIL_OT_layer_duplicate_object(struct wmOperatorType *ot); void GPENCIL_OT_layer_mask_add(struct wmOperatorType *ot); void GPENCIL_OT_layer_mask_remove(struct wmOperatorType *ot); +void GPENCIL_OT_layer_mask_move(struct wmOperatorType *ot); void GPENCIL_OT_hide(struct wmOperatorType *ot); void GPENCIL_OT_reveal(struct wmOperatorType *ot); @@ -443,6 +444,7 @@ void GPENCIL_OT_frame_clean_duplicate(struct wmOperatorType *ot); void GPENCIL_OT_convert(struct wmOperatorType *ot); void GPENCIL_OT_bake_mesh_animation(struct wmOperatorType *ot); +void GPENCIL_OT_bake_grease_pencil_animation(struct wmOperatorType *ot); void GPENCIL_OT_image_to_grease_pencil(struct wmOperatorType *ot); void GPENCIL_OT_trace_image(struct wmOperatorType *ot); @@ -537,6 +539,7 @@ void GPENCIL_OT_material_lock_unused(struct wmOperatorType *ot); void GPENCIL_OT_material_select(struct wmOperatorType *ot); void GPENCIL_OT_material_set(struct wmOperatorType *ot); void GPENCIL_OT_set_active_material(struct wmOperatorType *ot); +void GPENCIL_OT_materials_copy_to_object(struct wmOperatorType *ot); /* convert old 2.7 files to 2.8 */ void GPENCIL_OT_convert_old_files(struct wmOperatorType *ot); @@ -748,4 +751,7 @@ struct GP_EditableStrokes_Iter { } \ (void)0 +/* Reused items for bake operators. */ +extern const EnumPropertyItem rna_gpencil_reproject_type_items[]; + /* ****************************************************** */ diff --git a/source/blender/editors/gpencil/gpencil_interpolate.c b/source/blender/editors/gpencil/gpencil_interpolate.c index 7ca53779522..0062e363cdf 100644 --- a/source/blender/editors/gpencil/gpencil_interpolate.c +++ b/source/blender/editors/gpencil/gpencil_interpolate.c @@ -1434,36 +1434,32 @@ static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op) static void gpencil_interpolate_seq_ui(bContext *C, wmOperator *op) { uiLayout *layout = op->layout; - wmWindowManager *wm = CTX_wm_manager(C); uiLayout *col, *row; - PointerRNA ptr; - - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); const eGP_Interpolate_Type type = RNA_enum_get(op->ptr, "type"); uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); row = uiLayoutRow(layout, true); - uiItemR(row, &ptr, "step", 0, NULL, ICON_NONE); + uiItemR(row, op->ptr, "step", 0, NULL, ICON_NONE); row = uiLayoutRow(layout, true); - uiItemR(row, &ptr, "layers", 0, NULL, ICON_NONE); + uiItemR(row, op->ptr, "layers", 0, NULL, ICON_NONE); if (CTX_data_mode_enum(C) == CTX_MODE_EDIT_GPENCIL) { row = uiLayoutRow(layout, true); - uiItemR(row, &ptr, "interpolate_selected_only", 0, NULL, ICON_NONE); + uiItemR(row, op->ptr, "interpolate_selected_only", 0, NULL, ICON_NONE); } row = uiLayoutRow(layout, true); - uiItemR(row, &ptr, "flip", 0, NULL, ICON_NONE); + uiItemR(row, op->ptr, "flip", 0, NULL, ICON_NONE); col = uiLayoutColumn(layout, true); - uiItemR(col, &ptr, "smooth_factor", 0, NULL, ICON_NONE); - uiItemR(col, &ptr, "smooth_steps", 0, NULL, ICON_NONE); + uiItemR(col, op->ptr, "smooth_factor", 0, NULL, ICON_NONE); + uiItemR(col, op->ptr, "smooth_steps", 0, NULL, ICON_NONE); row = uiLayoutRow(layout, true); - uiItemR(row, &ptr, "type", 0, NULL, ICON_NONE); + uiItemR(row, op->ptr, "type", 0, NULL, ICON_NONE); if (type == GP_IPO_CURVEMAP) { /* Get an RNA pointer to ToolSettings to give to the custom curve. */ @@ -1477,16 +1473,16 @@ static void gpencil_interpolate_seq_ui(bContext *C, wmOperator *op) } else if (type != GP_IPO_LINEAR) { row = uiLayoutRow(layout, false); - uiItemR(row, &ptr, "easing", 0, NULL, ICON_NONE); + uiItemR(row, op->ptr, "easing", 0, NULL, ICON_NONE); if (type == GP_IPO_BACK) { row = uiLayoutRow(layout, false); - uiItemR(row, &ptr, "back", 0, NULL, ICON_NONE); + uiItemR(row, op->ptr, "back", 0, NULL, ICON_NONE); } else if (type == GP_IPO_ELASTIC) { row = uiLayoutRow(layout, false); - uiItemR(row, &ptr, "amplitude", 0, NULL, ICON_NONE); + uiItemR(row, op->ptr, "amplitude", 0, NULL, ICON_NONE); row = uiLayoutRow(layout, false); - uiItemR(row, &ptr, "period", 0, NULL, ICON_NONE); + uiItemR(row, op->ptr, "period", 0, NULL, ICON_NONE); } } } diff --git a/source/blender/editors/gpencil/gpencil_mesh.c b/source/blender/editors/gpencil/gpencil_mesh.c index b7ed77801c0..55468dffab0 100644 --- a/source/blender/editors/gpencil/gpencil_mesh.c +++ b/source/blender/editors/gpencil/gpencil_mesh.c @@ -402,25 +402,6 @@ static int gpencil_bake_mesh_animation_invoke(bContext *C, void GPENCIL_OT_bake_mesh_animation(wmOperatorType *ot) { - static const EnumPropertyItem reproject_type[] = { - {GP_REPROJECT_KEEP, "KEEP", 0, "No Reproject", ""}, - {GP_REPROJECT_FRONT, "FRONT", 0, "Front", "Reproject the strokes using the X-Z plane"}, - {GP_REPROJECT_SIDE, "SIDE", 0, "Side", "Reproject the strokes using the Y-Z plane"}, - {GP_REPROJECT_TOP, "TOP", 0, "Top", "Reproject the strokes using the X-Y plane"}, - {GP_REPROJECT_VIEW, - "VIEW", - 0, - "View", - "Reproject the strokes to end up on the same plane, as if drawn from the current viewpoint " - "using 'Cursor' Stroke Placement"}, - {GP_REPROJECT_CURSOR, - "CURSOR", - 0, - "Cursor", - "Reproject the strokes using the orientation of 3D cursor"}, - {0, NULL, 0, NULL, NULL}, - }; - static const EnumPropertyItem target_object_modes[] = { {GP_TARGET_OB_NEW, "NEW", 0, "New Object", ""}, {GP_TARGET_OB_SELECTED, "SELECTED", 0, "Selected Object", ""}, @@ -491,5 +472,10 @@ void GPENCIL_OT_bake_mesh_animation(wmOperatorType *ot) RNA_def_int( ot->srna, "frame_target", 1, 1, 100000, "Target Frame", "Destination frame", 1, 100000); - RNA_def_enum(ot->srna, "project_type", reproject_type, GP_REPROJECT_VIEW, "Projection Type", ""); + RNA_def_enum(ot->srna, + "project_type", + rna_gpencil_reproject_type_items, + GP_REPROJECT_VIEW, + "Projection Type", + ""); } diff --git a/source/blender/editors/gpencil/gpencil_ops.c b/source/blender/editors/gpencil/gpencil_ops.c index 1a6cb5670c4..0e9ce1d603f 100644 --- a/source/blender/editors/gpencil/gpencil_ops.c +++ b/source/blender/editors/gpencil/gpencil_ops.c @@ -601,6 +601,7 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_layer_mask_add); WM_operatortype_append(GPENCIL_OT_layer_mask_remove); + WM_operatortype_append(GPENCIL_OT_layer_mask_move); WM_operatortype_append(GPENCIL_OT_hide); WM_operatortype_append(GPENCIL_OT_reveal); @@ -621,6 +622,7 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_convert); WM_operatortype_append(GPENCIL_OT_bake_mesh_animation); + WM_operatortype_append(GPENCIL_OT_bake_grease_pencil_animation); WM_operatortype_append(GPENCIL_OT_image_to_grease_pencil); #ifdef WITH_POTRACE @@ -650,6 +652,7 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_material_to_vertex_color); WM_operatortype_append(GPENCIL_OT_extract_palette_vertex); + WM_operatortype_append(GPENCIL_OT_materials_copy_to_object); WM_operatortype_append(GPENCIL_OT_transform_fill); WM_operatortype_append(GPENCIL_OT_reset_transform_fill); diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index 9bd929aa2af..993065a3a70 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -1332,7 +1332,7 @@ static float view3d_point_depth(const RegionView3D *rv3d, const float co[3]) /* only erase stroke points that are visible */ static bool gpencil_stroke_eraser_is_occluded( - tGPsdata *p, bGPDlayer *gpl, const bGPDspoint *pt, const int x, const int y) + tGPsdata *p, bGPDlayer *gpl, bGPDspoint *pt, const int x, const int y) { Object *obact = (Object *)p->ownerPtr.data; Brush *brush = p->brush; @@ -1364,7 +1364,11 @@ static bool gpencil_stroke_eraser_is_occluded( mul_v3_m4v3(fpt, diff_mat, &pt->x); const float depth_pt = view3d_point_depth(rv3d, fpt); + /* Checked occlusion flag. */ + pt->flag |= GP_SPOINT_TEMP_TAG; if (depth_pt > depth_mval) { + /* Is occluded. */ + pt->flag |= GP_SPOINT_TEMP_TAG2; return true; } } @@ -1540,6 +1544,10 @@ static void gpencil_stroke_eraser_dostroke(tGPsdata *p, for (i = 0; i < gps->totpoints; i++) { bGPDspoint *pt = &gps->points[i]; pt->flag &= ~GP_SPOINT_TAG; + /* Occlusion already checked. */ + pt->flag &= ~GP_SPOINT_TEMP_TAG; + /* Point is occluded. */ + pt->flag &= ~GP_SPOINT_TEMP_TAG2; } /* First Pass: Loop over the points in the stroke @@ -1585,9 +1593,23 @@ static void gpencil_stroke_eraser_dostroke(tGPsdata *p, * - this assumes that linewidth is irrelevant */ if (gpencil_stroke_inside_circle(mval, radius, pc0[0], pc0[1], pc2[0], pc2[1])) { - if ((gpencil_stroke_eraser_is_occluded(p, gpl, pt0, pc0[0], pc0[1]) == false) || - (gpencil_stroke_eraser_is_occluded(p, gpl, pt1, pc1[0], pc1[1]) == false) || - (gpencil_stroke_eraser_is_occluded(p, gpl, pt2, pc2[0], pc2[1]) == false)) { + + bool is_occluded_pt0, is_occluded_pt1, is_occluded_pt2 = true; + is_occluded_pt0 = (pt0 && ((pt0->flag & GP_SPOINT_TEMP_TAG) != 0)) ? + ((pt0->flag & GP_SPOINT_TEMP_TAG2) != 0) : + gpencil_stroke_eraser_is_occluded(p, gpl, pt0, pc0[0], pc0[1]); + if (is_occluded_pt0) { + is_occluded_pt1 = ((pt1->flag & GP_SPOINT_TEMP_TAG) != 0) ? + ((pt1->flag & GP_SPOINT_TEMP_TAG2) != 0) : + gpencil_stroke_eraser_is_occluded(p, gpl, pt1, pc1[0], pc1[1]); + if (is_occluded_pt1) { + is_occluded_pt2 = ((pt2->flag & GP_SPOINT_TEMP_TAG) != 0) ? + ((pt2->flag & GP_SPOINT_TEMP_TAG2) != 0) : + gpencil_stroke_eraser_is_occluded(p, gpl, pt2, pc2[0], pc2[1]); + } + } + + if (!is_occluded_pt0 || !is_occluded_pt1 || !is_occluded_pt2) { /* Point is affected: */ /* Adjust thickness * - Influence of eraser falls off with distance from the middle of the eraser @@ -2120,7 +2142,7 @@ static void gpencil_paint_initstroke(tGPsdata *p, /* get active layer (or add a new one if non-existent) */ p->gpl = BKE_gpencil_layer_active_get(p->gpd); if (p->gpl == NULL) { - p->gpl = BKE_gpencil_layer_addnew(p->gpd, DATA_("GP_Layer"), true); + p->gpl = BKE_gpencil_layer_addnew(p->gpd, DATA_("GP_Layer"), true, false); changed = true; if (p->custom_color[3]) { copy_v3_v3(p->gpl->color, p->custom_color); diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c index 3d20e32ed49..1b0a40b1be1 100644 --- a/source/blender/editors/gpencil/gpencil_primitive.c +++ b/source/blender/editors/gpencil/gpencil_primitive.c @@ -314,7 +314,7 @@ static void gpencil_primitive_set_initdata(bContext *C, tGPDprimitive *tgpi) /* if layer doesn't exist, create a new one */ if (gpl == NULL) { - gpl = BKE_gpencil_layer_addnew(tgpi->gpd, DATA_("Primitives"), true); + gpl = BKE_gpencil_layer_addnew(tgpi->gpd, DATA_("Primitives"), true, false); } tgpi->gpl = gpl; diff --git a/source/blender/editors/gpencil/gpencil_trace_ops.c b/source/blender/editors/gpencil/gpencil_trace_ops.c index d2e5fa3db32..cd75f9313ad 100644 --- a/source/blender/editors/gpencil/gpencil_trace_ops.c +++ b/source/blender/editors/gpencil/gpencil_trace_ops.c @@ -207,7 +207,7 @@ static void trace_initialize_job_data(TraceJob *trace_job) trace_job->gpd = (bGPdata *)trace_job->ob_gpencil->data; trace_job->gpl = BKE_gpencil_layer_active_get(trace_job->gpd); if (trace_job->gpl == NULL) { - trace_job->gpl = BKE_gpencil_layer_addnew(trace_job->gpd, DATA_("Trace"), true); + trace_job->gpl = BKE_gpencil_layer_addnew(trace_job->gpd, DATA_("Trace"), true, false); } } diff --git a/source/blender/editors/include/ED_fileselect.h b/source/blender/editors/include/ED_fileselect.h index 983ae94b637..8118e3c6c69 100644 --- a/source/blender/editors/include/ED_fileselect.h +++ b/source/blender/editors/include/ED_fileselect.h @@ -110,7 +110,7 @@ struct FileAssetSelectParams *ED_fileselect_get_asset_params(const struct SpaceF void ED_fileselect_set_params_from_userdef(struct SpaceFile *sfile); void ED_fileselect_params_to_userdef(struct SpaceFile *sfile, - const int temp_win_size[], + const int temp_win_size[2], const bool is_maximized); void ED_fileselect_init_layout(struct SpaceFile *sfile, struct ARegion *region); diff --git a/source/blender/editors/include/ED_gizmo_library.h b/source/blender/editors/include/ED_gizmo_library.h index 58b2cb80bbe..571519e52f7 100644 --- a/source/blender/editors/include/ED_gizmo_library.h +++ b/source/blender/editors/include/ED_gizmo_library.h @@ -263,6 +263,11 @@ struct SnapObjectContext *ED_gizmotypes_snap_3d_context_ensure(struct Scene *sce typedef enum { ED_SNAPGIZMO_TOGGLE_ALWAYS_TRUE = 1 << 0, + ED_SNAPGIZMO_OCCLUSION_ALWAYS_TRUE = 1 << 1, + ED_SNAPGIZMO_OCCLUSION_ALWAYS_FALSE = 1 << 2, /* TODO. */ + ED_SNAPGIZMO_SNAP_ONLY_ACTIVE = 1 << 3, + ED_SNAPGIZMO_SNAP_EDIT_GEOM_FINAL = 1 << 4, + ED_SNAPGIZMO_SNAP_EDIT_GEOM_CAGE = 1 << 5, } eSnapGizmo; void ED_gizmotypes_snap_3d_flag_set(struct wmGizmo *gz, eSnapGizmo flag); diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h index d4ff374dc38..bad080e1609 100644 --- a/source/blender/editors/include/ED_gpencil.h +++ b/source/blender/editors/include/ED_gpencil.h @@ -251,6 +251,7 @@ void ED_gpencil_brush_draw_eraser(struct Brush *brush, int x, int y); /* ----------- Add Primitive Utilities -------------- */ +void ED_gpencil_create_blank(struct bContext *C, struct Object *ob, float mat[4][4]); void ED_gpencil_create_monkey(struct bContext *C, struct Object *ob, float mat[4][4]); void ED_gpencil_create_stroke(struct bContext *C, struct Object *ob, float mat[4][4]); void ED_gpencil_create_lineart(struct bContext *C, struct Object *ob); diff --git a/source/blender/editors/include/ED_keyframing.h b/source/blender/editors/include/ED_keyframing.h index 12d6f1fce54..179c9d5b30d 100644 --- a/source/blender/editors/include/ED_keyframing.h +++ b/source/blender/editors/include/ED_keyframing.h @@ -511,6 +511,7 @@ bool ED_autokeyframe_property(struct bContext *C, #define ANIM_KS_ROTATION_ID "Rotation" #define ANIM_KS_SCALING_ID "Scaling" #define ANIM_KS_LOC_ROT_SCALE_ID "LocRotScale" +#define ANIM_KS_LOC_ROT_SCALE_CPROP_ID "LocRotScaleCProp" #define ANIM_KS_AVAILABLE_ID "Available" #define ANIM_KS_WHOLE_CHARACTER_ID "WholeCharacter" #define ANIM_KS_WHOLE_CHARACTER_SELECTED_ID "WholeCharacterSelected" diff --git a/source/blender/editors/include/ED_screen.h b/source/blender/editors/include/ED_screen.h index 78c4c2a8eba..bdd7ec571dc 100644 --- a/source/blender/editors/include/ED_screen.h +++ b/source/blender/editors/include/ED_screen.h @@ -162,7 +162,7 @@ void ED_area_tag_redraw_no_rebuild(ScrArea *area); void ED_area_tag_redraw_regiontype(ScrArea *area, int type); void ED_area_tag_refresh(ScrArea *area); void ED_area_do_refresh(struct bContext *C, ScrArea *area); -struct AZone *ED_area_azones_update(ScrArea *area, const int mouse_xy[]); +struct AZone *ED_area_azones_update(ScrArea *area, const int mouse_xy[2]); void ED_area_status_text(ScrArea *area, const char *str); void ED_area_newspace(struct bContext *C, ScrArea *area, int type, const bool skip_region_exit); void ED_area_prevspace(struct bContext *C, ScrArea *area); @@ -200,8 +200,6 @@ ScrArea *ED_screen_areas_iter_next(const bScreen *screen, const ScrArea *area); /* screens */ void ED_screens_init(struct Main *bmain, struct wmWindowManager *wm); void ED_screen_draw_edges(struct wmWindow *win); -void ED_screen_draw_join_shape(struct ScrArea *sa1, struct ScrArea *sa2); -void ED_screen_draw_split_preview(struct ScrArea *area, const int dir, const float fac); void ED_screen_refresh(struct wmWindowManager *wm, struct wmWindow *win); void ED_screen_ensure_updated(struct wmWindowManager *wm, struct wmWindow *win, @@ -450,10 +448,10 @@ enum { }; /* SCREEN_OT_space_context_cycle direction */ -enum { +typedef enum eScreenCycle { SPACE_CONTEXT_CYCLE_PREV, SPACE_CONTEXT_CYCLE_NEXT, -}; +} eScreenCycle; #ifdef __cplusplus } diff --git a/source/blender/editors/include/ED_transform_snap_object_context.h b/source/blender/editors/include/ED_transform_snap_object_context.h index b7174964ef6..42e73bbf744 100644 --- a/source/blender/editors/include/ED_transform_snap_object_context.h +++ b/source/blender/editors/include/ED_transform_snap_object_context.h @@ -39,12 +39,19 @@ struct View3D; /* ED_transform_snap_object_*** API */ -typedef enum eSnapSelect { +typedef enum { SNAP_ALL = 0, SNAP_NOT_SELECTED = 1, SNAP_NOT_ACTIVE = 2, + SNAP_ONLY_ACTIVE = 3, } eSnapSelect; +typedef enum { + SNAP_GEOM_FINAL = 0, + SNAP_GEOM_CAGE = 1, + SNAP_GEOM_EDIT = 2, /* Bmesh for mesh-type. */ +} eSnapEditType; + /** used for storing multiple hits */ struct SnapObjectHitDepth { struct SnapObjectHitDepth *next, *prev; @@ -54,7 +61,7 @@ struct SnapObjectHitDepth { float no[3]; int index; - struct Object *ob; + struct Object *ob_eval; float obmat[4][4]; /* needed to tell which ray-cast this was part of, @@ -64,10 +71,10 @@ struct SnapObjectHitDepth { /** parameters that define which objects will be used to snap. */ struct SnapObjectParams { - /* special context sensitive handling for the active or selected object */ + /* Special context sensitive handling for the active or selected object. */ char snap_select; - /* use editmode cage */ - unsigned int use_object_edit_cage : 1; + /* Geometry for snapping in edit mode. */ + char edit_mode_type; /* snap to the closest element, use when using more than one snap type */ unsigned int use_occlusion_test : 1; /* exclude back facing geometry from snapping */ diff --git a/source/blender/editors/include/ED_view3d.h b/source/blender/editors/include/ED_view3d.h index 499f28beb60..66ec57c8a31 100644 --- a/source/blender/editors/include/ED_view3d.h +++ b/source/blender/editors/include/ED_view3d.h @@ -454,7 +454,7 @@ bool ED_view3d_calc_render_border(const struct Scene *scene, struct ARegion *region, struct rcti *rect); -void ED_view3d_clipping_calc_from_boundbox(float clip[6][4], +void ED_view3d_clipping_calc_from_boundbox(float clip[4][4], const struct BoundBox *clipbb, const bool is_flip); void ED_view3d_clipping_calc(struct BoundBox *bb, @@ -690,7 +690,7 @@ float ED_view3d_grid_scale(const struct Scene *scene, void ED_view3d_grid_steps(const struct Scene *scene, struct View3D *v3d, struct RegionView3D *rv3d, - float *r_grid_steps); + float r_grid_steps[8]); float ED_view3d_grid_view_scale(struct Scene *scene, struct View3D *v3d, struct ARegion *region, diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 81872428038..338b12f7985 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -186,17 +186,17 @@ enum { UI_RETURN_POPUP_OK = 1 << 5, }; -/* but->flag - general state flags. */ +/** #uiBut.flag general state flags. */ enum { - /** Warning, the first 6 flags are internal. */ - UI_BUT_ICON_SUBMENU = 1 << 6, - UI_BUT_ICON_PREVIEW = 1 << 7, + /* WARNING: the first 7 flags are internal (see #UI_SELECT definition). */ + UI_BUT_ICON_SUBMENU = 1 << 7, + UI_BUT_ICON_PREVIEW = 1 << 8, - UI_BUT_NODE_LINK = 1 << 8, - UI_BUT_NODE_ACTIVE = 1 << 9, - UI_BUT_DRAG_LOCK = 1 << 10, + UI_BUT_NODE_LINK = 1 << 9, + UI_BUT_NODE_ACTIVE = 1 << 10, + UI_BUT_DRAG_LOCK = 1 << 11, /** Grayed out and un-editable. */ - UI_BUT_DISABLED = 1 << 11, + UI_BUT_DISABLED = 1 << 12, UI_BUT_ANIMATED = 1 << 13, UI_BUT_ANIMATED_KEY = 1 << 14, @@ -2137,7 +2137,7 @@ void uiTemplateComponentMenu(uiLayout *layout, struct PointerRNA *ptr, const char *propname, const char *name); -void uiTemplateNodeSocket(uiLayout *layout, struct bContext *C, float *color); +void uiTemplateNodeSocket(uiLayout *layout, struct bContext *C, float color[4]); void uiTemplateCacheFile(uiLayout *layout, const struct bContext *C, struct PointerRNA *ptr, @@ -2405,9 +2405,12 @@ void uiItemS_ex(uiLayout *layout, float factor); void uiItemSpacer(uiLayout *layout); void uiItemPopoverPanel_ptr( - uiLayout *layout, struct bContext *C, struct PanelType *pt, const char *name, int icon); -void uiItemPopoverPanel( - uiLayout *layout, struct bContext *C, const char *panel_type, const char *name, int icon); + uiLayout *layout, const struct bContext *C, struct PanelType *pt, const char *name, int icon); +void uiItemPopoverPanel(uiLayout *layout, + const struct bContext *C, + const char *panel_type, + const char *name, + int icon); void uiItemPopoverPanelFromGroup(uiLayout *layout, struct bContext *C, int space_id, diff --git a/source/blender/editors/include/UI_resources.h b/source/blender/editors/include/UI_resources.h index 1820c2f133c..c99c7f681b3 100644 --- a/source/blender/editors/include/UI_resources.h +++ b/source/blender/editors/include/UI_resources.h @@ -451,7 +451,7 @@ int UI_ThemeMenuShadowWidth(void); /* only for buttons in theme editor! */ const unsigned char *UI_ThemeGetColorPtr(struct bTheme *btheme, int spacetype, int colorid); -void UI_make_axis_color(const unsigned char *src_col, unsigned char *dst_col, const char axis); +void UI_make_axis_color(const unsigned char src_col[3], unsigned char dst_col[3], const char axis); #ifdef __cplusplus } diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c index 90d604b3190..a31efefd99c 100644 --- a/source/blender/editors/interface/interface.c +++ b/source/blender/editors/interface/interface.c @@ -2327,6 +2327,14 @@ bool ui_but_is_float(const uiBut *but) return false; } +PropertyScaleType ui_but_scale_type(const uiBut *but) +{ + if (but->rnaprop) { + return RNA_property_ui_scale(but->rnaprop); + } + return PROP_SCALE_LINEAR; +} + bool ui_but_is_bool(const uiBut *but) { if (ELEM(but->type, diff --git a/source/blender/editors/interface/interface_eyedropper.c b/source/blender/editors/interface/interface_eyedropper.c index e4f502950ab..b52bfc81b7a 100644 --- a/source/blender/editors/interface/interface_eyedropper.c +++ b/source/blender/editors/interface/interface_eyedropper.c @@ -138,8 +138,8 @@ void eyedropper_draw_cursor_text_region(const struct bContext *C, } const int mval[2] = { - x - region->winrct.xmin, - y - region->winrct.ymin, + x - region->winrct.xmin, + y - region->winrct.ymin, }; eyedropper_draw_cursor_text_ex(mval[0], mval[1], name); diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index a5a5a69728e..5f98a501bec 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -126,6 +126,24 @@ #define UI_MAX_PASSWORD_STR 128 /** + * This is a lower limit on the soft minimum of the range. + * Usually the derived lower limit from the visible precision is higher, + * so this number is the backup minimum. + * + * Logarithmic scale does not work with a minimum value of zero, + * but we want to support it anyway. It is set to 0.5e... for + * correct rounding since when the tweaked value is lower than + * the log minimum (lower limit), it will snap to 0. + */ +#define UI_PROP_SCALE_LOG_MIN 0.5e-8f +/** + * This constant defines an offset for the precision change in + * snap rounding, when going to higher values. It is set to + * `0.5 - log10(3) = 0.03` to make the switch at `0.3` values. + */ +#define UI_PROP_SCALE_LOG_SNAP_OFFSET 0.03f + +/** * When #USER_CONTINUOUS_MOUSE is disabled or tablet input is used, * Use this as a maximum soft range for mapping cursor motion to the value. * Otherwise min/max of #FLT_MAX, #INT_MAX cause small adjustments to jump to large numbers. @@ -477,6 +495,7 @@ static bool ui_do_but_extra_operator_icon(bContext *C, static void ui_do_but_extra_operator_icons_mousemove(uiBut *but, uiHandleButtonData *data, const wmEvent *event); +static void ui_numedit_begin_set_values(uiBut *but, uiHandleButtonData *data); #ifdef USE_DRAG_MULTINUM static void ui_multibut_restore(bContext *C, uiHandleButtonData *data, uiBlock *block); @@ -1114,6 +1133,13 @@ static void ui_apply_but_TAB(bContext *C, uiBut *but, uiHandleButtonData *data) static void ui_apply_but_NUM(bContext *C, uiBut *but, uiHandleButtonData *data) { if (data->str) { + double value; + /* Check if the string value is a number and cancel if it's equal to the startvalue. */ + if (ui_but_string_eval_number(C, but, data->str, &value) && (value == data->startvalue)) { + data->cancel = true; + return; + } + if (ui_but_string_set(C, but, data->str)) { data->value = ui_but_value_get(but); } @@ -2320,16 +2346,16 @@ static int get_but_property_array_length(uiBut *but) } static void ui_but_set_float_array( - bContext *C, uiBut *but, uiHandleButtonData *data, float *values, int array_length) + bContext *C, uiBut *but, uiHandleButtonData *data, const float *values, const int values_len) { button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - for (int i = 0; i < array_length; i++) { + for (int i = 0; i < values_len; i++) { RNA_property_float_set_index(&but->rnapoin, but->rnaprop, i, values[i]); } if (data) { if (but->type == UI_BTYPE_UNITVEC) { - BLI_assert(array_length == 3); + BLI_assert(values_len == 3); copy_v3_v3(data->vec, values); } else { @@ -2340,56 +2366,39 @@ static void ui_but_set_float_array( button_activate_state(C, but, BUTTON_STATE_EXIT); } -static void float_array_to_string(float *values, - int array_length, +static void float_array_to_string(const float *values, + const int values_len, char *output, int output_len_max) { - /* to avoid buffer overflow attacks; numbers are quite arbitrary */ - BLI_assert(output_len_max > 15); - output_len_max -= 10; - - int current_index = 0; - output[current_index] = '['; - current_index++; - - for (int i = 0; i < array_length; i++) { - int length = BLI_snprintf( - output + current_index, output_len_max - current_index, "%f", values[i]); - current_index += length; - - if (i < array_length - 1) { - if (current_index < output_len_max) { - output[current_index + 0] = ','; - output[current_index + 1] = ' '; - current_index += 2; - } - } + const int values_end = values_len - 1; + int ofs = 0; + output[ofs++] = '['; + for (int i = 0; i < values_len; i++) { + ofs += BLI_snprintf_rlen( + output + ofs, output_len_max - ofs, (i != values_end) ? "%f, " : "%f]", values[i]); } - - output[current_index + 0] = ']'; - output[current_index + 1] = '\0'; } static void ui_but_copy_numeric_array(uiBut *but, char *output, int output_len_max) { - const int array_length = get_but_property_array_length(but); - float *values = alloca(array_length * sizeof(float)); + const int values_len = get_but_property_array_length(but); + float *values = alloca(values_len * sizeof(float)); RNA_property_float_get_array(&but->rnapoin, but->rnaprop, values); - float_array_to_string(values, array_length, output, output_len_max); + float_array_to_string(values, values_len, output, output_len_max); } -static bool parse_float_array(char *text, float *values, int expected_length) +static bool parse_float_array(char *text, float *values, int values_len_expected) { /* can parse max 4 floats for now */ - BLI_assert(0 <= expected_length && expected_length <= 4); + BLI_assert(0 <= values_len_expected && values_len_expected <= 4); float v[5]; - const int actual_length = sscanf( + const int values_len_actual = sscanf( text, "[%f, %f, %f, %f, %f]", &v[0], &v[1], &v[2], &v[3], &v[4]); - if (actual_length == expected_length) { - memcpy(values, v, sizeof(float) * expected_length); + if (values_len_actual == values_len_expected) { + memcpy(values, v, sizeof(float) * values_len_expected); return true; } return false; @@ -2400,16 +2409,16 @@ static void ui_but_paste_numeric_array(bContext *C, uiHandleButtonData *data, char *buf_paste) { - const int array_length = get_but_property_array_length(but); - if (array_length > 4) { + const int values_len = get_but_property_array_length(but); + if (values_len > 4) { /* not supported for now */ return; } - float *values = alloca(sizeof(float) * array_length); + float *values = alloca(sizeof(float) * values_len); - if (parse_float_array(buf_paste, values, array_length)) { - ui_but_set_float_array(C, but, data, values, array_length); + if (parse_float_array(buf_paste, values, values_len)) { + ui_but_set_float_array(C, but, data, values, values_len); } else { WM_report(RPT_ERROR, "Expected an array of numbers: [n, n, ...]"); @@ -3354,6 +3363,8 @@ static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data) if (is_num_but) { BLI_assert(data->is_str_dynamic == false); ui_but_convert_to_unit_alt_name(but, data->str, data->maxlen); + + ui_numedit_begin_set_values(but, data); } /* won't change from now on */ @@ -3890,6 +3901,14 @@ static void ui_do_but_textedit_select( /** \name Button Number Editing (various types) * \{ */ +static void ui_numedit_begin_set_values(uiBut *but, uiHandleButtonData *data) +{ + data->startvalue = ui_but_value_get(but); + data->origvalue = data->startvalue; + data->value = data->origvalue; + but->editval = &data->value; +} + static void ui_numedit_begin(uiBut *but, uiHandleButtonData *data) { if (but->type == UI_BTYPE_CURVE) { @@ -3915,19 +3934,21 @@ static void ui_numedit_begin(uiBut *but, uiHandleButtonData *data) but->editvec = data->vec; } else { - float softrange, softmin, softmax; + ui_numedit_begin_set_values(but, data); - data->startvalue = ui_but_value_get(but); - data->origvalue = data->startvalue; - data->value = data->origvalue; - but->editval = &data->value; + float softmin = but->softmin; + float softmax = but->softmax; + float softrange = softmax - softmin; + const PropertyScaleType scale_type = ui_but_scale_type(but); - softmin = but->softmin; - softmax = but->softmax; - softrange = softmax - softmin; + float log_min = (scale_type == PROP_SCALE_LOG) ? max_ff(softmin, UI_PROP_SCALE_LOG_MIN) : 0.0f; if ((but->type == UI_BTYPE_NUM) && (ui_but_is_cursor_warp(but) == false)) { uiButNumber *number_but = (uiButNumber *)but; + + if (scale_type == PROP_SCALE_LOG) { + log_min = max_ff(log_min, powf(10, -number_but->precision) * 0.5f); + } /* Use a minimum so we have a predictable range, * otherwise some float buttons get a large range. */ const float value_step_float_min = 0.1f; @@ -3976,7 +3997,31 @@ static void ui_numedit_begin(uiBut *but, uiHandleButtonData *data) } } - data->dragfstart = (softrange == 0.0f) ? 0.0f : ((float)data->value - softmin) / softrange; + if (softrange == 0.0f) { + data->dragfstart = 0.0f; + } + else { + switch (scale_type) { + case PROP_SCALE_LINEAR: { + data->dragfstart = ((float)data->value - softmin) / softrange; + break; + } + case PROP_SCALE_LOG: { + BLI_assert(log_min != 0.0f); + const float base = softmax / log_min; + data->dragfstart = logf((float)data->value / log_min) / logf(base); + break; + } + case PROP_SCALE_CUBIC: { + const float cubic_min = cube_f(softmin); + const float cubic_max = cube_f(softmax); + const float cubic_range = cubic_max - cubic_min; + const float f = ((float)data->value - softmin) * cubic_range / softrange + cubic_min; + data->dragfstart = (cbrtf(f) - softmin) / softrange; + break; + } + } + } data->dragf = data->dragfstart; data->drag_map_soft_min = softmin; @@ -4694,6 +4739,7 @@ static float ui_numedit_apply_snapf( /* pass */ } else { + const PropertyScaleType scale_type = ui_but_scale_type(but); float softrange = softmax - softmin; float fac = 1.0f; @@ -4731,31 +4777,30 @@ static float ui_numedit_apply_snapf( } } - if (snap == SNAP_ON) { - if (softrange < 2.10f) { - tempf = roundf(tempf * 10.0f) * 0.1f; - } - else if (softrange < 21.0f) { - tempf = roundf(tempf); - } - else { - tempf = roundf(tempf * 0.1f) * 10.0f; - } - } - else if (snap == SNAP_ON_SMALL) { - if (softrange < 2.10f) { - tempf = roundf(tempf * 100.0f) * 0.01f; - } - else if (softrange < 21.0f) { - tempf = roundf(tempf * 10.0f) * 0.1f; + BLI_assert(ELEM(snap, SNAP_ON, SNAP_ON_SMALL)); + switch (scale_type) { + case PROP_SCALE_LINEAR: + case PROP_SCALE_CUBIC: { + const float snap_fac = (snap == SNAP_ON_SMALL ? 0.1f : 1.0f); + if (softrange < 2.10f) { + tempf = roundf(tempf * 10.0f / snap_fac) * 0.1f * snap_fac; + } + else if (softrange < 21.0f) { + tempf = roundf(tempf / snap_fac) * snap_fac; + } + else { + tempf = roundf(tempf * 0.1f / snap_fac) * 10.0f * snap_fac; + } + break; } - else { - tempf = roundf(tempf); + case PROP_SCALE_LOG: { + const float snap_fac = powf(10.0f, + roundf(log10f(tempf) + UI_PROP_SCALE_LOG_SNAP_OFFSET) - + (snap == SNAP_ON_SMALL ? 2.0f : 1.0f)); + tempf = roundf(tempf / snap_fac) * snap_fac; + break; } } - else { - BLI_assert(0); - } if (fac != 1.0f) { tempf *= fac; @@ -4800,6 +4845,7 @@ static bool ui_numedit_but_NUM(uiButNumber *number_but, int lvalue, temp; bool changed = false; const bool is_float = ui_but_is_float(but); + const PropertyScaleType scale_type = ui_but_scale_type(but); /* prevent unwanted drag adjustments, test motion so modifier keys refresh. */ if ((is_motion || data->draglock) && (ui_but_dragedit_update_mval(data, mx) == false)) { @@ -4811,21 +4857,74 @@ static bool ui_numedit_but_NUM(uiButNumber *number_but, const float softmax = but->softmax; const float softrange = softmax - softmin; + const float log_min = (scale_type == PROP_SCALE_LOG) ? + max_ff(max_ff(softmin, UI_PROP_SCALE_LOG_MIN), + powf(10, -number_but->precision) * 0.5f) : + 0; + /* Mouse location isn't screen clamped to the screen so use a linear mapping * 2px == 1-int, or 1px == 1-ClickStep */ if (is_float) { fac *= 0.01f * number_but->step_size; - tempf = (float)data->startvalue + ((float)(mx - data->dragstartx) * fac); + switch (scale_type) { + case PROP_SCALE_LINEAR: { + tempf = (float)data->startvalue + (float)(mx - data->dragstartx) * fac; + break; + } + case PROP_SCALE_LOG: { + const float startvalue = max_ff((float)data->startvalue, log_min); + tempf = expf((float)(mx - data->dragstartx) * fac) * startvalue; + if (tempf <= log_min) { + tempf = 0.0f; + } + break; + } + case PROP_SCALE_CUBIC: { + tempf = cbrtf((float)data->startvalue) + (float)(mx - data->dragstartx) * fac; + tempf *= tempf * tempf; + break; + } + } + tempf = ui_numedit_apply_snapf(but, tempf, softmin, softmax, snap); #if 1 /* fake moving the click start, nicer for dragging back after passing the limit */ - if (tempf < softmin) { - data->dragstartx -= (softmin - tempf) / fac; - tempf = softmin; - } - else if (tempf > softmax) { - data->dragstartx += (tempf - softmax) / fac; - tempf = softmax; + switch (scale_type) { + case PROP_SCALE_LINEAR: { + if (tempf < softmin) { + data->dragstartx -= (softmin - tempf) / fac; + tempf = softmin; + } + else if (tempf > softmax) { + data->dragstartx -= (softmax - tempf) / fac; + tempf = softmax; + } + break; + } + case PROP_SCALE_LOG: { + if (tempf < log_min) { + data->dragstartx -= logf(log_min / (float)data->startvalue) / fac - + (float)(mx - data->dragstartx); + tempf = softmin; + } + else if (tempf > softmax) { + data->dragstartx -= logf(softmax / (float)data->startvalue) / fac - + (float)(mx - data->dragstartx); + tempf = softmax; + } + break; + } + case PROP_SCALE_CUBIC: { + if (tempf < softmin) { + data->dragstartx = mx - (int)((cbrtf(softmin) - cbrtf((float)data->startvalue)) / fac); + tempf = softmin; + } + else if (tempf > softmax) { + data->dragstartx = mx - (int)((cbrtf(softmax) - cbrtf((float)data->startvalue)) / fac); + tempf = softmax; + } + break; + } } #else CLAMP(tempf, softmin, softmax); @@ -4932,7 +5031,31 @@ static bool ui_numedit_but_NUM(uiButNumber *number_but, } data->draglastx = mx; - tempf = (softmin + data->dragf * softrange); + + switch (scale_type) { + case PROP_SCALE_LINEAR: { + tempf = (softmin + data->dragf * softrange); + break; + } + case PROP_SCALE_LOG: { + const float log_min = max_ff(max_ff(softmin, UI_PROP_SCALE_LOG_MIN), + powf(10.0f, -number_but->precision) * 0.5f); + const float base = softmax / log_min; + tempf = powf(base, data->dragf) * log_min; + if (tempf <= log_min) { + tempf = 0.0f; + } + break; + } + case PROP_SCALE_CUBIC: { + tempf = (softmin + data->dragf * softrange); + tempf *= tempf * tempf; + float cubic_min = softmin * softmin * softmin; + float cubic_max = softmax * softmax * softmax; + tempf = (tempf - cubic_min) / (cubic_max - cubic_min) * softrange + softmin; + break; + } + } if (!is_float) { temp = round_fl_to_int(tempf); @@ -5179,9 +5302,19 @@ static int ui_do_but_NUM( else { /* Float Value. */ if (but->drawflag & (UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT)) { + const PropertyScaleType scale_type = ui_but_scale_type(but); + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - const double value_step = (double)number_but->step_size * UI_PRECISION_FLOAT_SCALE; + double value_step; + if (scale_type == PROP_SCALE_LOG) { + value_step = powf(10.0f, + (roundf(log10f(data->value) + UI_PROP_SCALE_LOG_SNAP_OFFSET) - 1.0f) + + log10f(number_but->step_size)); + } + else { + value_step = (double)number_but->step_size * UI_PRECISION_FLOAT_SCALE; + } BLI_assert(value_step > 0.0f); const double value_test = (but->drawflag & UI_BUT_ACTIVE_LEFT) ? (double)max_ff(but->softmin, @@ -5229,6 +5362,8 @@ static bool ui_numedit_but_SLI(uiBut *but, return changed; } + const PropertyScaleType scale_type = ui_but_scale_type(but); + softmin = but->softmin; softmax = but->softmax; softrange = softmax - softmin; @@ -5270,7 +5405,24 @@ static bool ui_numedit_but_SLI(uiBut *but, #endif /* done correcting mouse */ - tempf = softmin + f * softrange; + switch (scale_type) { + case PROP_SCALE_LINEAR: { + tempf = softmin + f * softrange; + break; + } + case PROP_SCALE_LOG: { + tempf = powf(softmax / softmin, f) * softmin; + break; + } + case PROP_SCALE_CUBIC: { + const float cubicmin = cube_f(softmin); + const float cubicmax = cube_f(softmax); + const float cubicrange = cubicmax - cubicmin; + tempf = cube_f(softmin + f * softrange); + tempf = (tempf - cubicmin) / cubicrange * softrange + softmin; + break; + } + } temp = round_fl_to_int(tempf); if (snap) { @@ -5464,6 +5616,8 @@ static int ui_do_but_SLI( if (click) { if (click == 2) { + const PropertyScaleType scale_type = ui_but_scale_type(but); + /* nudge slider to the left or right */ float f, tempf, softmin, softmax, softrange; int temp; @@ -5488,14 +5642,20 @@ static int ui_do_but_SLI( f = (float)(mx - but->rect.xmin) / (BLI_rctf_size_x(&but->rect)); } - f = softmin + f * softrange; + if (scale_type == PROP_SCALE_LOG) { + f = powf(softmax / softmin, f) * softmin; + } + else { + f = softmin + f * softrange; + } if (!ui_but_is_float(but)) { + int value_step = 1; if (f < temp) { - temp--; + temp -= value_step; } else { - temp++; + temp += value_step; } if (temp >= softmin && temp <= softmax) { @@ -5506,14 +5666,23 @@ static int ui_do_but_SLI( } } else { - if (f < tempf) { - tempf -= 0.01f; - } - else { - tempf += 0.01f; - } - if (tempf >= softmin && tempf <= softmax) { + float value_step; + if (scale_type == PROP_SCALE_LOG) { + value_step = powf(10.0f, roundf(log10f(tempf) + UI_PROP_SCALE_LOG_SNAP_OFFSET) - 1.0f); + } + else { + value_step = 0.01f; + } + + if (f < tempf) { + tempf -= value_step; + } + else { + tempf += value_step; + } + + CLAMP(tempf, softmin, softmax); data->value = tempf; } else { diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index 4c96512b4f3..23856c41ceb 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -62,7 +62,7 @@ struct wmTimer; #define UI_MENU_PADDING (int)(0.2f * UI_UNIT_Y) #define UI_MENU_WIDTH_MIN (UI_UNIT_Y * 9) -/* some extra padding added to menus containing submenu icons */ +/** Some extra padding added to menus containing sub-menu icons. */ #define UI_MENU_SUBMENU_PADDING (6 * UI_DPI_FAC) /* menu scrolling */ @@ -74,28 +74,31 @@ struct wmTimer; #define UI_PANEL_MINX 100 #define UI_PANEL_MINY 70 -/* popover width (multiplied by 'U.widget_unit') */ +/** Popover width (multiplied by #U.widget_unit) */ #define UI_POPOVER_WIDTH_UNITS 10 -/* uiBut->flag */ +/** #uiBut.flag */ enum { - UI_SELECT = (1 << 0), /* use when the button is pressed */ - UI_SCROLLED = (1 << 1), /* temp hidden, scrolled away */ + /** Use when the button is pressed. */ + UI_SELECT = (1 << 0), + /** Temporarily hidden (scrolled out of the view). */ + UI_SCROLLED = (1 << 1), UI_ACTIVE = (1 << 2), UI_HAS_ICON = (1 << 3), UI_HIDDEN = (1 << 4), - UI_SELECT_DRAW = (1 << 5), /* Display selected, doesn't impact interaction. */ + /** Display selected, doesn't impact interaction. */ + UI_SELECT_DRAW = (1 << 5), /** Property search filter is active and the button does not match. */ - UI_SEARCH_FILTER_NO_MATCH = (1 << 12), - /* warn: rest of uiBut->flag in UI_interface.h */ + UI_SEARCH_FILTER_NO_MATCH = (1 << 6), + /* WARNING: rest of #uiBut.flag in UI_interface.h */ }; -/* uiBut->dragflag */ +/** #uiBut.dragflag */ enum { UI_BUT_DRAGPOIN_FREE = (1 << 0), }; -/* but->pie_dir */ +/** #uiBut.pie_dir */ typedef enum RadialDirection { UI_RADIAL_NONE = -1, UI_RADIAL_N = 0, @@ -126,13 +129,13 @@ extern const short ui_radial_dir_to_angle[8]; #define UI_BITBUT_ROW(min, max) \ (((max) >= 31 ? 0xFFFFFFFF : (1 << ((max) + 1)) - 1) - ((min) ? ((1 << (min)) - 1) : 0)) -/* split numbuts by ':' and align l/r */ +/** Split number-buttons by ':' and align left/right. */ #define USE_NUMBUTS_LR_ALIGN -/* Use new 'align' computation code. */ +/** Use new 'align' computation code. */ #define USE_UIBUT_SPATIAL_ALIGN -/* PieMenuData->flags */ +/** #PieMenuData.flags */ enum { /** pie menu item collision is detected at 90 degrees */ UI_PIE_DEGREES_RANGE_LARGE = (1 << 0), @@ -152,13 +155,13 @@ enum { #define PIE_CLICK_THRESHOLD_SQ 50.0f -/* max amount of items a radial menu (pie menu) can contain */ +/** The maximum number of items a radial menu (pie menu) can contain. */ #define PIE_MAX_ITEMS 8 struct uiBut { struct uiBut *next, *prev; - /* Pointer back to the layout item holding this button. */ + /** Pointer back to the layout item holding this button. */ uiLayout *layout; int flag, drawflag; eButType type; @@ -235,10 +238,10 @@ struct uiBut { short modifier_key; short iconadd; - /* UI_BTYPE_BLOCK data */ + /** #UI_BTYPE_BLOCK data */ uiBlockCreateFunc block_create_func; - /* UI_BTYPE_PULLDOWN/UI_BTYPE_MENU data */ + /** #UI_BTYPE_PULLDOWN / #UI_BTYPE_MENU data */ uiMenuCreateFunc menu_create_func; uiMenuStepFunc menu_step_func; @@ -252,9 +255,11 @@ struct uiBut { struct wmOperatorType *optype; struct PointerRNA *opptr; short opcontext; - uchar menu_key; /* 'a'-'z', always lower case */ - ListBase extra_op_icons; /* uiButExtraOpIcon */ + /** When non-zero, this is the key used to activate a menu items (`a-z` always lower case). */ + uchar menu_key; + + ListBase extra_op_icons; /** #uiButExtraOpIcon */ /* Draggable data, type is WM_DRAG_... */ char dragtype; @@ -263,10 +268,10 @@ struct uiBut { struct ImBuf *imb; float imb_scale; - /* active button data */ + /** Active button data (set when the user is hovering or interacting with a button). */ struct uiHandleButtonData *active; - /* Custom button data. */ + /** Custom button data (borrowed, not owned). */ void *custom_data; char *editstr; @@ -429,7 +434,7 @@ struct PieMenuData { float alphafac; }; -/* uiBlock.content_hints */ +/** #uiBlock.content_hints */ enum eBlockContentHints { /** In a menu block, if there is a single sub-menu button, we add some * padding to the right to put nicely aligned triangle icons there. */ @@ -463,7 +468,8 @@ struct uiBlock { struct Panel *panel; uiBlock *oldblock; - ListBase butstore; /* UI_butstore_* runtime function */ + /** Used for `UI_butstore_*` runtime function. */ + ListBase butstore; ListBase button_groups; /* #uiButtonGroup. */ @@ -479,7 +485,8 @@ struct uiBlock { rctf rect; float aspect; - uint puphash; /* popup menu hash for memory */ + /** Unique hash used to implement popup menu memory. */ + uint puphash; uiButHandleFunc func; void *func_arg1; @@ -494,10 +501,10 @@ struct uiBlock { uiBlockHandleFunc handle_func; void *handle_func_arg; - /* custom extra handling */ + /** Custom extra event handling. */ int (*block_event_func)(const struct bContext *C, struct uiBlock *, const struct wmEvent *); - /* extra draw function for custom blocks */ + /** Custom extra draw function for custom blocks. */ void (*drawextra)(const struct bContext *C, void *idv, void *arg1, void *arg2, rcti *rect); void *drawextra_arg1; void *drawextra_arg2; @@ -507,7 +514,7 @@ struct uiBlock { /** Hints about the buttons of this block. Used to avoid iterating over * buttons to find out if some criteria is met by any. Instead, check this * criteria when adding the button and set a flag here if it's met. */ - short content_hints; /* eBlockContentHints */ + short content_hints; /* #eBlockContentHints */ char direction; /** UI_BLOCK_THEME_STYLE_* */ @@ -521,11 +528,11 @@ struct uiBlock { const char *lockstr; bool lock; - /** to keep blocks while drawing and free them afterwards */ + /** To keep blocks while drawing and free them afterwards. */ bool active; - /** to avoid tooltip after click */ + /** To avoid tool-tip after click. */ bool tooltipdisabled; - /** UI_block_end done? */ + /** True when #UI_block_end has been called. */ bool endblock; /** for doing delayed */ @@ -535,12 +542,12 @@ struct uiBlock { /** for doing delayed */ int bounds, minbounds; - /** pull-downs, to detect outside, can differ per case how it is created. */ + /** Pull-downs, to detect outside, can differ per case how it is created. */ rctf safety; - /** uiSafetyRct list */ + /** #uiSafetyRct list */ ListBase saferct; - uiPopupBlockHandle *handle; /* handle */ + uiPopupBlockHandle *handle; /** use so presets can find the operator, * across menus and from nested popups which fail for operator context. */ @@ -555,10 +562,12 @@ struct uiBlock { /** \note only accessed by color picker templates. */ ColorPickerData color_pickers; - bool is_color_gamma_picker; /* Block for color picker with gamma baked in. */ + /** Block for color picker with gamma baked in. */ + bool is_color_gamma_picker; - /** display device name used to display this block, - * used by color widgets to transform colors from/to scene linear + /** + * Display device name used to display this block, + * used by color widgets to transform colors from/to scene linear. */ char display_device[64]; @@ -651,6 +660,7 @@ bool ui_but_context_poll_operator(struct bContext *C, struct wmOperatorType *ot, extern void ui_but_update(uiBut *but); extern void ui_but_update_edited(uiBut *but); +extern PropertyScaleType ui_but_scale_type(const uiBut *but) ATTR_WARN_UNUSED_RESULT; extern bool ui_but_is_float(const uiBut *but) ATTR_WARN_UNUSED_RESULT; extern bool ui_but_is_bool(const uiBut *but) ATTR_WARN_UNUSED_RESULT; extern bool ui_but_is_unit(const uiBut *but) ATTR_WARN_UNUSED_RESULT; @@ -671,9 +681,9 @@ void ui_block_cm_to_display_space_v3(uiBlock *block, float pixel[3]); /* interface_regions.c */ struct uiKeyNavLock { - /* Set when we're using key-input. */ + /** Set when we're using keyboard-input. */ bool is_keynav; - /* only used to check if we've moved the cursor */ + /** Only used to check if we've moved the cursor. */ int event_xy[2]; }; @@ -689,7 +699,7 @@ struct uiPopupBlockCreate { int event_xy[2]; - /* when popup is initialized from a button */ + /** Set when popup is initialized from a button. */ struct ARegion *butregion; uiBut *but; }; @@ -698,7 +708,7 @@ struct uiPopupBlockHandle { /* internal */ struct ARegion *region; - /* use only for 'UI_BLOCK_MOVEMOUSE_QUIT' popups */ + /** Use only for #UI_BLOCK_MOVEMOUSE_QUIT popups. */ float towards_xy[2]; double towardstime; bool dotowards; @@ -708,9 +718,9 @@ struct uiPopupBlockHandle { void (*cancel_func)(struct bContext *C, void *arg); void *popup_arg; - /* store data for refreshing popups */ + /** Store data for refreshing popups. */ struct uiPopupBlockCreate popup_create_vars; - /* true if we can re-create the popup using 'popup_create_vars' */ + /** True if we can re-create the popup using #uiPopupBlockHandle.popup_create_vars. */ bool can_refresh; bool refresh; @@ -730,7 +740,7 @@ struct uiPopupBlockHandle { int retvalue; float retvec[4]; - /* menu direction */ + /** Menu direction. */ int direction; /* Previous values so we don't resize or reposition on refresh. */ @@ -924,9 +934,7 @@ extern void ui_but_execute_end(struct bContext *C, void *active_back); extern void ui_but_active_free(const struct bContext *C, uiBut *but); extern int ui_but_menu_direction(uiBut *but); -extern void ui_but_text_password_hide(char password_str[UI_MAX_DRAW_STR], - uiBut *but, - const bool restore); +extern void ui_but_text_password_hide(char password_str[128], uiBut *but, const bool restore); extern uiBut *ui_but_find_select_in_enum(uiBut *but, int direction); bool ui_but_is_editing(const uiBut *but); float ui_block_calc_pie_segment(struct uiBlock *block, const float event_xy[2]); diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c index cabd98902a6..8f2871ce18b 100644 --- a/source/blender/editors/interface/interface_layout.c +++ b/source/blender/editors/interface/interface_layout.c @@ -3038,7 +3038,7 @@ void uiItemDecoratorR(uiLayout *layout, PointerRNA *ptr, const char *propname, i /* popover */ void uiItemPopoverPanel_ptr( - uiLayout *layout, bContext *C, PanelType *pt, const char *name, int icon) + uiLayout *layout, const bContext *C, PanelType *pt, const char *name, int icon) { if (!name) { name = CTX_IFACE_(pt->translation_context, pt->label); @@ -3067,7 +3067,7 @@ void uiItemPopoverPanel_ptr( } void uiItemPopoverPanel( - uiLayout *layout, bContext *C, const char *panel_type, const char *name, int icon) + uiLayout *layout, const bContext *C, const char *panel_type, const char *name, int icon) { PanelType *pt = WM_paneltype_find(panel_type, true); if (pt == NULL) { diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c index 1fb7a931c08..0cf3ad59903 100644 --- a/source/blender/editors/interface/interface_ops.c +++ b/source/blender/editors/interface/interface_ops.c @@ -1495,117 +1495,115 @@ static void edittranslation_find_po_file(const char *root, static int edittranslation_exec(bContext *C, wmOperator *op) { uiBut *but = UI_context_active_but_get(C); - int ret = OPERATOR_CANCELLED; - - if (but) { - wmOperatorType *ot; - PointerRNA ptr; - char popath[FILE_MAX]; - const char *root = U.i18ndir; - const char *uilng = BLT_lang_get(); - - uiStringInfo but_label = {BUT_GET_LABEL, NULL}; - uiStringInfo rna_label = {BUT_GET_RNA_LABEL, NULL}; - uiStringInfo enum_label = {BUT_GET_RNAENUM_LABEL, NULL}; - uiStringInfo but_tip = {BUT_GET_TIP, NULL}; - uiStringInfo rna_tip = {BUT_GET_RNA_TIP, NULL}; - uiStringInfo enum_tip = {BUT_GET_RNAENUM_TIP, NULL}; - uiStringInfo rna_struct = {BUT_GET_RNASTRUCT_IDENTIFIER, NULL}; - uiStringInfo rna_prop = {BUT_GET_RNAPROP_IDENTIFIER, NULL}; - uiStringInfo rna_enum = {BUT_GET_RNAENUM_IDENTIFIER, NULL}; - uiStringInfo rna_ctxt = {BUT_GET_RNA_LABEL_CONTEXT, NULL}; - - if (!BLI_is_dir(root)) { - BKE_report(op->reports, - RPT_ERROR, - "Please set your Preferences' 'Translation Branches " - "Directory' path to a valid directory"); - return OPERATOR_CANCELLED; - } - ot = WM_operatortype_find(EDTSRC_I18N_OP_NAME, 0); - if (ot == NULL) { - BKE_reportf(op->reports, - RPT_ERROR, - "Could not find operator '%s'! Please enable ui_translate add-on " - "in the User Preferences", - EDTSRC_I18N_OP_NAME); - return OPERATOR_CANCELLED; - } - /* Try to find a valid po file for current language... */ - edittranslation_find_po_file(root, uilng, popath, FILE_MAX); - /* printf("po path: %s\n", popath); */ - if (popath[0] == '\0') { - BKE_reportf( - op->reports, RPT_ERROR, "No valid po found for language '%s' under %s", uilng, root); - return OPERATOR_CANCELLED; - } - - UI_but_string_info_get(C, - but, - &but_label, - &rna_label, - &enum_label, - &but_tip, - &rna_tip, - &enum_tip, - &rna_struct, - &rna_prop, - &rna_enum, - &rna_ctxt, - NULL); - - WM_operator_properties_create_ptr(&ptr, ot); - RNA_string_set(&ptr, "lang", uilng); - RNA_string_set(&ptr, "po_file", popath); - RNA_string_set(&ptr, "but_label", but_label.strinfo); - RNA_string_set(&ptr, "rna_label", rna_label.strinfo); - RNA_string_set(&ptr, "enum_label", enum_label.strinfo); - RNA_string_set(&ptr, "but_tip", but_tip.strinfo); - RNA_string_set(&ptr, "rna_tip", rna_tip.strinfo); - RNA_string_set(&ptr, "enum_tip", enum_tip.strinfo); - RNA_string_set(&ptr, "rna_struct", rna_struct.strinfo); - RNA_string_set(&ptr, "rna_prop", rna_prop.strinfo); - RNA_string_set(&ptr, "rna_enum", rna_enum.strinfo); - RNA_string_set(&ptr, "rna_ctxt", rna_ctxt.strinfo); - ret = WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr); - - /* Clean up */ - if (but_label.strinfo) { - MEM_freeN(but_label.strinfo); - } - if (rna_label.strinfo) { - MEM_freeN(rna_label.strinfo); - } - if (enum_label.strinfo) { - MEM_freeN(enum_label.strinfo); - } - if (but_tip.strinfo) { - MEM_freeN(but_tip.strinfo); - } - if (rna_tip.strinfo) { - MEM_freeN(rna_tip.strinfo); - } - if (enum_tip.strinfo) { - MEM_freeN(enum_tip.strinfo); - } - if (rna_struct.strinfo) { - MEM_freeN(rna_struct.strinfo); - } - if (rna_prop.strinfo) { - MEM_freeN(rna_prop.strinfo); - } - if (rna_enum.strinfo) { - MEM_freeN(rna_enum.strinfo); - } - if (rna_ctxt.strinfo) { - MEM_freeN(rna_ctxt.strinfo); - } + if (but == NULL) { + BKE_report(op->reports, RPT_ERROR, "Active button not found"); + return OPERATOR_CANCELLED; + } - return ret; + wmOperatorType *ot; + PointerRNA ptr; + char popath[FILE_MAX]; + const char *root = U.i18ndir; + const char *uilng = BLT_lang_get(); + + uiStringInfo but_label = {BUT_GET_LABEL, NULL}; + uiStringInfo rna_label = {BUT_GET_RNA_LABEL, NULL}; + uiStringInfo enum_label = {BUT_GET_RNAENUM_LABEL, NULL}; + uiStringInfo but_tip = {BUT_GET_TIP, NULL}; + uiStringInfo rna_tip = {BUT_GET_RNA_TIP, NULL}; + uiStringInfo enum_tip = {BUT_GET_RNAENUM_TIP, NULL}; + uiStringInfo rna_struct = {BUT_GET_RNASTRUCT_IDENTIFIER, NULL}; + uiStringInfo rna_prop = {BUT_GET_RNAPROP_IDENTIFIER, NULL}; + uiStringInfo rna_enum = {BUT_GET_RNAENUM_IDENTIFIER, NULL}; + uiStringInfo rna_ctxt = {BUT_GET_RNA_LABEL_CONTEXT, NULL}; + + if (!BLI_is_dir(root)) { + BKE_report(op->reports, + RPT_ERROR, + "Please set your Preferences' 'Translation Branches " + "Directory' path to a valid directory"); + return OPERATOR_CANCELLED; + } + ot = WM_operatortype_find(EDTSRC_I18N_OP_NAME, 0); + if (ot == NULL) { + BKE_reportf(op->reports, + RPT_ERROR, + "Could not find operator '%s'! Please enable ui_translate add-on " + "in the User Preferences", + EDTSRC_I18N_OP_NAME); + return OPERATOR_CANCELLED; + } + /* Try to find a valid po file for current language... */ + edittranslation_find_po_file(root, uilng, popath, FILE_MAX); + /* printf("po path: %s\n", popath); */ + if (popath[0] == '\0') { + BKE_reportf( + op->reports, RPT_ERROR, "No valid po found for language '%s' under %s", uilng, root); + return OPERATOR_CANCELLED; } - BKE_report(op->reports, RPT_ERROR, "Active button not found"); - return OPERATOR_CANCELLED; + UI_but_string_info_get(C, + but, + &but_label, + &rna_label, + &enum_label, + &but_tip, + &rna_tip, + &enum_tip, + &rna_struct, + &rna_prop, + &rna_enum, + &rna_ctxt, + NULL); + + WM_operator_properties_create_ptr(&ptr, ot); + RNA_string_set(&ptr, "lang", uilng); + RNA_string_set(&ptr, "po_file", popath); + RNA_string_set(&ptr, "but_label", but_label.strinfo); + RNA_string_set(&ptr, "rna_label", rna_label.strinfo); + RNA_string_set(&ptr, "enum_label", enum_label.strinfo); + RNA_string_set(&ptr, "but_tip", but_tip.strinfo); + RNA_string_set(&ptr, "rna_tip", rna_tip.strinfo); + RNA_string_set(&ptr, "enum_tip", enum_tip.strinfo); + RNA_string_set(&ptr, "rna_struct", rna_struct.strinfo); + RNA_string_set(&ptr, "rna_prop", rna_prop.strinfo); + RNA_string_set(&ptr, "rna_enum", rna_enum.strinfo); + RNA_string_set(&ptr, "rna_ctxt", rna_ctxt.strinfo); + const int ret = WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr); + + /* Clean up */ + if (but_label.strinfo) { + MEM_freeN(but_label.strinfo); + } + if (rna_label.strinfo) { + MEM_freeN(rna_label.strinfo); + } + if (enum_label.strinfo) { + MEM_freeN(enum_label.strinfo); + } + if (but_tip.strinfo) { + MEM_freeN(but_tip.strinfo); + } + if (rna_tip.strinfo) { + MEM_freeN(rna_tip.strinfo); + } + if (enum_tip.strinfo) { + MEM_freeN(enum_tip.strinfo); + } + if (rna_struct.strinfo) { + MEM_freeN(rna_struct.strinfo); + } + if (rna_prop.strinfo) { + MEM_freeN(rna_prop.strinfo); + } + if (rna_enum.strinfo) { + MEM_freeN(rna_enum.strinfo); + } + if (rna_ctxt.strinfo) { + MEM_freeN(rna_ctxt.strinfo); + } + + return ret; } static void UI_OT_edittranslation_init(wmOperatorType *ot) diff --git a/source/blender/editors/interface/interface_region_menu_popup.c b/source/blender/editors/interface/interface_region_menu_popup.c index f234f0fbbf5..58a74a3473e 100644 --- a/source/blender/editors/interface/interface_region_menu_popup.c +++ b/source/blender/editors/interface/interface_region_menu_popup.c @@ -86,6 +86,17 @@ int ui_but_menu_step(uiBut *but, int direction) return 0; } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Popup Menu Memory + * + * Support menu-memory, a feature that positions the cursor + * over the previously used menu item. + * + * \note This is stored for each unique menu title. + * \{ */ + static uint ui_popup_string_hash(const char *str, const bool use_sep) { /* sometimes button contains hotkey, sometimes not, strip for proper compare */ @@ -392,7 +403,7 @@ uiPopupMenu *UI_popup_menu_begin_ex(bContext *C, pup->layout = UI_block_layout( pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style); - /* note, this intentionally differs from the menu & submenu default because many operators + /* note, this intentionally differs from the menu & sub-menu default because many operators * use popups like this to select one of their options - * where having invoke doesn't make sense */ uiLayoutSetOperatorContext(pup->layout, WM_OP_EXEC_REGION_WIN); diff --git a/source/blender/editors/interface/interface_region_tooltip.c b/source/blender/editors/interface/interface_region_tooltip.c index b11a727b173..accfb78ab94 100644 --- a/source/blender/editors/interface/interface_region_tooltip.c +++ b/source/blender/editors/interface/interface_region_tooltip.c @@ -947,12 +947,13 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but) /* button is disabled, we may be able to tell user why */ if (but->flag & UI_BUT_DISABLED) { const char *disabled_msg = NULL; + bool disabled_msg_free = false; /* if operator poll check failed, it can give pretty precise info why */ if (but->optype) { - CTX_wm_operator_poll_msg_set(C, NULL); + CTX_wm_operator_poll_msg_clear(C); WM_operator_poll_context(C, but->optype, but->opcontext); - disabled_msg = CTX_wm_operator_poll_msg_get(C); + disabled_msg = CTX_wm_operator_poll_msg_get(C, &disabled_msg_free); } /* alternatively, buttons can store some reasoning too */ else if (but->disabled_info) { @@ -967,6 +968,9 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but) }); field->text = BLI_sprintfN(TIP_("Disabled: %s"), disabled_msg); } + if (disabled_msg_free) { + MEM_freeN((void *)disabled_msg); + } } if ((U.flag & USER_TOOLTIPS_PYTHON) && !but->optype && rna_struct.strinfo) { diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index 0484565ee2e..3990ad68c4d 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -653,7 +653,7 @@ static void template_id_cb(bContext *C, void *arg_litem, void *arg_event) /* Only remap that specific ID usage to overriding local data-block. */ ID *override_id = BKE_lib_override_library_create_from_id(bmain, id, false); if (override_id != NULL) { - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); if (GS(override_id->name) == ID_OB) { Scene *scene = CTX_data_scene(C); @@ -672,7 +672,7 @@ static void template_id_cb(bContext *C, void *arg_litem, void *arg_event) } else { if (BKE_lib_id_make_local(bmain, id, false, 0)) { - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); /* reassign to get get proper updates/notifiers */ idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); @@ -1078,7 +1078,7 @@ static void template_ID(const bContext *C, char numstr[32]; short numstr_len; - numstr_len = BLI_snprintf(numstr, sizeof(numstr), "%d", ID_REAL_USERS(id)); + numstr_len = BLI_snprintf_rlen(numstr, sizeof(numstr), "%d", ID_REAL_USERS(id)); but = uiDefBut( block, @@ -7195,7 +7195,7 @@ void uiTemplateComponentMenu(uiLayout *layout, /** \name Node Socket Icon Template * \{ */ -void uiTemplateNodeSocket(uiLayout *layout, bContext *UNUSED(C), float *color) +void uiTemplateNodeSocket(uiLayout *layout, bContext *UNUSED(C), float color[4]) { uiBlock *block = uiLayoutGetBlock(layout); UI_block_align_begin(block); diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c index c9c1f56dc98..d2e6165a20f 100644 --- a/source/blender/editors/interface/interface_widgets.c +++ b/source/blender/editors/interface/interface_widgets.c @@ -3752,12 +3752,35 @@ static void widget_numslider( float factor, factor_ui; float factor_discard = 1.0f; /* No discard. */ const float value = (float)ui_but_value_get(but); - - if (but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PERCENTAGE)) { - factor = value / but->softmax; - } - else { - factor = (value - but->softmin) / (but->softmax - but->softmin); + const float softmin = but->softmin; + const float softmax = but->softmax; + const float softrange = softmax - softmin; + const PropertyScaleType scale_type = ui_but_scale_type(but); + + switch (scale_type) { + case PROP_SCALE_LINEAR: { + if (but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PERCENTAGE)) { + factor = value / softmax; + } + else { + factor = (value - softmin) / softrange; + } + break; + } + case PROP_SCALE_LOG: { + const float logmin = fmaxf(softmin, 0.5e-8f); + const float base = softmax / logmin; + factor = logf(value / logmin) / logf(base); + break; + } + case PROP_SCALE_CUBIC: { + const float cubicmin = cube_f(softmin); + const float cubicmax = cube_f(softmax); + const float cubicrange = cubicmax - cubicmin; + const float f = (value - softmin) * cubicrange / softrange + cubicmin; + factor = (cbrtf(f) - softmin) / softrange; + break; + } } const float width = (float)BLI_rcti_size_x(rect); diff --git a/source/blender/editors/interface/view2d_ops.c b/source/blender/editors/interface/view2d_ops.c index 7453cd17868..40c510af7e5 100644 --- a/source/blender/editors/interface/view2d_ops.c +++ b/source/blender/editors/interface/view2d_ops.c @@ -757,7 +757,7 @@ typedef struct v2dViewZoomData { } v2dViewZoomData; /** - * Clamp by convention rather then locking flags, + * Clamp by convention rather than locking flags, * for ndof and +/- keys */ static void view_zoom_axis_lock_defaults(bContext *C, bool r_do_zoom_xy[2]) diff --git a/source/blender/editors/io/io_alembic.c b/source/blender/editors/io/io_alembic.c index 1f1165a464b..28838d677f0 100644 --- a/source/blender/editors/io/io_alembic.c +++ b/source/blender/editors/io/io_alembic.c @@ -121,6 +121,7 @@ static int wm_alembic_export_exec(bContext *C, wmOperator *op) .uvs = RNA_boolean_get(op->ptr, "uvs"), .normals = RNA_boolean_get(op->ptr, "normals"), .vcolors = RNA_boolean_get(op->ptr, "vcolors"), + .orcos = RNA_boolean_get(op->ptr, "orcos"), .apply_subdiv = RNA_boolean_get(op->ptr, "apply_subdiv"), .curves_as_mesh = RNA_boolean_get(op->ptr, "curves_as_mesh"), .flatten_hierarchy = RNA_boolean_get(op->ptr, "flatten"), @@ -210,6 +211,7 @@ static void ui_alembic_export_settings(uiLayout *layout, PointerRNA *imfptr) uiItemR(col, imfptr, "normals", 0, NULL, ICON_NONE); uiItemR(col, imfptr, "vcolors", 0, NULL, ICON_NONE); + uiItemR(col, imfptr, "orcos", 0, NULL, ICON_NONE); uiItemR(col, imfptr, "face_sets", 0, NULL, ICON_NONE); uiItemR(col, imfptr, "curves_as_mesh", 0, NULL, ICON_NONE); @@ -240,22 +242,17 @@ static void ui_alembic_export_settings(uiLayout *layout, PointerRNA *imfptr) static void wm_alembic_export_draw(bContext *C, wmOperator *op) { - wmWindowManager *wm = CTX_wm_manager(C); - PointerRNA ptr; - - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); - /* Conveniently set start and end frame to match the scene's frame range. */ Scene *scene = CTX_data_scene(C); - if (scene != NULL && RNA_boolean_get(&ptr, "init_scene_frame_range")) { - RNA_int_set(&ptr, "start", SFRA); - RNA_int_set(&ptr, "end", EFRA); + if (scene != NULL && RNA_boolean_get(op->ptr, "init_scene_frame_range")) { + RNA_int_set(op->ptr, "start", SFRA); + RNA_int_set(op->ptr, "end", EFRA); - RNA_boolean_set(&ptr, "init_scene_frame_range", false); + RNA_boolean_set(op->ptr, "init_scene_frame_range", false); } - ui_alembic_export_settings(op->layout, &ptr); + ui_alembic_export_settings(op->layout, op->ptr); } static bool wm_alembic_export_check(bContext *UNUSED(C), wmOperator *op) @@ -383,6 +380,12 @@ void WM_OT_alembic_export(wmOperatorType *ot) RNA_def_boolean(ot->srna, "vcolors", 0, "Vertex Colors", "Export vertex colors"); + RNA_def_boolean(ot->srna, + "orcos", + true, + "Generated Coordinates", + "Export undeformed mesh vertex coordinates"); + RNA_def_boolean( ot->srna, "face_sets", 0, "Face Sets", "Export per face shading group assignments"); @@ -594,13 +597,9 @@ static void ui_alembic_import_settings(uiLayout *layout, PointerRNA *imfptr) uiItemR(col, imfptr, "validate_meshes", 0, NULL, ICON_NONE); } -static void wm_alembic_import_draw(bContext *C, wmOperator *op) +static void wm_alembic_import_draw(bContext *UNUSED(C), wmOperator *op) { - wmWindowManager *wm = CTX_wm_manager(C); - PointerRNA ptr; - - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); - ui_alembic_import_settings(op->layout, &ptr); + ui_alembic_import_settings(op->layout, op->ptr); } /* op->invoke, opens fileselect if path property not set, otherwise executes */ diff --git a/source/blender/editors/io/io_collada.c b/source/blender/editors/io/io_collada.c index c8e3e1814fc..859c12d7e52 100644 --- a/source/blender/editors/io/io_collada.c +++ b/source/blender/editors/io/io_collada.c @@ -400,13 +400,9 @@ static void uiCollada_exportSettings(uiLayout *layout, PointerRNA *imfptr) } } -static void wm_collada_export_draw(bContext *C, wmOperator *op) +static void wm_collada_export_draw(bContext *UNUSED(C), wmOperator *op) { - wmWindowManager *wm = CTX_wm_manager(C); - PointerRNA ptr; - - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); - uiCollada_exportSettings(op->layout, &ptr); + uiCollada_exportSettings(op->layout, op->ptr); } static bool wm_collada_export_check(bContext *UNUSED(C), wmOperator *op) @@ -798,13 +794,9 @@ static void uiCollada_importSettings(uiLayout *layout, PointerRNA *imfptr) uiItemR(box, imfptr, "keep_bind_info", 0, NULL, ICON_NONE); } -static void wm_collada_import_draw(bContext *C, wmOperator *op) +static void wm_collada_import_draw(bContext *UNUSED(C), wmOperator *op) { - wmWindowManager *wm = CTX_wm_manager(C); - PointerRNA ptr; - - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); - uiCollada_importSettings(op->layout, &ptr); + uiCollada_importSettings(op->layout, op->ptr); } void WM_OT_collada_import(wmOperatorType *ot) diff --git a/source/blender/editors/io/io_gpencil_export.c b/source/blender/editors/io/io_gpencil_export.c index 7b4b59902f9..b49be324372 100644 --- a/source/blender/editors/io/io_gpencil_export.c +++ b/source/blender/editors/io/io_gpencil_export.c @@ -215,14 +215,9 @@ static void ui_gpencil_export_svg_settings(uiLayout *layout, PointerRNA *imfptr) uiItemR(col, imfptr, "use_clip_camera", 0, NULL, ICON_NONE); } -static void wm_gpencil_export_svg_draw(bContext *C, wmOperator *op) +static void wm_gpencil_export_svg_draw(bContext *UNUSED(C), wmOperator *op) { - wmWindowManager *wm = CTX_wm_manager(C); - PointerRNA ptr; - - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); - - ui_gpencil_export_svg_settings(op->layout, &ptr); + ui_gpencil_export_svg_settings(op->layout, op->ptr); } static bool wm_gpencil_export_svg_poll(bContext *C) @@ -378,14 +373,9 @@ static void ui_gpencil_export_pdf_settings(uiLayout *layout, PointerRNA *imfptr) uiItemR(sub, imfptr, "use_normalized_thickness", 0, NULL, ICON_NONE); } -static void wm_gpencil_export_pdf_draw(bContext *C, wmOperator *op) +static void wm_gpencil_export_pdf_draw(bContext *UNUSED(C), wmOperator *op) { - wmWindowManager *wm = CTX_wm_manager(C); - PointerRNA ptr; - - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); - - ui_gpencil_export_pdf_settings(op->layout, &ptr); + ui_gpencil_export_pdf_settings(op->layout, op->ptr); } static bool wm_gpencil_export_pdf_poll(bContext *C) diff --git a/source/blender/editors/io/io_gpencil_import.c b/source/blender/editors/io/io_gpencil_import.c index e4fabc0c5de..a9911f1cef2 100644 --- a/source/blender/editors/io/io_gpencil_import.c +++ b/source/blender/editors/io/io_gpencil_import.c @@ -136,13 +136,9 @@ static void ui_gpencil_import_svg_settings(uiLayout *layout, PointerRNA *imfptr) uiItemR(col, imfptr, "scale", 0, NULL, ICON_NONE); } -static void wm_gpencil_import_svg_draw(bContext *C, wmOperator *op) +static void wm_gpencil_import_svg_draw(bContext *UNUSED(C), wmOperator *op) { - wmWindowManager *wm = CTX_wm_manager(C); - PointerRNA ptr; - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); - - ui_gpencil_import_svg_settings(op->layout, &ptr); + ui_gpencil_import_svg_settings(op->layout, op->ptr); } static bool wm_gpencil_import_svg_poll(bContext *C) diff --git a/source/blender/editors/mask/mask_shapekey.c b/source/blender/editors/mask/mask_shapekey.c index 1eb6613d8fe..81bf66da72c 100644 --- a/source/blender/editors/mask/mask_shapekey.c +++ b/source/blender/editors/mask/mask_shapekey.c @@ -220,8 +220,8 @@ void MASK_OT_shape_key_feather_reset(wmOperatorType *ot) } /* - * - loop over selected shapekeys. - * - find firstsel/lastsel pairs. + * - loop over selected shape-keys. + * - find first-selected/last-selected pairs. * - move these into a temp list. * - re-key all the original shapes. * - copy unselected values back from the original. diff --git a/source/blender/editors/mesh/editmesh_bevel.c b/source/blender/editors/mesh/editmesh_bevel.c index 51190268c4c..43492cd57af 100644 --- a/source/blender/editors/mesh/editmesh_bevel.c +++ b/source/blender/editors/mesh/editmesh_bevel.c @@ -911,76 +911,73 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event) static void edbm_bevel_ui(bContext *C, wmOperator *op) { uiLayout *layout = op->layout; - wmWindowManager *wm = CTX_wm_manager(C); uiLayout *col, *row; - PointerRNA ptr, toolsettings_ptr; + PointerRNA toolsettings_ptr; - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); - - int profile_type = RNA_enum_get(&ptr, "profile_type"); - int offset_type = RNA_enum_get(&ptr, "offset_type"); - bool affect_type = RNA_enum_get(&ptr, "affect"); + int profile_type = RNA_enum_get(op->ptr, "profile_type"); + int offset_type = RNA_enum_get(op->ptr, "offset_type"); + bool affect_type = RNA_enum_get(op->ptr, "affect"); uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); row = uiLayoutRow(layout, false); - uiItemR(row, &ptr, "affect", UI_ITEM_R_EXPAND, NULL, ICON_NONE); + uiItemR(row, op->ptr, "affect", UI_ITEM_R_EXPAND, NULL, ICON_NONE); uiItemS(layout); - uiItemR(layout, &ptr, "offset_type", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "offset_type", 0, NULL, ICON_NONE); if (offset_type == BEVEL_AMT_PERCENT) { - uiItemR(layout, &ptr, "offset_pct", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "offset_pct", 0, NULL, ICON_NONE); } else { - uiItemR(layout, &ptr, "offset", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "offset", 0, NULL, ICON_NONE); } - uiItemR(layout, &ptr, "segments", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "segments", 0, NULL, ICON_NONE); if (ELEM(profile_type, BEVEL_PROFILE_SUPERELLIPSE, BEVEL_PROFILE_CUSTOM)) { uiItemR(layout, - &ptr, + op->ptr, "profile", UI_ITEM_R_SLIDER, (profile_type == BEVEL_PROFILE_SUPERELLIPSE) ? IFACE_("Shape") : IFACE_("Miter Shape"), ICON_NONE); } - uiItemR(layout, &ptr, "material", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "material", 0, NULL, ICON_NONE); col = uiLayoutColumn(layout, true); - uiItemR(col, &ptr, "harden_normals", 0, NULL, ICON_NONE); - uiItemR(col, &ptr, "clamp_overlap", 0, NULL, ICON_NONE); - uiItemR(col, &ptr, "loop_slide", 0, NULL, ICON_NONE); + uiItemR(col, op->ptr, "harden_normals", 0, NULL, ICON_NONE); + uiItemR(col, op->ptr, "clamp_overlap", 0, NULL, ICON_NONE); + uiItemR(col, op->ptr, "loop_slide", 0, NULL, ICON_NONE); col = uiLayoutColumnWithHeading(layout, true, IFACE_("Mark")); uiLayoutSetActive(col, affect_type == BEVEL_AFFECT_EDGES); - uiItemR(col, &ptr, "mark_seam", 0, IFACE_("Seams"), ICON_NONE); - uiItemR(col, &ptr, "mark_sharp", 0, IFACE_("Sharp"), ICON_NONE); + uiItemR(col, op->ptr, "mark_seam", 0, IFACE_("Seams"), ICON_NONE); + uiItemR(col, op->ptr, "mark_sharp", 0, IFACE_("Sharp"), ICON_NONE); uiItemS(layout); col = uiLayoutColumn(layout, false); uiLayoutSetActive(col, affect_type == BEVEL_AFFECT_EDGES); - uiItemR(col, &ptr, "miter_outer", 0, IFACE_("Miter Outer"), ICON_NONE); - uiItemR(col, &ptr, "miter_inner", 0, IFACE_("Inner"), ICON_NONE); - if (RNA_enum_get(&ptr, "miter_inner") == BEVEL_MITER_ARC) { - uiItemR(col, &ptr, "spread", 0, NULL, ICON_NONE); + uiItemR(col, op->ptr, "miter_outer", 0, IFACE_("Miter Outer"), ICON_NONE); + uiItemR(col, op->ptr, "miter_inner", 0, IFACE_("Inner"), ICON_NONE); + if (RNA_enum_get(op->ptr, "miter_inner") == BEVEL_MITER_ARC) { + uiItemR(col, op->ptr, "spread", 0, NULL, ICON_NONE); } uiItemS(layout); col = uiLayoutColumn(layout, false); uiLayoutSetActive(col, affect_type == BEVEL_AFFECT_EDGES); - uiItemR(col, &ptr, "vmesh_method", 0, IFACE_("Intersection Type"), ICON_NONE); + uiItemR(col, op->ptr, "vmesh_method", 0, IFACE_("Intersection Type"), ICON_NONE); - uiItemR(layout, &ptr, "face_strength_mode", 0, IFACE_("Face Strength"), ICON_NONE); + uiItemR(layout, op->ptr, "face_strength_mode", 0, IFACE_("Face Strength"), ICON_NONE); uiItemS(layout); row = uiLayoutRow(layout, false); - uiItemR(row, &ptr, "profile_type", UI_ITEM_R_EXPAND, NULL, ICON_NONE); + uiItemR(row, op->ptr, "profile_type", UI_ITEM_R_EXPAND, NULL, ICON_NONE); if (profile_type == BEVEL_PROFILE_CUSTOM) { /* Get an RNA pointer to ToolSettings to give to the curve profile template code. */ Scene *scene = CTX_data_scene(C); diff --git a/source/blender/editors/mesh/editmesh_intersect.c b/source/blender/editors/mesh/editmesh_intersect.c index 141f120396a..d1f228e951a 100644 --- a/source/blender/editors/mesh/editmesh_intersect.c +++ b/source/blender/editors/mesh/editmesh_intersect.c @@ -251,32 +251,28 @@ static int edbm_intersect_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } -static void edbm_intersect_ui(bContext *C, wmOperator *op) +static void edbm_intersect_ui(bContext *UNUSED(C), wmOperator *op) { uiLayout *layout = op->layout; - wmWindowManager *wm = CTX_wm_manager(C); uiLayout *row; - PointerRNA ptr; - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); - - bool use_exact = RNA_enum_get(&ptr, "solver") == ISECT_SOLVER_EXACT; + bool use_exact = RNA_enum_get(op->ptr, "solver") == ISECT_SOLVER_EXACT; uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); row = uiLayoutRow(layout, false); - uiItemR(row, &ptr, "mode", UI_ITEM_R_EXPAND, NULL, ICON_NONE); + uiItemR(row, op->ptr, "mode", UI_ITEM_R_EXPAND, NULL, ICON_NONE); uiItemS(layout); row = uiLayoutRow(layout, false); - uiItemR(row, &ptr, "separate_mode", UI_ITEM_R_EXPAND, NULL, ICON_NONE); + uiItemR(row, op->ptr, "separate_mode", UI_ITEM_R_EXPAND, NULL, ICON_NONE); uiItemS(layout); row = uiLayoutRow(layout, false); - uiItemR(row, &ptr, "solver", UI_ITEM_R_EXPAND, NULL, ICON_NONE); + uiItemR(row, op->ptr, "solver", UI_ITEM_R_EXPAND, NULL, ICON_NONE); uiItemS(layout); if (!use_exact) { - uiItemR(layout, &ptr, "threshold", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "threshold", 0, NULL, ICON_NONE); } } @@ -418,32 +414,28 @@ static int edbm_intersect_boolean_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } -static void edbm_intersect_boolean_ui(bContext *C, wmOperator *op) +static void edbm_intersect_boolean_ui(bContext *UNUSED(C), wmOperator *op) { uiLayout *layout = op->layout; uiLayout *row; - wmWindowManager *wm = CTX_wm_manager(C); - PointerRNA ptr; - - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); - bool use_exact = RNA_enum_get(&ptr, "solver") == ISECT_SOLVER_EXACT; + bool use_exact = RNA_enum_get(op->ptr, "solver") == ISECT_SOLVER_EXACT; uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); row = uiLayoutRow(layout, false); - uiItemR(row, &ptr, "operation", UI_ITEM_R_EXPAND, NULL, ICON_NONE); + uiItemR(row, op->ptr, "operation", UI_ITEM_R_EXPAND, NULL, ICON_NONE); uiItemS(layout); row = uiLayoutRow(layout, false); - uiItemR(row, &ptr, "solver", UI_ITEM_R_EXPAND, NULL, ICON_NONE); + uiItemR(row, op->ptr, "solver", UI_ITEM_R_EXPAND, NULL, ICON_NONE); uiItemS(layout); - uiItemR(layout, &ptr, "use_swap", 0, NULL, ICON_NONE); - uiItemR(layout, &ptr, "use_self", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "use_swap", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "use_self", 0, NULL, ICON_NONE); if (!use_exact) { - uiItemR(layout, &ptr, "threshold", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "threshold", 0, NULL, ICON_NONE); } } diff --git a/source/blender/editors/mesh/editmesh_tools.c b/source/blender/editors/mesh/editmesh_tools.c index 33e4691bfb5..bb332a4094c 100644 --- a/source/blender/editors/mesh/editmesh_tools.c +++ b/source/blender/editors/mesh/editmesh_tools.c @@ -3694,21 +3694,18 @@ static const EnumPropertyItem *shape_itemf(bContext *C, static void edbm_blend_from_shape_ui(bContext *C, wmOperator *op) { uiLayout *layout = op->layout; - wmWindowManager *wm = CTX_wm_manager(C); - PointerRNA ptr; Object *obedit = CTX_data_edit_object(C); Mesh *me = obedit->data; PointerRNA ptr_key; - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); RNA_id_pointer_create((ID *)me->key, &ptr_key); uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); - uiItemPointerR(layout, &ptr, "shape", &ptr_key, "key_blocks", NULL, ICON_SHAPEKEY_DATA); - uiItemR(layout, &ptr, "blend", 0, NULL, ICON_NONE); - uiItemR(layout, &ptr, "add", 0, NULL, ICON_NONE); + uiItemPointerR(layout, op->ptr, "shape", &ptr_key, "key_blocks", NULL, ICON_SHAPEKEY_DATA); + uiItemR(layout, op->ptr, "blend", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "add", 0, NULL, ICON_NONE); } void MESH_OT_blend_from_shape(wmOperatorType *ot) @@ -5612,29 +5609,25 @@ static bool edbm_decimate_check(bContext *UNUSED(C), wmOperator *UNUSED(op)) return true; } -static void edbm_decimate_ui(bContext *C, wmOperator *op) +static void edbm_decimate_ui(bContext *UNUSED(C), wmOperator *op) { uiLayout *layout = op->layout, *row, *col, *sub; - wmWindowManager *wm = CTX_wm_manager(C); - PointerRNA ptr; - - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); uiLayoutSetPropSep(layout, true); - uiItemR(layout, &ptr, "ratio", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "ratio", 0, NULL, ICON_NONE); - uiItemR(layout, &ptr, "use_vertex_group", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "use_vertex_group", 0, NULL, ICON_NONE); col = uiLayoutColumn(layout, false); - uiLayoutSetActive(col, RNA_boolean_get(&ptr, "use_vertex_group")); - uiItemR(col, &ptr, "vertex_group_factor", 0, NULL, ICON_NONE); - uiItemR(col, &ptr, "invert_vertex_group", 0, NULL, ICON_NONE); + uiLayoutSetActive(col, RNA_boolean_get(op->ptr, "use_vertex_group")); + uiItemR(col, op->ptr, "vertex_group_factor", 0, NULL, ICON_NONE); + uiItemR(col, op->ptr, "invert_vertex_group", 0, NULL, ICON_NONE); row = uiLayoutRowWithHeading(layout, true, IFACE_("Symmetry")); - uiItemR(row, &ptr, "use_symmetry", 0, "", ICON_NONE); + uiItemR(row, op->ptr, "use_symmetry", 0, "", ICON_NONE); sub = uiLayoutRow(row, true); - uiLayoutSetActive(sub, RNA_boolean_get(&ptr, "use_symmetry")); - uiItemR(sub, &ptr, "symmetry_axis", UI_ITEM_R_EXPAND, NULL, ICON_NONE); + uiLayoutSetActive(sub, RNA_boolean_get(op->ptr, "use_symmetry")); + uiItemR(sub, op->ptr, "symmetry_axis", UI_ITEM_R_EXPAND, NULL, ICON_NONE); } void MESH_OT_decimate(wmOperatorType *ot) diff --git a/source/blender/editors/mesh/editmesh_undo.c b/source/blender/editors/mesh/editmesh_undo.c index 79385e28aa9..112de68b52c 100644 --- a/source/blender/editors/mesh/editmesh_undo.c +++ b/source/blender/editors/mesh/editmesh_undo.c @@ -93,6 +93,12 @@ typedef struct BArrayCustomData { #endif typedef struct UndoMesh { + /** + * This undo-meshes in `um_arraystore.local_links`. + * Not to be confused with the next and previous undo steps. + */ + struct UndoMesh *local_next, *local_prev; + Mesh me; int selectmode; @@ -128,7 +134,10 @@ static struct { struct BArrayStore_AtSize bs_stride; int users; - /* We could have the undo API pass in the previous state, for now store a local list */ + /** + * A list of #UndoMesh items ordered from oldest to newest + * used to access previous undo data for a mesh. + */ ListBase local_links; # ifdef USE_ARRAY_STORE_THREAD @@ -520,11 +529,63 @@ static void um_arraystore_free(UndoMesh *um) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Array Store Utilities + * \{ */ + +/** + * Create an array of #UndoMesh from `objects`. + * + * where each element in the resulting array is the most recently created + * undo-mesh for the object's mesh. + * When no undo-mesh can be found that array index is NULL. + * + * This is used for de-duplicating memory between undo steps, + * failure to find the undo step will store a full duplicate in memory. + * define `DEBUG_PRINT` to check memory is de-duplicating as expected. + */ +static UndoMesh **mesh_undostep_reference_elems_from_objects(Object **object, int object_len) +{ + /* Map: `Mesh.id.session_uuid` -> `UndoMesh`. */ + GHash *uuid_map = BLI_ghash_ptr_new_ex(__func__, object_len); + UndoMesh **um_references = MEM_callocN(sizeof(UndoMesh *) * object_len, __func__); + for (int i = 0; i < object_len; i++) { + const Mesh *me = object[i]->data; + BLI_ghash_insert(uuid_map, POINTER_FROM_INT(me->id.session_uuid), &um_references[i]); + } + int uuid_map_len = object_len; + + /* Loop backwards over all previous mesh undo data until either: + * - All elements have been found (where `um_references` we'll have every element set). + * - There are no undo steps left to look for. */ + UndoMesh *um_iter = um_arraystore.local_links.last; + while (um_iter && (uuid_map_len != 0)) { + UndoMesh **um_p; + if ((um_p = BLI_ghash_popkey(uuid_map, POINTER_FROM_INT(um_iter->me.id.session_uuid), NULL))) { + *um_p = um_iter; + uuid_map_len--; + } + um_iter = um_iter->local_prev; + } + BLI_assert(uuid_map_len == BLI_ghash_len(uuid_map)); + BLI_ghash_free(uuid_map, NULL, NULL); + if (uuid_map_len == object_len) { + MEM_freeN(um_references); + um_references = NULL; + } + return um_references; +} + +/** \} */ + #endif /* USE_ARRAY_STORE */ /* for callbacks */ /* undo simply makes copies of a bmesh */ -static void *undomesh_from_editmesh(UndoMesh *um, BMEditMesh *em, Key *key) +/** + * \param um_ref: The reference to use for de-duplicating memory between undo-steps. + */ +static void *undomesh_from_editmesh(UndoMesh *um, BMEditMesh *em, Key *key, UndoMesh *um_ref) { BLI_assert(BLI_array_is_zeroed(um, 1)); #ifdef USE_ARRAY_STORE_THREAD @@ -560,14 +621,8 @@ static void *undomesh_from_editmesh(UndoMesh *um, BMEditMesh *em, Key *key) #ifdef USE_ARRAY_STORE { - /* We could be more clever here, - * the previous undo state may be from a separate mesh. */ - const UndoMesh *um_ref = um_arraystore.local_links.last ? - ((LinkData *)um_arraystore.local_links.last)->data : - NULL; - /* Add ourselves. */ - BLI_addtail(&um_arraystore.local_links, BLI_genericNodeN(um)); + BLI_addtail(&um_arraystore.local_links, um); # ifdef USE_ARRAY_STORE_THREAD if (um_arraystore.task_pool == NULL) { @@ -583,6 +638,8 @@ static void *undomesh_from_editmesh(UndoMesh *um, BMEditMesh *em, Key *key) um_arraystore_compact_with_info(um, um_ref); # endif } +#else + UNUSED_VARS(um_ref); #endif return um; @@ -682,11 +739,9 @@ static void undomesh_free_data(UndoMesh *um) /* we need to expand so any allocations in custom-data are freed with the mesh */ um_arraystore_expand(um); - { - LinkData *link = BLI_findptr(&um_arraystore.local_links, um, offsetof(LinkData, data)); - BLI_remlink(&um_arraystore.local_links, link); - MEM_freeN(link); - } + BLI_assert(BLI_findindex(&um_arraystore.local_links, um) != -1); + BLI_remlink(&um_arraystore.local_links, um); + um_arraystore_free(um); #endif @@ -720,7 +775,6 @@ static Object *editmesh_object_from_context(bContext *C) * \{ */ typedef struct MeshUndoStep_Elem { - struct MeshUndoStep_Elem *next, *prev; UndoRefID_Object obedit_ref; UndoMesh data; } MeshUndoStep_Elem; @@ -749,6 +803,12 @@ static bool mesh_undosys_step_encode(struct bContext *C, struct Main *bmain, Und us->elems = MEM_callocN(sizeof(*us->elems) * objects_len, __func__); us->elems_len = objects_len; + UndoMesh **um_references = NULL; + +#ifdef USE_ARRAY_STORE + um_references = mesh_undostep_reference_elems_from_objects(objects, objects_len); +#endif + for (uint i = 0; i < objects_len; i++) { Object *ob = objects[i]; MeshUndoStep_Elem *elem = &us->elems[i]; @@ -756,12 +816,22 @@ static bool mesh_undosys_step_encode(struct bContext *C, struct Main *bmain, Und elem->obedit_ref.ptr = ob; Mesh *me = elem->obedit_ref.ptr->data; BMEditMesh *em = me->edit_mesh; - undomesh_from_editmesh(&elem->data, me->edit_mesh, me->key); + undomesh_from_editmesh( + &elem->data, me->edit_mesh, me->key, um_references ? um_references[i] : NULL); em->needs_flush_to_id = 1; us->step.data_size += elem->data.undo_size; + +#ifdef USE_ARRAY_STORE + /** As this is only data storage it is safe to set the session ID here. */ + elem->data.me.id.session_uuid = me->id.session_uuid; +#endif } MEM_freeN(objects); + if (um_references != NULL) { + MEM_freeN(um_references); + } + bmain->is_memfile_undo_flush_needed = true; return true; diff --git a/source/blender/editors/mesh/editmesh_utils.c b/source/blender/editors/mesh/editmesh_utils.c index 2e98f0558f3..19c9909039c 100644 --- a/source/blender/editors/mesh/editmesh_utils.c +++ b/source/blender/editors/mesh/editmesh_utils.c @@ -1721,7 +1721,7 @@ void EDBM_project_snap_verts( SCE_SNAP_MODE_FACE, &(const struct SnapObjectParams){ .snap_select = SNAP_NOT_ACTIVE, - .use_object_edit_cage = false, + .edit_mode_type = SNAP_GEOM_FINAL, .use_occlusion_test = true, }, mval, diff --git a/source/blender/editors/object/CMakeLists.txt b/source/blender/editors/object/CMakeLists.txt index 77b5379ddd4..4338b043fc9 100644 --- a/source/blender/editors/object/CMakeLists.txt +++ b/source/blender/editors/object/CMakeLists.txt @@ -87,7 +87,7 @@ if(WITH_INTERNATIONAL) endif() if(WITH_EXPERIMENTAL_FEATURES) - add_definitions(-DWITH_GEOMETRY_NODES) + add_definitions(-DWITH_SIMULATION_DATABLOCK) add_definitions(-DWITH_POINT_CLOUD) add_definitions(-DWITH_HAIR_NODES) endif() diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c index 4c28b24b8d9..ef500be0133 100644 --- a/source/blender/editors/object/object_add.c +++ b/source/blender/editors/object/object_add.c @@ -117,6 +117,7 @@ #include "ED_physics.h" #include "ED_render.h" #include "ED_screen.h" +#include "ED_select_utils.h" #include "ED_transform.h" #include "ED_view3d.h" @@ -1311,6 +1312,7 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) const int type = RNA_enum_get(op->ptr, "type"); const bool use_in_front = RNA_boolean_get(op->ptr, "use_in_front"); + const bool use_lights = RNA_boolean_get(op->ptr, "use_lights"); const int stroke_depth_order = RNA_enum_get(op->ptr, "stroke_depth_order"); ushort local_view_bits; @@ -1322,12 +1324,14 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) if (!ED_object_add_generic_get_opts(C, op, 'Y', loc, rot, NULL, NULL, &local_view_bits, NULL)) { return OPERATOR_CANCELLED; } - /* add new object if not currently editing a GP object, - * or if "empty" was chosen (i.e. user wants a blank GP canvas) - */ - if ((gpd == NULL) || (GPENCIL_ANY_MODE(gpd) == false) || (type == GP_EMPTY)) { + /* Add new object if not currently editing a GP object. */ + if ((gpd == NULL) || (GPENCIL_ANY_MODE(gpd) == false)) { const char *ob_name = NULL; switch (type) { + case GP_EMPTY: { + ob_name = "GPencil"; + break; + } case GP_MONKEY: { ob_name = "Suzanne"; break; @@ -1358,6 +1362,13 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) /* create relevant geometry */ switch (type) { + case GP_EMPTY: { + float mat[4][4]; + + ED_object_new_primitive_matrix(C, ob, loc, rot, mat); + ED_gpencil_create_blank(C, ob, mat); + break; + } case GP_STROKE: { float radius = RNA_float_get(op->ptr, "radius"); float mat[4][4]; @@ -1422,6 +1433,13 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) id_us_plus(&md->target_material->id); } + if (use_lights) { + ob->dtx |= OB_USE_GPENCIL_LIGHTS; + } + else { + ob->dtx &= ~OB_USE_GPENCIL_LIGHTS; + } + /* Stroke object is drawn in front of meshes by default. */ if (use_in_front) { ob->dtx |= OB_DRAW_IN_FRONT; @@ -1434,10 +1452,6 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) break; } - case GP_EMPTY: - /* do nothing */ - break; - default: BKE_report(op->reports, RPT_WARNING, "Not implemented"); break; @@ -1468,6 +1482,7 @@ static void object_add_ui(bContext *UNUSED(C), wmOperator *op) int type = RNA_enum_get(op->ptr, "type"); if (type == GP_LRT_COLLECTION || type == GP_LRT_OBJECT || type == GP_LRT_SCENE) { + uiItemR(layout, op->ptr, "use_lights", 0, NULL, ICON_NONE); uiItemR(layout, op->ptr, "use_in_front", 0, NULL, ICON_NONE); bool in_front = RNA_boolean_get(op->ptr, "use_in_front"); uiLayout *row = uiLayoutRow(layout, false); @@ -1514,6 +1529,8 @@ void OBJECT_OT_gpencil_add(wmOperatorType *ot) false, "In Front", "Show line art grease pencil in front of everything"); + RNA_def_boolean( + ot->srna, "use_lights", false, "Use Lights", "Use lights for this grease pencil object"); RNA_def_enum( ot->srna, "stroke_depth_order", @@ -2131,8 +2148,7 @@ static void copy_object_set_idnew(bContext *C) FOREACH_MAIN_ID_END; #endif - BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false); - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); } /** \} */ @@ -2453,7 +2469,7 @@ static void make_object_duplilist_real(bContext *C, free_object_duplilist(lb_duplis); - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); base->object->transflag &= ~OB_DUPLI; DEG_id_tag_update(&base->object->id, ID_RECALC_COPY_ON_WRITE); @@ -2468,7 +2484,7 @@ static int object_duplicates_make_real_exec(bContext *C, wmOperator *op) const bool use_base_parent = RNA_boolean_get(op->ptr, "use_base_parent"); const bool use_hierarchy = RNA_boolean_get(op->ptr, "use_hierarchy"); - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); CTX_DATA_BEGIN (C, Base *, base, selected_editable_bases) { make_object_duplilist_real(C, depsgraph, scene, base, use_base_parent, use_hierarchy); @@ -2680,6 +2696,7 @@ static int object_convert_exec(bContext *C, wmOperator *op) int a, mballConverted = 0; bool gpencilConverted = false; + bool gpencilCurveConverted = false; /* don't forget multiple users! */ @@ -2892,6 +2909,7 @@ static int object_convert_exec(bContext *C, wmOperator *op) me_eval = BKE_mesh_copy_for_eval(me_eval, false); /* Full (edge-angle based) draw calculation should ideally be performed. */ BKE_mesh_edges_set_draw_render(me_eval); + BKE_object_material_from_eval_data(bmain, newob, &me_eval->id); BKE_mesh_nomain_to_mesh(me_eval, newob->data, newob, &CD_MASK_MESH, true); BKE_object_free_modifiers(newob, 0); /* after derivedmesh calls! */ } @@ -2964,6 +2982,16 @@ static int object_convert_exec(bContext *C, wmOperator *op) /* meshes doesn't use displist */ BKE_object_free_curve_cache(newob); } + else if (target == OB_GPENCIL) { + ushort local_view_bits = (v3d && v3d->localvd) ? v3d->local_view_uuid : 0; + Object *ob_gpencil = ED_gpencil_add_object(C, newob->loc, local_view_bits); + copy_v3_v3(ob_gpencil->rot, newob->rot); + copy_v3_v3(ob_gpencil->scale, newob->scale); + BKE_gpencil_convert_curve(bmain, scene, ob_gpencil, newob, false, 1.0f, 0.0f); + gpencilConverted = true; + gpencilCurveConverted = true; + basen = NULL; + } } else if (ELEM(ob->type, OB_CURVE, OB_SURF)) { ob->flag |= OB_DONE; @@ -3145,6 +3173,17 @@ static int object_convert_exec(bContext *C, wmOperator *op) FOREACH_SCENE_OBJECT_END; } } + else { + /* Remove Text curves converted to Grease Pencil object to avoid duplicated curves. */ + if (gpencilCurveConverted) { + FOREACH_SCENE_OBJECT_BEGIN (scene, ob_delete) { + if (ELEM(ob_delete->type, OB_CURVE) && (ob_delete->flag & OB_DONE)) { + ED_object_base_free_and_unlink(bmain, scene, ob_delete); + } + } + FOREACH_SCENE_OBJECT_END; + } + } // XXX ED_object_editmode_enter(C, 0); // XXX exit_editmode(C, EM_FREEDATA|); /* freedata, but no undo */ @@ -3168,24 +3207,21 @@ static int object_convert_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } -static void object_convert_ui(bContext *C, wmOperator *op) +static void object_convert_ui(bContext *UNUSED(C), wmOperator *op) { uiLayout *layout = op->layout; - wmWindowManager *wm = CTX_wm_manager(C); - PointerRNA ptr; uiLayoutSetPropSep(layout, true); - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); - uiItemR(layout, &ptr, "target", 0, NULL, ICON_NONE); - uiItemR(layout, &ptr, "keep_original", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "target", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "keep_original", 0, NULL, ICON_NONE); - if (RNA_enum_get(&ptr, "target") == OB_GPENCIL) { - uiItemR(layout, &ptr, "thickness", 0, NULL, ICON_NONE); - uiItemR(layout, &ptr, "angle", 0, NULL, ICON_NONE); - uiItemR(layout, &ptr, "offset", 0, NULL, ICON_NONE); - uiItemR(layout, &ptr, "seams", 0, NULL, ICON_NONE); - uiItemR(layout, &ptr, "faces", 0, NULL, ICON_NONE); + if (RNA_enum_get(op->ptr, "target") == OB_GPENCIL) { + uiItemR(layout, op->ptr, "thickness", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "angle", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "offset", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "seams", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "faces", 0, NULL, ICON_NONE); } } @@ -3333,7 +3369,7 @@ Base *ED_object_add_duplicate( DEG_id_tag_update_ex(bmain, (ID *)ob->data, ID_RECALC_EDITORS); } - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); return basen; } @@ -3349,8 +3385,7 @@ static int duplicate_exec(bContext *C, wmOperator *op) /* We need to handle that here ourselves, because we may duplicate several objects, in which case * we also want to remap pointers between those... */ - BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false); - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); CTX_DATA_BEGIN (C, Base *, base, selected_bases) { Base *basen = object_add_duplicate_internal( @@ -3465,7 +3500,7 @@ static int object_add_named_exec(bContext *C, wmOperator *op) /* object_add_duplicate_internal() doesn't deselect other objects, unlike object_add_common() or * BKE_view_layer_base_deselect_all(). */ - ED_object_base_deselect_all(view_layer, NULL, BA_DESELECT); + ED_object_base_deselect_all(view_layer, NULL, SEL_DESELECT); ED_object_base_select(basen, BA_SELECT); ED_object_base_activate(C, basen); diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c index d3b4b91881c..5be572baec5 100644 --- a/source/blender/editors/object/object_edit.c +++ b/source/blender/editors/object/object_edit.c @@ -1592,25 +1592,8 @@ static const EnumPropertyItem *object_mode_set_itemsf(bContext *C, const Object *ob = CTX_data_active_object(C); if (ob) { - const bool use_mode_particle_edit = ED_object_particle_edit_mode_supported(ob); while (input->identifier) { - if ((input->value == OB_MODE_EDIT && OB_TYPE_SUPPORT_EDITMODE(ob->type)) || - (input->value == OB_MODE_POSE && (ob->type == OB_ARMATURE)) || - (input->value == OB_MODE_PARTICLE_EDIT && use_mode_particle_edit) || - (ELEM(input->value, - OB_MODE_SCULPT, - OB_MODE_VERTEX_PAINT, - OB_MODE_WEIGHT_PAINT, - OB_MODE_TEXTURE_PAINT) && - (ob->type == OB_MESH)) || - (ELEM(input->value, - OB_MODE_EDIT_GPENCIL, - OB_MODE_PAINT_GPENCIL, - OB_MODE_SCULPT_GPENCIL, - OB_MODE_WEIGHT_GPENCIL, - OB_MODE_VERTEX_GPENCIL) && - (ob->type == OB_GPENCIL)) || - (input->value == OB_MODE_OBJECT)) { + if (ED_object_mode_compat_test(ob, input->value)) { RNA_enum_item_add(&item, &totitem, input); } input++; diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h index ededcbfdba8..5bf04e195fe 100644 --- a/source/blender/editors/object/object_intern.h +++ b/source/blender/editors/object/object_intern.h @@ -157,10 +157,6 @@ bool edit_modifier_poll_generic(struct bContext *C, const bool is_liboverride_allowed); void edit_modifier_properties(struct wmOperatorType *ot); bool edit_modifier_invoke_properties(struct bContext *C, struct wmOperator *op); -bool edit_modifier_invoke_properties_with_hover(struct bContext *C, - struct wmOperator *op, - const struct wmEvent *event, - int *r_retval); struct ModifierData *edit_modifier_property_get(struct wmOperator *op, struct Object *ob, diff --git a/source/blender/editors/object/object_modes.c b/source/blender/editors/object/object_modes.c index ff1855cafc5..dcb294bcb12 100644 --- a/source/blender/editors/object/object_modes.c +++ b/source/blender/editors/object/object_modes.c @@ -115,43 +115,46 @@ static const char *object_mode_op_string(eObjectMode mode) */ bool ED_object_mode_compat_test(const Object *ob, eObjectMode mode) { - if (ob) { - if (mode == OB_MODE_OBJECT) { - return true; - } + if (mode == OB_MODE_OBJECT) { + return true; + } - switch (ob->type) { - case OB_MESH: - if (mode & (OB_MODE_EDIT | OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | - OB_MODE_TEXTURE_PAINT | OB_MODE_PARTICLE_EDIT)) { - return true; - } - break; - case OB_CURVE: - case OB_SURF: - case OB_FONT: - case OB_MBALL: - if (mode & OB_MODE_EDIT) { - return true; - } - break; - case OB_LATTICE: - if (mode & (OB_MODE_EDIT | OB_MODE_WEIGHT_PAINT)) { - return true; - } - break; - case OB_ARMATURE: - if (mode & (OB_MODE_EDIT | OB_MODE_POSE)) { - return true; - } - break; - case OB_GPENCIL: - if (mode & (OB_MODE_EDIT | OB_MODE_EDIT_GPENCIL | OB_MODE_PAINT_GPENCIL | - OB_MODE_SCULPT_GPENCIL | OB_MODE_WEIGHT_GPENCIL | OB_MODE_VERTEX_GPENCIL)) { + switch (ob->type) { + case OB_MESH: + if (mode & (OB_MODE_EDIT | OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | + OB_MODE_TEXTURE_PAINT)) { + return true; + } + if (mode & OB_MODE_PARTICLE_EDIT) { + if (ED_object_particle_edit_mode_supported(ob)) { return true; } - break; - } + } + break; + case OB_CURVE: + case OB_SURF: + case OB_FONT: + case OB_MBALL: + if (mode & OB_MODE_EDIT) { + return true; + } + break; + case OB_LATTICE: + if (mode & (OB_MODE_EDIT | OB_MODE_WEIGHT_PAINT)) { + return true; + } + break; + case OB_ARMATURE: + if (mode & (OB_MODE_EDIT | OB_MODE_POSE)) { + return true; + } + break; + case OB_GPENCIL: + if (mode & (OB_MODE_EDIT_GPENCIL | OB_MODE_PAINT_GPENCIL | OB_MODE_SCULPT_GPENCIL | + OB_MODE_WEIGHT_GPENCIL | OB_MODE_VERTEX_GPENCIL)) { + return true; + } + break; } return false; diff --git a/source/blender/editors/object/object_modifier.c b/source/blender/editors/object/object_modifier.c index 49c07b28f07..e14e5cbd44b 100644 --- a/source/blender/editors/object/object_modifier.c +++ b/source/blender/editors/object/object_modifier.c @@ -63,6 +63,7 @@ #include "BKE_lattice.h" #include "BKE_lib_id.h" #include "BKE_main.h" +#include "BKE_material.h" #include "BKE_mesh.h" #include "BKE_mesh_mapping.h" #include "BKE_mesh_runtime.h" @@ -210,7 +211,7 @@ ModifierData *ED_object_modifier_add( /* special cases */ if (type == eModifierType_Softbody) { if (!ob->soft) { - ob->soft = sbNew(scene); + ob->soft = sbNew(); ob->softflag |= OB_SB_GOAL | OB_SB_EDGES; } } @@ -772,6 +773,8 @@ static bool modifier_apply_obdata( return false; } + Main *bmain = DEG_get_bmain(depsgraph); + BKE_object_material_from_eval_data(bmain, ob, &mesh_applied->id); BKE_mesh_nomain_to_mesh(mesh_applied, me, ob, &CD_MASK_MESH, true); if (md_eval->type == eModifierType_Multires) { @@ -1120,10 +1123,10 @@ bool edit_modifier_invoke_properties(bContext *C, wmOperator *op) * with a UI panel below the mouse cursor, unless a specific modifier is set with a context * pointer. Used in order to apply modifier operators on hover over their panels. */ -bool edit_modifier_invoke_properties_with_hover(bContext *C, - wmOperator *op, - const wmEvent *event, - int *r_retval) +static bool edit_modifier_invoke_properties_with_hover(bContext *C, + wmOperator *op, + const wmEvent *event, + int *r_retval) { if (RNA_struct_property_is_set(op->ptr, "modifier")) { return true; diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c index a7308002e76..127d8681d3c 100644 --- a/source/blender/editors/object/object_relations.c +++ b/source/blender/editors/object/object_relations.c @@ -801,7 +801,7 @@ bool ED_object_parent_set(ReportList *reports, * so we check this by assuming that the parent is selected too. */ /* XXX currently this should only happen for meshes, curves, surfaces, - * and lattices - this stuff isn't available for metas yet */ + * and lattices - this stuff isn't available for meta-balls yet. */ if (ELEM(ob->type, OB_MESH, OB_CURVE, OB_SURF, OB_FONT, OB_LATTICE)) { ModifierData *md; @@ -1944,7 +1944,7 @@ void ED_object_single_user(Main *bmain, Scene *scene, Object *ob) ob->flag |= OB_DONE; single_object_users(bmain, scene, NULL, OB_DONE, false); - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); } static void single_obdata_users( @@ -2644,7 +2644,7 @@ static int make_single_user_exec(bContext *C, wmOperator *op) single_object_action_users(bmain, scene, view_layer, v3d, flag); } - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); WM_event_add_notifier(C, NC_WINDOW, NULL); diff --git a/source/blender/editors/render/render_preview.c b/source/blender/editors/render/render_preview.c index 10e0a143d9b..e28fe8a5d04 100644 --- a/source/blender/editors/render/render_preview.c +++ b/source/blender/editors/render/render_preview.c @@ -70,6 +70,7 @@ #include "BKE_node.h" #include "BKE_object.h" #include "BKE_scene.h" +#include "BKE_screen.h" #include "BKE_texture.h" #include "BKE_world.h" @@ -692,8 +693,9 @@ struct ObjectPreviewData { int sizey; }; -static Object *object_preview_camera_create( - Main *preview_main, ViewLayer *view_layer, Object *preview_object, int sizex, int sizey) +static Object *object_preview_camera_create(Main *preview_main, + ViewLayer *view_layer, + Object *preview_object) { Object *camera = BKE_object_add(preview_main, view_layer, OB_CAMERA, "Preview Camera"); @@ -701,18 +703,17 @@ static Object *object_preview_camera_create( float dummyscale[3]; mat4_to_loc_rot_size(camera->loc, rotmat, dummyscale, preview_object->obmat); - /* Camera is Y up, so needs additional 90deg rotation around X to match object's Z up. */ + /* Camera is Y up, so needs additional rotations to obliquely face the front. */ float drotmat[3][3]; - axis_angle_to_mat3_single(drotmat, 'X', M_PI_2); + const float eul[3] = {M_PI * 0.4f, 0.0f, M_PI * 0.1f}; + eul_to_mat3(drotmat, eul); mul_m3_m3_post(rotmat, drotmat); camera->rotmode = ROT_MODE_QUAT; mat3_to_quat(camera->quat, rotmat); - /* shader_preview_render() does this too. */ - if (sizex > sizey) { - ((Camera *)camera->data)->lens *= (float)sizey / (float)sizex; - } + /* Nice focal length for close portraiture. */ + ((Camera *)camera->data)->lens = 85; return camera; } @@ -730,11 +731,8 @@ static Scene *object_preview_scene_create(const struct ObjectPreviewData *previe BKE_collection_object_add(preview_data->pr_main, scene->master_collection, preview_data->object); - Object *camera_object = object_preview_camera_create(preview_data->pr_main, - view_layer, - preview_data->object, - preview_data->sizex, - preview_data->sizey); + Object *camera_object = object_preview_camera_create( + preview_data->pr_main, view_layer, preview_data->object); scene->camera = camera_object; scene->r.xsch = preview_data->sizex; @@ -779,16 +777,21 @@ static void object_preview_render(IconPreview *preview, IconPreviewSize *preview U.pixelsize = 2.0f; + View3DShading shading; + BKE_screen_view3d_shading_init(&shading); + /* Enable shadows, makes it a bit easier to see the shape. */ + shading.flag |= V3D_SHADING_SHADOW; + ImBuf *ibuf = ED_view3d_draw_offscreen_imbuf_simple( depsgraph, DEG_get_evaluated_scene(depsgraph), - NULL, - OB_SOLID, + &shading, + OB_TEXTURE, DEG_get_evaluated_object(depsgraph, scene->camera), preview_sized->sizex, preview_sized->sizey, IB_rect, - V3D_OFSDRAW_NONE, + V3D_OFSDRAW_OVERRIDE_SCENE_SETTINGS, R_ALPHAPREMUL, NULL, NULL, diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c index bd2b1c4c553..2814a4c9351 100644 --- a/source/blender/editors/screen/area.c +++ b/source/blender/editors/screen/area.c @@ -1172,12 +1172,12 @@ static void region_azones_add(const bScreen *screen, ScrArea *area, ARegion *reg } /* dir is direction to check, not the splitting edge direction! */ -static int rct_fits(const rcti *rect, char dir, int size) +static int rct_fits(const rcti *rect, const eScreenAxis dir_axis, int size) { - if (dir == 'h') { + if (dir_axis == SCREEN_AXIS_H) { return BLI_rcti_size_x(rect) + 1 - size; } - /* 'v' */ + /* Vertical. */ return BLI_rcti_size_y(rect) + 1 - size; } @@ -1398,7 +1398,8 @@ static void region_rect_recursive( region->flag |= RGN_FLAG_TOO_SMALL; } } - else if (rct_fits(remainder, 'v', 1) < 0 || rct_fits(remainder, 'h', 1) < 0) { + else if (rct_fits(remainder, SCREEN_AXIS_V, 1) < 0 || + rct_fits(remainder, SCREEN_AXIS_H, 1) < 0) { /* remainder is too small for any usage */ region->flag |= RGN_FLAG_TOO_SMALL; } @@ -1410,11 +1411,11 @@ static void region_rect_recursive( else if (ELEM(alignment, RGN_ALIGN_TOP, RGN_ALIGN_BOTTOM)) { rcti *winrct = (region->overlap) ? overlap_remainder : remainder; - if ((prefsizey == 0) || (rct_fits(winrct, 'v', prefsizey) < 0)) { + if ((prefsizey == 0) || (rct_fits(winrct, SCREEN_AXIS_V, prefsizey) < 0)) { region->flag |= RGN_FLAG_TOO_SMALL; } else { - int fac = rct_fits(winrct, 'v', prefsizey); + int fac = rct_fits(winrct, SCREEN_AXIS_V, prefsizey); if (fac < 0) { prefsizey += fac; @@ -1436,11 +1437,11 @@ static void region_rect_recursive( else if (ELEM(alignment, RGN_ALIGN_LEFT, RGN_ALIGN_RIGHT)) { rcti *winrct = (region->overlap) ? overlap_remainder : remainder; - if ((prefsizex == 0) || (rct_fits(winrct, 'h', prefsizex) < 0)) { + if ((prefsizex == 0) || (rct_fits(winrct, SCREEN_AXIS_H, prefsizex) < 0)) { region->flag |= RGN_FLAG_TOO_SMALL; } else { - int fac = rct_fits(winrct, 'h', prefsizex); + int fac = rct_fits(winrct, SCREEN_AXIS_H, prefsizex); if (fac < 0) { prefsizex += fac; @@ -1464,7 +1465,7 @@ static void region_rect_recursive( region->winrct = *remainder; if (alignment == RGN_ALIGN_HSPLIT) { - if (rct_fits(remainder, 'h', prefsizex) > 4) { + if (rct_fits(remainder, SCREEN_AXIS_H, prefsizex) > 4) { region->winrct.xmax = BLI_rcti_cent_x(remainder); remainder->xmin = region->winrct.xmax + 1; } @@ -1473,7 +1474,7 @@ static void region_rect_recursive( } } else { - if (rct_fits(remainder, 'v', prefsizey) > 4) { + if (rct_fits(remainder, SCREEN_AXIS_V, prefsizey) > 4) { region->winrct.ymax = BLI_rcti_cent_y(remainder); remainder->ymin = region->winrct.ymax + 1; } @@ -1526,8 +1527,8 @@ static void region_rect_recursive( BLI_rcti_init(remainder, 0, 0, 0, 0); } - /* Fix any negative dimensions. This can happen when a quad split 3d view gets to small. (see - * T72200). */ + /* Fix any negative dimensions. This can happen when a quad split 3d view gets too small. + * (see T72200). */ BLI_rcti_sanitize(®ion->winrct); quad++; diff --git a/source/blender/editors/screen/screen_draw.c b/source/blender/editors/screen/screen_draw.c index 2ba7ef8f972..2c45524ef94 100644 --- a/source/blender/editors/screen/screen_draw.c +++ b/source/blender/editors/screen/screen_draw.c @@ -33,184 +33,11 @@ #include "WM_api.h" +#include "UI_interface.h" #include "UI_resources.h" #include "screen_intern.h" -/** - * Draw horizontal shape visualizing future joining - * (left as well right direction of future joining). - */ -static void draw_horizontal_join_shape(ScrArea *area, char dir, uint pos) -{ - const float width = screen_geom_area_width(area) - 1; - const float height = screen_geom_area_height(area) - 1; - - float w, h; - if (height < width) { - h = height / 8; - w = height / 4; - } - else { - h = width / 8; - w = width / 4; - } - - vec2f points[10]; - points[0].x = area->v1->vec.x; - points[0].y = area->v1->vec.y + height / 2; - - points[1].x = area->v1->vec.x; - points[1].y = area->v1->vec.y; - - points[2].x = area->v4->vec.x - w; - points[2].y = area->v4->vec.y; - - points[3].x = area->v4->vec.x - w; - points[3].y = area->v4->vec.y + height / 2 - 2 * h; - - points[4].x = area->v4->vec.x - 2 * w; - points[4].y = area->v4->vec.y + height / 2; - - points[5].x = area->v4->vec.x - w; - points[5].y = area->v4->vec.y + height / 2 + 2 * h; - - points[6].x = area->v3->vec.x - w; - points[6].y = area->v3->vec.y; - - points[7].x = area->v2->vec.x; - points[7].y = area->v2->vec.y; - - points[8].x = area->v4->vec.x; - points[8].y = area->v4->vec.y + height / 2 - h; - - points[9].x = area->v4->vec.x; - points[9].y = area->v4->vec.y + height / 2 + h; - - if (dir == 'l') { - /* when direction is left, then we flip direction of arrow */ - float cx = area->v1->vec.x + width; - for (int i = 0; i < 10; i++) { - points[i].x -= cx; - points[i].x = -points[i].x; - points[i].x += area->v1->vec.x; - } - } - - immBegin(GPU_PRIM_TRI_FAN, 5); - - for (int i = 0; i < 5; i++) { - immVertex2f(pos, points[i].x, points[i].y); - } - - immEnd(); - - immBegin(GPU_PRIM_TRI_FAN, 5); - - for (int i = 4; i < 8; i++) { - immVertex2f(pos, points[i].x, points[i].y); - } - - immVertex2f(pos, points[0].x, points[0].y); - immEnd(); - - immRectf(pos, points[2].x, points[2].y, points[8].x, points[8].y); - immRectf(pos, points[6].x, points[6].y, points[9].x, points[9].y); -} - -/** - * Draw vertical shape visualizing future joining (up/down direction). - */ -static void draw_vertical_join_shape(ScrArea *area, char dir, uint pos) -{ - const float width = screen_geom_area_width(area) - 1; - const float height = screen_geom_area_height(area) - 1; - - float w, h; - if (height < width) { - h = height / 4; - w = height / 8; - } - else { - h = width / 4; - w = width / 8; - } - - vec2f points[10]; - points[0].x = area->v1->vec.x + width / 2; - points[0].y = area->v3->vec.y; - - points[1].x = area->v2->vec.x; - points[1].y = area->v2->vec.y; - - points[2].x = area->v1->vec.x; - points[2].y = area->v1->vec.y + h; - - points[3].x = area->v1->vec.x + width / 2 - 2 * w; - points[3].y = area->v1->vec.y + h; - - points[4].x = area->v1->vec.x + width / 2; - points[4].y = area->v1->vec.y + 2 * h; - - points[5].x = area->v1->vec.x + width / 2 + 2 * w; - points[5].y = area->v1->vec.y + h; - - points[6].x = area->v4->vec.x; - points[6].y = area->v4->vec.y + h; - - points[7].x = area->v3->vec.x; - points[7].y = area->v3->vec.y; - - points[8].x = area->v1->vec.x + width / 2 - w; - points[8].y = area->v1->vec.y; - - points[9].x = area->v1->vec.x + width / 2 + w; - points[9].y = area->v1->vec.y; - - if (dir == 'u') { - /* when direction is up, then we flip direction of arrow */ - float cy = area->v1->vec.y + height; - for (int i = 0; i < 10; i++) { - points[i].y -= cy; - points[i].y = -points[i].y; - points[i].y += area->v1->vec.y; - } - } - - immBegin(GPU_PRIM_TRI_FAN, 5); - - for (int i = 0; i < 5; i++) { - immVertex2f(pos, points[i].x, points[i].y); - } - - immEnd(); - - immBegin(GPU_PRIM_TRI_FAN, 5); - - for (int i = 4; i < 8; i++) { - immVertex2f(pos, points[i].x, points[i].y); - } - - immVertex2f(pos, points[0].x, points[0].y); - immEnd(); - - immRectf(pos, points[2].x, points[2].y, points[8].x, points[8].y); - immRectf(pos, points[6].x, points[6].y, points[9].x, points[9].y); -} - -/** - * Draw join shape due to direction of joining. - */ -static void draw_join_shape(ScrArea *area, char dir, uint pos) -{ - if (ELEM(dir, 'u', 'd')) { - draw_vertical_join_shape(area, dir, pos); - } - else { - draw_horizontal_join_shape(area, dir, pos); - } -} - #define CORNER_RESOLUTION 3 static void do_vert_pair(GPUVertBuf *vbo, uint pos, uint *vidx, int corner, int i) @@ -290,28 +117,6 @@ static GPUBatch *batch_screen_edges_get(int *corner_len) #undef CORNER_RESOLUTION -/** - * Draw screen area darker with arrow (visualization of future joining). - */ -static void scrarea_draw_shape_dark(ScrArea *area, char dir, uint pos) -{ - GPU_blend(GPU_BLEND_ALPHA); - immUniformColor4ub(0, 0, 0, 50); - - draw_join_shape(area, dir, pos); -} - -/** - * Draw screen area lighter with arrow shape ("eraser" of previous dark shape). - */ -static void scrarea_draw_shape_light(ScrArea *area, char UNUSED(dir), uint pos) -{ - GPU_blend(GPU_BLEND_ALPHA); - immUniformColor4ub(255, 255, 255, 25); - - immRectf(pos, area->v1->vec.x, area->v1->vec.y, area->v3->vec.x, area->v3->vec.y); -} - static void drawscredge_area_draw( int sizex, int sizey, short x1, short y1, short x2, short y2, float edge_thickness) { @@ -427,53 +232,97 @@ void ED_screen_draw_edges(wmWindow *win) } /** - * The blended join arrows. + * Visual indication of the two areas involved in a proposed join. * * \param sa1: Area from which the resultant originates. * \param sa2: Target area that will be replaced. */ -void ED_screen_draw_join_shape(ScrArea *sa1, ScrArea *sa2) +void screen_draw_join_highlight(ScrArea *sa1, ScrArea *sa2) { - uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + const eScreenDir dir = area_getorientation(sa1, sa2); + if (dir == SCREEN_DIR_NONE) { + return; + } + + /* Rect of the combined areas.*/ + const bool vertical = SCREEN_DIR_IS_VERTICAL(dir); + const rctf combined = { + .xmin = vertical ? MAX2(sa1->totrct.xmin, sa2->totrct.xmin) : + MIN2(sa1->totrct.xmin, sa2->totrct.xmin), + .xmax = vertical ? MIN2(sa1->totrct.xmax, sa2->totrct.xmax) : + MAX2(sa1->totrct.xmax, sa2->totrct.xmax), + .ymin = vertical ? MIN2(sa1->totrct.ymin, sa2->totrct.ymin) : + MAX2(sa1->totrct.ymin, sa2->totrct.ymin), + .ymax = vertical ? MAX2(sa1->totrct.ymax, sa2->totrct.ymax) : + MIN2(sa1->totrct.ymax, sa2->totrct.ymax), + }; + + uint pos_id = GPU_vertformat_attr_add( + immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + GPU_blend(GPU_BLEND_ALPHA); - GPU_line_width(1); - - /* blended join arrow */ - int dir = area_getorientation(sa1, sa2); - int dira = -1; - if (dir != -1) { - switch (dir) { - case 0: /* W */ - dir = 'r'; - dira = 'l'; - break; - case 1: /* N */ - dir = 'd'; - dira = 'u'; - break; - case 2: /* E */ - dir = 'l'; - dira = 'r'; - break; - case 3: /* S */ - dir = 'u'; - dira = 'd'; - break; + /* Highlight source (sa1) within combined area. */ + immUniformColor4fv((const float[4]){1.0f, 1.0f, 1.0f, 0.10f}); + immRectf(pos_id, + MAX2(sa1->totrct.xmin, combined.xmin), + MAX2(sa1->totrct.ymin, combined.ymin), + MIN2(sa1->totrct.xmax, combined.xmax), + MIN2(sa1->totrct.ymax, combined.ymax)); + + /* Highlight destination (sa2) within combined area. */ + immUniformColor4fv((const float[4]){0.0f, 0.0f, 0.0f, 0.25f}); + immRectf(pos_id, + MAX2(sa2->totrct.xmin, combined.xmin), + MAX2(sa2->totrct.ymin, combined.ymin), + MIN2(sa2->totrct.xmax, combined.xmax), + MIN2(sa2->totrct.ymax, combined.ymax)); + + int offset1; + int offset2; + area_getoffsets(sa1, sa2, dir, &offset1, &offset2); + if (offset1 < 0 || offset2 > 0) { + /* Show partial areas that will be closed. */ + immUniformColor4fv((const float[4]){0.0f, 0.0f, 0.0f, 0.8f}); + if (vertical) { + if (sa1->totrct.xmin < combined.xmin) { + immRectf(pos_id, sa1->totrct.xmin, sa1->totrct.ymin, combined.xmin, sa1->totrct.ymax); + } + if (sa2->totrct.xmin < combined.xmin) { + immRectf(pos_id, sa2->totrct.xmin, sa2->totrct.ymin, combined.xmin, sa2->totrct.ymax); + } + if (sa1->totrct.xmax > combined.xmax) { + immRectf(pos_id, combined.xmax, sa1->totrct.ymin, sa1->totrct.xmax, sa1->totrct.ymax); + } + if (sa2->totrct.xmax > combined.xmax) { + immRectf(pos_id, combined.xmax, sa2->totrct.ymin, sa2->totrct.xmax, sa2->totrct.ymax); + } + } + else { + if (sa1->totrct.ymin < combined.ymin) { + immRectf(pos_id, sa1->totrct.xmin, combined.ymin, sa1->totrct.xmax, sa1->totrct.ymin); + } + if (sa2->totrct.ymin < combined.ymin) { + immRectf(pos_id, sa2->totrct.xmin, combined.ymin, sa2->totrct.xmax, sa2->totrct.ymin); + } + if (sa1->totrct.ymax > combined.ymax) { + immRectf(pos_id, sa1->totrct.xmin, sa1->totrct.ymax, sa1->totrct.xmax, combined.ymax); + } + if (sa2->totrct.ymax > combined.ymax) { + immRectf(pos_id, sa2->totrct.xmin, sa2->totrct.ymax, sa2->totrct.xmax, combined.ymax); + } } - - GPU_blend(GPU_BLEND_ALPHA); - - scrarea_draw_shape_dark(sa2, dir, pos); - scrarea_draw_shape_light(sa1, dira, pos); - - GPU_blend(GPU_BLEND_NONE); } immUnbindProgram(); + GPU_blend(GPU_BLEND_NONE); + + /* Outline the combined area. */ + UI_draw_roundbox_corner_set(UI_CNR_ALL); + UI_draw_roundbox_4fv(&combined, false, 7 * U.pixelsize, (float[4]){1.0f, 1.0f, 1.0f, 0.8f}); } -void ED_screen_draw_split_preview(ScrArea *area, const int dir, const float fac) +void screen_draw_split_preview(ScrArea *area, const eScreenAxis dir_axis, const float fac) { uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); @@ -485,7 +334,7 @@ void ED_screen_draw_split_preview(ScrArea *area, const int dir, const float fac) immBegin(GPU_PRIM_LINES, 2); - if (dir == 'h') { + if (dir_axis == SCREEN_AXIS_H) { const float y = (1 - fac) * area->totrct.ymin + fac * area->totrct.ymax; immVertex2f(pos, area->totrct.xmin, y); @@ -503,7 +352,7 @@ void ED_screen_draw_split_preview(ScrArea *area, const int dir, const float fac) immEnd(); } else { - BLI_assert(dir == 'v'); + BLI_assert(dir_axis == SCREEN_AXIS_V); const float x = (1 - fac) * area->totrct.xmin + fac * area->totrct.xmax; immVertex2f(pos, x, area->totrct.ymin); diff --git a/source/blender/editors/screen/screen_edit.c b/source/blender/editors/screen/screen_edit.c index 7ad8eada3b9..6fb5f33d836 100644 --- a/source/blender/editors/screen/screen_edit.c +++ b/source/blender/editors/screen/screen_edit.c @@ -104,8 +104,12 @@ static void screen_delarea(bContext *C, bScreen *screen, ScrArea *area) MEM_freeN(area); } -ScrArea *area_split( - const wmWindow *win, bScreen *screen, ScrArea *area, char dir, float fac, int merge) +ScrArea *area_split(const wmWindow *win, + bScreen *screen, + ScrArea *area, + const eScreenAxis dir_axis, + const float fac, + const bool merge) { ScrArea *newa = NULL; @@ -116,7 +120,7 @@ ScrArea *area_split( rcti window_rect; WM_window_rect_calc(win, &window_rect); - short split = screen_geom_find_area_split_point(area, &window_rect, dir, fac); + short split = screen_geom_find_area_split_point(area, &window_rect, dir_axis, fac); if (split == 0) { return NULL; } @@ -125,7 +129,7 @@ ScrArea *area_split( * normally it shouldn't matter which is used since the copy should match the original * however with viewport rendering and python console this isn't the case. - campbell */ - if (dir == 'h') { + if (dir_axis == SCREEN_AXIS_H) { /* new vertices */ ScrVert *sv1 = screen_geom_vertex_add(screen, area->v1->vec.x, split); ScrVert *sv2 = screen_geom_vertex_add(screen, area->v4->vec.x, split); @@ -279,47 +283,44 @@ void screen_new_activate_prepare(const wmWindow *win, bScreen *screen_new) screen_new->do_draw = true; } -/* with area as center, sb is located at: 0=W, 1=N, 2=E, 3=S */ -/* -1 = not valid check */ -/* used with join operator */ -int area_getorientation(ScrArea *area, ScrArea *sb) +/** + * with `sa_a` as center, `sa_b` is located at: 0=W, 1=N, 2=E, 3=S + * -1 = not valid check. + * used with join operator. + */ +eScreenDir area_getorientation(ScrArea *sa_a, ScrArea *sa_b) { - if (area == NULL || sb == NULL) { - return -1; + if (sa_a == NULL || sa_b == NULL || sa_a == sa_b) { + return SCREEN_DIR_NONE; } - ScrVert *saBL = area->v1; - ScrVert *saTL = area->v2; - ScrVert *saTR = area->v3; - ScrVert *saBR = area->v4; + const vec2s *sa_bl = &sa_a->v1->vec; + const vec2s *sa_tl = &sa_a->v2->vec; + const vec2s *sa_tr = &sa_a->v3->vec; + const vec2s *sa_br = &sa_a->v4->vec; - ScrVert *sbBL = sb->v1; - ScrVert *sbTL = sb->v2; - ScrVert *sbTR = sb->v3; - ScrVert *sbBR = sb->v4; + const vec2s *sb_bl = &sa_b->v1->vec; + const vec2s *sb_tl = &sa_b->v2->vec; + const vec2s *sb_tr = &sa_b->v3->vec; + const vec2s *sb_br = &sa_b->v4->vec; - if (saBL->vec.x == sbBR->vec.x && saTL->vec.x == sbTR->vec.x) { /* area to right of sb = W */ - if ((abs(saBL->vec.y - sbBR->vec.y) <= AREAJOINTOLERANCE) && - (abs(saTL->vec.y - sbTR->vec.y) <= AREAJOINTOLERANCE)) { + if (sa_bl->x == sb_br->x && sa_tl->x == sb_tr->x) { /* sa_a to right of sa_b = W */ + if ((MIN2(sa_tl->y, sb_tr->y) - MAX2(sa_bl->y, sb_br->y)) > AREAJOINTOLERANCEY) { return 0; } } - else if (saTL->vec.y == sbBL->vec.y && - saTR->vec.y == sbBR->vec.y) { /* area to bottom of sb = N */ - if ((abs(saTL->vec.x - sbBL->vec.x) <= AREAJOINTOLERANCE) && - (abs(saTR->vec.x - sbBR->vec.x) <= AREAJOINTOLERANCE)) { + else if (sa_tl->y == sb_bl->y && sa_tr->y == sb_br->y) { /* sa_a to bottom of sa_b = N */ + if ((MIN2(sa_tr->x, sb_br->x) - MAX2(sa_tl->x, sb_bl->x)) > AREAJOINTOLERANCEX) { return 1; } } - else if (saTR->vec.x == sbTL->vec.x && saBR->vec.x == sbBL->vec.x) { /* area to left of sb = E */ - if ((abs(saTR->vec.y - sbTL->vec.y) <= AREAJOINTOLERANCE) && - (abs(saBR->vec.y - sbBL->vec.y) <= AREAJOINTOLERANCE)) { + else if (sa_tr->x == sb_tl->x && sa_br->x == sb_bl->x) { /* sa_a to left of sa_b = E */ + if ((MIN2(sa_tr->y, sb_tl->y) - MAX2(sa_br->y, sb_bl->y)) > AREAJOINTOLERANCEY) { return 2; } } - else if (saBL->vec.y == sbTL->vec.y && saBR->vec.y == sbTR->vec.y) { /* area on top of sb = S*/ - if ((abs(saBL->vec.x - sbTL->vec.x) <= AREAJOINTOLERANCE) && - (abs(saBR->vec.x - sbTR->vec.x) <= AREAJOINTOLERANCE)) { + else if (sa_bl->y == sb_tl->y && sa_br->y == sb_tr->y) { /* sa_a on top of sa_b = S */ + if ((MIN2(sa_br->x, sb_tr->x) - MAX2(sa_bl->x, sb_tl->x)) > AREAJOINTOLERANCEX) { return 3; } } @@ -327,6 +328,39 @@ int area_getorientation(ScrArea *area, ScrArea *sb) return -1; } +/** + * Get alignment offset of adjacent areas. 'dir' value is like #area_getorientation(). + */ +void area_getoffsets( + ScrArea *sa_a, ScrArea *sa_b, const eScreenDir dir, int *r_offset1, int *r_offset2) +{ + if (sa_a == NULL || sa_b == NULL) { + *r_offset1 = INT_MAX; + *r_offset2 = INT_MAX; + } + else if (dir == SCREEN_DIR_W) { /* West: sa on right and sa_b to the left. */ + *r_offset1 = sa_b->v3->vec.y - sa_a->v2->vec.y; + *r_offset2 = sa_b->v4->vec.y - sa_a->v1->vec.y; + } + else if (dir == SCREEN_DIR_N) { /* North: sa below and sa_b above. */ + *r_offset1 = sa_a->v2->vec.x - sa_b->v1->vec.x; + *r_offset2 = sa_a->v3->vec.x - sa_b->v4->vec.x; + } + else if (dir == SCREEN_DIR_E) { /* East: sa on left and sa_b to the right. */ + *r_offset1 = sa_b->v2->vec.y - sa_a->v3->vec.y; + *r_offset2 = sa_b->v1->vec.y - sa_a->v4->vec.y; + } + else if (dir == SCREEN_DIR_S) { /* South: sa above and sa_b below. */ + *r_offset1 = sa_a->v1->vec.x - sa_b->v2->vec.x; + *r_offset2 = sa_a->v4->vec.x - sa_b->v3->vec.x; + } + else { + BLI_assert(dir == SCREEN_DIR_NONE); + *r_offset1 = INT_MAX; + *r_offset2 = INT_MAX; + } +} + /* Screen verts with horizontal position equal to from_x are moved to to_x. */ static void screen_verts_halign(const wmWindow *win, const bScreen *screen, @@ -358,11 +392,11 @@ static void screen_verts_valign(const wmWindow *win, /* Adjust all screen edges to allow joining two areas. 'dir' value is like area_getorientation(). */ static void screen_areas_align( - bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2, const int dir) + bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2, const eScreenDir dir) { wmWindow *win = CTX_wm_window(C); - if (ELEM(dir, 0, 2)) { + if (SCREEN_DIR_IS_HORIZONTAL(dir)) { /* horizontal join, use average for new top and bottom. */ int top = (sa1->v2->vec.y + sa2->v2->vec.y) / 2; int bottom = (sa1->v4->vec.y + sa2->v4->vec.y) / 2; @@ -390,41 +424,47 @@ static void screen_areas_align( } } -/* Helper function to join 2 areas, it has a return value, 0=failed 1=success - * used by the split, join operators - */ -int screen_area_join(bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2) +/* Simple join of two areas without any splitting. Will return false if not possible. */ +static bool screen_area_join_aligned(bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2) { - int dir = area_getorientation(sa1, sa2); + const eScreenDir dir = area_getorientation(sa1, sa2); + if (dir == SCREEN_DIR_NONE) { + return false; + } - if (dir == -1) { - return 0; + int offset1; + int offset2; + area_getoffsets(sa1, sa2, dir, &offset1, &offset2); + + int tolerance = SCREEN_DIR_IS_HORIZONTAL(dir) ? AREAJOINTOLERANCEY : AREAJOINTOLERANCEX; + if ((abs(offset1) >= tolerance) || (abs(offset2) >= tolerance)) { + return false; } - /* Align areas if they are not. Do sanity checking before getting here. */ + /* Align areas if they are not. */ screen_areas_align(C, screen, sa1, sa2, dir); - if (dir == 0) { /* sa1 to right of sa2 = W */ - sa1->v1 = sa2->v1; /* BL */ - sa1->v2 = sa2->v2; /* TL */ + if (dir == SCREEN_DIR_W) { /* sa1 to right of sa2 = West. */ + sa1->v1 = sa2->v1; /* BL */ + sa1->v2 = sa2->v2; /* TL */ screen_geom_edge_add(screen, sa1->v2, sa1->v3); screen_geom_edge_add(screen, sa1->v1, sa1->v4); } - else if (dir == 1) { /* sa1 to bottom of sa2 = N */ - sa1->v2 = sa2->v2; /* TL */ - sa1->v3 = sa2->v3; /* TR */ + else if (dir == SCREEN_DIR_N) { /* sa1 to bottom of sa2 = North. */ + sa1->v2 = sa2->v2; /* TL */ + sa1->v3 = sa2->v3; /* TR */ screen_geom_edge_add(screen, sa1->v1, sa1->v2); screen_geom_edge_add(screen, sa1->v3, sa1->v4); } - else if (dir == 2) { /* sa1 to left of sa2 = E */ - sa1->v3 = sa2->v3; /* TR */ - sa1->v4 = sa2->v4; /* BR */ + else if (dir == SCREEN_DIR_E) { /* sa1 to left of sa2 = East. */ + sa1->v3 = sa2->v3; /* TR */ + sa1->v4 = sa2->v4; /* BR */ screen_geom_edge_add(screen, sa1->v2, sa1->v3); screen_geom_edge_add(screen, sa1->v1, sa1->v4); } - else if (dir == 3) { /* sa1 on top of sa2 = S */ - sa1->v1 = sa2->v1; /* BL */ - sa1->v4 = sa2->v4; /* BR */ + else if (dir == SCREEN_DIR_S) { /* sa1 on top of sa2 = South. */ + sa1->v1 = sa2->v1; /* BL */ + sa1->v4 = sa2->v4; /* BR */ screen_geom_edge_add(screen, sa1->v1, sa1->v2); screen_geom_edge_add(screen, sa1->v3, sa1->v4); } @@ -434,7 +474,104 @@ int screen_area_join(bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2) /* Update preview thumbnail */ BKE_icon_changed(screen->id.icon_id); - return 1; + return true; +} + +/* Slice off and return new area. "Reverse" gives right/bottom, rather than left/top. */ +static ScrArea *screen_area_trim( + bContext *C, bScreen *screen, ScrArea **area, int size, eScreenDir dir, bool reverse) +{ + const bool vertical = SCREEN_DIR_IS_VERTICAL(dir); + if (abs(size) < (vertical ? AREAJOINTOLERANCEX : AREAJOINTOLERANCEY)) { + return NULL; + } + + /* Measurement with ScrVerts because winx and winy might not be correct at this time. */ + float fac = abs(size) / (float)(vertical ? ((*area)->v3->vec.x - (*area)->v1->vec.x) : + ((*area)->v3->vec.y - (*area)->v1->vec.y)); + fac = (reverse == vertical) ? 1.0f - fac : fac; + ScrArea *newsa = area_split( + CTX_wm_window(C), screen, *area, vertical ? SCREEN_AXIS_V : SCREEN_AXIS_H, fac, true); + + /* area_split always returns smallest of the two areas, so might have to swap. */ + if (((fac > 0.5f) == vertical) != reverse) { + ScrArea *temp = *area; + *area = newsa; + newsa = temp; + } + + return newsa; +} + +/* Join any two neighboring areas. Might create new areas, kept if over min_remainder. */ +static bool screen_area_join_ex( + bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2, bool close_all_remainders) +{ + const eScreenDir dir = area_getorientation(sa1, sa2); + if (dir == SCREEN_DIR_NONE) { + return false; + } + + int offset1; + int offset2; + area_getoffsets(sa1, sa2, dir, &offset1, &offset2); + + /* Split Left/Top into new area if overhanging. */ + ScrArea *side1 = screen_area_trim(C, screen, (offset1 > 0) ? &sa2 : &sa1, offset1, dir, false); + + /* Split Right/Bottom into new area if overhanging. */ + ScrArea *side2 = screen_area_trim(C, screen, (offset2 > 0) ? &sa1 : &sa2, offset2, dir, true); + + /* The two areas now line up, so join them. */ + screen_area_join_aligned(C, screen, sa1, sa2); + + if (close_all_remainders || offset1 < 0 || offset2 > 0) { + /* Close both if trimming `sa1`. */ + screen_area_close(C, screen, side1); + screen_area_close(C, screen, side2); + } + + BKE_icon_changed(screen->id.icon_id); + return true; +} + +/* Join any two neighboring areas. Might involve complex changes. */ +int screen_area_join(bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2) +{ + return screen_area_join_ex(C, screen, sa1, sa2, false); +} + +/* Close a screen area, allowing most-aligned neighbor to take its place. */ +bool screen_area_close(struct bContext *C, bScreen *screen, ScrArea *area) +{ + if (area == NULL) { + return false; + } + + ScrArea *sa2 = NULL; + float best_alignment = 0.0f; + + LISTBASE_FOREACH (ScrArea *, neighbor, &screen->areabase) { + const eScreenDir dir = area_getorientation(area, neighbor); + /* Must at least partially share an edge and not be a global area. */ + if ((dir != SCREEN_DIR_NONE) && (neighbor->global == NULL)) { + /* Winx/Winy might not be updated yet, so get lengths from verts. */ + const bool vertical = SCREEN_DIR_IS_VERTICAL(dir); + const int area_length = vertical ? (area->v3->vec.x - area->v1->vec.x) : + (area->v3->vec.y - area->v1->vec.y); + const int ar_length = vertical ? (neighbor->v3->vec.x - neighbor->v1->vec.x) : + (neighbor->v3->vec.y - neighbor->v1->vec.y); + /* Calculate the ratio of the lengths of the shared edges. */ + float alignment = MIN2(area_length, ar_length) / (float)MAX2(area_length, ar_length); + if (alignment > best_alignment) { + best_alignment = alignment; + sa2 = neighbor; + } + } + } + + /* Join from neighbor into this area to close it. */ + return screen_area_join_ex(C, screen, sa2, area, true); } /* ****************** EXPORTED API TO OTHER MODULES *************************** */ diff --git a/source/blender/editors/screen/screen_geometry.c b/source/blender/editors/screen/screen_geometry.c index ac159f4d633..51edad0332b 100644 --- a/source/blender/editors/screen/screen_geometry.c +++ b/source/blender/editors/screen/screen_geometry.c @@ -304,7 +304,7 @@ void screen_geom_vertices_scale(const wmWindow *win, bScreen *screen) */ short screen_geom_find_area_split_point(const ScrArea *area, const rcti *window_rect, - char dir, + const eScreenAxis dir_axis, float fac) { const int cur_area_width = screen_geom_area_width(area); @@ -313,17 +313,21 @@ short screen_geom_find_area_split_point(const ScrArea *area, const short area_min_y = ED_area_headersize(); /* area big enough? */ - if ((dir == 'v') && (cur_area_width <= 2 * area_min_x)) { - return 0; + if (dir_axis == SCREEN_AXIS_V) { + if (cur_area_width <= 2 * area_min_x) { + return 0; + } } - if ((dir == 'h') && (cur_area_height <= 2 * area_min_y)) { - return 0; + else if (dir_axis == SCREEN_AXIS_H) { + if (cur_area_height <= 2 * area_min_y) { + return 0; + } } /* to be sure */ CLAMP(fac, 0.0f, 1.0f); - if (dir == 'h') { + if (dir_axis == SCREEN_AXIS_H) { short y = area->v1->vec.y + round_fl_to_short(fac * cur_area_height); int area_min = area_min_y; @@ -373,13 +377,13 @@ void screen_geom_select_connected_edge(const wmWindow *win, ScrEdge *edge) { bScreen *screen = WM_window_get_active_screen(win); - /* 'dir' is the direction of EDGE */ - char dir; + /* 'dir_axis' is the direction of EDGE */ + eScreenAxis dir_axis; if (edge->v1->vec.x == edge->v2->vec.x) { - dir = 'v'; + dir_axis = SCREEN_AXIS_V; } else { - dir = 'h'; + dir_axis = SCREEN_AXIS_H; } ED_screen_verts_iter(win, screen, sv) @@ -396,13 +400,13 @@ void screen_geom_select_connected_edge(const wmWindow *win, ScrEdge *edge) oneselected = false; LISTBASE_FOREACH (ScrEdge *, se, &screen->edgebase) { if (se->v1->flag + se->v2->flag == 1) { - if (dir == 'h') { + if (dir_axis == SCREEN_AXIS_H) { if (se->v1->vec.y == se->v2->vec.y) { se->v1->flag = se->v2->flag = 1; oneselected = true; } } - if (dir == 'v') { + else if (dir_axis == SCREEN_AXIS_V) { if (se->v1->vec.x == se->v2->vec.x) { se->v1->flag = se->v2->flag = 1; oneselected = true; diff --git a/source/blender/editors/screen/screen_intern.h b/source/blender/editors/screen/screen_intern.h index c51ff559786..683f2844371 100644 --- a/source/blender/editors/screen/screen_intern.h +++ b/source/blender/editors/screen/screen_intern.h @@ -29,12 +29,37 @@ struct bContextDataResult; /* internal exports only */ +typedef enum eScreenDir { + /** This can mean unset, unknown or invalid. */ + SCREEN_DIR_NONE = -1, + /** West/Left. */ + SCREEN_DIR_W = 0, + /** North/Up. */ + SCREEN_DIR_N = 1, + /** East/Right. */ + SCREEN_DIR_E = 2, + /** South/Down. */ + SCREEN_DIR_S = 3, +} eScreenDir; + +#define SCREEN_DIR_IS_VERTICAL(dir) (ELEM(dir, SCREEN_DIR_N, SCREEN_DIR_S)) +#define SCREEN_DIR_IS_HORIZONTAL(dir) (ELEM(dir, SCREEN_DIR_W, SCREEN_DIR_E)) + +typedef enum eScreenAxis { + /** Horizontal. */ + SCREEN_AXIS_H = 'h', + /** Vertical. */ + SCREEN_AXIS_V = 'v', +} eScreenAxis; + #define AZONESPOTW UI_HEADER_OFFSET /* width of corner #AZone - max */ #define AZONESPOTH (0.6f * U.widget_unit) /* height of corner #AZone */ #define AZONEFADEIN (5.0f * U.widget_unit) /* when #AZone is totally visible */ #define AZONEFADEOUT (6.5f * U.widget_unit) /* when we start seeing the #AZone */ -#define AREAJOINTOLERANCE (1.0f * U.widget_unit) /* Edges must be close to allow joining. */ +/* Edges must be within these to allow joining. */ +#define AREAJOINTOLERANCEX (AREAMINX * U.dpi_fac) +#define AREAJOINTOLERANCEY (HEADERY * U.dpi_fac) /* Expanded interaction influence of area borders. */ #define BORDERPADDING (U.dpi_fac + U.pixelsize) @@ -44,6 +69,10 @@ void ED_area_data_copy(ScrArea *area_dst, ScrArea *area_src, const bool do_free) void ED_area_data_swap(ScrArea *area_dst, ScrArea *area_src); void region_toggle_hidden(struct bContext *C, ARegion *region, const bool do_fade); +/* screen_draw.c */ +void screen_draw_join_highlight(struct ScrArea *sa1, struct ScrArea *sa2); +void screen_draw_split_preview(struct ScrArea *area, const eScreenAxis dir_axis, const float fac); + /* screen_edit.c */ bScreen *screen_add(struct Main *bmain, const char *name, const rcti *rect); void screen_data_copy(bScreen *to, bScreen *from); @@ -54,11 +83,17 @@ void screen_change_prepare(bScreen *screen_old, struct Main *bmain, struct bContext *C, wmWindow *win); -ScrArea *area_split( - const wmWindow *win, bScreen *screen, ScrArea *area, char dir, float fac, int merge); +ScrArea *area_split(const wmWindow *win, + bScreen *screen, + ScrArea *area, + const eScreenAxis dir_axis, + const float fac, + const bool merge); int screen_area_join(struct bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2); -int area_getorientation(ScrArea *area, ScrArea *sb); - +eScreenDir area_getorientation(ScrArea *sa_a, ScrArea *sa_b); +void area_getoffsets( + ScrArea *sa_a, ScrArea *sa_b, const eScreenDir dir, int *r_offset1, int *r_offset2); +bool screen_area_close(struct bContext *C, bScreen *screen, ScrArea *area); struct AZone *ED_area_actionzone_find_xy(ScrArea *area, const int xy[2]); /* screen_geometry.c */ @@ -80,7 +115,7 @@ ScrEdge *screen_geom_find_active_scredge(const wmWindow *win, void screen_geom_vertices_scale(const wmWindow *win, bScreen *screen); short screen_geom_find_area_split_point(const ScrArea *area, const rcti *window_rect, - char dir, + const eScreenAxis dir_axis, float fac); void screen_geom_select_connected_edge(const wmWindow *win, ScrEdge *edge); diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c index 765877d6a8e..6b8d4e73f12 100644 --- a/source/blender/editors/screen/screen_ops.c +++ b/source/blender/editors/screen/screen_ops.c @@ -694,7 +694,9 @@ static bool screen_active_editable(bContext *C) typedef struct sActionzoneData { ScrArea *sa1, *sa2; AZone *az; - int x, y, gesture_dir, modifier; + int x, y; + eScreenDir gesture_dir; + int modifier; } sActionzoneData; /* quick poll to save operators to be created and handled */ @@ -1045,16 +1047,16 @@ static int actionzone_modal(bContext *C, wmOperator *op, const wmEvent *event) /* Calculate gesture cardinal direction. */ if (delta_y > abs(delta_x)) { - sad->gesture_dir = 'n'; + sad->gesture_dir = SCREEN_DIR_N; } else if (delta_x >= abs(delta_y)) { - sad->gesture_dir = 'e'; + sad->gesture_dir = SCREEN_DIR_E; } else if (delta_y < -abs(delta_x)) { - sad->gesture_dir = 's'; + sad->gesture_dir = SCREEN_DIR_S; } else { - sad->gesture_dir = 'w'; + sad->gesture_dir = SCREEN_DIR_W; } bool is_gesture; @@ -1071,22 +1073,24 @@ static int actionzone_modal(bContext *C, wmOperator *op, const wmEvent *event) /* Are we still in same area? */ if (BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y) == sad->sa1) { /* Same area, so possible split. */ - WM_cursor_set( - win, (ELEM(sad->gesture_dir, 'n', 's')) ? WM_CURSOR_H_SPLIT : WM_CURSOR_V_SPLIT); + WM_cursor_set(win, + SCREEN_DIR_IS_VERTICAL(sad->gesture_dir) ? WM_CURSOR_H_SPLIT : + WM_CURSOR_V_SPLIT); is_gesture = (delta_max > split_threshold); } else { /* Different area, so possible join. */ - if (sad->gesture_dir == 'n') { + if (sad->gesture_dir == SCREEN_DIR_N) { WM_cursor_set(win, WM_CURSOR_N_ARROW); } - else if (sad->gesture_dir == 's') { + else if (sad->gesture_dir == SCREEN_DIR_S) { WM_cursor_set(win, WM_CURSOR_S_ARROW); } - else if (sad->gesture_dir == 'e') { + else if (sad->gesture_dir == SCREEN_DIR_E) { WM_cursor_set(win, WM_CURSOR_E_ARROW); } else { + BLI_assert(sad->gesture_dir == SCREEN_DIR_W); WM_cursor_set(win, WM_CURSOR_W_ARROW); } is_gesture = (delta_max > join_threshold); @@ -1395,6 +1399,58 @@ static void SCREEN_OT_area_dupli(wmOperatorType *ot) /** \} */ /* -------------------------------------------------------------------- */ +/** \name Area Close Operator + * + * Close selected area, replace by expanding a neighbor + * \{ */ + +/* operator callback */ +static int area_close_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *UNUSED(event)) +{ + ScrArea *area = CTX_wm_area(C); + if ((area != NULL) && screen_area_close(C, CTX_wm_screen(C), area)) { + WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL); + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +static bool area_close_poll(bContext *C) +{ + if (!ED_operator_areaactive(C)) { + return false; + } + + ScrArea *area = CTX_wm_area(C); + + if (ED_area_is_global(area)) { + return false; + } + + bScreen *screen = CTX_wm_screen(C); + + /* Can this area join with ANY other area? */ + LISTBASE_FOREACH (ScrArea *, ar, &screen->areabase) { + if (area_getorientation(ar, area) != -1) { + return true; + } + } + + return false; +} + +static void SCREEN_OT_area_close(wmOperatorType *ot) +{ + ot->name = "Close Area"; + ot->description = "Close selected area"; + ot->idname = "SCREEN_OT_area_close"; + ot->invoke = area_close_invoke; + ot->poll = area_close_poll; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Move Area Edge Operator * \{ */ @@ -1428,7 +1484,7 @@ static void SCREEN_OT_area_dupli(wmOperatorType *ot) typedef struct sAreaMoveData { int bigger, smaller, origval, step; - char dir; + eScreenAxis dir_axis; enum AreaMoveSnapType { /* Snapping disabled */ SNAP_NONE = 0, @@ -1447,7 +1503,7 @@ typedef struct sAreaMoveData { * need window bounds in order to get correct limits */ static void area_move_set_limits(wmWindow *win, bScreen *screen, - int dir, + const eScreenAxis dir_axis, int *bigger, int *smaller, bool *use_bigger_smaller_snap) @@ -1500,7 +1556,7 @@ static void area_move_set_limits(wmWindow *win, WM_window_rect_calc(win, &window_rect); LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { - if (dir == 'h') { + if (dir_axis == SCREEN_AXIS_H) { int areamin = ED_area_headersize(); if (area->v1->vec.y > window_rect.ymin) { @@ -1563,8 +1619,8 @@ static bool area_move_init(bContext *C, wmOperator *op) sAreaMoveData *md = MEM_callocN(sizeof(sAreaMoveData), "sAreaMoveData"); op->customdata = md; - md->dir = screen_geom_edge_is_horizontal(actedge) ? 'h' : 'v'; - if (md->dir == 'h') { + md->dir_axis = screen_geom_edge_is_horizontal(actedge) ? SCREEN_AXIS_H : SCREEN_AXIS_V; + if (md->dir_axis == SCREEN_AXIS_H) { md->origval = actedge->v1->vec.y; } else { @@ -1579,7 +1635,8 @@ static bool area_move_init(bContext *C, wmOperator *op) } bool use_bigger_smaller_snap = false; - area_move_set_limits(win, screen, md->dir, &md->bigger, &md->smaller, &use_bigger_smaller_snap); + area_move_set_limits( + win, screen, md->dir_axis, &md->bigger, &md->smaller, &use_bigger_smaller_snap); md->snap_type = use_bigger_smaller_snap ? SNAP_BIGGER_SMALLER_ONLY : SNAP_AREAGRID; @@ -1590,7 +1647,7 @@ static int area_snap_calc_location(const bScreen *screen, const enum AreaMoveSnapType snap_type, const int delta, const int origval, - const int dir, + const eScreenAxis dir_axis, const int bigger, const int smaller) { @@ -1615,7 +1672,7 @@ static int area_snap_calc_location(const bScreen *screen, break; case SNAP_FRACTION_AND_ADJACENT: { - const int axis = (dir == 'v') ? 0 : 1; + const int axis = (dir_axis == SCREEN_AXIS_V) ? 0 : 1; int snap_dist_best = INT_MAX; { const float div_array[] = { @@ -1683,7 +1740,7 @@ static int area_snap_calc_location(const bScreen *screen, static void area_move_apply_do(const bContext *C, int delta, const int origval, - const int dir, + const eScreenAxis dir_axis, const int bigger, const int smaller, const enum AreaMoveSnapType snap_type) @@ -1701,11 +1758,12 @@ static void area_move_apply_do(const bContext *C, final_loc = origval + delta; } else { - final_loc = area_snap_calc_location(screen, snap_type, delta, origval, dir, bigger, smaller); + final_loc = area_snap_calc_location( + screen, snap_type, delta, origval, dir_axis, bigger, smaller); } BLI_assert(final_loc != -1); - short axis = (dir == 'v') ? 0 : 1; + short axis = (dir_axis == SCREEN_AXIS_V) ? 0 : 1; ED_screen_verts_iter(win, screen, v1) { @@ -1761,7 +1819,7 @@ static void area_move_apply(bContext *C, wmOperator *op) sAreaMoveData *md = op->customdata; int delta = RNA_int_get(op->ptr, "delta"); - area_move_apply_do(C, delta, md->origval, md->dir, md->bigger, md->smaller, md->snap_type); + area_move_apply_do(C, delta, md->origval, md->dir_axis, md->bigger, md->smaller, md->snap_type); } static void area_move_exit(bContext *C, wmOperator *op) @@ -1826,7 +1884,7 @@ static int area_move_modal(bContext *C, wmOperator *op, const wmEvent *event) int x = RNA_int_get(op->ptr, "x"); int y = RNA_int_get(op->ptr, "y"); - int delta = (md->dir == 'v') ? event->x - x : event->y - y; + const int delta = (md->dir_axis == SCREEN_AXIS_V) ? event->x - x : event->y - y; RNA_int_set(op->ptr, "delta", delta); area_move_apply(C, op); @@ -1892,7 +1950,7 @@ static void SCREEN_OT_area_move(wmOperatorType *ot) /* * operator state vars: * fac spit point - * dir direction 'v' or 'h' + * dir direction #SCREEN_AXIS_V or #SCREEN_AXIS_H * * operator customdata: * area pointer to (active) area @@ -1929,7 +1987,7 @@ typedef struct sAreaSplitData { int delta; /* delta move edge */ int origmin, origsize; /* to calculate fac, for property storage */ int previewmode; /* draw previewline, then split */ - void *draw_callback; /* call `ED_screen_draw_split_preview` */ + void *draw_callback; /* call `screen_draw_split_preview` */ bool do_snap; ScrEdge *nedge; /* new edge */ @@ -1944,10 +2002,10 @@ static void area_split_draw_cb(const struct wmWindow *UNUSED(win), void *userdat sAreaSplitData *sd = op->customdata; if (sd->sarea) { - int dir = RNA_enum_get(op->ptr, "direction"); + const eScreenAxis dir_axis = RNA_enum_get(op->ptr, "direction"); float fac = RNA_float_get(op->ptr, "factor"); - ED_screen_draw_split_preview(sd->sarea, dir, fac); + screen_draw_split_preview(sd->sarea, dir_axis, fac); } } @@ -1974,14 +2032,18 @@ static bool area_split_init(bContext *C, wmOperator *op) } /* required properties */ - int dir = RNA_enum_get(op->ptr, "direction"); + const eScreenAxis dir_axis = RNA_enum_get(op->ptr, "direction"); /* minimal size */ - if (dir == 'v' && area->winx < 2 * AREAMINX) { - return false; + if (dir_axis == SCREEN_AXIS_V) { + if (area->winx < 2 * AREAMINX) { + return false; + } } - if (dir == 'h' && area->winy < 2 * ED_area_headersize()) { - return false; + else { + if (area->winy < 2 * ED_area_headersize()) { + return false; + } } /* custom data */ @@ -1989,7 +2051,7 @@ static bool area_split_init(bContext *C, wmOperator *op) op->customdata = sd; sd->sarea = area; - if (dir == 'v') { + if (dir_axis == SCREEN_AXIS_V) { sd->origmin = area->v1->vec.x; sd->origsize = area->v4->vec.x - sd->origmin; } @@ -2038,9 +2100,9 @@ static bool area_split_apply(bContext *C, wmOperator *op) sAreaSplitData *sd = (sAreaSplitData *)op->customdata; float fac = RNA_float_get(op->ptr, "factor"); - int dir = RNA_enum_get(op->ptr, "direction"); + const eScreenAxis dir_axis = RNA_enum_get(op->ptr, "direction"); - sd->narea = area_split(win, screen, sd->sarea, dir, fac, 0); /* 0 = no merge */ + sd->narea = area_split(win, screen, sd->sarea, dir_axis, fac, false); /* false = no merge */ if (sd->narea == NULL) { return false; @@ -2057,7 +2119,7 @@ static bool area_split_apply(bContext *C, wmOperator *op) sd->nedge->v1->editflag = 1; sd->nedge->v2->editflag = 1; - if (dir == 'h') { + if (dir_axis == SCREEN_AXIS_H) { sd->origval = sd->nedge->v1->vec.y; } else { @@ -2106,8 +2168,8 @@ static void area_split_exit(bContext *C, wmOperator *op) static void area_split_preview_update_cursor(bContext *C, wmOperator *op) { wmWindow *win = CTX_wm_window(C); - int dir = RNA_enum_get(op->ptr, "direction"); - WM_cursor_set(win, dir == 'h' ? WM_CURSOR_H_SPLIT : WM_CURSOR_V_SPLIT); + const eScreenAxis dir_axis = RNA_enum_get(op->ptr, "direction"); + WM_cursor_set(win, (dir_axis == SCREEN_AXIS_H) ? WM_CURSOR_H_SPLIT : WM_CURSOR_V_SPLIT); } /* UI callback, adds new handler */ @@ -2123,7 +2185,7 @@ static int area_split_invoke(bContext *C, wmOperator *op, const wmEvent *event) PropertyRNA *prop_factor = RNA_struct_find_property(op->ptr, "factor"); PropertyRNA *prop_cursor = RNA_struct_find_property(op->ptr, "cursor"); - int dir; + eScreenAxis dir_axis; if (event->type == EVT_ACTIONZONE_AREA) { sActionzoneData *sad = event->customdata; @@ -2151,12 +2213,12 @@ static int area_split_invoke(bContext *C, wmOperator *op, const wmEvent *event) float factor; /* Prepare operator state vars. */ - if (ELEM(sad->gesture_dir, 'n', 's')) { - dir = 'h'; + if (SCREEN_DIR_IS_VERTICAL(sad->gesture_dir)) { + dir_axis = SCREEN_AXIS_H; factor = factor_h; } else { - dir = 'v'; + dir_axis = SCREEN_AXIS_V; factor = factor_v; } @@ -2166,7 +2228,7 @@ static int area_split_invoke(bContext *C, wmOperator *op, const wmEvent *event) RNA_property_float_set(op->ptr, prop_factor, factor); - RNA_property_enum_set(op->ptr, prop_dir, dir); + RNA_property_enum_set(op->ptr, prop_dir, dir_axis); /* general init, also non-UI case, adds customdata, sets area and defaults */ if (!area_split_init(C, op)) { @@ -2178,8 +2240,8 @@ static int area_split_invoke(bContext *C, wmOperator *op, const wmEvent *event) if (area == NULL) { return OPERATOR_CANCELLED; } - dir = RNA_property_enum_get(op->ptr, prop_dir); - if (dir == 'h') { + dir_axis = RNA_property_enum_get(op->ptr, prop_dir); + if (dir_axis == SCREEN_AXIS_H) { RNA_property_float_set( op->ptr, prop_factor, ((float)(event->x - area->v1->vec.x)) / (float)area->winx); } @@ -2212,9 +2274,9 @@ static int area_split_invoke(bContext *C, wmOperator *op, const wmEvent *event) return OPERATOR_CANCELLED; } - dir = screen_geom_edge_is_horizontal(actedge) ? 'v' : 'h'; + dir_axis = screen_geom_edge_is_horizontal(actedge) ? SCREEN_AXIS_V : SCREEN_AXIS_H; - RNA_property_enum_set(op->ptr, prop_dir, dir); + RNA_property_enum_set(op->ptr, prop_dir, dir_axis); /* special case, adds customdata, sets defaults */ if (!area_split_menu_init(C, op)) { @@ -2227,7 +2289,7 @@ static int area_split_invoke(bContext *C, wmOperator *op, const wmEvent *event) if (event->type == EVT_ACTIONZONE_AREA) { /* do the split */ if (area_split_apply(C, op)) { - area_move_set_limits(win, screen, dir, &sd->bigger, &sd->smaller, NULL); + area_move_set_limits(win, screen, dir_axis, &sd->bigger, &sd->smaller, NULL); /* add temp handler for edge move or cancel */ G.moving |= G_TRANSFORM_WM; @@ -2315,8 +2377,9 @@ static int area_split_modal(bContext *C, wmOperator *op, const wmEvent *event) else { if (event->val == KM_PRESS) { if (sd->sarea) { - int dir = RNA_property_enum_get(op->ptr, prop_dir); - RNA_property_enum_set(op->ptr, prop_dir, (dir == 'v') ? 'h' : 'v'); + const eScreenAxis dir_axis = RNA_property_enum_get(op->ptr, prop_dir); + RNA_property_enum_set( + op->ptr, prop_dir, (dir_axis == SCREEN_AXIS_V) ? SCREEN_AXIS_H : SCREEN_AXIS_V); area_split_preview_update_cursor(C, op); update_factor = true; } @@ -2337,9 +2400,9 @@ static int area_split_modal(bContext *C, wmOperator *op, const wmEvent *event) } if (update_factor) { - const int dir = RNA_property_enum_get(op->ptr, prop_dir); + const eScreenAxis dir_axis = RNA_property_enum_get(op->ptr, prop_dir); - sd->delta = (dir == 'v') ? event->x - sd->origval : event->y - sd->origval; + sd->delta = (dir_axis == SCREEN_AXIS_V) ? event->x - sd->origval : event->y - sd->origval; if (sd->previewmode == 0) { if (sd->do_snap) { @@ -2347,12 +2410,12 @@ static int area_split_modal(bContext *C, wmOperator *op, const wmEvent *event) SNAP_FRACTION_AND_ADJACENT, sd->delta, sd->origval, - dir, + dir_axis, sd->bigger, sd->smaller); sd->delta = snap_loc - sd->origval; } - area_move_apply_do(C, sd->delta, sd->origval, dir, sd->bigger, sd->smaller, SNAP_NONE); + area_move_apply_do(C, sd->delta, sd->origval, dir_axis, sd->bigger, sd->smaller, SNAP_NONE); } else { if (sd->sarea) { @@ -2363,7 +2426,7 @@ static int area_split_modal(bContext *C, wmOperator *op, const wmEvent *event) if (sd->sarea) { ScrArea *area = sd->sarea; - if (dir == 'v') { + if (dir_axis == SCREEN_AXIS_V) { sd->origmin = area->v1->vec.x; sd->origsize = area->v4->vec.x - sd->origmin; } @@ -2379,7 +2442,7 @@ static int area_split_modal(bContext *C, wmOperator *op, const wmEvent *event) SNAP_FRACTION_AND_ADJACENT, sd->delta, sd->origval, - dir, + dir_axis, sd->origmin + sd->origsize, -sd->origmin); @@ -2401,8 +2464,8 @@ static int area_split_modal(bContext *C, wmOperator *op, const wmEvent *event) } static const EnumPropertyItem prop_direction_items[] = { - {'h', "HORIZONTAL", 0, "Horizontal", ""}, - {'v', "VERTICAL", 0, "Vertical", ""}, + {SCREEN_AXIS_H, "HORIZONTAL", 0, "Horizontal", ""}, + {SCREEN_AXIS_V, "VERTICAL", 0, "Vertical", ""}, {0, NULL, 0, NULL, NULL}, }; @@ -2423,7 +2486,7 @@ static void SCREEN_OT_area_split(wmOperatorType *ot) ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL; /* rna */ - RNA_def_enum(ot->srna, "direction", prop_direction_items, 'h', "Direction", ""); + RNA_def_enum(ot->srna, "direction", prop_direction_items, SCREEN_AXIS_H, "Direction", ""); RNA_def_float(ot->srna, "factor", 0.5f, 0.0, 1.0, "Factor", "", 0.0, 1.0); RNA_def_int_vector( ot->srna, "cursor", 2, NULL, INT_MIN, INT_MAX, "Cursor", "", INT_MIN, INT_MAX); @@ -3218,9 +3281,10 @@ static void SCREEN_OT_screen_full_area(wmOperatorType *ot) */ typedef struct sAreaJoinData { - ScrArea *sa1; /* first area to be considered */ - ScrArea *sa2; /* second area to be considered */ - void *draw_callback; /* call `ED_screen_draw_join_shape` */ + ScrArea *sa1; /* Potential source area (kept). */ + ScrArea *sa2; /* Potential target area (removed or reduced). */ + eScreenDir dir; /* Direction of potential join. */ + void *draw_callback; /* call #screen_draw_join_highlight */ } sAreaJoinData; @@ -3229,8 +3293,8 @@ static void area_join_draw_cb(const struct wmWindow *UNUSED(win), void *userdata const wmOperator *op = userdata; sAreaJoinData *sd = op->customdata; - if (sd->sa1 && sd->sa2) { - ED_screen_draw_join_shape(sd->sa1, sd->sa2); + if (sd->sa1 && sd->sa2 && (sd->dir != SCREEN_DIR_NONE)) { + screen_draw_join_highlight(sd->sa1, sd->sa2); } } @@ -3252,6 +3316,7 @@ static bool area_join_init(bContext *C, wmOperator *op, ScrArea *sa1, ScrArea *s jd->sa1 = sa1; jd->sa2 = sa2; + jd->dir = SCREEN_DIR_NONE; op->customdata = jd; @@ -3264,7 +3329,7 @@ static bool area_join_init(bContext *C, wmOperator *op, ScrArea *sa1, ScrArea *s static bool area_join_apply(bContext *C, wmOperator *op) { sAreaJoinData *jd = (sAreaJoinData *)op->customdata; - if (!jd) { + if (!jd || (jd->dir == SCREEN_DIR_NONE)) { return false; } @@ -3366,61 +3431,30 @@ static int area_join_modal(bContext *C, wmOperator *op, const wmEvent *event) case MOUSEMOVE: { ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y); - int dir = -1; + jd->dir = area_getorientation(jd->sa1, jd->sa2); - if (area) { - if (jd->sa1 != area) { - dir = area_getorientation(jd->sa1, area); - if (dir != -1) { - jd->sa2 = area; - } - else { - /* we are not bordering on the previously selected area - * we check if area has common border with the one marked for removal - * in this case we can swap areas. - */ - dir = area_getorientation(area, jd->sa2); - if (dir != -1) { - jd->sa1 = jd->sa2; - jd->sa2 = area; - } - else { - jd->sa2 = NULL; - } - } - WM_event_add_notifier(C, NC_WINDOW, NULL); - } - else { - /* we are back in the area previously selected for keeping - * we swap the areas if possible to allow user to choose */ - if (jd->sa2 != NULL) { - jd->sa1 = jd->sa2; - jd->sa2 = area; - dir = area_getorientation(jd->sa1, jd->sa2); - if (dir == -1) { - printf("oops, didn't expect that!\n"); - } - } - else { - dir = area_getorientation(jd->sa1, area); - if (dir != -1) { - jd->sa2 = area; - } - } - WM_event_add_notifier(C, NC_WINDOW, NULL); - } + if (area == jd->sa1) { + /* Hovering current source, so change direction. */ + jd->sa1 = jd->sa2; + jd->sa2 = area; + jd->dir = area_getorientation(jd->sa1, jd->sa2); + } + else if (area != jd->sa2) { + jd->dir = SCREEN_DIR_NONE; } - if (dir == 1) { + WM_event_add_notifier(C, NC_WINDOW, NULL); + + if (jd->dir == SCREEN_DIR_N) { WM_cursor_set(win, WM_CURSOR_N_ARROW); } - else if (dir == 3) { + else if (jd->dir == SCREEN_DIR_S) { WM_cursor_set(win, WM_CURSOR_S_ARROW); } - else if (dir == 2) { + else if (jd->dir == SCREEN_DIR_E) { WM_cursor_set(win, WM_CURSOR_E_ARROW); } - else if (dir == 0) { + else if (jd->dir == SCREEN_DIR_W) { WM_cursor_set(win, WM_CURSOR_W_ARROW); } else { @@ -3431,6 +3465,10 @@ static int area_join_modal(bContext *C, wmOperator *op, const wmEvent *event) } case LEFTMOUSE: if (event->val == KM_RELEASE) { + if (jd->dir == SCREEN_DIR_NONE) { + area_join_cancel(C, op); + return OPERATOR_CANCELLED; + } ED_area_tag_redraw(jd->sa1); ED_area_tag_redraw(jd->sa2); @@ -3501,7 +3539,7 @@ static int screen_area_options_invoke(bContext *C, wmOperator *op, const wmEvent &ptr); /* store initial mouse cursor position. */ RNA_int_set_array(&ptr, "cursor", &event->x); - RNA_enum_set(&ptr, "direction", 'v'); + RNA_enum_set(&ptr, "direction", SCREEN_AXIS_V); /* Horizontal Split */ uiItemFullO(layout, @@ -3514,7 +3552,7 @@ static int screen_area_options_invoke(bContext *C, wmOperator *op, const wmEvent &ptr); /* store initial mouse cursor position. */ RNA_int_set_array(&ptr, "cursor", &event->x); - RNA_enum_set(&ptr, "direction", 'h'); + RNA_enum_set(&ptr, "direction", SCREEN_AXIS_H); if (sa1 && sa2) { uiItemS(layout); @@ -4077,6 +4115,69 @@ static void SCREEN_OT_header_toggle_menus(wmOperatorType *ot) /** \name Region Context Menu Operator (Header/Footer/Navbar) * \{ */ +static void screen_area_menu_items(ScrArea *area, uiLayout *layout) +{ + if (ED_area_is_global(area)) { + return; + } + + PointerRNA ptr; + + /* Mouse position as if in middle of area. */ + const int loc[2] = {BLI_rcti_cent_x(&area->totrct), BLI_rcti_cent_y(&area->totrct)}; + + /* Vertical Split */ + uiItemFullO(layout, + "SCREEN_OT_area_split", + IFACE_("Vertical Split"), + ICON_NONE, + NULL, + WM_OP_INVOKE_DEFAULT, + 0, + &ptr); + + RNA_int_set_array(&ptr, "cursor", loc); + RNA_enum_set(&ptr, "direction", SCREEN_AXIS_V); + + /* Horizontal Split */ + uiItemFullO(layout, + "SCREEN_OT_area_split", + IFACE_("Horizontal Split"), + ICON_NONE, + NULL, + WM_OP_INVOKE_DEFAULT, + 0, + &ptr); + + RNA_int_set_array(&ptr, "cursor", &loc[0]); + RNA_enum_set(&ptr, "direction", SCREEN_AXIS_H); + + uiItemS(layout); + + if (area->spacetype != SPACE_FILE) { + uiItemO(layout, + area->full ? IFACE_("Restore Areas") : IFACE_("Maximize Area"), + ICON_NONE, + "SCREEN_OT_screen_full_area"); + + if (!area->full) { + uiItemFullO(layout, + "SCREEN_OT_screen_full_area", + IFACE_("Full Screen Area"), + ICON_NONE, + NULL, + WM_OP_INVOKE_DEFAULT, + 0, + &ptr); + RNA_boolean_set(&ptr, "use_hide_panels", true); + } + } + + uiItemO(layout, NULL, ICON_NONE, "SCREEN_OT_area_dupli"); + uiItemS(layout); + uiItemO(layout, NULL, ICON_NONE, "SCREEN_OT_area_close"); +} + void ED_screens_header_tools_menu_create(bContext *C, uiLayout *layout, void *UNUSED(arg)) { ScrArea *area = CTX_wm_area(C); @@ -4110,17 +4211,9 @@ void ED_screens_header_tools_menu_create(bContext *C, uiLayout *layout, void *UN if (!ELEM(area->spacetype, SPACE_TOPBAR)) { uiItemS(layout); - uiItemO(layout, but_flip_str, ICON_NONE, "SCREEN_OT_region_flip"); - } - - /* File browser should be fullscreen all the time, top-bar should - * never be. But other regions can be maximized/restored. */ - if (!ELEM(area->spacetype, SPACE_FILE, SPACE_TOPBAR)) { uiItemS(layout); - - const char *but_str = area->full ? IFACE_("Tile Area") : IFACE_("Maximize Area"); - uiItemO(layout, but_str, ICON_NONE, "SCREEN_OT_screen_full_area"); + screen_area_menu_items(area, layout); } } @@ -4142,14 +4235,8 @@ void ED_screens_footer_tools_menu_create(bContext *C, uiLayout *layout, void *UN uiItemO(layout, but_flip_str, ICON_NONE, "SCREEN_OT_region_flip"); - /* File browser should be fullscreen all the time, top-bar should - * never be. But other regions can be maximized/restored... */ - if (!ELEM(area->spacetype, SPACE_FILE, SPACE_TOPBAR)) { - uiItemS(layout); - - const char *but_str = area->full ? IFACE_("Tile Area") : IFACE_("Maximize Area"); - uiItemO(layout, but_str, ICON_NONE, "SCREEN_OT_screen_full_area"); - } + uiItemS(layout); + screen_area_menu_items(area, layout); } void ED_screens_navigation_bar_tools_menu_create(bContext *C, uiLayout *layout, void *UNUSED(arg)) @@ -5344,7 +5431,7 @@ static void context_cycle_prop_get(bScreen *screen, static int space_context_cycle_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { - const int direction = RNA_enum_get(op->ptr, "direction"); + const eScreenCycle direction = RNA_enum_get(op->ptr, "direction"); PointerRNA ptr; PropertyRNA *prop; @@ -5393,7 +5480,7 @@ static int space_workspace_cycle_invoke(bContext *C, wmOperator *op, const wmEve } Main *bmain = CTX_data_main(C); - const int direction = RNA_enum_get(op->ptr, "direction"); + const eScreenCycle direction = RNA_enum_get(op->ptr, "direction"); WorkSpace *workspace_src = WM_window_get_active_workspace(win); WorkSpace *workspace_dst = NULL; @@ -5469,6 +5556,7 @@ void ED_operatortypes_screen(void) WM_operatortype_append(SCREEN_OT_area_move); WM_operatortype_append(SCREEN_OT_area_split); WM_operatortype_append(SCREEN_OT_area_join); + WM_operatortype_append(SCREEN_OT_area_close); WM_operatortype_append(SCREEN_OT_area_options); WM_operatortype_append(SCREEN_OT_area_dupli); WM_operatortype_append(SCREEN_OT_area_swap); diff --git a/source/blender/editors/screen/screendump.c b/source/blender/editors/screen/screendump.c index 3df63d423e2..6df96b1e30f 100644 --- a/source/blender/editors/screen/screendump.c +++ b/source/blender/editors/screen/screendump.c @@ -199,10 +199,9 @@ static bool screenshot_draw_check_prop(PointerRNA *UNUSED(ptr), return !(STREQ(prop_id, "filepath")); } -static void screenshot_draw(bContext *C, wmOperator *op) +static void screenshot_draw(bContext *UNUSED(C), wmOperator *op) { uiLayout *layout = op->layout; - wmWindowManager *wm = CTX_wm_manager(C); ScreenshotData *scd = op->customdata; uiLayoutSetPropSep(layout, true); @@ -214,9 +213,8 @@ static void screenshot_draw(bContext *C, wmOperator *op) uiTemplateImageSettings(layout, &ptr, false); /* main draw call */ - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); uiDefAutoButsRNA( - layout, &ptr, screenshot_draw_check_prop, NULL, NULL, UI_BUT_LABEL_ALIGN_NONE, false); + layout, op->ptr, screenshot_draw_check_prop, NULL, NULL, UI_BUT_LABEL_ALIGN_NONE, false); } static bool screenshot_poll(bContext *C) diff --git a/source/blender/editors/sculpt_paint/paint_cursor.c b/source/blender/editors/sculpt_paint/paint_cursor.c index 7740fb42c37..3829aeebbeb 100644 --- a/source/blender/editors/sculpt_paint/paint_cursor.c +++ b/source/blender/editors/sculpt_paint/paint_cursor.c @@ -1324,6 +1324,13 @@ static bool paint_cursor_context_init(bContext *C, copy_v3_fl(pcontext->outline_col, 0.8f); } + const bool is_brush_tool = PAINT_brush_tool_poll(C); + if (!is_brush_tool) { + /* Use a default color for tools that are not brushes. */ + pcontext->outline_alpha = 0.8f; + copy_v3_fl(pcontext->outline_col, 0.8f); + } + pcontext->is_stroke_active = pcontext->ups->stroke_active; return true; @@ -1610,9 +1617,11 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext * pcontext->radius); } + const bool is_brush_tool = PAINT_brush_tool_poll(pcontext->C); + /* Pose brush updates and rotation origins. */ - if (brush->sculpt_tool == SCULPT_TOOL_POSE) { + if (is_brush_tool && brush->sculpt_tool == SCULPT_TOOL_POSE) { /* Just after switching to the Pose Brush, the active vertex can be the same and the * cursor won't be tagged to update, so always initialize the preview chain if it is * null before drawing it. */ @@ -1645,7 +1654,7 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext * 2); } - if (brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) { + if (is_brush_tool && brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) { paint_cursor_preview_boundary_data_update(pcontext, update_previews); paint_cursor_preview_boundary_data_pivot_draw(pcontext); } @@ -1666,17 +1675,18 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext * GPU_matrix_mul(pcontext->vc.obact->obmat); /* Drawing Cursor overlays in 3D object space. */ - if (brush->sculpt_tool == SCULPT_TOOL_GRAB && (brush->flag & BRUSH_GRAB_ACTIVE_VERTEX)) { + if (is_brush_tool && brush->sculpt_tool == SCULPT_TOOL_GRAB && + (brush->flag & BRUSH_GRAB_ACTIVE_VERTEX)) { SCULPT_geometry_preview_lines_update(pcontext->C, pcontext->ss, pcontext->radius); sculpt_geometry_preview_lines_draw( pcontext->pos, pcontext->brush, pcontext->is_multires, pcontext->ss); } - if (brush->sculpt_tool == SCULPT_TOOL_POSE) { + if (is_brush_tool && brush->sculpt_tool == SCULPT_TOOL_POSE) { paint_cursor_pose_brush_segments_draw(pcontext); } - if (brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) { + if (is_brush_tool && brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) { SCULPT_boundary_edges_preview_draw( pcontext->pos, pcontext->ss, pcontext->outline_col, pcontext->outline_alpha); SCULPT_boundary_pivot_line_preview_draw(pcontext->pos, pcontext->ss); @@ -1692,7 +1702,7 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext * paint_cursor_draw_main_inactive_cursor(pcontext); /* Cloth brush local simulation areas. */ - if (brush->sculpt_tool == SCULPT_TOOL_CLOTH && + if (is_brush_tool && brush->sculpt_tool == SCULPT_TOOL_CLOTH && brush->cloth_simulation_area_type != BRUSH_CLOTH_SIMULATION_AREA_GLOBAL) { const float white[3] = {1.0f, 1.0f, 1.0f}; const float zero_v[3] = {0.0f}; @@ -1704,7 +1714,7 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext * } /* Layer brush height. */ - if (brush->sculpt_tool == SCULPT_TOOL_LAYER) { + if (is_brush_tool && brush->sculpt_tool == SCULPT_TOOL_LAYER) { SCULPT_layer_brush_height_preview_draw(pcontext->pos, brush, pcontext->radius, diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index 3ca0d853d6a..7341d984c91 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -87,7 +87,7 @@ struct ViewContext *paint_stroke_view_context(struct PaintStroke *stroke); void *paint_stroke_mode_data(struct PaintStroke *stroke); float paint_stroke_distance_get(struct PaintStroke *stroke); void paint_stroke_set_mode_data(struct PaintStroke *stroke, void *mode_data); -bool paint_poll(struct bContext *C); +bool PAINT_brush_tool_poll(struct bContext *C); void paint_cursor_start(struct Paint *p, bool (*poll)(struct bContext *C)); void paint_cursor_delete_textures(void); @@ -304,7 +304,7 @@ bool paint_curve_poll(struct bContext *C); bool facemask_paint_poll(struct bContext *C); void flip_v3_v3(float out[3], const float in[3], const enum ePaintSymmetryFlags symm); -void flip_qt_qt(float out[3], const float in[3], const enum ePaintSymmetryFlags symm); +void flip_qt_qt(float out[4], const float in[4], const enum ePaintSymmetryFlags symm); /* stroke operator */ typedef enum BrushStrokeMode { diff --git a/source/blender/editors/sculpt_paint/paint_ops.c b/source/blender/editors/sculpt_paint/paint_ops.c index e1dc8fa30b9..fed89e02e8f 100644 --- a/source/blender/editors/sculpt_paint/paint_ops.c +++ b/source/blender/editors/sculpt_paint/paint_ops.c @@ -139,13 +139,14 @@ static int brush_scale_size_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); Paint *paint = BKE_paint_get_active_from_context(C); Brush *brush = BKE_paint_brush(paint); + const bool is_gpencil = (brush && brush->gpencil_settings != NULL); // Object *ob = CTX_data_active_object(C); float scalar = RNA_float_get(op->ptr, "scalar"); if (brush) { /* pixel radius */ { - const int old_size = BKE_brush_size_get(scene, brush); + const int old_size = (!is_gpencil) ? BKE_brush_size_get(scene, brush) : brush->size; int size = (int)(scalar * old_size); if (abs(old_size - size) < U.pixelsize) { @@ -156,6 +157,12 @@ static int brush_scale_size_exec(bContext *C, wmOperator *op) size -= U.pixelsize; } } + /* Grease Pencil does not use unified size. */ + if (is_gpencil) { + brush->size = max_ii(size, 1); + WM_main_add_notifier(NC_BRUSH | NA_EDITED, brush); + return OPERATOR_FINISHED; + } BKE_brush_size_set(scene, brush, size); } diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c index 49ddf2f82d8..b093f07226e 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.c +++ b/source/blender/editors/sculpt_paint/paint_stroke.c @@ -1462,7 +1462,7 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) if (paint_supports_smooth_stroke(br, mode)) { stroke->stroke_cursor = WM_paint_cursor_activate( - SPACE_TYPE_ANY, RGN_TYPE_ANY, paint_poll, paint_draw_smooth_cursor, stroke); + SPACE_TYPE_ANY, RGN_TYPE_ANY, PAINT_brush_tool_poll, paint_draw_smooth_cursor, stroke); } stroke->stroke_init = true; @@ -1489,7 +1489,7 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) if (br->flag & BRUSH_LINE) { stroke->stroke_cursor = WM_paint_cursor_activate( - SPACE_TYPE_ANY, RGN_TYPE_ANY, paint_poll, paint_draw_line_cursor, stroke); + SPACE_TYPE_ANY, RGN_TYPE_ANY, PAINT_brush_tool_poll, paint_draw_line_cursor, stroke); } first_dab = true; @@ -1659,7 +1659,7 @@ void paint_stroke_set_mode_data(PaintStroke *stroke, void *mode_data) stroke->mode_data = mode_data; } -bool paint_poll(bContext *C) +bool PAINT_brush_tool_poll(bContext *C) { Paint *p = BKE_paint_get_active_from_context(C); Object *ob = CTX_data_active_object(C); diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index 964e5bdaa90..2e1dd928f96 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -780,6 +780,10 @@ static void sculpt_vertex_neighbors_get_faces(SculptSession *ss, iter->neighbors = iter->neighbors_fixed; for (int i = 0; i < ss->pmap[index].count; i++) { + if (ss->face_sets[vert_map->indices[i]] < 0) { + /* Skip connectivity from hidden faces. */ + continue; + } const MPoly *p = &ss->mpoly[vert_map->indices[i]]; uint f_adj_v[2]; if (poly_get_adj_loops_from_vert(p, ss->mloop, index, f_adj_v) != -1) { @@ -6602,7 +6606,7 @@ bool SCULPT_poll_view3d(bContext *C) bool SCULPT_poll(bContext *C) { - return SCULPT_mode_poll(C) && paint_poll(C); + return SCULPT_mode_poll(C) && PAINT_brush_tool_poll(C); } static const char *sculpt_tool_name(Sculpt *sd) diff --git a/source/blender/editors/sound/sound_ops.c b/source/blender/editors/sound/sound_ops.c index 85616f6356d..fe0a53ae964 100644 --- a/source/blender/editors/sound/sound_ops.c +++ b/source/blender/editors/sound/sound_ops.c @@ -52,6 +52,7 @@ #include "RNA_enum_types.h" #include "SEQ_iterator.h" +#include "SEQ_utils.h" #include "UI_interface.h" @@ -258,7 +259,7 @@ static void sound_update_animation_flags(Scene *scene) scene->id.tag |= LIB_TAG_DOIT; SEQ_ALL_BEGIN (scene->ed, seq) { - SEQ_iterator_recursive_apply(seq, sound_update_animation_flags_fn, scene); + SEQ_recursive_apply(seq, sound_update_animation_flags_fn, scene); } SEQ_ALL_END; diff --git a/source/blender/editors/space_buttons/CMakeLists.txt b/source/blender/editors/space_buttons/CMakeLists.txt index c71e5e49d8d..b5f6874fcfc 100644 --- a/source/blender/editors/space_buttons/CMakeLists.txt +++ b/source/blender/editors/space_buttons/CMakeLists.txt @@ -50,7 +50,7 @@ if(WITH_FREESTYLE) endif() if(WITH_EXPERIMENTAL_FEATURES) - add_definitions(-DWITH_GEOMETRY_NODES) + add_definitions(-DWITH_SIMULATION_DATABLOCK) add_definitions(-DWITH_POINT_CLOUD) add_definitions(-DWITH_HAIR_NODES) endif() diff --git a/source/blender/editors/space_buttons/buttons_context.c b/source/blender/editors/space_buttons/buttons_context.c index 1699e704a4d..aeb2c04656e 100644 --- a/source/blender/editors/space_buttons/buttons_context.c +++ b/source/blender/editors/space_buttons/buttons_context.c @@ -975,7 +975,8 @@ int /*eContextResult*/ buttons_context(const bContext *C, if (matnr < 0) { matnr = 0; } - CTX_data_pointer_set(result, &ob->id, &RNA_MaterialSlot, &ob->mat[matnr]); + /* Keep aligned with rna_Object_material_slots_get. */ + CTX_data_pointer_set(result, &ob->id, &RNA_MaterialSlot, POINTER_FROM_INT(matnr + 1)); } } diff --git a/source/blender/editors/space_buttons/buttons_intern.h b/source/blender/editors/space_buttons/buttons_intern.h index 74e7bc11c26..7564fa4b930 100644 --- a/source/blender/editors/space_buttons/buttons_intern.h +++ b/source/blender/editors/space_buttons/buttons_intern.h @@ -35,6 +35,7 @@ struct bContext; struct bContextDataResult; struct bNode; struct bNodeTree; +struct bNodeSocket; struct wmOperatorType; struct SpaceProperties_Runtime { @@ -66,6 +67,7 @@ typedef struct ButsTextureUser { struct bNodeTree *ntree; struct bNode *node; + struct bNodeSocket *socket; const char *category; int icon; diff --git a/source/blender/editors/space_buttons/buttons_texture.c b/source/blender/editors/space_buttons/buttons_texture.c index 43128ed00fa..97e3cb750c1 100644 --- a/source/blender/editors/space_buttons/buttons_texture.c +++ b/source/blender/editors/space_buttons/buttons_texture.c @@ -75,15 +75,16 @@ static SpaceProperties *find_space_properties(const bContext *C); /************************* Texture User **************************/ -static void buttons_texture_user_node_property_add(ListBase *users, - ID *id, - PointerRNA ptr, - PropertyRNA *prop, - bNodeTree *ntree, - bNode *node, - const char *category, - int icon, - const char *name) +static void buttons_texture_user_socket_property_add(ListBase *users, + ID *id, + PointerRNA ptr, + PropertyRNA *prop, + bNodeTree *ntree, + bNode *node, + bNodeSocket *socket, + const char *category, + int icon, + const char *name) { ButsTextureUser *user = MEM_callocN(sizeof(ButsTextureUser), "ButsTextureUser"); @@ -92,6 +93,7 @@ static void buttons_texture_user_node_property_add(ListBase *users, user->prop = prop; user->ntree = ntree; user->node = node; + user->socket = socket; user->category = category; user->icon = icon; user->name = name; @@ -181,25 +183,29 @@ static void buttons_texture_modifier_geonodes_users_add(Object *ob, /* Recurse into the node group */ buttons_texture_modifier_geonodes_users_add(ob, nmd, (bNodeTree *)node->id, users); } - else if (node->type == GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE) { - RNA_pointer_create(&node_tree->id, &RNA_Node, node, &ptr); - prop = RNA_struct_find_property(&ptr, "texture"); - if (prop == NULL) { + LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { + if (socket->flag & SOCK_UNAVAIL) { + continue; + } + if (socket->type != SOCK_TEXTURE) { continue; } + RNA_pointer_create(&node_tree->id, &RNA_NodeSocket, socket, &ptr); + prop = RNA_struct_find_property(&ptr, "default_value"); PointerRNA texptr = RNA_property_pointer_get(&ptr, prop); Tex *tex = (RNA_struct_is_a(texptr.type, &RNA_Texture)) ? (Tex *)texptr.data : NULL; if (tex != NULL) { - buttons_texture_user_node_property_add(users, - &ob->id, - ptr, - prop, - node_tree, - node, - N_("Geometry Nodes"), - RNA_struct_ui_icon(ptr.type), - nmd->modifier.name); + buttons_texture_user_socket_property_add(users, + &ob->id, + ptr, + prop, + node_tree, + node, + socket, + N_("Geometry Nodes"), + RNA_struct_ui_icon(ptr.type), + nmd->modifier.name); } } } diff --git a/source/blender/editors/space_clip/clip_buttons.c b/source/blender/editors/space_clip/clip_buttons.c index d555238e949..7379891543b 100644 --- a/source/blender/editors/space_clip/clip_buttons.c +++ b/source/blender/editors/space_clip/clip_buttons.c @@ -809,12 +809,12 @@ void uiTemplateMovieclipInformation(uiLayout *layout, char str[1024]; size_t ofs = 0; - ofs += BLI_snprintf(str + ofs, sizeof(str) - ofs, TIP_("%d x %d"), width, height); + ofs += BLI_snprintf_rlen(str + ofs, sizeof(str) - ofs, TIP_("%d x %d"), width, height); if (ibuf) { if (ibuf->rect_float) { if (ibuf->channels != 4) { - ofs += BLI_snprintf( + ofs += BLI_snprintf_rlen( str + ofs, sizeof(str) - ofs, TIP_(", %d float channel(s)"), ibuf->channels); } else if (ibuf->planes == R_IMF_PLANES_RGBA) { @@ -837,7 +837,7 @@ void uiTemplateMovieclipInformation(uiLayout *layout, short frs_sec; float frs_sec_base; if (IMB_anim_get_fps(clip->anim, &frs_sec, &frs_sec_base, true)) { - ofs += BLI_snprintf( + ofs += BLI_snprintf_rlen( str + ofs, sizeof(str) - ofs, TIP_(", %.2f fps"), (float)frs_sec / frs_sec_base); } } diff --git a/source/blender/editors/space_file/file_draw.c b/source/blender/editors/space_file/file_draw.c index c1dcf2e56d3..189b9b4c874 100644 --- a/source/blender/editors/space_file/file_draw.c +++ b/source/blender/editors/space_file/file_draw.c @@ -323,6 +323,7 @@ static void file_draw_preview(uiBlock *block, int ex, ey; bool show_outline = !is_icon && (file->typeflag & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_BLENDER)); + const bool is_offline = (file->attributes & FILE_ATTR_OFFLINE); BLI_assert(imb != NULL); @@ -419,14 +420,14 @@ static void file_draw_preview(uiBlock *block, icon_x, icon_y, icon, icon_aspect / U.dpi_fac, icon_opacity, 0.0f, icon_color, false); } - if (is_link) { - /* Arrow icon to indicate it is a shortcut, link, or alias. */ + if (is_link || is_offline) { + /* Icon at bottom to indicate it is a shortcut, link, alias, or offline. */ float icon_x, icon_y; icon_x = xco + (2.0f * UI_DPI_FAC); icon_y = yco + (2.0f * UI_DPI_FAC); - const int arrow = ICON_LOOP_FORWARDS; + const int arrow = is_link ? ICON_LOOP_FORWARDS : ICON_URL; if (!is_icon) { - /* Arrow at very bottom-left if preview style. */ + /* At very bottom-left if preview style. */ const uchar dark[4] = {0, 0, 0, 255}; const uchar light[4] = {255, 255, 255, 255}; UI_icon_draw_ex(icon_x + 1, icon_y - 1, arrow, 1.0f / U.dpi_fac, 0.2f, 0.0f, dark, false); diff --git a/source/blender/editors/space_file/file_intern.h b/source/blender/editors/space_file/file_intern.h index 309b280177c..f1d0197b9ae 100644 --- a/source/blender/editors/space_file/file_intern.h +++ b/source/blender/editors/space_file/file_intern.h @@ -63,6 +63,7 @@ void FILE_OT_bookmark_move(struct wmOperatorType *ot); void FILE_OT_reset_recent(wmOperatorType *ot); void FILE_OT_hidedot(struct wmOperatorType *ot); void FILE_OT_execute(struct wmOperatorType *ot); +void FILE_OT_mouse_execute(struct wmOperatorType *ot); void FILE_OT_cancel(struct wmOperatorType *ot); void FILE_OT_parent(struct wmOperatorType *ot); void FILE_OT_directory_new(struct wmOperatorType *ot); diff --git a/source/blender/editors/space_file/file_ops.c b/source/blender/editors/space_file/file_ops.c index 856bd5b1bc3..36f8476d0c9 100644 --- a/source/blender/editors/space_file/file_ops.c +++ b/source/blender/editors/space_file/file_ops.c @@ -536,6 +536,14 @@ void FILE_OT_select_box(wmOperatorType *ot) /** \name Select Pick Operator * \{ */ +static rcti file_select_mval_to_select_rect(const int mval[2]) +{ + rcti rect; + rect.xmin = rect.xmax = mval[0]; + rect.ymin = rect.ymax = mval[1]; + return rect; +} + static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) { ARegion *region = CTX_wm_region(C); @@ -551,8 +559,7 @@ static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) return OPERATOR_CANCELLED; } - rect.xmin = rect.xmax = event->mval[0]; - rect.ymin = rect.ymax = event->mval[1]; + rect = file_select_mval_to_select_rect(event->mval); if (!ED_fileselect_layout_is_inside_pt(sfile->layout, ®ion->v2d, rect.xmin, rect.ymin)) { return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; @@ -1711,14 +1718,14 @@ bool file_draw_check_exists(SpaceFile *sfile) /** \name Execute File Window Operator * \{ */ -static int file_exec(bContext *C, wmOperator *exec_op) +/** + * Execute the active file, as set in the file select params. + */ +static bool file_execute(bContext *C, SpaceFile *sfile) { Main *bmain = CTX_data_main(C); - wmWindowManager *wm = CTX_wm_manager(C); - SpaceFile *sfile = CTX_wm_space_file(C); FileSelectParams *params = ED_fileselect_get_active_params(sfile); - struct FileDirEntry *file = filelist_file(sfile->files, params->active_file); - char filepath[FILE_MAX]; + FileDirEntry *file = filelist_file(sfile->files, params->active_file); if (file && file->redirection_path) { /* redirection_path is an absolute path that takes precedence @@ -1753,22 +1760,7 @@ static int file_exec(bContext *C, wmOperator *exec_op) /* opening file - sends events now, so things get handled on windowqueue level */ else if (sfile->op) { wmOperator *op = sfile->op; - - /* When used as a macro, for double-click, to prevent closing when double-clicking on item. */ - if (RNA_boolean_get(exec_op->ptr, "need_active")) { - const int numfiles = filelist_files_ensure(sfile->files); - int i, active = 0; - - for (i = 0; i < numfiles; i++) { - if (filelist_entry_select_index_get(sfile->files, i, CHECK_ALL)) { - active = 1; - break; - } - } - if (active == 0) { - return OPERATOR_CANCELLED; - } - } + char filepath[FILE_MAX]; sfile->op = NULL; @@ -1788,50 +1780,94 @@ static int file_exec(bContext *C, wmOperator *exec_op) BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), BLENDER_BOOKMARK_FILE); fsmenu_write_file(ED_fsmenu_get(), filepath); - WM_event_fileselect_event(wm, op, EVT_FILESELECT_EXEC); + WM_event_fileselect_event(CTX_wm_manager(C), op, EVT_FILESELECT_EXEC); } return OPERATOR_FINISHED; } -static int file_exec_invoke(bContext *C, wmOperator *op, const wmEvent *event) +static int file_exec(bContext *C, wmOperator *UNUSED(op)) { - ARegion *region = CTX_wm_region(C); SpaceFile *sfile = CTX_wm_space_file(C); - if (!ED_fileselect_layout_is_inside_pt( - sfile->layout, ®ion->v2d, event->mval[0], event->mval[1])) { - return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + if (!file_execute(C, sfile)) { + return OPERATOR_CANCELLED; } - return file_exec(C, op); + return OPERATOR_FINISHED; } void FILE_OT_execute(struct wmOperatorType *ot) { - PropertyRNA *prop; - /* identifiers */ ot->name = "Execute File Window"; ot->description = "Execute selected file"; ot->idname = "FILE_OT_execute"; /* api callbacks */ - ot->invoke = file_exec_invoke; ot->exec = file_exec; /* Important since handler is on window level. * * Avoid using #file_operator_poll since this is also used for entering directories * which is used even when the file manager doesn't have an operator. */ ot->poll = ED_operator_file_active; +} - /* properties */ - prop = RNA_def_boolean(ot->srna, - "need_active", - 0, - "Need Active", - "Only execute if there's an active selected file in the file list"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); +/** + * \returns false if the mouse doesn't hover a selectable item. + */ +static bool file_ensure_hovered_is_active(bContext *C, const wmEvent *event) +{ + rcti rect = file_select_mval_to_select_rect(event->mval); + if (file_select(C, &rect, FILE_SEL_ADD, false, false) == FILE_SELECT_NOTHING) { + return false; + } + + return true; +} + +static int file_execute_mouse_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) +{ + ARegion *region = CTX_wm_region(C); + SpaceFile *sfile = CTX_wm_space_file(C); + + if (!ED_fileselect_layout_is_inside_pt( + sfile->layout, ®ion->v2d, event->mval[0], event->mval[1])) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } + + /* Note that this isn't needed practically, because the keymap already activates the hovered item + * on mouse-press. This execute operator is called afterwards on the double-click event then. + * However relying on this would be fragile and could break with keymap changes, so better to + * have this mouse-execute operator that makes sure once more that the hovered file is active. */ + if (!file_ensure_hovered_is_active(C, event)) { + return OPERATOR_CANCELLED; + } + + if (!file_execute(C, sfile)) { + return OPERATOR_CANCELLED; + } + + return OPERATOR_FINISHED; +} + +/** + * Variation of #FILE_OT_execute that accounts for some mouse specific handling. Otherwise calls + * the same logic. + */ +void FILE_OT_mouse_execute(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Execute File"; + ot->description = + "Perform the current execute action for the file under the cursor (e.g. open the file)"; + ot->idname = "FILE_OT_mouse_execute"; + + /* api callbacks */ + ot->invoke = file_execute_mouse_invoke; + ot->poll = ED_operator_file_active; + + ot->flag = OPTYPE_INTERNAL; } /** \} */ @@ -2227,23 +2263,24 @@ void FILE_OT_filepath_drop(wmOperatorType *ot) * \{ */ /** - * Create a new, non-existing folder name, returns 1 if successful, 0 if name couldn't be created. + * Create a new, non-existing folder name, returns true if successful, + * false if name couldn't be created. * The actual name is returned in 'name', 'folder' contains the complete path, * including the new folder name. */ -static int new_folder_path(const char *parent, char *folder, char *name) +static bool new_folder_path(const char *parent, char folder[FILE_MAX], char name[FILE_MAXFILE]) { int i = 1; int len = 0; BLI_strncpy(name, "New Folder", FILE_MAXFILE); - BLI_join_dirfile(folder, FILE_MAX, parent, name); /* XXX, not real length */ + BLI_join_dirfile(folder, FILE_MAX, parent, name); /* check whether folder with the name already exists, in this case * add number to the name. Check length of generated name to avoid * crazy case of huge number of folders each named 'New Folder (x)' */ while (BLI_exists(folder) && (len < FILE_MAXFILE)) { len = BLI_snprintf(name, FILE_MAXFILE, "New Folder(%d)", i); - BLI_join_dirfile(folder, FILE_MAX, parent, name); /* XXX, not real length */ + BLI_join_dirfile(folder, FILE_MAX, parent, name); i++; } diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c index c2b3a7ac0ee..37a32164cfc 100644 --- a/source/blender/editors/space_file/filelist.c +++ b/source/blender/editors/space_file/filelist.c @@ -1601,37 +1601,51 @@ static void filelist_cache_previews_push(FileList *filelist, FileDirEntry *entry BLI_assert(cache->flags & FLC_PREVIEWS_ACTIVE); - if (!entry->preview_icon_id && !(entry->flags & FILE_ENTRY_INVALID_PREVIEW) && - (entry->typeflag & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT | - FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB))) { - FileListEntryPreview *preview = MEM_mallocN(sizeof(*preview), __func__); - FileListInternEntry *intern_entry = filelist->filelist_intern.filtered[index]; + if (!entry->preview_icon_id && (entry->attributes & FILE_ATTR_OFFLINE)) { + entry->flags |= FILE_ENTRY_INVALID_PREVIEW; + return; + } - if (entry->redirection_path) { - BLI_strncpy(preview->path, entry->redirection_path, FILE_MAXDIR); - } - else { - BLI_join_dirfile( - preview->path, sizeof(preview->path), filelist->filelist.root, entry->relpath); - } + if (entry->preview_icon_id) { + return; + } - preview->index = index; - preview->flags = entry->typeflag; - preview->in_memory_preview = intern_entry->local_data.preview_image; - preview->icon_id = 0; - // printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img); + if (entry->flags & FILE_ENTRY_INVALID_PREVIEW) { + return; + } + + if (!(entry->typeflag & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT | + FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB))) { + return; + } - filelist_cache_preview_ensure_running(cache); + FileListEntryPreview *preview = MEM_mallocN(sizeof(*preview), __func__); + FileListInternEntry *intern_entry = filelist->filelist_intern.filtered[index]; - FileListEntryPreviewTaskData *preview_taskdata = MEM_mallocN(sizeof(*preview_taskdata), - __func__); - preview_taskdata->preview = preview; - BLI_task_pool_push(cache->previews_pool, - filelist_cache_preview_runf, - preview_taskdata, - true, - filelist_cache_preview_freef); + if (entry->redirection_path) { + BLI_strncpy(preview->path, entry->redirection_path, FILE_MAXDIR); + } + else { + BLI_join_dirfile( + preview->path, sizeof(preview->path), filelist->filelist.root, entry->relpath); } + + preview->index = index; + preview->flags = entry->typeflag; + preview->in_memory_preview = intern_entry->local_data.preview_image; + preview->icon_id = 0; + // printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img); + + filelist_cache_preview_ensure_running(cache); + + FileListEntryPreviewTaskData *preview_taskdata = MEM_mallocN(sizeof(*preview_taskdata), + __func__); + preview_taskdata->preview = preview; + BLI_task_pool_push(cache->previews_pool, + filelist_cache_preview_runf, + preview_taskdata, + true, + filelist_cache_preview_freef); } static void filelist_cache_init(FileListEntryCache *cache, size_t cache_size) @@ -2360,17 +2374,19 @@ bool filelist_file_cache_block(struct FileList *filelist, const int index) // printf("Re-queueing previews...\n"); - /* Note we try to preview first images around given index - i.e. assumed visible ones. */ if (cache->flags & FLC_PREVIEWS_ACTIVE) { - for (i = 0; ((index + i) < end_index) || ((index - i) >= start_index); i++) { - if ((index - i) >= start_index) { - const int idx = (cache->block_cursor + (index - start_index) - i) % cache_size; - filelist_cache_previews_push(filelist, cache->block_entries[idx], index - i); - } - if ((index + i) < end_index) { - const int idx = (cache->block_cursor + (index - start_index) + i) % cache_size; - filelist_cache_previews_push(filelist, cache->block_entries[idx], index + i); - } + /* Note we try to preview first images around given index - i.e. assumed visible ones. */ + int block_index = cache->block_cursor + (index - start_index); + int offs_max = max_ii(end_index - index, index - start_index); + for (i = 0; i <= offs_max; i++) { + int offs = i; + do { + int offs_idx = index + offs; + if (start_index <= offs_idx && offs_idx < end_index) { + int offs_block_idx = (block_index + offs) % (int)cache_size; + filelist_cache_previews_push(filelist, cache->block_entries[offs_block_idx], offs_idx); + } + } while ((offs = -offs) < 0); /* Switch between negative and positive offset. */ } } diff --git a/source/blender/editors/space_file/space_file.c b/source/blender/editors/space_file/space_file.c index 993b1d9b69c..12bc0a68ca6 100644 --- a/source/blender/editors/space_file/space_file.c +++ b/source/blender/editors/space_file/space_file.c @@ -467,6 +467,15 @@ static void file_listener(const wmSpaceTypeListenerParams *params) break; } break; + case NC_ID: { + switch (wmn->action) { + case NA_RENAME: + /* Force list to update sorting (with a full reset for now). */ + file_reset_filelist_showing_main_data(area, sfile); + break; + } + break; + } case NC_ASSET: { switch (wmn->action) { case NA_SELECTED: @@ -654,6 +663,7 @@ static void file_operatortypes(void) WM_operatortype_append(FILE_OT_highlight); WM_operatortype_append(FILE_OT_sort_column_ui_context); WM_operatortype_append(FILE_OT_execute); + WM_operatortype_append(FILE_OT_mouse_execute); WM_operatortype_append(FILE_OT_cancel); WM_operatortype_append(FILE_OT_parent); WM_operatortype_append(FILE_OT_previous); diff --git a/source/blender/editors/space_image/image_buttons.c b/source/blender/editors/space_image/image_buttons.c index 6fb64de7e85..d909bfd1864 100644 --- a/source/blender/editors/space_image/image_buttons.c +++ b/source/blender/editors/space_image/image_buttons.c @@ -1218,11 +1218,12 @@ void uiTemplateImageInfo(uiLayout *layout, bContext *C, Image *ima, ImageUser *i const int len = MAX_IMAGE_INFO_LEN; int ofs = 0; - ofs += BLI_snprintf(str + ofs, len - ofs, TIP_("%d x %d, "), ibuf->x, ibuf->y); + ofs += BLI_snprintf_rlen(str + ofs, len - ofs, TIP_("%d x %d, "), ibuf->x, ibuf->y); if (ibuf->rect_float) { if (ibuf->channels != 4) { - ofs += BLI_snprintf(str + ofs, len - ofs, TIP_("%d float channel(s)"), ibuf->channels); + ofs += BLI_snprintf_rlen( + str + ofs, len - ofs, TIP_("%d float channel(s)"), ibuf->channels); } else if (ibuf->planes == R_IMF_PLANES_RGBA) { ofs += BLI_strncpy_rlen(str + ofs, TIP_(" RGBA float"), len - ofs); diff --git a/source/blender/editors/space_image/image_ops.c b/source/blender/editors/space_image/image_ops.c index 83dd5098a30..6053253790a 100644 --- a/source/blender/editors/space_image/image_ops.c +++ b/source/blender/editors/space_image/image_ops.c @@ -1479,18 +1479,16 @@ static bool image_open_draw_check_prop(PointerRNA *UNUSED(ptr), return !(STR_ELEM(prop_id, "filepath", "directory", "filename")); } -static void image_open_draw(bContext *C, wmOperator *op) +static void image_open_draw(bContext *UNUSED(C), wmOperator *op) { uiLayout *layout = op->layout; - wmWindowManager *wm = CTX_wm_manager(C); ImageOpenData *iod = op->customdata; ImageFormatData *imf = &iod->im_format; - PointerRNA imf_ptr, ptr; + PointerRNA imf_ptr; /* main draw call */ - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); uiDefAutoButsRNA( - layout, &ptr, image_open_draw_check_prop, NULL, NULL, UI_BUT_LABEL_ALIGN_NONE, false); + layout, op->ptr, image_open_draw_check_prop, NULL, NULL, UI_BUT_LABEL_ALIGN_NONE, false); /* image template */ RNA_pointer_create(NULL, &RNA_ImageFormatSettings, imf, &imf_ptr); @@ -2001,12 +1999,11 @@ static bool image_save_as_draw_check_prop(PointerRNA *ptr, ((STREQ(prop_id, "relative_path")) && RNA_boolean_get(ptr, "copy"))); } -static void image_save_as_draw(bContext *C, wmOperator *op) +static void image_save_as_draw(bContext *UNUSED(C), wmOperator *op) { uiLayout *layout = op->layout; - wmWindowManager *wm = CTX_wm_manager(C); ImageSaveData *isd = op->customdata; - PointerRNA imf_ptr, ptr; + PointerRNA imf_ptr; const bool is_multiview = RNA_boolean_get(op->ptr, "show_multiview"); /* image template */ @@ -2014,9 +2011,8 @@ static void image_save_as_draw(bContext *C, wmOperator *op) uiTemplateImageSettings(layout, &imf_ptr, false); /* main draw call */ - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); uiDefAutoButsRNA( - layout, &ptr, image_save_as_draw_check_prop, NULL, NULL, UI_BUT_LABEL_ALIGN_NONE, false); + layout, op->ptr, image_save_as_draw_check_prop, NULL, NULL, UI_BUT_LABEL_ALIGN_NONE, false); /* multiview template */ if (is_multiview) { @@ -2614,38 +2610,34 @@ static int image_new_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(e return WM_operator_props_dialog_popup(C, op, 300); } -static void image_new_draw(bContext *C, wmOperator *op) +static void image_new_draw(bContext *UNUSED(C), wmOperator *op) { uiLayout *col; uiLayout *layout = op->layout; - wmWindowManager *wm = CTX_wm_manager(C); - PointerRNA ptr; #if 0 Scene *scene = CTX_data_scene(C); const bool is_multiview = (scene->r.scemode & R_MULTIVIEW) != 0; #endif - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); - /* copy of WM_operator_props_dialog_popup() layout */ uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); col = uiLayoutColumn(layout, false); - uiItemR(col, &ptr, "name", 0, NULL, ICON_NONE); - uiItemR(col, &ptr, "width", 0, NULL, ICON_NONE); - uiItemR(col, &ptr, "height", 0, NULL, ICON_NONE); - uiItemR(col, &ptr, "color", 0, NULL, ICON_NONE); - uiItemR(col, &ptr, "alpha", 0, NULL, ICON_NONE); - uiItemR(col, &ptr, "generated_type", 0, NULL, ICON_NONE); - uiItemR(col, &ptr, "float", 0, NULL, ICON_NONE); - uiItemR(col, &ptr, "tiled", 0, NULL, ICON_NONE); + uiItemR(col, op->ptr, "name", 0, NULL, ICON_NONE); + uiItemR(col, op->ptr, "width", 0, NULL, ICON_NONE); + uiItemR(col, op->ptr, "height", 0, NULL, ICON_NONE); + uiItemR(col, op->ptr, "color", 0, NULL, ICON_NONE); + uiItemR(col, op->ptr, "alpha", 0, NULL, ICON_NONE); + uiItemR(col, op->ptr, "generated_type", 0, NULL, ICON_NONE); + uiItemR(col, op->ptr, "float", 0, NULL, ICON_NONE); + uiItemR(col, op->ptr, "tiled", 0, NULL, ICON_NONE); #if 0 if (is_multiview) { uiItemL(col[0], "", ICON_NONE); - uiItemR(col[1], &ptr, "use_stereo_3d", 0, NULL, ICON_NONE); + uiItemR(col[1], op->ptr, "use_stereo_3d", 0, NULL, ICON_NONE); } #endif } @@ -3991,26 +3983,22 @@ static int tile_add_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(ev return WM_operator_props_dialog_popup(C, op, 300); } -static void tile_add_draw(bContext *C, wmOperator *op) +static void tile_add_draw(bContext *UNUSED(C), wmOperator *op) { uiLayout *col; uiLayout *layout = op->layout; - wmWindowManager *wm = CTX_wm_manager(C); - PointerRNA ptr; - - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); col = uiLayoutColumn(layout, false); - uiItemR(col, &ptr, "number", 0, NULL, ICON_NONE); - uiItemR(col, &ptr, "count", 0, NULL, ICON_NONE); - uiItemR(col, &ptr, "label", 0, NULL, ICON_NONE); - uiItemR(layout, &ptr, "fill", 0, NULL, ICON_NONE); + uiItemR(col, op->ptr, "number", 0, NULL, ICON_NONE); + uiItemR(col, op->ptr, "count", 0, NULL, ICON_NONE); + uiItemR(col, op->ptr, "label", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "fill", 0, NULL, ICON_NONE); - if (RNA_boolean_get(&ptr, "fill")) { - draw_fill_tile(&ptr, layout); + if (RNA_boolean_get(op->ptr, "fill")) { + draw_fill_tile(op->ptr, layout); } } @@ -4128,13 +4116,9 @@ static int tile_fill_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(e return WM_operator_props_dialog_popup(C, op, 300); } -static void tile_fill_draw(bContext *C, wmOperator *op) +static void tile_fill_draw(bContext *UNUSED(C), wmOperator *op) { - wmWindowManager *wm = CTX_wm_manager(C); - PointerRNA ptr; - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); - - draw_fill_tile(&ptr, op->layout); + draw_fill_tile(op->ptr, op->layout); } void IMAGE_OT_tile_fill(wmOperatorType *ot) diff --git a/source/blender/editors/space_info/info_ops.c b/source/blender/editors/space_info/info_ops.c index 0583628be43..aaf9852e212 100644 --- a/source/blender/editors/space_info/info_ops.c +++ b/source/blender/editors/space_info/info_ops.c @@ -56,7 +56,9 @@ #include "info_intern.h" -/********************* pack blend file libraries operator *********************/ +/* -------------------------------------------------------------------- */ +/** \name Pack Blend File Libraries Operator + * \{ */ static int pack_libraries_exec(bContext *C, wmOperator *op) { @@ -70,9 +72,11 @@ static int pack_libraries_exec(bContext *C, wmOperator *op) void FILE_OT_pack_libraries(wmOperatorType *ot) { /* identifiers */ - ot->name = "Pack Blender Libraries"; + ot->name = "Pack Linked Libraries"; ot->idname = "FILE_OT_pack_libraries"; - ot->description = "Pack all used Blender library files into the current .blend"; + ot->description = + "Store all data-blocks linked from other .blend files in the current .blend file. " + "Library references are preserved so the linked data-blocks can be unpacked again"; /* api callbacks */ ot->exec = pack_libraries_exec; @@ -90,18 +94,24 @@ static int unpack_libraries_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Unpack Blend File Libraries Operator + * \{ */ + static int unpack_libraries_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { return WM_operator_confirm_message( - C, op, "Unpack Blender Libraries - creates directories, all new paths should work"); + C, op, "Unpack Linked Libraries - creates directories, all new paths should work"); } void FILE_OT_unpack_libraries(wmOperatorType *ot) { /* identifiers */ - ot->name = "Unpack Blender Libraries"; + ot->name = "Unpack Linked Libraries"; ot->idname = "FILE_OT_unpack_libraries"; - ot->description = "Unpack all used Blender library files from this .blend file"; + ot->description = "Restore all packed linked data-blocks to their original locations"; /* api callbacks */ ot->invoke = unpack_libraries_invoke; @@ -111,7 +121,11 @@ void FILE_OT_unpack_libraries(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -/********************* toggle auto-pack operator *********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Toggle Auto-Pack Operator + * \{ */ static int autopack_toggle_exec(bContext *C, wmOperator *op) { @@ -131,7 +145,7 @@ static int autopack_toggle_exec(bContext *C, wmOperator *op) void FILE_OT_autopack_toggle(wmOperatorType *ot) { /* identifiers */ - ot->name = "Automatically Pack Into .blend"; + ot->name = "Automatically Pack Resources"; ot->idname = "FILE_OT_autopack_toggle"; ot->description = "Automatically pack all external files into the .blend file"; @@ -142,7 +156,11 @@ void FILE_OT_autopack_toggle(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -/********************* pack all operator *********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Pack All Operator + * \{ */ static int pack_all_exec(bContext *C, wmOperator *op) { @@ -176,9 +194,9 @@ static int pack_all_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(ev void FILE_OT_pack_all(wmOperatorType *ot) { /* identifiers */ - ot->name = "Pack All Into .blend"; + ot->name = "Pack Resources"; ot->idname = "FILE_OT_pack_all"; - ot->description = "Pack all used external files into the .blend"; + ot->description = "Pack all used external files into this .blend"; /* api callbacks */ ot->exec = pack_all_exec; @@ -188,7 +206,11 @@ void FILE_OT_pack_all(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -/********************* unpack all operator *********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Unpack All Operator + * \{ */ static const EnumPropertyItem unpack_all_method_items[] = { {PF_USE_LOCAL, "USE_LOCAL", 0, "Use files in current directory (create when necessary)", ""}, @@ -263,7 +285,7 @@ static int unpack_all_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED( void FILE_OT_unpack_all(wmOperatorType *ot) { /* identifiers */ - ot->name = "Unpack All Into Files"; + ot->name = "Unpack Resources"; ot->idname = "FILE_OT_unpack_all"; ot->description = "Unpack all files packed into this .blend to external ones"; @@ -279,7 +301,11 @@ void FILE_OT_unpack_all(wmOperatorType *ot) ot->srna, "method", unpack_all_method_items, PF_USE_LOCAL, "Method", "How to unpack"); } -/********************* unpack single item operator *********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Unpack Single Item Operator + * \{ */ static const EnumPropertyItem unpack_item_method_items[] = { {PF_USE_LOCAL, "USE_LOCAL", 0, "Use file from current directory (create when necessary)", ""}, @@ -373,7 +399,11 @@ void FILE_OT_unpack_item(wmOperatorType *ot) INT_MAX); } -/********************* make paths relative operator *********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Make Paths Relative Operator + * \{ */ static int make_paths_relative_exec(bContext *C, wmOperator *op) { @@ -395,7 +425,7 @@ static int make_paths_relative_exec(bContext *C, wmOperator *op) void FILE_OT_make_paths_relative(wmOperatorType *ot) { /* identifiers */ - ot->name = "Make All Paths Relative"; + ot->name = "Make Paths Relative"; ot->idname = "FILE_OT_make_paths_relative"; ot->description = "Make all paths to external files relative to current .blend"; @@ -406,7 +436,11 @@ void FILE_OT_make_paths_relative(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -/********************* make paths absolute operator *********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Make Paths Absolute Operator + * \{ */ static int make_paths_absolute_exec(bContext *C, wmOperator *op) { @@ -428,7 +462,7 @@ static int make_paths_absolute_exec(bContext *C, wmOperator *op) void FILE_OT_make_paths_absolute(wmOperatorType *ot) { /* identifiers */ - ot->name = "Make All Paths Absolute"; + ot->name = "Make Paths Absolute"; ot->idname = "FILE_OT_make_paths_absolute"; ot->description = "Make all paths to external files absolute"; @@ -439,7 +473,11 @@ void FILE_OT_make_paths_absolute(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -/********************* report missing files operator *********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Report Missing Files Operator + * \{ */ static int report_missing_files_exec(bContext *C, wmOperator *op) { @@ -465,7 +503,11 @@ void FILE_OT_report_missing_files(wmOperatorType *ot) ot->flag = 0; /* only reports so no need to undo/register */ } -/********************* find missing files operator *********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Find Missing Files Operator + * \{ */ static int find_missing_files_exec(bContext *C, wmOperator *op) { @@ -516,7 +558,11 @@ void FILE_OT_find_missing_files(wmOperatorType *ot) FILE_SORT_DEFAULT); } -/********************* report box operator *********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Report Box Operator + * \{ */ /* Hard to decide whether to keep this as an operator, * or turn it into a hardcoded ui control feature, @@ -621,3 +667,5 @@ void INFO_OT_reports_display_update(wmOperatorType *ot) } /* report operators */ + +/** \} */ diff --git a/source/blender/editors/space_info/info_stats.c b/source/blender/editors/space_info/info_stats.c index ffac5c982d6..cf847fa18a8 100644 --- a/source/blender/editors/space_info/info_stats.c +++ b/source/blender/editors/space_info/info_stats.c @@ -505,14 +505,14 @@ static void get_stats_string( LayerCollection *layer_collection = view_layer->active_collection; if (object_mode == OB_MODE_OBJECT) { - *ofs += BLI_snprintf(info + *ofs, - len - *ofs, - "%s | ", - BKE_collection_ui_name_get(layer_collection->collection)); + *ofs += BLI_snprintf_rlen(info + *ofs, + len - *ofs, + "%s | ", + BKE_collection_ui_name_get(layer_collection->collection)); } if (ob) { - *ofs += BLI_snprintf(info + *ofs, len - *ofs, "%s | ", ob->id.name + 2); + *ofs += BLI_snprintf_rlen(info + *ofs, len - *ofs, "%s | ", ob->id.name + 2); } if (obedit) { @@ -521,72 +521,72 @@ static void get_stats_string( } if (obedit->type == OB_MESH) { - *ofs += BLI_snprintf(info + *ofs, - len - *ofs, - TIP_("Verts:%s/%s | Edges:%s/%s | Faces:%s/%s | Tris:%s"), - stats_fmt->totvertsel, - stats_fmt->totvert, - stats_fmt->totedgesel, - stats_fmt->totedge, - stats_fmt->totfacesel, - stats_fmt->totface, - stats_fmt->tottri); + *ofs += BLI_snprintf_rlen(info + *ofs, + len - *ofs, + TIP_("Verts:%s/%s | Edges:%s/%s | Faces:%s/%s | Tris:%s"), + stats_fmt->totvertsel, + stats_fmt->totvert, + stats_fmt->totedgesel, + stats_fmt->totedge, + stats_fmt->totfacesel, + stats_fmt->totface, + stats_fmt->tottri); } else if (obedit->type == OB_ARMATURE) { - *ofs += BLI_snprintf(info + *ofs, - len - *ofs, - TIP_("Joints:%s/%s | Bones:%s/%s"), - stats_fmt->totvertsel, - stats_fmt->totvert, - stats_fmt->totbonesel, - stats_fmt->totbone); + *ofs += BLI_snprintf_rlen(info + *ofs, + len - *ofs, + TIP_("Joints:%s/%s | Bones:%s/%s"), + stats_fmt->totvertsel, + stats_fmt->totvert, + stats_fmt->totbonesel, + stats_fmt->totbone); } else { - *ofs += BLI_snprintf( + *ofs += BLI_snprintf_rlen( info + *ofs, len - *ofs, TIP_("Verts:%s/%s"), stats_fmt->totvertsel, stats_fmt->totvert); } } else if (ob && (object_mode & OB_MODE_POSE)) { - *ofs += BLI_snprintf( + *ofs += BLI_snprintf_rlen( info + *ofs, len - *ofs, TIP_("Bones:%s/%s"), stats_fmt->totbonesel, stats_fmt->totbone); } else if ((ob) && (ob->type == OB_GPENCIL)) { - *ofs += BLI_snprintf(info + *ofs, - len - *ofs, - TIP_("Layers:%s | Frames:%s | Strokes:%s | Points:%s"), - stats_fmt->totgplayer, - stats_fmt->totgpframe, - stats_fmt->totgpstroke, - stats_fmt->totgppoint); + *ofs += BLI_snprintf_rlen(info + *ofs, + len - *ofs, + TIP_("Layers:%s | Frames:%s | Strokes:%s | Points:%s"), + stats_fmt->totgplayer, + stats_fmt->totgpframe, + stats_fmt->totgpstroke, + stats_fmt->totgppoint); } else if (ob && (object_mode & OB_MODE_SCULPT)) { if (stats_is_object_dynamic_topology_sculpt(ob)) { - *ofs += BLI_snprintf(info + *ofs, - len - *ofs, - TIP_("Verts:%s | Tris:%s"), - stats_fmt->totvert, - stats_fmt->tottri); + *ofs += BLI_snprintf_rlen(info + *ofs, + len - *ofs, + TIP_("Verts:%s | Tris:%s"), + stats_fmt->totvert, + stats_fmt->tottri); } else { - *ofs += BLI_snprintf(info + *ofs, - len - *ofs, - TIP_("Verts:%s/%s | Faces:%s/%s"), - stats_fmt->totvertsculpt, - stats_fmt->totvert, - stats_fmt->totfacesculpt, - stats_fmt->totface); + *ofs += BLI_snprintf_rlen(info + *ofs, + len - *ofs, + TIP_("Verts:%s/%s | Faces:%s/%s"), + stats_fmt->totvertsculpt, + stats_fmt->totvert, + stats_fmt->totfacesculpt, + stats_fmt->totface); } } else { - *ofs += BLI_snprintf(info + *ofs, - len - *ofs, - TIP_("Verts:%s | Faces:%s | Tris:%s"), - stats_fmt->totvert, - stats_fmt->totface, - stats_fmt->tottri); + *ofs += BLI_snprintf_rlen(info + *ofs, + len - *ofs, + TIP_("Verts:%s | Faces:%s | Tris:%s"), + stats_fmt->totvert, + stats_fmt->totface, + stats_fmt->tottri); } - *ofs += BLI_snprintf( + *ofs += BLI_snprintf_rlen( info + *ofs, len - *ofs, TIP_(" | Objects:%s/%s"), stats_fmt->totobjsel, stats_fmt->totobj); } @@ -613,11 +613,11 @@ static const char *info_statusbar_string(Main *bmain, /* Memory status. */ if (statusbar_flag & STATUSBAR_SHOW_MEMORY) { if (info[0]) { - ofs += BLI_snprintf(info + ofs, len - ofs, " | "); + ofs += BLI_snprintf_rlen(info + ofs, len - ofs, " | "); } uintptr_t mem_in_use = MEM_get_memory_in_use(); BLI_str_format_byte_unit(formatted_mem, mem_in_use, false); - ofs += BLI_snprintf(info + ofs, len, TIP_("Memory: %s"), formatted_mem); + ofs += BLI_snprintf_rlen(info + ofs, len, TIP_("Memory: %s"), formatted_mem); } /* GPU VRAM status. */ @@ -627,27 +627,27 @@ static const char *info_statusbar_string(Main *bmain, float gpu_total_gb = gpu_tot_mem_kb / 1048576.0f; float gpu_free_gb = gpu_free_mem_kb / 1048576.0f; if (info[0]) { - ofs += BLI_snprintf(info + ofs, len - ofs, " | "); + ofs += BLI_snprintf_rlen(info + ofs, len - ofs, " | "); } if (gpu_free_mem_kb && gpu_tot_mem_kb) { - ofs += BLI_snprintf(info + ofs, - len - ofs, - TIP_("VRAM: %.1f/%.1f GiB"), - gpu_total_gb - gpu_free_gb, - gpu_total_gb); + ofs += BLI_snprintf_rlen(info + ofs, + len - ofs, + TIP_("VRAM: %.1f/%.1f GiB"), + gpu_total_gb - gpu_free_gb, + gpu_total_gb); } else { /* Can only show amount of GPU VRAM available. */ - ofs += BLI_snprintf(info + ofs, len - ofs, TIP_("VRAM: %.1f GiB Free"), gpu_free_gb); + ofs += BLI_snprintf_rlen(info + ofs, len - ofs, TIP_("VRAM: %.1f GiB Free"), gpu_free_gb); } } /* Blender version. */ if (statusbar_flag & STATUSBAR_SHOW_VERSION) { if (info[0]) { - ofs += BLI_snprintf(info + ofs, len - ofs, " | "); + ofs += BLI_snprintf_rlen(info + ofs, len - ofs, " | "); } - ofs += BLI_snprintf(info + ofs, len - ofs, TIP_("%s"), BKE_blender_version_string()); + ofs += BLI_snprintf_rlen(info + ofs, len - ofs, TIP_("%s"), BKE_blender_version_string()); } return info; @@ -697,7 +697,8 @@ void ED_info_draw_stats( UI_FontThemeColor(font_id, TH_TEXT_HI); BLF_enable(font_id, BLF_SHADOW); - BLF_shadow(font_id, 5, (const float[4]){0.0f, 0.0f, 0.0f, 1.0f}); + const float shadow_color[4] = {0.0f, 0.0f, 0.0f, 1.0f}; + BLF_shadow(font_id, 5, shadow_color); BLF_shadow_offset(font_id, 1, -1); /* Translated labels for each stat row. */ diff --git a/source/blender/editors/space_info/space_info.c b/source/blender/editors/space_info/space_info.c index dfc0abee704..e56bb44b1e6 100644 --- a/source/blender/editors/space_info/space_info.c +++ b/source/blender/editors/space_info/space_info.c @@ -255,11 +255,10 @@ static void info_header_region_message_subscribe(const wmRegionMessageSubscribeP struct wmMsgBus *mbus = params->message_bus; ARegion *region = params->region; - wmMsgSubscribeValue msg_sub_value_region_tag_redraw = { - .owner = region, - .user_data = region, - .notify = ED_region_do_msg_notify_tag_redraw, - }; + wmMsgSubscribeValue msg_sub_value_region_tag_redraw = {NULL}; + msg_sub_value_region_tag_redraw.owner = region; + msg_sub_value_region_tag_redraw.user_data = region; + msg_sub_value_region_tag_redraw.notify = ED_region_do_msg_notify_tag_redraw; WM_msg_subscribe_rna_anon_prop(mbus, Window, view_layer, &msg_sub_value_region_tag_redraw); WM_msg_subscribe_rna_anon_prop(mbus, ViewLayer, name, &msg_sub_value_region_tag_redraw); diff --git a/source/blender/editors/space_nla/nla_draw.c b/source/blender/editors/space_nla/nla_draw.c index 8bf6f2698e0..7d4011e0812 100644 --- a/source/blender/editors/space_nla/nla_draw.c +++ b/source/blender/editors/space_nla/nla_draw.c @@ -266,7 +266,7 @@ static void nla_strip_get_color_inside(AnimData *adt, NlaStrip *strip, float col } else if (strip->type == NLASTRIP_TYPE_META) { /* Meta Clip */ - /* TODO: should temporary metas get different colors too? */ + /* TODO: should temporary meta-strips get different colors too? */ if (strip->flag & NLASTRIP_FLAG_SELECT) { /* selected - use a bold purple color */ UI_GetThemeColor3fv(TH_NLA_META_SEL, color); diff --git a/source/blender/editors/space_node/drawnode.c b/source/blender/editors/space_node/drawnode.c index 5110c14ef4d..6b4366b2966 100644 --- a/source/blender/editors/space_node/drawnode.c +++ b/source/blender/editors/space_node/drawnode.c @@ -456,7 +456,9 @@ static void node_draw_frame(const bContext *C, } /* label */ - node_draw_frame_label(ntree, node, snode->runtime->aspect); + if (node->label[0] != '\0') { + node_draw_frame_label(ntree, node, snode->runtime->aspect); + } UI_block_end(C, node->block); UI_block_draw(C, node->block); @@ -3332,12 +3334,14 @@ static const float std_node_socket_colors[][4] = { {0.39, 0.78, 0.39, 1.0}, /* SOCK_SHADER */ {0.80, 0.65, 0.84, 1.0}, /* SOCK_BOOLEAN */ {0.0, 0.0, 0.0, 1.0}, /*__SOCK_MESH (deprecated) */ - {0.25, 0.75, 0.26, 1.0}, /* SOCK_INT */ + {0.35, 0.55, 0.36, 1.0}, /* SOCK_INT */ {0.44, 0.70, 1.00, 1.0}, /* SOCK_STRING */ {0.93, 0.62, 0.36, 1.0}, /* SOCK_OBJECT */ - {0.89, 0.76, 0.43, 1.0}, /* SOCK_IMAGE */ + {0.39, 0.22, 0.39, 1.0}, /* SOCK_IMAGE */ {0.00, 0.84, 0.64, 1.0}, /* SOCK_GEOMETRY */ {0.96, 0.96, 0.96, 1.0}, /* SOCK_COLLECTION */ + {0.62, 0.31, 0.64, 1.0}, /* SOCK_TEXTURE */ + {0.92, 0.46, 0.51, 1.0}, /* SOCK_MATERIAL */ }; /* common color callbacks for standard types */ @@ -3478,6 +3482,14 @@ static void std_node_socket_draw( uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, text, 0); break; } + case SOCK_TEXTURE: { + uiTemplateID(layout, C, ptr, "default_value", "texture.new", NULL, NULL, 0, ICON_NONE, NULL); + break; + } + case SOCK_MATERIAL: { + uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, text, 0); + break; + } default: node_socket_button_label(C, layout, ptr, node_ptr, text); break; diff --git a/source/blender/editors/space_node/node_edit.c b/source/blender/editors/space_node/node_edit.c index d4780534a83..50fa8b28468 100644 --- a/source/blender/editors/space_node/node_edit.c +++ b/source/blender/editors/space_node/node_edit.c @@ -1713,8 +1713,6 @@ static int node_mute_exec(bContext *C, wmOperator *UNUSED(op)) } } - do_tag_update |= ED_node_is_geometry(snode); - snode_notify(C, snode); if (do_tag_update) { snode_dag_update(C, snode); @@ -1755,8 +1753,6 @@ static int node_delete_exec(bContext *C, wmOperator *UNUSED(op)) } } - do_tag_update |= ED_node_is_geometry(snode); - ntreeUpdateTree(CTX_data_main(C), snode->edittree); snode_notify(C, snode); diff --git a/source/blender/editors/space_node/node_relationships.c b/source/blender/editors/space_node/node_relationships.c index 91fe8f5ec89..28c660b0632 100644 --- a/source/blender/editors/space_node/node_relationships.c +++ b/source/blender/editors/space_node/node_relationships.c @@ -852,8 +852,6 @@ static void node_link_exit(bContext *C, wmOperator *op, bool apply_links) } ntree->is_updating = false; - do_tag_update |= ED_node_is_geometry(snode); - ntreeUpdateTree(bmain, ntree); snode_notify(C, snode); if (do_tag_update) { @@ -1291,8 +1289,6 @@ static int cut_links_exec(bContext *C, wmOperator *op) } } - do_tag_update |= ED_node_is_geometry(snode); - if (found) { ntreeUpdateTree(CTX_data_main(C), snode->edittree); snode_notify(C, snode); @@ -1399,8 +1395,6 @@ static int mute_links_exec(bContext *C, wmOperator *op) link->flag &= ~NODE_LINK_TEST; } - do_tag_update |= ED_node_is_geometry(snode); - ntreeUpdateTree(CTX_data_main(C), snode->edittree); snode_notify(C, snode); if (do_tag_update) { @@ -1882,28 +1876,63 @@ void ED_node_link_intersect_test(ScrArea *area, int test) } } -/* assumes sockets in list */ -static bNodeSocket *socket_best_match(ListBase *sockets) -{ - /* find type range */ - int maxtype = 0; +static int get_main_socket_priority(const bNodeSocket *socket) +{ + switch ((eNodeSocketDatatype)socket->type) { + case __SOCK_MESH: + case SOCK_CUSTOM: + return -1; + case SOCK_BOOLEAN: + return 0; + case SOCK_INT: + return 1; + case SOCK_FLOAT: + return 2; + case SOCK_VECTOR: + return 3; + case SOCK_RGBA: + return 4; + case SOCK_STRING: + case SOCK_SHADER: + case SOCK_OBJECT: + case SOCK_IMAGE: + case SOCK_GEOMETRY: + case SOCK_COLLECTION: + case SOCK_TEXTURE: + case SOCK_MATERIAL: + return 5; + } + return -1; +} + +/** Get the "main" socket of a socket list using a heuristic based on socket types. */ +static bNodeSocket *get_main_socket(ListBase *sockets) +{ + /* find priority range */ + int maxpriority = -1; LISTBASE_FOREACH (bNodeSocket *, sock, sockets) { - maxtype = max_ii(sock->type, maxtype); + if (sock->flag & SOCK_UNAVAIL) { + continue; + } + maxpriority = max_ii(get_main_socket_priority(sock), maxpriority); } - /* try all types, starting from 'highest' (i.e. colors, vectors, values) */ - for (int type = maxtype; type >= 0; type--) { + /* try all priorities, starting from 'highest' */ + for (int priority = maxpriority; priority >= 0; priority--) { LISTBASE_FOREACH (bNodeSocket *, sock, sockets) { - if (!nodeSocketIsHidden(sock) && type == sock->type) { + if (!nodeSocketIsHidden(sock) && priority == get_main_socket_priority(sock)) { return sock; } } } - /* no visible sockets, unhide first of highest type */ - for (int type = maxtype; type >= 0; type--) { + /* no visible sockets, unhide first of highest priority */ + for (int priority = maxpriority; priority >= 0; priority--) { LISTBASE_FOREACH (bNodeSocket *, sock, sockets) { - if (type == sock->type) { + if (sock->flag & SOCK_UNAVAIL) { + continue; + } + if (priority == get_main_socket_priority(sock)) { sock->flag &= ~SOCK_HIDDEN; return sock; } @@ -2248,8 +2277,8 @@ void ED_node_link_insert(Main *bmain, ScrArea *area) } if (link) { - bNodeSocket *best_input = socket_best_match(&select->inputs); - bNodeSocket *best_output = socket_best_match(&select->outputs); + bNodeSocket *best_input = get_main_socket(&select->inputs); + bNodeSocket *best_output = get_main_socket(&select->outputs); if (best_input && best_output) { bNode *node = link->tonode; diff --git a/source/blender/editors/space_outliner/outliner_draw.c b/source/blender/editors/space_outliner/outliner_draw.c index 92cd50560e4..328a787c768 100644 --- a/source/blender/editors/space_outliner/outliner_draw.c +++ b/source/blender/editors/space_outliner/outliner_draw.c @@ -2106,7 +2106,7 @@ static void outliner_draw_mode_column_toggle(uiBlock *block, tip); UI_but_func_set(but, outliner_mode_toggle_fn, tselem, NULL); UI_but_flag_enable(but, UI_BUT_DRAG_LOCK); - /* Mode toggling handles it's own undo state because undo steps need to be grouped. */ + /* Mode toggling handles its own undo state because undo steps need to be grouped. */ UI_but_flag_disable(but, UI_BUT_UNDO); if (ID_IS_LINKED(&ob->id)) { diff --git a/source/blender/editors/space_outliner/outliner_tools.c b/source/blender/editors/space_outliner/outliner_tools.c index 456c2079f27..da47fd29549 100644 --- a/source/blender/editors/space_outliner/outliner_tools.c +++ b/source/blender/editors/space_outliner/outliner_tools.c @@ -740,7 +740,7 @@ static void id_local_fn(bContext *C, BKE_lib_id_clear_library_data(bmain, tselem->id); } else { - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); } } else if (ID_IS_OVERRIDE_LIBRARY_REAL(tselem->id)) { @@ -852,7 +852,7 @@ static void id_override_library_create_fn(bContext *C, success = BKE_lib_override_library_create_from_id(bmain, id_root, true) != NULL; /* Cleanup. */ - BKE_main_id_clear_newpoins(bmain); + BKE_main_id_newptr_and_tag_clear(bmain); BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); } @@ -902,7 +902,7 @@ static void id_override_library_reset_fn(bContext *C, } static void id_override_library_resync_fn(bContext *C, - ReportList *UNUSED(reports), + ReportList *reports, Scene *scene, TreeElement *te, TreeStoreElem *UNUSED(tsep), @@ -931,7 +931,7 @@ static void id_override_library_resync_fn(bContext *C, } BKE_lib_override_library_resync( - bmain, scene, CTX_data_view_layer(C), id_root, NULL, do_hierarchy_enforce, true); + bmain, scene, CTX_data_view_layer(C), id_root, NULL, do_hierarchy_enforce, true, reports); WM_event_add_notifier(C, NC_WINDOW, NULL); } diff --git a/source/blender/editors/space_outliner/outliner_tree.c b/source/blender/editors/space_outliner/outliner_tree.c index bed7683703f..90389fc1be2 100644 --- a/source/blender/editors/space_outliner/outliner_tree.c +++ b/source/blender/editors/space_outliner/outliner_tree.c @@ -1925,5 +1925,5 @@ void outliner_build_tree(Main *mainvar, outliner_filter_tree(space_outliner, view_layer); outliner_restore_scrolling_position(space_outliner, region, &focus); - BKE_main_id_clear_newpoins(mainvar); + BKE_main_id_newptr_and_tag_clear(mainvar); } diff --git a/source/blender/editors/space_outliner/space_outliner.c b/source/blender/editors/space_outliner/space_outliner.c index 5d774049e3e..728be1ccaaf 100644 --- a/source/blender/editors/space_outliner/space_outliner.c +++ b/source/blender/editors/space_outliner/space_outliner.c @@ -155,6 +155,8 @@ static void outliner_main_region_listener(const wmRegionListenerParams *params) case NC_OBJECT: switch (wmn->data) { case ND_TRANSFORM: + ED_region_tag_redraw_no_rebuild(region); + break; case ND_BONE_ACTIVE: case ND_BONE_SELECT: case ND_DRAW: diff --git a/source/blender/editors/space_sequencer/sequencer_add.c b/source/blender/editors/space_sequencer/sequencer_add.c index 68c0f4f4bdb..ac31e0e7c37 100644 --- a/source/blender/editors/space_sequencer/sequencer_add.c +++ b/source/blender/editors/space_sequencer/sequencer_add.c @@ -158,7 +158,7 @@ static void sequencer_generic_props__internal(wmOperatorType *ot, int flag) ot->prop = RNA_def_boolean(ot->srna, "set_view_transform", true, - "Set view transform", + "Set View Transform", "Set appropriate view transform based on media colorspace"); } } @@ -754,18 +754,16 @@ static int sequencer_add_movie_strip_invoke(bContext *C, return OPERATOR_RUNNING_MODAL; } -static void sequencer_add_draw(bContext *C, wmOperator *op) +static void sequencer_add_draw(bContext *UNUSED(C), wmOperator *op) { uiLayout *layout = op->layout; - wmWindowManager *wm = CTX_wm_manager(C); SequencerAddData *sad = op->customdata; ImageFormatData *imf = &sad->im_format; - PointerRNA imf_ptr, ptr; + PointerRNA imf_ptr; /* Main draw call. */ - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); uiDefAutoButsRNA( - layout, &ptr, sequencer_add_draw_check_fn, NULL, NULL, UI_BUT_LABEL_ALIGN_NONE, false); + layout, op->ptr, sequencer_add_draw_check_fn, NULL, NULL, UI_BUT_LABEL_ALIGN_NONE, false); /* Image template. */ RNA_pointer_create(NULL, &RNA_ImageFormatSettings, imf, &imf_ptr); diff --git a/source/blender/editors/space_sequencer/sequencer_edit.c b/source/blender/editors/space_sequencer/sequencer_edit.c index 5d616969a4f..ebd4c0090b4 100644 --- a/source/blender/editors/space_sequencer/sequencer_edit.c +++ b/source/blender/editors/space_sequencer/sequencer_edit.c @@ -290,7 +290,7 @@ static int sequencer_snap_exec(bContext *C, wmOperator *op) snap_frame = RNA_int_get(op->ptr, "frame"); - /* Check metas. */ + /* Check meta-strips. */ for (seq = ed->seqbasep->first; seq; seq = seq->next) { if (seq->flag & SELECT && !(seq->depth == 0 && seq->flag & SEQ_LOCK) && SEQ_transform_sequence_can_be_translated(seq)) { @@ -348,7 +348,7 @@ static int sequencer_snap_exec(bContext *C, wmOperator *op) } } - SEQ_sort(scene); + SEQ_sort(SEQ_active_seqbase_get(ed)); DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS); WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); @@ -1443,7 +1443,7 @@ static int sequencer_split_exec(bContext *C, wmOperator *op) } } - SEQ_sort(scene); + SEQ_sort(SEQ_active_seqbase_get(ed)); } if (changed) { WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); @@ -1485,26 +1485,22 @@ static int sequencer_split_invoke(bContext *C, wmOperator *op, const wmEvent *ev return sequencer_split_exec(C, op); } -static void sequencer_split_ui(bContext *C, wmOperator *op) +static void sequencer_split_ui(bContext *UNUSED(C), wmOperator *op) { uiLayout *layout = op->layout; - wmWindowManager *wm = CTX_wm_manager(C); uiLayoutSetPropSep(layout, true); uiLayoutSetPropDecorate(layout, false); - PointerRNA ptr; - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); - uiLayout *row = uiLayoutRow(layout, false); - uiItemR(row, &ptr, "type", UI_ITEM_R_EXPAND, NULL, ICON_NONE); - uiItemR(layout, &ptr, "frame", 0, NULL, ICON_NONE); - uiItemR(layout, &ptr, "side", 0, NULL, ICON_NONE); + uiItemR(row, op->ptr, "type", UI_ITEM_R_EXPAND, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "frame", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "side", 0, NULL, ICON_NONE); uiItemS(layout); - uiItemR(layout, &ptr, "use_cursor_position", 0, NULL, ICON_NONE); - if (RNA_boolean_get(&ptr, "use_cursor_position")) { - uiItemR(layout, &ptr, "channel", 0, NULL, ICON_NONE); + uiItemR(layout, op->ptr, "use_cursor_position", 0, NULL, ICON_NONE); + if (RNA_boolean_get(op->ptr, "use_cursor_position")) { + uiItemR(layout, op->ptr, "channel", 0, NULL, ICON_NONE); } } @@ -1581,17 +1577,6 @@ void SEQUENCER_OT_split(struct wmOperatorType *ot) /** \name Duplicate Strips Operator * \{ */ -static int apply_unique_name_fn(Sequence *seq, void *arg_pt) -{ - Scene *scene = (Scene *)arg_pt; - char name[sizeof(seq->name) - 2]; - - BLI_strncpy_utf8(name, seq->name + 2, sizeof(name)); - SEQ_sequence_base_unique_name_recursive(&scene->ed->seqbase, seq); - SEQ_dupe_animdata(scene, name, seq->name + 2); - return 1; -} - static int sequencer_add_duplicate_exec(bContext *C, wmOperator *UNUSED(op)) { Scene *scene = CTX_data_scene(C); @@ -1612,7 +1597,7 @@ static int sequencer_add_duplicate_exec(bContext *C, wmOperator *UNUSED(op)) BLI_movelisttolist(ed->seqbasep, &nseqbase); for (; seq; seq = seq->next) { - SEQ_iterator_recursive_apply(seq, apply_unique_name_fn, scene); + SEQ_ensure_unique_name(seq, scene); } WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); @@ -1833,7 +1818,7 @@ static int sequencer_separate_images_exec(bContext *C, wmOperator *op) } } - SEQ_sort(scene); + SEQ_sort(SEQ_active_seqbase_get(ed)); WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); @@ -1871,13 +1856,13 @@ static int sequencer_meta_toggle_exec(bContext *C, wmOperator *UNUSED(op)) Sequence *active_seq = SEQ_select_active_get(scene); if (active_seq && active_seq->type == SEQ_TYPE_META && active_seq->flag & SELECT) { - /* Enter metastrip. */ + /* Enter meta-strip. */ SEQ_meta_stack_alloc(ed, active_seq); SEQ_seqbase_active_set(ed, &active_seq->seqbase); SEQ_select_active_set(scene, NULL); } else { - /* Exit metastrip if possible. */ + /* Exit meta-strip if possible. */ if (BLI_listbase_is_empty(&ed->metastack)) { return OPERATOR_CANCELLED; } @@ -1899,7 +1884,7 @@ void SEQUENCER_OT_meta_toggle(wmOperatorType *ot) /* Identifiers. */ ot->name = "Toggle Meta Strip"; ot->idname = "SEQUENCER_OT_meta_toggle"; - ot->description = "Toggle a metastrip (to edit enclosed strips)"; + ot->description = "Toggle a meta-strip (to edit enclosed strips)"; /* Api callbacks. */ ot->exec = sequencer_meta_toggle_exec; @@ -1967,7 +1952,7 @@ void SEQUENCER_OT_meta_make(wmOperatorType *ot) /* Identifiers. */ ot->name = "Make Meta Strip"; ot->idname = "SEQUENCER_OT_meta_make"; - ot->description = "Group selected strips into a metastrip"; + ot->description = "Group selected strips into a meta-strip"; /* Api callbacks. */ ot->exec = sequencer_meta_make_exec; @@ -2018,7 +2003,7 @@ static int sequencer_meta_separate_exec(bContext *C, wmOperator *UNUSED(op)) } } - SEQ_sort(scene); + SEQ_sort(active_seqbase); DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS); WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); @@ -2030,7 +2015,7 @@ void SEQUENCER_OT_meta_separate(wmOperatorType *ot) /* Identifiers. */ ot->name = "UnMeta Strip"; ot->idname = "SEQUENCER_OT_meta_separate"; - ot->description = "Put the contents of a metastrip back in the sequencer"; + ot->description = "Put the contents of a meta-strip back in the sequencer"; /* Api callbacks. */ ot->exec = sequencer_meta_separate_exec; @@ -2241,7 +2226,7 @@ static int sequencer_swap_exec(bContext *C, wmOperator *op) } } - SEQ_sort(scene); + SEQ_sort(SEQ_active_seqbase_get(ed)); WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); @@ -2278,43 +2263,45 @@ void SEQUENCER_OT_swap(wmOperatorType *ot) static int sequencer_rendersize_exec(bContext *C, wmOperator *UNUSED(op)) { - int retval = OPERATOR_CANCELLED; Scene *scene = CTX_data_scene(C); Sequence *active_seq = SEQ_select_active_get(scene); StripElem *se = NULL; - if (active_seq == NULL) { + if (active_seq == NULL || active_seq->strip == NULL) { return OPERATOR_CANCELLED; } - if (active_seq->strip) { - switch (active_seq->type) { - case SEQ_TYPE_IMAGE: - se = SEQ_render_give_stripelem(active_seq, scene->r.cfra); - break; - case SEQ_TYPE_MOVIE: - se = active_seq->strip->stripdata; - break; - case SEQ_TYPE_SCENE: - case SEQ_TYPE_META: - case SEQ_TYPE_SOUND_RAM: - case SEQ_TYPE_SOUND_HD: - default: - break; - } + switch (active_seq->type) { + case SEQ_TYPE_IMAGE: + se = SEQ_render_give_stripelem(active_seq, scene->r.cfra); + break; + case SEQ_TYPE_MOVIE: + se = active_seq->strip->stripdata; + break; + default: + return OPERATOR_CANCELLED; } - if (se) { - /* Prevent setting the render size if sequence values aren't initialized. */ - if ((se->orig_width > 0) && (se->orig_height > 0)) { - scene->r.xsch = se->orig_width; - scene->r.ysch = se->orig_height; - WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, scene); - retval = OPERATOR_FINISHED; - } + if (se == NULL) { + return OPERATOR_CANCELLED; + } + + /* Prevent setting the render size if sequence values aren't initialized. */ + if (se->orig_width <= 0 || se->orig_height <= 0) { + return OPERATOR_CANCELLED; } - return retval; + scene->r.xsch = se->orig_width; + scene->r.ysch = se->orig_height; + + active_seq->strip->transform->scale_x = active_seq->strip->transform->scale_y = 1.0f; + active_seq->strip->transform->xofs = active_seq->strip->transform->yofs = 0.0f; + + SEQ_relations_invalidate_cache_preprocessed(scene, active_seq); + WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, scene); + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_SEQUENCER, NULL); + + return OPERATOR_FINISHED; } void SEQUENCER_OT_rendersize(wmOperatorType *ot) @@ -2467,7 +2454,7 @@ static int sequencer_paste_exec(bContext *C, wmOperator *op) for (iseq = iseq_first; iseq; iseq = iseq->next) { /* Make sure, that pasted strips have unique names. */ - SEQ_iterator_recursive_apply(iseq, apply_unique_name_fn, scene); + SEQ_ensure_unique_name(iseq, scene); /* Translate after name has been changed, otherwise this will affect animdata of original * strip. */ SEQ_transform_translate_sequence(scene, iseq, ofs); diff --git a/source/blender/editors/space_sequencer/sequencer_select.c b/source/blender/editors/space_sequencer/sequencer_select.c index a9f8a70d61e..cfc11afce13 100644 --- a/source/blender/editors/space_sequencer/sequencer_select.c +++ b/source/blender/editors/space_sequencer/sequencer_select.c @@ -26,7 +26,6 @@ #include <string.h> #include "BLI_blenlib.h" -#include "BLI_ghash.h" #include "BLI_math.h" #include "BLI_utildefines.h" @@ -1187,7 +1186,7 @@ static int sequencer_select_side_of_frame_exec(bContext *C, wmOperator *op) case 1: test = (timeline_frame <= seq->startdisp); break; - case 0: + case 2: test = (timeline_frame <= seq->enddisp) && (timeline_frame >= seq->startdisp); break; } @@ -1210,6 +1209,7 @@ void SEQUENCER_OT_select_side_of_frame(wmOperatorType *ot) static const EnumPropertyItem sequencer_select_left_right_types[] = { {-1, "LEFT", 0, "Left", "Select to the left of the current frame"}, {1, "RIGHT", 0, "Right", "Select to the right of the current frame"}, + {2, "CURRENT", 0, "Current Frame", "Select intersecting with the current frame"}, {0, NULL, 0, NULL, NULL}, }; @@ -1622,64 +1622,47 @@ static bool select_grouped_time_overlap(Editing *ed, Sequence *actseq) return changed; } -static bool select_grouped_effect_link(Editing *ed, Sequence *actseq, const int channel) +/* Query strips that are in lower channel and intersect in time with seq_reference. */ +static void query_lower_channel_strips(Sequence *seq_reference, + ListBase *seqbase, + SeqCollection *collection) { - bool changed = false; - const bool is_audio = ((actseq->type == SEQ_TYPE_META) || SEQ_IS_SOUND(actseq)); - int startdisp = actseq->startdisp; - int enddisp = actseq->enddisp; - int machine = actseq->machine; - SeqIterator iter; - - LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) { - seq->tmp = NULL; - } - - actseq->tmp = POINTER_FROM_INT(true); - - Sequence *seq = NULL; - for (SEQ_iterator_begin(ed, &iter, true); iter.valid; SEQ_iterator_next(&iter)) { - seq = iter.seq; - - /* Ignore all seqs already selected. */ - /* Ignore all seqs not sharing some time with active one. */ - /* Ignore all seqs of incompatible types (audio vs video). */ - if (!SEQ_CHANNEL_CHECK(seq, channel) || (seq->flag & SELECT) || (seq->startdisp >= enddisp) || - (seq->enddisp < startdisp) || (!is_audio && SEQ_IS_SOUND(seq)) || - (is_audio && !((seq->type == SEQ_TYPE_META) || SEQ_IS_SOUND(seq)))) { - continue; + LISTBASE_FOREACH (Sequence *, seq_test, seqbase) { + if (seq_test->machine > seq_reference->machine) { + continue; /* Not lower channel. */ } + if (seq_test->enddisp <= seq_reference->startdisp || + seq_test->startdisp >= seq_reference->enddisp) { + continue; /* Not intersecting in time. */ + } + SEQ_collection_append_strip(seq_test, collection); + } +} - /* If the seq is an effect one, we need extra checking. */ - if (SEQ_IS_EFFECT(seq) && ((seq->seq1 && seq->seq1->tmp) || (seq->seq2 && seq->seq2->tmp) || - (seq->seq3 && seq->seq3->tmp))) { - if (startdisp > seq->startdisp) { - startdisp = seq->startdisp; - } - if (enddisp < seq->enddisp) { - enddisp = seq->enddisp; - } - if (machine < seq->machine) { - machine = seq->machine; - } - - seq->tmp = POINTER_FROM_INT(true); +/* Select all strips within time range and with lower channel of initial selection. Then select + * effect chains of these strips. */ +static bool select_grouped_effect_link(Editing *ed, + Sequence *UNUSED(actseq), + const int UNUSED(channel)) +{ + ListBase *seqbase = SEQ_active_seqbase_get(ed); - seq->flag |= SELECT; - changed = true; + /* Get collection of strips. */ + SeqCollection *collection = SEQ_query_selected_strips(seqbase); + const int selected_strip_count = BLI_gset_len(collection->set); + SEQ_collection_expand(seqbase, collection, query_lower_channel_strips); + SEQ_collection_expand(seqbase, collection, SEQ_query_strip_effect_chain); - /* Unfortunately, we must restart checks from the beginning. */ - SEQ_iterator_end(&iter); - SEQ_iterator_begin(ed, &iter, true); - } + /* Check if other strips will be affected. */ + const bool changed = BLI_gset_len(collection->set) > selected_strip_count; - /* Video strips below active one, or any strip for audio (order doesn't matter here). */ - else if (seq->machine < machine || is_audio) { - seq->flag |= SELECT; - changed = true; - } + /* Actual logic. */ + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, collection) { + seq->flag |= SELECT; } - SEQ_iterator_end(&iter); + + SEQ_collection_free(collection); return changed; } diff --git a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc index acdf30d0ea7..1f0b5d5d13e 100644 --- a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc +++ b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc @@ -256,7 +256,7 @@ static std::unique_ptr<DataSource> get_data_source(const bContext *C) return {}; } Object *object_orig = (Object *)used_id; - if (!ELEM(object_orig->type, OB_MESH, OB_POINTCLOUD)) { + if (!ELEM(object_orig->type, OB_MESH, OB_POINTCLOUD, OB_VOLUME)) { return {}; } Object *object_eval = DEG_get_evaluated_object(depsgraph, object_orig); diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh b/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh index d1e80f1d87e..c9b73aabf96 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh @@ -50,7 +50,7 @@ class CellValue { std::optional<bool> value_bool; std::optional<float2> value_float2; std::optional<float3> value_float3; - std::optional<Color4f> value_color; + std::optional<ColorGeometry4f> value_color; std::optional<ObjectCellValue> value_object; std::optional<CollectionCellValue> value_collection; }; diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc index 018431225e5..452885959f6 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -57,44 +57,45 @@ std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values( { std::lock_guard lock{mutex_}; - bke::ReadAttributePtr attribute_ptr = component_->attribute_try_get_for_read(column_id.name); - if (!attribute_ptr) { + bke::ReadAttributeLookup attribute = component_->attribute_try_get_for_read(column_id.name); + if (!attribute) { return {}; } - const bke::ReadAttribute *attribute = scope_.add(std::move(attribute_ptr), __func__); - if (attribute->domain() != domain_) { + const fn::GVArray *varray = scope_.add(std::move(attribute.varray), __func__); + if (attribute.domain != domain_) { return {}; } - int domain_size = attribute->size(); - switch (attribute->custom_data_type()) { + int domain_size = varray->size(); + const CustomDataType type = bke::cpp_type_to_custom_data_type(varray->type()); + switch (type) { case CD_PROP_FLOAT: return column_values_from_function( - column_id.name, domain_size, [attribute](int index, CellValue &r_cell_value) { + column_id.name, domain_size, [varray](int index, CellValue &r_cell_value) { float value; - attribute->get(index, &value); + varray->get(index, &value); r_cell_value.value_float = value; }); case CD_PROP_INT32: return column_values_from_function( - column_id.name, domain_size, [attribute](int index, CellValue &r_cell_value) { + column_id.name, domain_size, [varray](int index, CellValue &r_cell_value) { int value; - attribute->get(index, &value); + varray->get(index, &value); r_cell_value.value_int = value; }); case CD_PROP_BOOL: return column_values_from_function( - column_id.name, domain_size, [attribute](int index, CellValue &r_cell_value) { + column_id.name, domain_size, [varray](int index, CellValue &r_cell_value) { bool value; - attribute->get(index, &value); + varray->get(index, &value); r_cell_value.value_bool = value; }); case CD_PROP_FLOAT2: { return column_values_from_function( column_id.name, domain_size, - [attribute](int index, CellValue &r_cell_value) { + [varray](int index, CellValue &r_cell_value) { float2 value; - attribute->get(index, &value); + varray->get(index, &value); r_cell_value.value_float2 = value; }, default_float2_column_width); @@ -103,9 +104,9 @@ std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values( return column_values_from_function( column_id.name, domain_size, - [attribute](int index, CellValue &r_cell_value) { + [varray](int index, CellValue &r_cell_value) { float3 value; - attribute->get(index, &value); + varray->get(index, &value); r_cell_value.value_float3 = value; }, default_float3_column_width); @@ -114,9 +115,9 @@ std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values( return column_values_from_function( column_id.name, domain_size, - [attribute](int index, CellValue &r_cell_value) { - Color4f value; - attribute->get(index, &value); + [varray](int index, CellValue &r_cell_value) { + ColorGeometry4f value; + varray->get(index, &value); r_cell_value.value_color = value; }, default_color_column_width); @@ -260,7 +261,7 @@ void InstancesDataSource::foreach_default_column_ids( SpreadsheetColumnID column_id; column_id.name = (char *)"Name"; fn(column_id); - for (const char *name : {"Position", "Rotation", "Scale"}) { + for (const char *name : {"Position", "Rotation", "Scale", "ID"}) { column_id.name = (char *)name; fn(column_id); } @@ -275,25 +276,31 @@ std::unique_ptr<ColumnValues> InstancesDataSource::get_column_values( const int size = this->tot_rows(); if (STREQ(column_id.name, "Name")) { - Span<InstancedData> instance_data = component_->instanced_data(); + Span<int> reference_handles = component_->instance_reference_handles(); + Span<InstanceReference> references = component_->references(); std::unique_ptr<ColumnValues> values = column_values_from_function( - "Name", size, [instance_data](int index, CellValue &r_cell_value) { - const InstancedData &data = instance_data[index]; - if (data.type == INSTANCE_DATA_TYPE_OBJECT) { - if (data.data.object != nullptr) { - r_cell_value.value_object = ObjectCellValue{data.data.object}; + "Name", size, [reference_handles, references](int index, CellValue &r_cell_value) { + const InstanceReference &reference = references[reference_handles[index]]; + switch (reference.type()) { + case InstanceReference::Type::Object: { + Object &object = reference.object(); + r_cell_value.value_object = ObjectCellValue{&object}; + break; } - } - else if (data.type == INSTANCE_DATA_TYPE_COLLECTION) { - if (data.data.collection != nullptr) { - r_cell_value.value_collection = CollectionCellValue{data.data.collection}; + case InstanceReference::Type::Collection: { + Collection &collection = reference.collection(); + r_cell_value.value_collection = CollectionCellValue{&collection}; + break; + } + case InstanceReference::Type::None: { + break; } } }); values->default_width = 8.0f; return values; } - Span<float4x4> transforms = component_->transforms(); + Span<float4x4> transforms = component_->instance_transforms(); if (STREQ(column_id.name, "Position")) { return column_values_from_function( column_id.name, @@ -321,6 +328,15 @@ std::unique_ptr<ColumnValues> InstancesDataSource::get_column_values( }, default_float3_column_width); } + Span<int> ids = component_->instance_ids(); + if (STREQ(column_id.name, "ID")) { + /* Make the column a bit wider by default, since the IDs tend to be large numbers. */ + return column_values_from_function( + column_id.name, + size, + [ids](int index, CellValue &r_cell_value) { r_cell_value.value_int = ids[index]; }, + 5.5f); + } return {}; } diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc b/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc index f1ca65817f6..8079763a339 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc @@ -170,7 +170,7 @@ class SpreadsheetLayoutDrawer : public SpreadsheetDrawer { this->draw_float_vector(params, Span(&value.x, 3)); } else if (cell_value.value_color.has_value()) { - const Color4f value = *cell_value.value_color; + const ColorGeometry4f value = *cell_value.value_color; this->draw_float_vector(params, Span(&value.r, 4)); } else if (cell_value.value_object.has_value()) { diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c index e6916c34a88..b5274c2357e 100644 --- a/source/blender/editors/space_view3d/space_view3d.c +++ b/source/blender/editors/space_view3d/space_view3d.c @@ -1595,7 +1595,6 @@ static void space_view3d_refresh(const bContext *C, ScrArea *UNUSED(area)) } const char *view3d_context_dir[] = { - "active_base", "active_object", NULL, }; @@ -1608,20 +1607,6 @@ static int view3d_context(const bContext *C, const char *member, bContextDataRes if (CTX_data_dir(member)) { CTX_data_dir_set(result, view3d_context_dir); } - else if (CTX_data_equals(member, "active_base")) { - Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - if (view_layer->basact) { - Object *ob = view_layer->basact->object; - /* if hidden but in edit mode, we still display, can happen with animation */ - if ((view_layer->basact->flag & BASE_VISIBLE_DEPSGRAPH) != 0 || - (ob->mode != OB_MODE_OBJECT)) { - CTX_data_pointer_set(result, &scene->id, &RNA_ObjectBase, view_layer->basact); - } - } - - return 1; - } else if (CTX_data_equals(member, "active_object")) { /* In most cases the active object is the `view_layer->basact->object`. * For the 3D view however it can be NULL when hidden. diff --git a/source/blender/editors/space_view3d/view3d_draw.c b/source/blender/editors/space_view3d/view3d_draw.c index f3a279ee12b..4a595c716b6 100644 --- a/source/blender/editors/space_view3d/view3d_draw.c +++ b/source/blender/editors/space_view3d/view3d_draw.c @@ -1288,6 +1288,11 @@ static void draw_viewport_name(ARegion *region, View3D *v3d, int xoffset, int *y name_array[name_array_len++] = IFACE_(" (Local)"); } + /* Indicate that clipping region is enabled. */ + if (rv3d->rflag & RV3D_CLIPPING) { + name_array[name_array_len++] = IFACE_(" (Clipped)"); + } + if (name_array_len > 1) { BLI_string_join_array(tmpstr, sizeof(tmpstr), name_array, name_array_len); name = tmpstr; @@ -2017,7 +2022,6 @@ ImBuf *ED_view3d_draw_offscreen_imbuf_simple(Depsgraph *depsgraph, source_shading_settings = shading_override; } memcpy(&v3d.shading, source_shading_settings, sizeof(View3DShading)); - v3d.shading.type = drawtype; if (drawtype == OB_MATERIAL) { v3d.shading.flag = V3D_SHADING_SCENE_WORLD | V3D_SHADING_SCENE_LIGHTS; @@ -2027,8 +2031,17 @@ ImBuf *ED_view3d_draw_offscreen_imbuf_simple(Depsgraph *depsgraph, v3d.shading.flag = V3D_SHADING_SCENE_WORLD_RENDER | V3D_SHADING_SCENE_LIGHTS_RENDER; v3d.shading.render_pass = SCE_PASS_COMBINED; } + else if (drawtype == OB_TEXTURE) { + drawtype = OB_SOLID; + v3d.shading.light = V3D_LIGHTING_STUDIO; + v3d.shading.color_type = V3D_SHADING_TEXTURE_COLOR; + } + v3d.shading.type = drawtype; v3d.flag2 = V3D_HIDE_OVERLAYS; + /* HACK: When rendering gpencil objects this opacity is used to mix vertex colors in when not in + * render mode. */ + v3d.overlay.gpencil_vertex_paint_opacity = 1.0f; if (draw_flags & V3D_OFSDRAW_SHOW_ANNOTATION) { v3d.flag2 |= V3D_SHOW_ANNOTATION; @@ -2067,7 +2080,7 @@ ImBuf *ED_view3d_draw_offscreen_imbuf_simple(Depsgraph *depsgraph, return ED_view3d_draw_offscreen_imbuf(depsgraph, scene, - drawtype, + v3d.shading.type, &v3d, ®ion, width, diff --git a/source/blender/editors/space_view3d/view3d_edit.c b/source/blender/editors/space_view3d/view3d_edit.c index 5f3d71cc190..967ad966320 100644 --- a/source/blender/editors/space_view3d/view3d_edit.c +++ b/source/blender/editors/space_view3d/view3d_edit.c @@ -2507,7 +2507,7 @@ static void view_dolly_to_vector_3d(ARegion *region, madd_v3_v3v3fl(rv3d->ofs, orig_ofs, dvec, -(1.0f - dfac)); } -static void viewdolly_apply(ViewOpsData *vod, const int xy[2], const short zoom_invert) +static void viewdolly_apply(ViewOpsData *vod, const int xy[2], const bool zoom_invert) { float zfac = 1.0; @@ -5065,7 +5065,7 @@ void ED_view3d_cursor3d_position_rotation(bContext *C, SCE_SNAP_MODE_FACE, &(const struct SnapObjectParams){ .snap_select = SNAP_ALL, - .use_object_edit_cage = false, + .edit_mode_type = SNAP_GEOM_FINAL, .use_occlusion_test = true, }, mval_fl, diff --git a/source/blender/editors/space_view3d/view3d_gizmo_ruler.c b/source/blender/editors/space_view3d/view3d_gizmo_ruler.c index e202276831c..0d568363b00 100644 --- a/source/blender/editors/space_view3d/view3d_gizmo_ruler.c +++ b/source/blender/editors/space_view3d/view3d_gizmo_ruler.c @@ -338,7 +338,7 @@ static bool view3d_ruler_item_mousemove(struct Depsgraph *depsgraph, SCE_SNAP_MODE_FACE, &(const struct SnapObjectParams){ .snap_select = SNAP_ALL, - .use_object_edit_cage = true, + .edit_mode_type = SNAP_GEOM_CAGE, }, mval_fl, NULL, @@ -352,7 +352,7 @@ static bool view3d_ruler_item_mousemove(struct Depsgraph *depsgraph, depsgraph, &(const struct SnapObjectParams){ .snap_select = SNAP_ALL, - .use_object_edit_cage = true, + .edit_mode_type = SNAP_GEOM_CAGE, }, ray_start, ray_normal, @@ -446,7 +446,7 @@ static bool view3d_ruler_to_gpencil(bContext *C, wmGizmoGroup *gzgroup) gpl = view3d_ruler_layer_get(gpd); if (gpl == NULL) { - gpl = BKE_gpencil_layer_addnew(gpd, ruler_name, false); + gpl = BKE_gpencil_layer_addnew(gpd, ruler_name, false, false); copy_v4_v4(gpl->color, U.gpencil_new_layer_col); gpl->thickness = 1; gpl->flag |= GP_LAYER_HIDE | GP_LAYER_IS_RULER; @@ -1124,12 +1124,13 @@ static void WIDGETGROUP_ruler_setup(const bContext *C, wmGizmoGroup *gzgroup) const wmGizmoType *gzt_snap; gzt_snap = WM_gizmotype_find("GIZMO_GT_snap_3d", true); gizmo = WM_gizmo_new_ptr(gzt_snap, gzgroup, NULL); + RNA_enum_set(gizmo->ptr, "snap_elements_force", (SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE | /* SCE_SNAP_MODE_VOLUME | SCE_SNAP_MODE_GRID | SCE_SNAP_MODE_INCREMENT | */ SCE_SNAP_MODE_EDGE_PERPENDICULAR | SCE_SNAP_MODE_EDGE_MIDPOINT)); - + ED_gizmotypes_snap_3d_flag_set(gizmo, ED_SNAPGIZMO_SNAP_EDIT_GEOM_CAGE); WM_gizmo_set_color(gizmo, (float[4]){1.0f, 1.0f, 1.0f, 1.0f}); wmOperatorType *ot = WM_operatortype_find("VIEW3D_OT_ruler_add", true); diff --git a/source/blender/editors/space_view3d/view3d_placement.c b/source/blender/editors/space_view3d/view3d_placement.c index 7347e528edc..e602521f6a2 100644 --- a/source/blender/editors/space_view3d/view3d_placement.c +++ b/source/blender/editors/space_view3d/view3d_placement.c @@ -252,7 +252,7 @@ static int dot_v3_array_find_max_index(const float dirs[][3], } /** - * Re-order \a mat so \a axis_align uses it's own axis which is closest to \a v. + * Re-order \a mat so \a axis_align uses its own axis which is closest to \a v. */ static bool mat3_align_axis_to_v3(float mat[3][3], const int axis_align, const float v[3]) { @@ -323,7 +323,7 @@ static bool idp_poject_surface_normal(SnapObjectContext *snap_context, SCE_SNAP_MODE_FACE, &(const struct SnapObjectParams){ .snap_select = SNAP_ALL, - .use_object_edit_cage = true, + .edit_mode_type = SNAP_GEOM_EDIT, }, mval_fl, NULL, @@ -941,7 +941,7 @@ static void view3d_interactive_add_calc_plane(bContext *C, SCE_SNAP_MODE_FACE, &(const struct SnapObjectParams){ .snap_select = SNAP_ALL, - .use_object_edit_cage = true, + .edit_mode_type = SNAP_GEOM_EDIT, }, mval_fl, NULL, @@ -1762,7 +1762,7 @@ static void WIDGETGROUP_placement_setup(const bContext *UNUSED(C), wmGizmoGroup gizmo->flag |= WM_GIZMO_HIDDEN_KEYMAP; } - /* Sets the gizmos custom-data which has it's own free callback. */ + /* Sets the gizmos custom-data which has its own free callback. */ preview_plane_cursor_setup(gzgroup); } @@ -2061,7 +2061,7 @@ static void cursor_plane_draw(bContext *C, int x, int y, void *customdata) GPU_matrix_projection_set(rv3d->winmat); GPU_matrix_set(rv3d->viewmat); - const float scale_mod = U.gizmo_size * 2 * U.dpi_fac; + const float scale_mod = U.gizmo_size * 2 * U.dpi_fac / U.pixelsize; float final_scale = (scale_mod * pixel_size); diff --git a/source/blender/editors/space_view3d/view3d_walk.c b/source/blender/editors/space_view3d/view3d_walk.c index cbd65e3175d..ab4cf0c2135 100644 --- a/source/blender/editors/space_view3d/view3d_walk.c +++ b/source/blender/editors/space_view3d/view3d_walk.c @@ -407,7 +407,7 @@ static bool walk_floor_distance_get(RegionView3D *rv3d, &(const struct SnapObjectParams){ .snap_select = SNAP_ALL, /* Avoid having to convert the edit-mesh to a regular mesh. */ - .use_object_edit_cage = true, + .edit_mode_type = SNAP_GEOM_EDIT, }, ray_start, ray_normal, diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c index 5fc27a8b434..17bfc23aea7 100644 --- a/source/blender/editors/transform/transform.c +++ b/source/blender/editors/transform/transform.c @@ -74,7 +74,7 @@ static void drawTransformApply(const struct bContext *C, ARegion *region, void *arg); -static void initSnapSpatial(TransInfo *t, float r_snap[3]); +static void initSnapSpatial(TransInfo *t, float r_snap[2]); bool transdata_check_local_islands(TransInfo *t, short around) { @@ -1631,7 +1631,7 @@ static void initSnapSpatial(TransInfo *t, float r_snap[2]) /** * \note caller needs to free 't' on a 0 return - * \warning \a event might be NULL (when tweaking from redo panel) + * \warning \a event might be NULL (when tweaking from redo panel) * \see #saveTransform which writes these values back. */ bool initTransform(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *event, int mode) diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h index 1fffeb65f44..f03defe26e2 100644 --- a/source/blender/editors/transform/transform.h +++ b/source/blender/editors/transform/transform.h @@ -88,12 +88,11 @@ typedef enum { CTX_TEXTURE_SPACE = (1 << 9), CTX_NO_PET = (1 << 10), - CTX_NO_MIRROR = (1 << 11), - CTX_AUTOCONFIRM = (1 << 12), + CTX_AUTOCONFIRM = (1 << 11), /** When transforming object's, adjust the object data so it stays in the same place. */ - CTX_OBMODE_XFORM_OBDATA = (1 << 13), + CTX_OBMODE_XFORM_OBDATA = (1 << 12), /** Transform object parents without moving their children. */ - CTX_OBMODE_XFORM_SKIP_CHILDREN = (1 << 14), + CTX_OBMODE_XFORM_SKIP_CHILDREN = (1 << 13), } eTContext; /** #TransInfo.flag */ @@ -105,51 +104,50 @@ typedef enum { /** restrictions flags */ T_NO_CONSTRAINT = 1 << 2, T_NULL_ONE = 1 << 3, - T_NO_ZERO = 1 << 4, - T_ALL_RESTRICTIONS = T_NO_CONSTRAINT | T_NULL_ONE | T_NO_ZERO, + T_ALL_RESTRICTIONS = T_NO_CONSTRAINT | T_NULL_ONE, - T_PROP_EDIT = 1 << 5, - T_PROP_CONNECTED = 1 << 6, - T_PROP_PROJECTED = 1 << 7, + T_PROP_EDIT = 1 << 4, + T_PROP_CONNECTED = 1 << 5, + T_PROP_PROJECTED = 1 << 6, T_PROP_EDIT_ALL = T_PROP_EDIT | T_PROP_CONNECTED | T_PROP_PROJECTED, - T_V3D_ALIGN = 1 << 8, + T_V3D_ALIGN = 1 << 7, /** For 2D views such as UV or f-curve. */ - T_2D_EDIT = 1 << 9, - T_CLIP_UV = 1 << 10, + T_2D_EDIT = 1 << 8, + T_CLIP_UV = 1 << 9, /** Auto-IK is on. */ - T_AUTOIK = 1 << 11, + T_AUTOIK = 1 << 10, /** Don't use mirror even if the data-block option is set. */ - T_NO_MIRROR = 1 << 12, + T_NO_MIRROR = 1 << 11, /** To indicate that the value set in the `value` parameter is the final * value of the transformation, modified only by the constrain. */ - T_INPUT_IS_VALUES_FINAL = 1 << 13, + T_INPUT_IS_VALUES_FINAL = 1 << 12, /** To specify if we save back settings at the end. */ - T_MODAL = 1 << 14, + T_MODAL = 1 << 13, /** No re-topology (projection). */ - T_NO_PROJECT = 1 << 15, + T_NO_PROJECT = 1 << 14, - T_RELEASE_CONFIRM = 1 << 16, + T_RELEASE_CONFIRM = 1 << 15, /** Alternative transformation. used to add offset to tracking markers. */ - T_ALT_TRANSFORM = 1 << 17, + T_ALT_TRANSFORM = 1 << 16, /** #TransInfo.center has been set, don't change it. */ - T_OVERRIDE_CENTER = 1 << 18, + T_OVERRIDE_CENTER = 1 << 17, - T_MODAL_CURSOR_SET = 1 << 19, + T_MODAL_CURSOR_SET = 1 << 18, - T_CLNOR_REBUILD = 1 << 20, + T_CLNOR_REBUILD = 1 << 19, /** Merges unselected into selected after transforming (runs after transforming). */ - T_AUTOMERGE = 1 << 21, + T_AUTOMERGE = 1 << 20, /** Runs auto-merge & splits. */ - T_AUTOSPLIT = 1 << 22, + T_AUTOSPLIT = 1 << 21, } eTFlag; /** #TransInfo.modifiers */ @@ -432,14 +430,14 @@ typedef struct TransCustomDataContainer { /** * Container for Transform Data * - * Used to implement multi-object modes, so each object can have it's + * Used to implement multi-object modes, so each object can have its * own data array as well as object matrix, local center etc. * * Anything that can't be shared between all objects * and doesn't make sense to store for every vertex (in the #TransDataContainer.data). * * \note at some point this could be used to store non object containers - * although this only makes sense if each container has it's own matrices, + * although this only makes sense if each container has its own matrices, * otherwise all elements may as well be stored in one array (#TransDataContainer.data), * as is already done for curve-objects, f-curves. etc. */ diff --git a/source/blender/editors/transform/transform_convert.c b/source/blender/editors/transform/transform_convert.c index 7239bed1eeb..b4175faacf4 100644 --- a/source/blender/editors/transform/transform_convert.c +++ b/source/blender/editors/transform/transform_convert.c @@ -1147,8 +1147,7 @@ static void init_TransDataContainers(TransInfo *t, for (int i = 0; i < objects_len; i++) { TransDataContainer *tc = &t->data_container[i]; - if (((t->flag & T_NO_MIRROR) == 0) && ((t->options & CTX_NO_MIRROR) == 0) && - (objects[i]->type == OB_MESH)) { + if (!(t->flag & T_NO_MIRROR) && (objects[i]->type == OB_MESH)) { tc->use_mirror_axis_x = (((Mesh *)objects[i]->data)->symmetry & ME_SYMMETRY_X) != 0; tc->use_mirror_axis_y = (((Mesh *)objects[i]->data)->symmetry & ME_SYMMETRY_Y) != 0; tc->use_mirror_axis_z = (((Mesh *)objects[i]->data)->symmetry & ME_SYMMETRY_Z) != 0; @@ -1497,91 +1496,89 @@ void createTransData(bContext *C, TransInfo *t) /** \name Transform Data Recalc/Flush * \{ */ -void clipMirrorModifier(TransInfo *t) +void transform_convert_clip_mirror_modifier_apply(TransDataContainer *tc) { - FOREACH_TRANS_DATA_CONTAINER (t, tc) { - Object *ob = tc->obedit; - ModifierData *md = ob->modifiers.first; - float tolerance[3] = {0.0f, 0.0f, 0.0f}; - int axis = 0; - - for (; md; md = md->next) { - if ((md->type == eModifierType_Mirror) && (md->mode & eModifierMode_Realtime)) { - MirrorModifierData *mmd = (MirrorModifierData *)md; - - if (mmd->flag & MOD_MIR_CLIPPING) { - axis = 0; - if (mmd->flag & MOD_MIR_AXIS_X) { - axis |= 1; - tolerance[0] = mmd->tolerance; - } - if (mmd->flag & MOD_MIR_AXIS_Y) { - axis |= 2; - tolerance[1] = mmd->tolerance; - } - if (mmd->flag & MOD_MIR_AXIS_Z) { - axis |= 4; - tolerance[2] = mmd->tolerance; - } - if (axis) { - float mtx[4][4], imtx[4][4]; - int i; + Object *ob = tc->obedit; + ModifierData *md = ob->modifiers.first; + float tolerance[3] = {0.0f, 0.0f, 0.0f}; + int axis = 0; + + for (; md; md = md->next) { + if ((md->type == eModifierType_Mirror) && (md->mode & eModifierMode_Realtime)) { + MirrorModifierData *mmd = (MirrorModifierData *)md; + + if (mmd->flag & MOD_MIR_CLIPPING) { + axis = 0; + if (mmd->flag & MOD_MIR_AXIS_X) { + axis |= 1; + tolerance[0] = mmd->tolerance; + } + if (mmd->flag & MOD_MIR_AXIS_Y) { + axis |= 2; + tolerance[1] = mmd->tolerance; + } + if (mmd->flag & MOD_MIR_AXIS_Z) { + axis |= 4; + tolerance[2] = mmd->tolerance; + } + if (axis) { + float mtx[4][4], imtx[4][4]; + int i; - if (mmd->mirror_ob) { - float obinv[4][4]; + if (mmd->mirror_ob) { + float obinv[4][4]; - invert_m4_m4(obinv, mmd->mirror_ob->obmat); - mul_m4_m4m4(mtx, obinv, ob->obmat); - invert_m4_m4(imtx, mtx); - } + invert_m4_m4(obinv, mmd->mirror_ob->obmat); + mul_m4_m4m4(mtx, obinv, ob->obmat); + invert_m4_m4(imtx, mtx); + } - TransData *td = tc->data; - for (i = 0; i < tc->data_len; i++, td++) { - int clip; - float loc[3], iloc[3]; + TransData *td = tc->data; + for (i = 0; i < tc->data_len; i++, td++) { + int clip; + float loc[3], iloc[3]; - if (td->loc == NULL) { - break; - } + if (td->loc == NULL) { + break; + } - if (td->flag & TD_SKIP) { - continue; - } + if (td->flag & TD_SKIP) { + continue; + } - copy_v3_v3(loc, td->loc); - copy_v3_v3(iloc, td->iloc); + copy_v3_v3(loc, td->loc); + copy_v3_v3(iloc, td->iloc); - if (mmd->mirror_ob) { - mul_m4_v3(mtx, loc); - mul_m4_v3(mtx, iloc); - } + if (mmd->mirror_ob) { + mul_m4_v3(mtx, loc); + mul_m4_v3(mtx, iloc); + } - clip = 0; - if (axis & 1) { - if (fabsf(iloc[0]) <= tolerance[0] || loc[0] * iloc[0] < 0.0f) { - loc[0] = 0.0f; - clip = 1; - } + clip = 0; + if (axis & 1) { + if (fabsf(iloc[0]) <= tolerance[0] || loc[0] * iloc[0] < 0.0f) { + loc[0] = 0.0f; + clip = 1; } + } - if (axis & 2) { - if (fabsf(iloc[1]) <= tolerance[1] || loc[1] * iloc[1] < 0.0f) { - loc[1] = 0.0f; - clip = 1; - } + if (axis & 2) { + if (fabsf(iloc[1]) <= tolerance[1] || loc[1] * iloc[1] < 0.0f) { + loc[1] = 0.0f; + clip = 1; } - if (axis & 4) { - if (fabsf(iloc[2]) <= tolerance[2] || loc[2] * iloc[2] < 0.0f) { - loc[2] = 0.0f; - clip = 1; - } + } + if (axis & 4) { + if (fabsf(iloc[2]) <= tolerance[2] || loc[2] * iloc[2] < 0.0f) { + loc[2] = 0.0f; + clip = 1; } - if (clip) { - if (mmd->mirror_ob) { - mul_m4_v3(imtx, loc); - } - copy_v3_v3(td->loc, loc); + } + if (clip) { + if (mmd->mirror_ob) { + mul_m4_v3(imtx, loc); } + copy_v3_v3(td->loc, loc); } } } @@ -1669,38 +1666,6 @@ void animrecord_check_state(TransInfo *t, struct Object *ob) } } -static void recalcData_cursor_image(TransInfo *t) -{ - TransDataContainer *tc = t->data_container; - TransData *td = tc->data; - float aspect_inv[2]; - - aspect_inv[0] = 1.0f / t->aspect[0]; - aspect_inv[1] = 1.0f / t->aspect[1]; - - td->loc[0] = td->loc[0] * aspect_inv[0]; - td->loc[1] = td->loc[1] * aspect_inv[1]; - - DEG_id_tag_update(&t->scene->id, ID_RECALC_COPY_ON_WRITE); -} - -static void recalcData_cursor(TransInfo *t) -{ - DEG_id_tag_update(&t->scene->id, ID_RECALC_COPY_ON_WRITE); -} - -static void recalcData_obedit(TransInfo *t) -{ - if (t->state != TRANS_CANCEL) { - applyProject(t); - } - FOREACH_TRANS_DATA_CONTAINER (t, tc) { - if (tc->data_len) { - DEG_id_tag_update(tc->obedit->data, 0); /* sets recalc flags */ - } - } -} - /* called for updating while transform acts, once per redraw */ void recalcData(TransInfo *t) { @@ -1767,7 +1732,7 @@ void recalcData(TransInfo *t) recalcData_tracking(t); break; case TC_MBALL_VERTS: - recalcData_obedit(t); + recalcData_mball(t); break; case TC_LATTICE_VERTS: recalcData_lattice(t); diff --git a/source/blender/editors/transform/transform_convert.h b/source/blender/editors/transform/transform_convert.h index 36a51d57f64..11550ec8803 100644 --- a/source/blender/editors/transform/transform_convert.h +++ b/source/blender/editors/transform/transform_convert.h @@ -45,7 +45,7 @@ bool clipUVTransform(TransInfo *t, float vec[2], const bool resize); void clipUVData(TransInfo *t); /* transform_convert_mesh.c */ -void mesh_customdatacorrect_init(TransInfo *t); +void transform_convert_mesh_customdatacorrect_init(TransInfo *t); /* transform_convert_sequencer.c */ int transform_convert_sequencer_get_snap_bound(TransInfo *t); @@ -62,7 +62,7 @@ void calc_distanceCurveVerts(TransData *head, TransData *tail, bool cyclic); struct TransDataCurveHandleFlags *initTransDataCurveHandles(TransData *td, struct BezTriple *bezt); char transform_convert_frame_side_dir_get(TransInfo *t, float cframe); bool FrameOnMouseSide(char side, float frame, float cframe); -void clipMirrorModifier(TransInfo *t); +void transform_convert_clip_mirror_modifier_apply(TransDataContainer *tc); void animrecord_check_state(TransInfo *t, struct Object *ob); /* transform_convert_action.c */ @@ -84,6 +84,8 @@ void special_aftertrans_update__pose(bContext *C, TransInfo *t); /* transform_convert_cursor.c */ void createTransCursor_image(TransInfo *t); void createTransCursor_view3d(TransInfo *t); +void recalcData_cursor_image(TransInfo *t); +void recalcData_cursor(TransInfo *t); /* transform_convert_curve.c */ void createTransCurveVerts(TransInfo *t); @@ -109,6 +111,7 @@ void special_aftertrans_update__mask(bContext *C, TransInfo *t); /* transform_convert_mball.c */ void createTransMBallVerts(TransInfo *t); +void recalcData_mball(TransInfo *t); /* transform_convert_mesh.c */ struct TransIslandData { diff --git a/source/blender/editors/transform/transform_convert_armature.c b/source/blender/editors/transform/transform_convert_armature.c index 7f24a0fa5f8..aaea9d05f84 100644 --- a/source/blender/editors/transform/transform_convert_armature.c +++ b/source/blender/editors/transform/transform_convert_armature.c @@ -1268,9 +1268,6 @@ void recalcData_edit_armature(TransInfo *t) restoreBones(tc); } } - - /* Tag for redraw/invalidate overlay cache. */ - DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); } } diff --git a/source/blender/editors/transform/transform_convert_cursor.c b/source/blender/editors/transform/transform_convert_cursor.c index 67d85f9610b..1f3eff31205 100644 --- a/source/blender/editors/transform/transform_convert_cursor.c +++ b/source/blender/editors/transform/transform_convert_cursor.c @@ -134,3 +134,29 @@ void createTransCursor_view3d(TransInfo *t) } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Recalc Cursor + * \{ */ + +void recalcData_cursor_image(TransInfo *t) +{ + TransDataContainer *tc = t->data_container; + TransData *td = tc->data; + float aspect_inv[2]; + + aspect_inv[0] = 1.0f / t->aspect[0]; + aspect_inv[1] = 1.0f / t->aspect[1]; + + td->loc[0] = td->loc[0] * aspect_inv[0]; + td->loc[1] = td->loc[1] * aspect_inv[1]; + + DEG_id_tag_update(&t->scene->id, ID_RECALC_COPY_ON_WRITE); +} + +void recalcData_cursor(TransInfo *t) +{ + DEG_id_tag_update(&t->scene->id, ID_RECALC_COPY_ON_WRITE); +} + +/** \} */ diff --git a/source/blender/editors/transform/transform_convert_curve.c b/source/blender/editors/transform/transform_convert_curve.c index 3ff83e00f43..e57fd85470f 100644 --- a/source/blender/editors/transform/transform_convert_curve.c +++ b/source/blender/editors/transform/transform_convert_curve.c @@ -441,7 +441,6 @@ void createTransCurveVerts(TransInfo *t) void recalcData_curve(TransInfo *t) { if (t->state != TRANS_CANCEL) { - clipMirrorModifier(t); applyProject(t); } @@ -450,7 +449,7 @@ void recalcData_curve(TransInfo *t) ListBase *nurbs = BKE_curve_editNurbs_get(cu); Nurb *nu = nurbs->first; - DEG_id_tag_update(tc->obedit->data, 0); /* sets recalc flags */ + DEG_id_tag_update(tc->obedit->data, ID_RECALC_GEOMETRY); if (t->state == TRANS_CANCEL) { while (nu) { @@ -460,7 +459,10 @@ void recalcData_curve(TransInfo *t) } } else { - /* Normal updating */ + /* Apply clipping after so we never project past the clip plane T25423. */ + transform_convert_clip_mirror_modifier_apply(tc); + + /* Normal updating. */ BKE_curve_dimension_update(cu); } } diff --git a/source/blender/editors/transform/transform_convert_lattice.c b/source/blender/editors/transform/transform_convert_lattice.c index 20ac7dcb998..fbfce41d555 100644 --- a/source/blender/editors/transform/transform_convert_lattice.c +++ b/source/blender/editors/transform/transform_convert_lattice.c @@ -122,7 +122,7 @@ void recalcData_lattice(TransInfo *t) FOREACH_TRANS_DATA_CONTAINER (t, tc) { Lattice *la = tc->obedit->data; - DEG_id_tag_update(tc->obedit->data, 0); /* sets recalc flags */ + DEG_id_tag_update(tc->obedit->data, ID_RECALC_GEOMETRY); if (la->editlatt->latt->flag & LT_OUTSIDE) { outside_lattice(la->editlatt->latt); } diff --git a/source/blender/editors/transform/transform_convert_mball.c b/source/blender/editors/transform/transform_convert_mball.c index 6f5c0318054..f38f3ccf421 100644 --- a/source/blender/editors/transform/transform_convert_mball.c +++ b/source/blender/editors/transform/transform_convert_mball.c @@ -30,6 +30,8 @@ #include "BKE_context.h" #include "transform.h" +#include "transform_snap.h" + #include "transform_convert.h" /* -------------------------------------------------------------------- */ @@ -128,3 +130,21 @@ void createTransMBallVerts(TransInfo *t) } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Recalc Meta Ball + * \{ */ + +void recalcData_mball(TransInfo *t) +{ + if (t->state != TRANS_CANCEL) { + applyProject(t); + } + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + if (tc->data_len) { + DEG_id_tag_update(tc->obedit->data, ID_RECALC_GEOMETRY); + } + } +} + +/** \} */ diff --git a/source/blender/editors/transform/transform_convert_mesh.c b/source/blender/editors/transform/transform_convert_mesh.c index 5c05e35feb4..f715228e25e 100644 --- a/source/blender/editors/transform/transform_convert_mesh.c +++ b/source/blender/editors/transform/transform_convert_mesh.c @@ -48,7 +48,575 @@ #include "transform_convert.h" +/* -------------------------------------------------------------------- */ +/** \name Container TransCustomData Creation + * \{ */ + +struct TransCustomDataMergeGroup { + /** map {BMVert: TransCustomDataLayerVert} */ + struct LinkNode **cd_loop_groups; +}; + +struct TransCustomDataLayer { + BMesh *bm; + struct MemArena *arena; + + struct GHash *origfaces; + struct BMesh *bm_origfaces; + + /* Special handle for multi-resolution. */ + int cd_loop_mdisp_offset; + + /* Optionally merge custom-data groups (this keeps UVs connected for example). */ + struct { + /** map {BMVert: TransDataBasic} */ + struct GHash *origverts; + struct TransCustomDataMergeGroup *data; + int data_len; + /** Array size of 'layer_math_map_len' + * maps #TransCustomDataLayerVert.cd_group index to absolute #CustomData layer index */ + int *customdatalayer_map; + /** Number of math BMLoop layers. */ + int customdatalayer_map_len; + } merge_group; + + bool use_merge_group; +}; + +static void tc_mesh_customdatacorrect_free_fn(struct TransInfo *UNUSED(t), + struct TransDataContainer *UNUSED(tc), + struct TransCustomData *custom_data) +{ + struct TransCustomDataLayer *tcld = custom_data->data; + bmesh_edit_end(tcld->bm, BMO_OPTYPE_FLAG_UNTAN_MULTIRES); + + if (tcld->bm_origfaces) { + BM_mesh_free(tcld->bm_origfaces); + } + if (tcld->origfaces) { + BLI_ghash_free(tcld->origfaces, NULL, NULL); + } + if (tcld->merge_group.origverts) { + BLI_ghash_free(tcld->merge_group.origverts, NULL, NULL); + } + if (tcld->arena) { + BLI_memarena_free(tcld->arena); + } + if (tcld->merge_group.customdatalayer_map) { + MEM_freeN(tcld->merge_group.customdatalayer_map); + } + + MEM_freeN(tcld); + custom_data->data = NULL; +} + #define USE_FACE_SUBSTITUTE +#ifdef USE_FACE_SUBSTITUTE +# define FACE_SUBSTITUTE_INDEX INT_MIN + +/** + * Search for a neighboring face with area and preferably without selected vertex. + * Used to replace area-less faces in custom-data correction. + */ +static BMFace *tc_mesh_customdatacorrect_find_best_face_substitute(BMFace *f) +{ + BMFace *best_face = NULL; + BMLoop *l; + BMIter liter; + BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) { + BMLoop *l_radial_next = l->radial_next; + BMFace *f_test = l_radial_next->f; + if (f_test == f) { + continue; + } + if (is_zero_v3(f_test->no)) { + continue; + } + + /* Check the loops edge isn't selected. */ + if (!BM_elem_flag_test(l_radial_next->v, BM_ELEM_SELECT) && + !BM_elem_flag_test(l_radial_next->next->v, BM_ELEM_SELECT)) { + /* Prefer edges with unselected vertices. + * Useful for extrude. */ + best_face = f_test; + break; + } + if (best_face == NULL) { + best_face = f_test; + } + } + return best_face; +} + +static void tc_mesh_customdatacorrect_face_substitute_set(struct TransCustomDataLayer *tcld, + BMFace *f, + BMFace *f_copy) +{ + BLI_assert(is_zero_v3(f->no)); + BMesh *bm = tcld->bm; + /* It is impossible to calculate the loops weights of a face without area. + * Find a substitute. */ + BMFace *f_substitute = tc_mesh_customdatacorrect_find_best_face_substitute(f); + if (f_substitute) { + /* Copy the custom-data from the substitute face. */ + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + BM_loop_interp_from_face(bm, l_iter, f_substitute, false, false); + } while ((l_iter = l_iter->next) != l_first); + + /* Use the substitute face as the reference during the transformation. */ + BMFace *f_substitute_copy = BM_face_copy(tcld->bm_origfaces, bm, f_substitute, true, true); + + /* Hack: reference substitute face in `f_copy->no`. + * `tcld->origfaces` is already used to restore the initial value. */ + BM_elem_index_set(f_copy, FACE_SUBSTITUTE_INDEX); + *((BMFace **)&f_copy->no[0]) = f_substitute_copy; + } +} + +static BMFace *tc_mesh_customdatacorrect_face_substitute_get(BMFace *f_copy) +{ + BLI_assert(BM_elem_index_get(f_copy) == FACE_SUBSTITUTE_INDEX); + return *((BMFace **)&f_copy->no[0]); +} + +#endif /* USE_FACE_SUBSTITUTE */ + +static void tc_mesh_customdatacorrect_init_vert(struct TransCustomDataLayer *tcld, + struct TransDataBasic *td, + const int index) +{ + BMesh *bm = tcld->bm; + BMVert *v = td->extra; + BMIter liter; + int j, l_num; + float *loop_weights; + + // BM_ITER_ELEM (l, &liter, sv->v, BM_LOOPS_OF_VERT) { + BM_iter_init(&liter, bm, BM_LOOPS_OF_VERT, v); + l_num = liter.count; + loop_weights = tcld->use_merge_group ? BLI_array_alloca(loop_weights, l_num) : NULL; + for (j = 0; j < l_num; j++) { + BMLoop *l = BM_iter_step(&liter); + BMLoop *l_prev, *l_next; + + /* Generic custom-data correction. Copy face data. */ + void **val_p; + if (!BLI_ghash_ensure_p(tcld->origfaces, l->f, &val_p)) { + BMFace *f_copy = BM_face_copy(tcld->bm_origfaces, bm, l->f, true, true); + *val_p = f_copy; +#ifdef USE_FACE_SUBSTITUTE + if (is_zero_v3(l->f->no)) { + tc_mesh_customdatacorrect_face_substitute_set(tcld, l->f, f_copy); + } +#endif + } + + if (tcld->use_merge_group) { + if ((l_prev = BM_loop_find_prev_nodouble(l, l->next, FLT_EPSILON)) && + (l_next = BM_loop_find_next_nodouble(l, l_prev, FLT_EPSILON))) { + loop_weights[j] = angle_v3v3v3(l_prev->v->co, l->v->co, l_next->v->co); + } + else { + loop_weights[j] = 0.0f; + } + } + } + + if (tcld->use_merge_group) { + /* Store cd_loop_groups. */ + struct TransCustomDataMergeGroup *merge_data = &tcld->merge_group.data[index]; + if (l_num != 0) { + merge_data->cd_loop_groups = BLI_memarena_alloc( + tcld->arena, tcld->merge_group.customdatalayer_map_len * sizeof(void *)); + for (j = 0; j < tcld->merge_group.customdatalayer_map_len; j++) { + const int layer_nr = tcld->merge_group.customdatalayer_map[j]; + merge_data->cd_loop_groups[j] = BM_vert_loop_groups_data_layer_create( + bm, v, layer_nr, loop_weights, tcld->arena); + } + } + else { + merge_data->cd_loop_groups = NULL; + } + + BLI_ghash_insert(tcld->merge_group.origverts, v, td); + } +} + +static void tc_mesh_customdatacorrect_init_container_generic(TransDataContainer *UNUSED(tc), + struct TransCustomDataLayer *tcld) +{ + BMesh *bm = tcld->bm; + + struct GHash *origfaces = BLI_ghash_ptr_new(__func__); + struct BMesh *bm_origfaces = BM_mesh_create(&bm_mesh_allocsize_default, + &((struct BMeshCreateParams){ + .use_toolflags = false, + })); + + /* We need to have matching loop custom-data. */ + BM_mesh_copy_init_customdata_all_layers(bm_origfaces, bm, BM_LOOP, NULL); + + tcld->origfaces = origfaces; + tcld->bm_origfaces = bm_origfaces; + + bmesh_edit_begin(bm, BMO_OPTYPE_FLAG_UNTAN_MULTIRES); + tcld->cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS); +} + +static void tc_mesh_customdatacorrect_init_container_merge_group(TransDataContainer *tc, + struct TransCustomDataLayer *tcld) +{ + BMesh *bm = tcld->bm; + BLI_assert(CustomData_has_math(&bm->ldata)); + + /* TODO: We don't need `layer_math_map` when there are no loops linked + * to one of the sliding vertices. */ + + /* Over allocate, only 'math' layers are indexed. */ + int *customdatalayer_map = MEM_mallocN(sizeof(int) * bm->ldata.totlayer, __func__); + int layer_math_map_len = 0; + for (int i = 0; i < bm->ldata.totlayer; i++) { + if (CustomData_layer_has_math(&bm->ldata, i)) { + customdatalayer_map[layer_math_map_len++] = i; + } + } + BLI_assert(layer_math_map_len != 0); + + tcld->merge_group.data_len = tc->data_len + tc->data_mirror_len; + tcld->merge_group.customdatalayer_map = customdatalayer_map; + tcld->merge_group.customdatalayer_map_len = layer_math_map_len; + tcld->merge_group.origverts = BLI_ghash_ptr_new_ex(__func__, tcld->merge_group.data_len); + tcld->merge_group.data = BLI_memarena_alloc( + tcld->arena, tcld->merge_group.data_len * sizeof(*tcld->merge_group.data)); +} + +static struct TransCustomDataLayer *tc_mesh_customdatacorrect_create(TransDataContainer *tc, + const bool use_merge_group) +{ + BMEditMesh *em = BKE_editmesh_from_object(tc->obedit); + BMesh *bm = em->bm; + + if (bm->shapenr > 1) { + /* Don't do this at all for non-basis shape keys, too easy to + * accidentally break uv maps or vertex colors then */ + /* create copies of faces for custom-data projection. */ + return NULL; + } + if (!CustomData_has_math(&bm->ldata) && !CustomData_has_layer(&bm->ldata, CD_MDISPS)) { + /* There is no custom-data to correct. */ + return NULL; + } + + struct TransCustomDataLayer *tcld = MEM_callocN(sizeof(*tcld), __func__); + tcld->bm = bm; + tcld->arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__); + + /* Init `cd_loop_mdisp_offset` to -1 to avoid problems with a valid index. */ + tcld->cd_loop_mdisp_offset = -1; + tcld->use_merge_group = use_merge_group; + + tc_mesh_customdatacorrect_init_container_generic(tc, tcld); + + if (tcld->use_merge_group) { + tc_mesh_customdatacorrect_init_container_merge_group(tc, tcld); + } + + { + /* Setup Verts. */ + int i = 0; + + TransData *tob = tc->data; + for (int j = tc->data_len; j--; tob++, i++) { + tc_mesh_customdatacorrect_init_vert(tcld, (TransDataBasic *)tob, i); + } + + TransDataMirror *td_mirror = tc->data_mirror; + for (int j = tc->data_mirror_len; j--; td_mirror++, i++) { + tc_mesh_customdatacorrect_init_vert(tcld, (TransDataBasic *)td_mirror, i); + } + } + + return tcld; +} + +static void tc_mesh_customdata_create(TransDataContainer *tc, const bool use_merge_group) +{ + struct TransCustomDataLayer *customdatacorrect; + customdatacorrect = tc_mesh_customdatacorrect_create(tc, use_merge_group); + + if (!customdatacorrect) { + return; + } + + BLI_assert(tc->custom.type.data == NULL); + tc->custom.type.data = customdatacorrect; + tc->custom.type.free_cb = tc_mesh_customdatacorrect_free_fn; +} + +void transform_convert_mesh_customdatacorrect_init(TransInfo *t) +{ + bool use_merge_group = false; + if (ELEM(t->mode, TFM_EDGE_SLIDE, TFM_VERT_SLIDE)) { + if (!(t->settings->uvcalc_flag & UVCALC_TRANSFORM_CORRECT_SLIDE)) { + /* No custom-data correction. */ + return; + } + use_merge_group = true; + } + else if (ELEM(t->mode, + TFM_TRANSLATION, + TFM_ROTATION, + TFM_RESIZE, + TFM_TOSPHERE, + TFM_SHEAR, + TFM_BEND, + TFM_SHRINKFATTEN, + TFM_TRACKBALL, + TFM_PUSHPULL, + TFM_ALIGN)) { + { + if (!(t->settings->uvcalc_flag & UVCALC_TRANSFORM_CORRECT)) { + /* No custom-data correction. */ + return; + } + use_merge_group = (t->settings->uvcalc_flag & UVCALC_TRANSFORM_CORRECT_KEEP_CONNECTED) != 0; + } + } + else { + return; + } + + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + if (tc->custom.type.data != NULL) { + tc_mesh_customdatacorrect_free_fn(t, tc, &tc->custom.type); + } + + tc_mesh_customdata_create(tc, use_merge_group); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name CustomData Layer Correction Apply + * \{ */ + +/** + * If we're sliding the vert, return its original location, if not, the current location is good. + */ +static const float *tc_mesh_vert_orig_co_get(struct TransCustomDataLayer *tcld, BMVert *v) +{ + TransDataBasic *td = BLI_ghash_lookup(tcld->merge_group.origverts, v); + return td ? td->iloc : v->co; +} + +static void tc_mesh_customdatacorrect_apply_vert(struct TransCustomDataLayer *tcld, + struct TransDataBasic *td, + struct TransCustomDataMergeGroup *merge_data, + bool do_loop_mdisps) +{ + BMesh *bm = tcld->bm; + BMVert *v = td->extra; + const float *co_orig_3d = td->iloc; + + BMIter liter; + int j, l_num; + float *loop_weights; + const bool is_moved = (len_squared_v3v3(v->co, co_orig_3d) > FLT_EPSILON); + const bool do_loop_weight = is_moved && tcld->merge_group.customdatalayer_map_len; + const float *v_proj_axis = v->no; + /* original (l->prev, l, l->next) projections for each loop ('l' remains unchanged) */ + float v_proj[3][3]; + + if (do_loop_weight) { + project_plane_normalized_v3_v3v3(v_proj[1], co_orig_3d, v_proj_axis); + } + + // BM_ITER_ELEM (l, &liter, sv->v, BM_LOOPS_OF_VERT) + BM_iter_init(&liter, bm, BM_LOOPS_OF_VERT, v); + l_num = liter.count; + loop_weights = do_loop_weight ? BLI_array_alloca(loop_weights, l_num) : NULL; + for (j = 0; j < l_num; j++) { + BMFace *f_copy; /* the copy of 'f' */ + BMLoop *l = BM_iter_step(&liter); + + f_copy = BLI_ghash_lookup(tcld->origfaces, l->f); + +#ifdef USE_FACE_SUBSTITUTE + /* In some faces it is not possible to calculate interpolation, + * so we use a substitute. */ + if (BM_elem_index_get(f_copy) == FACE_SUBSTITUTE_INDEX) { + f_copy = tc_mesh_customdatacorrect_face_substitute_get(f_copy); + } +#endif + + /* only loop data, no vertex data since that contains shape keys, + * and we do not want to mess up other shape keys */ + BM_loop_interp_from_face(bm, l, f_copy, false, false); + + /* weight the loop */ + if (do_loop_weight) { + const float eps = 1.0e-8f; + const BMLoop *l_prev = l->prev; + const BMLoop *l_next = l->next; + const float *co_prev = tc_mesh_vert_orig_co_get(tcld, l_prev->v); + const float *co_next = tc_mesh_vert_orig_co_get(tcld, l_next->v); + bool co_prev_ok; + bool co_next_ok; + + /* In the unlikely case that we're next to a zero length edge - + * walk around the to the next. + * + * Since we only need to check if the vertex is in this corner, + * its not important _which_ loop - as long as its not overlapping + * 'sv->co_orig_3d', see: T45096. */ + project_plane_normalized_v3_v3v3(v_proj[0], co_prev, v_proj_axis); + while (UNLIKELY(((co_prev_ok = (len_squared_v3v3(v_proj[1], v_proj[0]) > eps)) == false) && + ((l_prev = l_prev->prev) != l->next))) { + co_prev = tc_mesh_vert_orig_co_get(tcld, l_prev->v); + project_plane_normalized_v3_v3v3(v_proj[0], co_prev, v_proj_axis); + } + project_plane_normalized_v3_v3v3(v_proj[2], co_next, v_proj_axis); + while (UNLIKELY(((co_next_ok = (len_squared_v3v3(v_proj[1], v_proj[2]) > eps)) == false) && + ((l_next = l_next->next) != l->prev))) { + co_next = tc_mesh_vert_orig_co_get(tcld, l_next->v); + project_plane_normalized_v3_v3v3(v_proj[2], co_next, v_proj_axis); + } + + if (co_prev_ok && co_next_ok) { + const float dist = dist_signed_squared_to_corner_v3v3v3( + v->co, UNPACK3(v_proj), v_proj_axis); + + loop_weights[j] = (dist >= 0.0f) ? 1.0f : ((dist <= -eps) ? 0.0f : (1.0f + (dist / eps))); + if (UNLIKELY(!isfinite(loop_weights[j]))) { + loop_weights[j] = 0.0f; + } + } + else { + loop_weights[j] = 0.0f; + } + } + } + + if (tcld->use_merge_group) { + struct LinkNode **cd_loop_groups = merge_data->cd_loop_groups; + if (tcld->merge_group.customdatalayer_map_len && cd_loop_groups) { + if (do_loop_weight) { + for (j = 0; j < tcld->merge_group.customdatalayer_map_len; j++) { + BM_vert_loop_groups_data_layer_merge_weights( + bm, cd_loop_groups[j], tcld->merge_group.customdatalayer_map[j], loop_weights); + } + } + else { + for (j = 0; j < tcld->merge_group.customdatalayer_map_len; j++) { + BM_vert_loop_groups_data_layer_merge( + bm, cd_loop_groups[j], tcld->merge_group.customdatalayer_map[j]); + } + } + } + } + + /* Special handling for multires + * + * Interpolate from every other loop (not ideal) + * However values will only be taken from loops which overlap other mdisps. + */ + const bool update_loop_mdisps = is_moved && do_loop_mdisps && (tcld->cd_loop_mdisp_offset != -1); + if (update_loop_mdisps) { + float(*faces_center)[3] = BLI_array_alloca(faces_center, l_num); + BMLoop *l; + + BM_ITER_ELEM_INDEX (l, &liter, v, BM_LOOPS_OF_VERT, j) { + BM_face_calc_center_median(l->f, faces_center[j]); + } + + BM_ITER_ELEM_INDEX (l, &liter, v, BM_LOOPS_OF_VERT, j) { + BMFace *f_copy = BLI_ghash_lookup(tcld->origfaces, l->f); + float f_copy_center[3]; + BMIter liter_other; + BMLoop *l_other; + int j_other; + + BM_face_calc_center_median(f_copy, f_copy_center); + + BM_ITER_ELEM_INDEX (l_other, &liter_other, v, BM_LOOPS_OF_VERT, j_other) { + BM_face_interp_multires_ex(bm, + l_other->f, + f_copy, + faces_center[j_other], + f_copy_center, + tcld->cd_loop_mdisp_offset); + } + } + } +} + +static void tc_mesh_customdatacorrect_apply(TransDataContainer *tc, bool is_final) +{ + if (!tc->custom.type.data) { + return; + } + struct TransCustomDataLayer *tcld = tc->custom.type.data; + const bool use_merge_group = tcld->use_merge_group; + + struct TransCustomDataMergeGroup *merge_data = tcld->merge_group.data; + TransData *tob = tc->data; + for (int i = tc->data_len; i--; tob++) { + tc_mesh_customdatacorrect_apply_vert(tcld, (TransDataBasic *)tob, merge_data, is_final); + + if (use_merge_group) { + merge_data++; + } + } + + TransDataMirror *td_mirror = tc->data_mirror; + for (int i = tc->data_mirror_len; i--; td_mirror++) { + tc_mesh_customdatacorrect_apply_vert(tcld, (TransDataBasic *)td_mirror, merge_data, is_final); + + if (use_merge_group) { + merge_data++; + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name CustomData Layer Correction Restore + * \{ */ + +static void tc_mesh_customdatacorrect_restore(struct TransInfo *t) +{ + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + struct TransCustomDataLayer *tcld = tc->custom.type.data; + if (!tcld) { + continue; + } + + BMesh *bm = tcld->bm; + BMesh *bm_copy = tcld->bm_origfaces; + + GHashIterator gh_iter; + GHASH_ITER (gh_iter, tcld->origfaces) { + BMFace *f = BLI_ghashIterator_getKey(&gh_iter); + BMFace *f_copy = BLI_ghashIterator_getValue(&gh_iter); + BLI_assert(f->len == f_copy->len); + + BMLoop *l_iter, *l_first, *l_copy; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + l_copy = BM_FACE_FIRST_LOOP(f_copy); + do { + /* TODO: Restore only the elements that transform. */ + BM_elem_attrs_copy(bm_copy, bm, l_copy, l_iter); + l_copy = l_copy->next; + } while ((l_iter = l_iter->next) != l_first); + } + } +} + +/** \} */ /* -------------------------------------------------------------------- */ /** \name Island Creation @@ -741,10 +1309,10 @@ void transform_convert_mesh_crazyspace_free(struct TransMeshDataCrazySpace *r_cr /** \name Edit Mesh Verts Transform Creation * \{ */ -static void transdata_center_get(const struct TransIslandData *island_data, - const int island_index, - const float iloc[3], - float r_center[3]) +static void tc_mesh_transdata_center_copy(const struct TransIslandData *island_data, + const int island_index, + const float iloc[3], + float r_center[3]) { if (island_data->center && island_index != -1) { copy_v3_v3(r_center, island_data->center[island_index]); @@ -783,7 +1351,7 @@ static void VertsToTransData(TransInfo *t, no = eve->no; } - transdata_center_get(island_data, island_index, td->iloc, td->center); + tc_mesh_transdata_center_copy(island_data, island_index, td->iloc, td->center); if ((island_index != -1) && island_data->axismtx) { copy_m3_m3(td->axismtx, island_data->axismtx[island_index]); @@ -973,7 +1541,8 @@ void createTransEditVerts(TransInfo *t) copy_v3_v3(td_mirror->iloc, eve->co); td_mirror->flag = mirror_data.vert_map[a].flag; td_mirror->loc_src = v_src->co; - transdata_center_get(&island_data, island_index, td_mirror->iloc, td_mirror->center); + tc_mesh_transdata_center_copy( + &island_data, island_index, td_mirror->iloc, td_mirror->center); td_mirror++; } @@ -1045,590 +1614,39 @@ void createTransEditVerts(TransInfo *t) /** \} */ /* -------------------------------------------------------------------- */ -/** \name CustomData Layer Correction +/** \name Recalc Mesh Data * \{ */ -struct TransCustomDataMergeGroup { - /** map {BMVert: TransCustomDataLayerVert} */ - struct LinkNode **cd_loop_groups; -}; - -struct TransCustomDataLayer { - BMesh *bm; - struct MemArena *arena; - - struct GHash *origfaces; - struct BMesh *bm_origfaces; - - /* Special handle for multi-resolution. */ - int cd_loop_mdisp_offset; - - /* Optionally merge custom-data groups (this keeps UVs connected for example). */ - struct { - /** map {BMVert: TransDataBasic} */ - struct GHash *origverts; - struct TransCustomDataMergeGroup *data; - int data_len; - /** Array size of 'layer_math_map_len' - * maps #TransCustomDataLayerVert.cd_group index to absolute #CustomData layer index */ - int *customdatalayer_map; - /** Number of math BMLoop layers. */ - int customdatalayer_map_len; - } merge_group; - - bool use_merge_group; -}; - -static void mesh_customdatacorrect_free_cb(struct TransInfo *UNUSED(t), - struct TransDataContainer *UNUSED(tc), - struct TransCustomData *custom_data) -{ - struct TransCustomDataLayer *tcld = custom_data->data; - bmesh_edit_end(tcld->bm, BMO_OPTYPE_FLAG_UNTAN_MULTIRES); - - if (tcld->bm_origfaces) { - BM_mesh_free(tcld->bm_origfaces); - } - if (tcld->origfaces) { - BLI_ghash_free(tcld->origfaces, NULL, NULL); - } - if (tcld->merge_group.origverts) { - BLI_ghash_free(tcld->merge_group.origverts, NULL, NULL); - } - if (tcld->arena) { - BLI_memarena_free(tcld->arena); - } - if (tcld->merge_group.customdatalayer_map) { - MEM_freeN(tcld->merge_group.customdatalayer_map); - } - - MEM_freeN(tcld); - custom_data->data = NULL; -} - -#ifdef USE_FACE_SUBSTITUTE - -# define FACE_SUBSTITUTE_INDEX INT_MIN - -/** - * Search for a neighboring face with area and preferably without selected vertex. - * Used to replace area-less faces in custom-data correction. - */ -static BMFace *mesh_customdatacorrect_find_best_face_substitute(BMFace *f) -{ - BMFace *best_face = NULL; - BMLoop *l; - BMIter liter; - BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) { - BMLoop *l_radial_next = l->radial_next; - BMFace *f_test = l_radial_next->f; - if (f_test == f) { - continue; - } - if (is_zero_v3(f_test->no)) { - continue; - } - - /* Check the loops edge isn't selected. */ - if (!BM_elem_flag_test(l_radial_next->v, BM_ELEM_SELECT) && - !BM_elem_flag_test(l_radial_next->next->v, BM_ELEM_SELECT)) { - /* Prefer edges with unselected vertices. - * Useful for extrude. */ - best_face = f_test; - break; - } - if (best_face == NULL) { - best_face = f_test; - } - } - return best_face; -} - -static void mesh_customdatacorrect_face_substitute_set(struct TransCustomDataLayer *tcld, - BMFace *f, - BMFace *f_copy) -{ - BLI_assert(is_zero_v3(f->no)); - BMesh *bm = tcld->bm; - /* It is impossible to calculate the loops weights of a face without area. - * Find a substitute. */ - BMFace *f_substitute = mesh_customdatacorrect_find_best_face_substitute(f); - if (f_substitute) { - /* Copy the custom-data from the substitute face. */ - BMLoop *l_iter, *l_first; - l_iter = l_first = BM_FACE_FIRST_LOOP(f); - do { - BM_loop_interp_from_face(bm, l_iter, f_substitute, false, false); - } while ((l_iter = l_iter->next) != l_first); - - /* Use the substitute face as the reference during the transformation. */ - BMFace *f_substitute_copy = BM_face_copy(tcld->bm_origfaces, bm, f_substitute, true, true); - - /* Hack: reference substitute face in `f_copy->no`. - * `tcld->origfaces` is already used to restore the initial value. */ - BM_elem_index_set(f_copy, FACE_SUBSTITUTE_INDEX); - *((BMFace **)&f_copy->no[0]) = f_substitute_copy; - } -} - -static BMFace *mesh_customdatacorrect_face_substitute_get(BMFace *f_copy) -{ - BLI_assert(BM_elem_index_get(f_copy) == FACE_SUBSTITUTE_INDEX); - return *((BMFace **)&f_copy->no[0]); -} - -#endif /* USE_FACE_SUBSTITUTE */ - -static void mesh_customdatacorrect_init_vert(struct TransCustomDataLayer *tcld, - struct TransDataBasic *td, - const int index) -{ - BMesh *bm = tcld->bm; - BMVert *v = td->extra; - BMIter liter; - int j, l_num; - float *loop_weights; - - // BM_ITER_ELEM (l, &liter, sv->v, BM_LOOPS_OF_VERT) { - BM_iter_init(&liter, bm, BM_LOOPS_OF_VERT, v); - l_num = liter.count; - loop_weights = tcld->use_merge_group ? BLI_array_alloca(loop_weights, l_num) : NULL; - for (j = 0; j < l_num; j++) { - BMLoop *l = BM_iter_step(&liter); - BMLoop *l_prev, *l_next; - - /* Generic custom-data correction. Copy face data. */ - void **val_p; - if (!BLI_ghash_ensure_p(tcld->origfaces, l->f, &val_p)) { - BMFace *f_copy = BM_face_copy(tcld->bm_origfaces, bm, l->f, true, true); - *val_p = f_copy; -#ifdef USE_FACE_SUBSTITUTE - if (is_zero_v3(l->f->no)) { - mesh_customdatacorrect_face_substitute_set(tcld, l->f, f_copy); - } -#endif - } - - if (tcld->use_merge_group) { - if ((l_prev = BM_loop_find_prev_nodouble(l, l->next, FLT_EPSILON)) && - (l_next = BM_loop_find_next_nodouble(l, l_prev, FLT_EPSILON))) { - loop_weights[j] = angle_v3v3v3(l_prev->v->co, l->v->co, l_next->v->co); - } - else { - loop_weights[j] = 0.0f; - } - } - } - - if (tcld->use_merge_group) { - /* Store cd_loop_groups. */ - struct TransCustomDataMergeGroup *merge_data = &tcld->merge_group.data[index]; - if (l_num != 0) { - merge_data->cd_loop_groups = BLI_memarena_alloc( - tcld->arena, tcld->merge_group.customdatalayer_map_len * sizeof(void *)); - for (j = 0; j < tcld->merge_group.customdatalayer_map_len; j++) { - const int layer_nr = tcld->merge_group.customdatalayer_map[j]; - merge_data->cd_loop_groups[j] = BM_vert_loop_groups_data_layer_create( - bm, v, layer_nr, loop_weights, tcld->arena); - } - } - else { - merge_data->cd_loop_groups = NULL; - } - - BLI_ghash_insert(tcld->merge_group.origverts, v, td); - } -} - -static void mesh_customdatacorrect_init_container_generic(TransDataContainer *UNUSED(tc), - struct TransCustomDataLayer *tcld) -{ - BMesh *bm = tcld->bm; - - struct GHash *origfaces = BLI_ghash_ptr_new(__func__); - struct BMesh *bm_origfaces = BM_mesh_create(&bm_mesh_allocsize_default, - &((struct BMeshCreateParams){ - .use_toolflags = false, - })); - - /* We need to have matching loop custom-data. */ - BM_mesh_copy_init_customdata_all_layers(bm_origfaces, bm, BM_LOOP, NULL); - - tcld->origfaces = origfaces; - tcld->bm_origfaces = bm_origfaces; - - bmesh_edit_begin(bm, BMO_OPTYPE_FLAG_UNTAN_MULTIRES); - tcld->cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS); -} - -static void mesh_customdatacorrect_init_container_merge_group(TransDataContainer *tc, - struct TransCustomDataLayer *tcld) -{ - BMesh *bm = tcld->bm; - BLI_assert(CustomData_has_math(&bm->ldata)); - - /* TODO: We don't need `layer_math_map` when there are no loops linked - * to one of the sliding vertices. */ - - /* Over allocate, only 'math' layers are indexed. */ - int *customdatalayer_map = MEM_mallocN(sizeof(int) * bm->ldata.totlayer, __func__); - int layer_math_map_len = 0; - for (int i = 0; i < bm->ldata.totlayer; i++) { - if (CustomData_layer_has_math(&bm->ldata, i)) { - customdatalayer_map[layer_math_map_len++] = i; - } - } - BLI_assert(layer_math_map_len != 0); - - tcld->merge_group.data_len = tc->data_len + tc->data_mirror_len; - tcld->merge_group.customdatalayer_map = customdatalayer_map; - tcld->merge_group.customdatalayer_map_len = layer_math_map_len; - tcld->merge_group.origverts = BLI_ghash_ptr_new_ex(__func__, tcld->merge_group.data_len); - tcld->merge_group.data = BLI_memarena_alloc( - tcld->arena, tcld->merge_group.data_len * sizeof(*tcld->merge_group.data)); -} - -static void mesh_customdatacorrect_init_container(TransDataContainer *tc, - const bool use_merge_group) -{ - if (tc->custom.type.data) { - /* The custom-data correction has been initiated before. - * Free since some modes have different settings. */ - mesh_customdatacorrect_free_cb(NULL, tc, &tc->custom.type); - } - - BMEditMesh *em = BKE_editmesh_from_object(tc->obedit); - BMesh *bm = em->bm; - - if (bm->shapenr > 1) { - /* Don't do this at all for non-basis shape keys, too easy to - * accidentally break uv maps or vertex colors then */ - /* create copies of faces for custom-data projection. */ - return; - } - if (!CustomData_has_math(&bm->ldata) && !CustomData_has_layer(&bm->ldata, CD_MDISPS)) { - /* There is no custom-data to correct. */ - return; - } - - struct TransCustomDataLayer *tcld = MEM_callocN(sizeof(*tcld), __func__); - tcld->bm = bm; - tcld->arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__); - - /* Init `cd_loop_mdisp_offset` to -1 to avoid problems with a valid index. */ - tcld->cd_loop_mdisp_offset = -1; - tcld->use_merge_group = use_merge_group; - - mesh_customdatacorrect_init_container_generic(tc, tcld); - - if (tcld->use_merge_group) { - mesh_customdatacorrect_init_container_merge_group(tc, tcld); - } - - { - /* Setup Verts. */ - int i = 0; - - TransData *tob = tc->data; - for (int j = tc->data_len; j--; tob++, i++) { - mesh_customdatacorrect_init_vert(tcld, (TransDataBasic *)tob, i); - } - - TransDataMirror *td_mirror = tc->data_mirror; - for (int j = tc->data_mirror_len; j--; td_mirror++, i++) { - mesh_customdatacorrect_init_vert(tcld, (TransDataBasic *)td_mirror, i); - } - } - - tc->custom.type.data = tcld; - tc->custom.type.free_cb = mesh_customdatacorrect_free_cb; -} - -void mesh_customdatacorrect_init(TransInfo *t) +static void tc_mesh_transdata_mirror_apply(TransDataContainer *tc) { - bool use_merge_group = false; - if (ELEM(t->mode, TFM_EDGE_SLIDE, TFM_VERT_SLIDE)) { - if (!(t->settings->uvcalc_flag & UVCALC_TRANSFORM_CORRECT_SLIDE)) { - /* No custom-data correction. */ - return; - } - use_merge_group = true; - } - else if (ELEM(t->mode, - TFM_TRANSLATION, - TFM_ROTATION, - TFM_RESIZE, - TFM_TOSPHERE, - TFM_SHEAR, - TFM_BEND, - TFM_SHRINKFATTEN, - TFM_TRACKBALL, - TFM_PUSHPULL, - TFM_ALIGN)) { - { - if (!(t->settings->uvcalc_flag & UVCALC_TRANSFORM_CORRECT)) { - /* No custom-data correction. */ - return; - } - use_merge_group = (t->settings->uvcalc_flag & UVCALC_TRANSFORM_CORRECT_KEEP_CONNECTED) != 0; - } - } - else { - return; - } - - FOREACH_TRANS_DATA_CONTAINER (t, tc) { - mesh_customdatacorrect_init_container(tc, use_merge_group); - } -} - -/** - * If we're sliding the vert, return its original location, if not, the current location is good. - */ -static const float *trans_vert_orig_co_get(struct TransCustomDataLayer *tcld, BMVert *v) -{ - TransDataBasic *td = BLI_ghash_lookup(tcld->merge_group.origverts, v); - return td ? td->iloc : v->co; -} - -static void mesh_customdatacorrect_apply_vert(struct TransCustomDataLayer *tcld, - struct TransDataBasic *td, - struct TransCustomDataMergeGroup *merge_data, - bool do_loop_mdisps) -{ - BMesh *bm = tcld->bm; - BMVert *v = td->extra; - const float *co_orig_3d = td->iloc; - - BMIter liter; - int j, l_num; - float *loop_weights; - const bool is_moved = (len_squared_v3v3(v->co, co_orig_3d) > FLT_EPSILON); - const bool do_loop_weight = is_moved && tcld->merge_group.customdatalayer_map_len; - const float *v_proj_axis = v->no; - /* original (l->prev, l, l->next) projections for each loop ('l' remains unchanged) */ - float v_proj[3][3]; - - if (do_loop_weight) { - project_plane_normalized_v3_v3v3(v_proj[1], co_orig_3d, v_proj_axis); - } - - // BM_ITER_ELEM (l, &liter, sv->v, BM_LOOPS_OF_VERT) - BM_iter_init(&liter, bm, BM_LOOPS_OF_VERT, v); - l_num = liter.count; - loop_weights = do_loop_weight ? BLI_array_alloca(loop_weights, l_num) : NULL; - for (j = 0; j < l_num; j++) { - BMFace *f_copy; /* the copy of 'f' */ - BMLoop *l = BM_iter_step(&liter); - - f_copy = BLI_ghash_lookup(tcld->origfaces, l->f); - -#ifdef USE_FACE_SUBSTITUTE - /* In some faces it is not possible to calculate interpolation, - * so we use a substitute. */ - if (BM_elem_index_get(f_copy) == FACE_SUBSTITUTE_INDEX) { - f_copy = mesh_customdatacorrect_face_substitute_get(f_copy); - } -#endif - - /* only loop data, no vertex data since that contains shape keys, - * and we do not want to mess up other shape keys */ - BM_loop_interp_from_face(bm, l, f_copy, false, false); - - /* weight the loop */ - if (do_loop_weight) { - const float eps = 1.0e-8f; - const BMLoop *l_prev = l->prev; - const BMLoop *l_next = l->next; - const float *co_prev = trans_vert_orig_co_get(tcld, l_prev->v); - const float *co_next = trans_vert_orig_co_get(tcld, l_next->v); - bool co_prev_ok; - bool co_next_ok; - - /* In the unlikely case that we're next to a zero length edge - - * walk around the to the next. - * - * Since we only need to check if the vertex is in this corner, - * its not important _which_ loop - as long as its not overlapping - * 'sv->co_orig_3d', see: T45096. */ - project_plane_normalized_v3_v3v3(v_proj[0], co_prev, v_proj_axis); - while (UNLIKELY(((co_prev_ok = (len_squared_v3v3(v_proj[1], v_proj[0]) > eps)) == false) && - ((l_prev = l_prev->prev) != l->next))) { - co_prev = trans_vert_orig_co_get(tcld, l_prev->v); - project_plane_normalized_v3_v3v3(v_proj[0], co_prev, v_proj_axis); - } - project_plane_normalized_v3_v3v3(v_proj[2], co_next, v_proj_axis); - while (UNLIKELY(((co_next_ok = (len_squared_v3v3(v_proj[1], v_proj[2]) > eps)) == false) && - ((l_next = l_next->next) != l->prev))) { - co_next = trans_vert_orig_co_get(tcld, l_next->v); - project_plane_normalized_v3_v3v3(v_proj[2], co_next, v_proj_axis); - } - - if (co_prev_ok && co_next_ok) { - const float dist = dist_signed_squared_to_corner_v3v3v3( - v->co, UNPACK3(v_proj), v_proj_axis); - - loop_weights[j] = (dist >= 0.0f) ? 1.0f : ((dist <= -eps) ? 0.0f : (1.0f + (dist / eps))); - if (UNLIKELY(!isfinite(loop_weights[j]))) { - loop_weights[j] = 0.0f; + if (tc->use_mirror_axis_any) { + int i; + TransData *td; + for (i = 0, td = tc->data; i < tc->data_len; i++, td++) { + if (td->flag & (TD_MIRROR_EDGE_X | TD_MIRROR_EDGE_Y | TD_MIRROR_EDGE_Z)) { + if (td->flag & TD_MIRROR_EDGE_X) { + td->loc[0] = 0.0f; } - } - else { - loop_weights[j] = 0.0f; - } - } - } - - if (tcld->use_merge_group) { - struct LinkNode **cd_loop_groups = merge_data->cd_loop_groups; - if (tcld->merge_group.customdatalayer_map_len && cd_loop_groups) { - if (do_loop_weight) { - for (j = 0; j < tcld->merge_group.customdatalayer_map_len; j++) { - BM_vert_loop_groups_data_layer_merge_weights( - bm, cd_loop_groups[j], tcld->merge_group.customdatalayer_map[j], loop_weights); + if (td->flag & TD_MIRROR_EDGE_Y) { + td->loc[1] = 0.0f; } - } - else { - for (j = 0; j < tcld->merge_group.customdatalayer_map_len; j++) { - BM_vert_loop_groups_data_layer_merge( - bm, cd_loop_groups[j], tcld->merge_group.customdatalayer_map[j]); + if (td->flag & TD_MIRROR_EDGE_Z) { + td->loc[2] = 0.0f; } } } - } - - /* Special handling for multires - * - * Interpolate from every other loop (not ideal) - * However values will only be taken from loops which overlap other mdisps. - */ - const bool update_loop_mdisps = is_moved && do_loop_mdisps && (tcld->cd_loop_mdisp_offset != -1); - if (update_loop_mdisps) { - float(*faces_center)[3] = BLI_array_alloca(faces_center, l_num); - BMLoop *l; - - BM_ITER_ELEM_INDEX (l, &liter, v, BM_LOOPS_OF_VERT, j) { - BM_face_calc_center_median(l->f, faces_center[j]); - } - - BM_ITER_ELEM_INDEX (l, &liter, v, BM_LOOPS_OF_VERT, j) { - BMFace *f_copy = BLI_ghash_lookup(tcld->origfaces, l->f); - float f_copy_center[3]; - BMIter liter_other; - BMLoop *l_other; - int j_other; - - BM_face_calc_center_median(f_copy, f_copy_center); - - BM_ITER_ELEM_INDEX (l_other, &liter_other, v, BM_LOOPS_OF_VERT, j_other) { - BM_face_interp_multires_ex(bm, - l_other->f, - f_copy, - faces_center[j_other], - f_copy_center, - tcld->cd_loop_mdisp_offset); - } - } - } -} - -static void mesh_customdatacorrect_apply(TransInfo *t, bool is_final) -{ - FOREACH_TRANS_DATA_CONTAINER (t, tc) { - if (!tc->custom.type.data) { - continue; - } - struct TransCustomDataLayer *tcld = tc->custom.type.data; - const bool use_merge_group = tcld->use_merge_group; - - struct TransCustomDataMergeGroup *merge_data = tcld->merge_group.data; - TransData *tob = tc->data; - for (int i = tc->data_len; i--; tob++) { - mesh_customdatacorrect_apply_vert(tcld, (TransDataBasic *)tob, merge_data, is_final); - - if (use_merge_group) { - merge_data++; - } - } TransDataMirror *td_mirror = tc->data_mirror; - for (int i = tc->data_mirror_len; i--; td_mirror++) { - mesh_customdatacorrect_apply_vert(tcld, (TransDataBasic *)td_mirror, merge_data, is_final); - - if (use_merge_group) { - merge_data++; + for (i = 0; i < tc->data_mirror_len; i++, td_mirror++) { + copy_v3_v3(td_mirror->loc, td_mirror->loc_src); + if (td_mirror->flag & TD_MIRROR_X) { + td_mirror->loc[0] *= -1; } - } - } -} - -static void mesh_customdatacorrect_restore(struct TransInfo *t) -{ - FOREACH_TRANS_DATA_CONTAINER (t, tc) { - struct TransCustomDataLayer *tcld = tc->custom.type.data; - if (!tcld) { - continue; - } - - BMesh *bm = tcld->bm; - BMesh *bm_copy = tcld->bm_origfaces; - - GHashIterator gh_iter; - GHASH_ITER (gh_iter, tcld->origfaces) { - BMFace *f = BLI_ghashIterator_getKey(&gh_iter); - BMFace *f_copy = BLI_ghashIterator_getValue(&gh_iter); - BLI_assert(f->len == f_copy->len); - - BMLoop *l_iter, *l_first, *l_copy; - l_iter = l_first = BM_FACE_FIRST_LOOP(f); - l_copy = BM_FACE_FIRST_LOOP(f_copy); - do { - /* TODO: Restore only the elements that transform. */ - BM_elem_attrs_copy(bm_copy, bm, l_copy, l_iter); - l_copy = l_copy->next; - } while ((l_iter = l_iter->next) != l_first); - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Recalc Mesh Data - * \{ */ - -static void mesh_apply_to_mirror(TransInfo *t) -{ - FOREACH_TRANS_DATA_CONTAINER (t, tc) { - if (tc->use_mirror_axis_any) { - int i; - TransData *td; - for (i = 0, td = tc->data; i < tc->data_len; i++, td++) { - if (td->flag & (TD_MIRROR_EDGE_X | TD_MIRROR_EDGE_Y | TD_MIRROR_EDGE_Z)) { - if (td->flag & TD_MIRROR_EDGE_X) { - td->loc[0] = 0.0f; - } - if (td->flag & TD_MIRROR_EDGE_Y) { - td->loc[1] = 0.0f; - } - if (td->flag & TD_MIRROR_EDGE_Z) { - td->loc[2] = 0.0f; - } - } + if (td_mirror->flag & TD_MIRROR_Y) { + td_mirror->loc[1] *= -1; } - - TransDataMirror *td_mirror = tc->data_mirror; - for (i = 0; i < tc->data_mirror_len; i++, td_mirror++) { - copy_v3_v3(td_mirror->loc, td_mirror->loc_src); - if (td_mirror->flag & TD_MIRROR_X) { - td_mirror->loc[0] *= -1; - } - if (td_mirror->flag & TD_MIRROR_Y) { - td_mirror->loc[1] *= -1; - } - if (td_mirror->flag & TD_MIRROR_Z) { - td_mirror->loc[2] *= -1; - } + if (td_mirror->flag & TD_MIRROR_Z) { + td_mirror->loc[2] *= -1; } } } @@ -1637,24 +1655,28 @@ static void mesh_apply_to_mirror(TransInfo *t) void recalcData_mesh(TransInfo *t) { bool is_canceling = t->state == TRANS_CANCEL; - /* mirror modifier clipping? */ + /* Apply corrections. */ if (!is_canceling) { - /* apply clipping after so we never project past the clip plane T25423. */ applyProject(t); - clipMirrorModifier(t); - if ((t->flag & T_NO_MIRROR) == 0 && (t->options & CTX_NO_MIRROR) == 0) { - mesh_apply_to_mirror(t); - } + bool do_mirror = !(t->flag & T_NO_MIRROR); + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + /* Apply clipping after so we never project past the clip plane T25423. */ + transform_convert_clip_mirror_modifier_apply(tc); + + if (do_mirror) { + tc_mesh_transdata_mirror_apply(tc); + } - mesh_customdatacorrect_apply(t, false); + tc_mesh_customdatacorrect_apply(tc, false); + } } else { - mesh_customdatacorrect_restore(t); + tc_mesh_customdatacorrect_restore(t); } FOREACH_TRANS_DATA_CONTAINER (t, tc) { - DEG_id_tag_update(tc->obedit->data, 0); /* sets recalc flags */ + DEG_id_tag_update(tc->obedit->data, ID_RECALC_GEOMETRY); BMEditMesh *em = BKE_editmesh_from_object(tc->obedit); EDBM_mesh_normals_update(em); BKE_editmesh_looptri_calc(em); @@ -1674,7 +1696,9 @@ void special_aftertrans_update__mesh(bContext *UNUSED(C), TransInfo *t) if (!is_canceling && ELEM(t->mode, TFM_EDGE_SLIDE, TFM_VERT_SLIDE)) { /* NOTE(joeedh): Handle multi-res re-projection, * done on transform completion since it's really slow. */ - mesh_customdatacorrect_apply(t, true); + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + tc_mesh_customdatacorrect_apply(tc, true); + } } if (use_automerge) { diff --git a/source/blender/editors/transform/transform_convert_mesh_skin.c b/source/blender/editors/transform/transform_convert_mesh_skin.c index 5dbb1947773..7c61da31f72 100644 --- a/source/blender/editors/transform/transform_convert_mesh_skin.c +++ b/source/blender/editors/transform/transform_convert_mesh_skin.c @@ -47,9 +47,9 @@ /** \name Edit Mesh #CD_MVERT_SKIN Transform Creation * \{ */ -static float *mesh_skin_transdata_center(const struct TransIslandData *island_data, - const int island_index, - BMVert *eve) +static float *tc_mesh_skin_transdata_center(const struct TransIslandData *island_data, + const int island_index, + BMVert *eve) { if (island_data->center && island_index != -1) { return island_data->center[island_index]; @@ -57,11 +57,11 @@ static float *mesh_skin_transdata_center(const struct TransIslandData *island_da return eve->co; } -static void mesh_skin_transdata_create(TransDataBasic *td, - BMEditMesh *em, - BMVert *eve, - const struct TransIslandData *island_data, - const int island_index) +static void tc_mesh_skin_transdata_create(TransDataBasic *td, + BMEditMesh *em, + BMVert *eve, + const struct TransIslandData *island_data, + const int island_index) { BLI_assert(BM_elem_flag_test(eve, BM_ELEM_HIDDEN) == 0); MVertSkin *vs = CustomData_bmesh_get(&em->bm->vdata, eve->head.data, CD_MVERT_SKIN); @@ -78,7 +78,7 @@ static void mesh_skin_transdata_create(TransDataBasic *td, td->flag |= TD_SELECTED; } - copy_v3_v3(td->center, mesh_skin_transdata_center(island_data, island_index, eve)); + copy_v3_v3(td->center, tc_mesh_skin_transdata_center(island_data, island_index, eve)); td->extra = eve; } @@ -209,7 +209,7 @@ void createTransMeshSkin(TransInfo *t) } if (mirror_data.vert_map && mirror_data.vert_map[a].index != -1) { - mesh_skin_transdata_create( + tc_mesh_skin_transdata_create( (TransDataBasic *)td_mirror, em, eve, &island_data, island_index); int elem_index = mirror_data.vert_map[a].index; @@ -221,7 +221,7 @@ void createTransMeshSkin(TransInfo *t) td_mirror++; } else if (prop_mode || BM_elem_flag_test(eve, BM_ELEM_SELECT)) { - mesh_skin_transdata_create((TransDataBasic *)td, em, eve, &island_data, island_index); + tc_mesh_skin_transdata_create((TransDataBasic *)td, em, eve, &island_data, island_index); if (t->around == V3D_AROUND_LOCAL_ORIGINS) { createSpaceNormal(td->axismtx, eve->no); @@ -275,7 +275,7 @@ void createTransMeshSkin(TransInfo *t) /** \name Recalc Mesh Data * \{ */ -static void mesh_skin_apply_to_mirror(TransInfo *t) +static void tc_mesh_skin_apply_to_mirror(TransInfo *t) { FOREACH_TRANS_DATA_CONTAINER (t, tc) { if (tc->use_mirror_axis_any) { @@ -292,13 +292,13 @@ void recalcData_mesh_skin(TransInfo *t) bool is_canceling = t->state == TRANS_CANCEL; /* mirror modifier clipping? */ if (!is_canceling) { - if ((t->flag & T_NO_MIRROR) == 0 && (t->options & CTX_NO_MIRROR) == 0) { - mesh_skin_apply_to_mirror(t); + if (!(t->flag & T_NO_MIRROR)) { + tc_mesh_skin_apply_to_mirror(t); } } FOREACH_TRANS_DATA_CONTAINER (t, tc) { - DEG_id_tag_update(tc->obedit->data, 0); /* sets recalc flags */ + DEG_id_tag_update(tc->obedit->data, ID_RECALC_GEOMETRY); BMEditMesh *em = BKE_editmesh_from_object(tc->obedit); EDBM_mesh_normals_update(em); BKE_editmesh_looptri_calc(em); diff --git a/source/blender/editors/transform/transform_convert_mesh_uv.c b/source/blender/editors/transform/transform_convert_mesh_uv.c index a5f90e9ac5f..d91a2a8be4b 100644 --- a/source/blender/editors/transform/transform_convert_mesh_uv.c +++ b/source/blender/editors/transform/transform_convert_mesh_uv.c @@ -475,7 +475,7 @@ void recalcData_uv(TransInfo *t) FOREACH_TRANS_DATA_CONTAINER (t, tc) { if (tc->data_len) { - DEG_id_tag_update(tc->obedit->data, 0); + DEG_id_tag_update(tc->obedit->data, ID_RECALC_GEOMETRY); } } } diff --git a/source/blender/editors/transform/transform_convert_sequencer.c b/source/blender/editors/transform/transform_convert_sequencer.c index 30418471d6d..34be89e5ed9 100644 --- a/source/blender/editors/transform/transform_convert_sequencer.c +++ b/source/blender/editors/transform/transform_convert_sequencer.c @@ -491,7 +491,7 @@ static void freeSeqData(TransInfo *t, TransDataContainer *tc, TransCustomData *c } } - SEQ_sort(t->scene); + SEQ_sort(seqbasep); } else { /* Canceled, need to update the strips display */ @@ -707,7 +707,7 @@ static void flushTransSeq(TransInfo *t) /* originally TFM_TIME_EXTEND, transform changes */ if (ELEM(t->mode, TFM_SEQ_SLIDE, TFM_TIME_TRANSLATE)) { - /* Special annoying case here, need to calc metas with TFM_TIME_EXTEND only */ + /* Special annoying case here, need to calc meta-strips with TFM_TIME_EXTEND only */ /* calc all meta's then effects T27953. */ for (seq = seqbasep->first; seq; seq = seq->next) { diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c index f00467d3dfc..71c91221fbb 100644 --- a/source/blender/editors/transform/transform_generics.c +++ b/source/blender/editors/transform/transform_generics.c @@ -527,7 +527,7 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve } else { /* Avoid mirroring for unsupported contexts. */ - t->options |= CTX_NO_MIRROR; + t->flag |= T_NO_MIRROR; } /* setting PET flag only if property exist in operator. Otherwise, assume it's not supported */ diff --git a/source/blender/editors/transform/transform_mode.c b/source/blender/editors/transform/transform_mode.c index ad0cb5f27aa..350be247014 100644 --- a/source/blender/editors/transform/transform_mode.c +++ b/source/blender/editors/transform/transform_mode.c @@ -524,7 +524,7 @@ void constraintSizeLim(TransInfo *t, TransData *td) /** \name Transform (Rotation Utils) * \{ */ /* Used by Transform Rotation and Transform Normal Rotation */ -void headerRotation(TransInfo *t, char str[UI_MAX_DRAW_STR], float final) +void headerRotation(TransInfo *t, char *str, const int str_size, float final) { size_t ofs = 0; @@ -533,25 +533,21 @@ void headerRotation(TransInfo *t, char str[UI_MAX_DRAW_STR], float final) outputNumInput(&(t->num), c, &t->scene->unit); - ofs += BLI_snprintf(str + ofs, - UI_MAX_DRAW_STR - ofs, - TIP_("Rotation: %s %s %s"), - &c[0], - t->con.text, - t->proptext); + ofs += BLI_snprintf_rlen( + str + ofs, str_size - ofs, TIP_("Rotation: %s %s %s"), &c[0], t->con.text, t->proptext); } else { - ofs += BLI_snprintf(str + ofs, - UI_MAX_DRAW_STR - ofs, - TIP_("Rotation: %.2f%s %s"), - RAD2DEGF(final), - t->con.text, - t->proptext); + ofs += BLI_snprintf_rlen(str + ofs, + str_size - ofs, + TIP_("Rotation: %.2f%s %s"), + RAD2DEGF(final), + t->con.text, + t->proptext); } if (t->flag & T_PROP_EDIT_ALL) { - ofs += BLI_snprintf( - str + ofs, UI_MAX_DRAW_STR - ofs, TIP_(" Proportional size: %.2f"), t->prop_size); + ofs += BLI_snprintf_rlen( + str + ofs, str_size - ofs, TIP_(" Proportional size: %.2f"), t->prop_size); } } @@ -811,7 +807,7 @@ void ElementRotation( /* -------------------------------------------------------------------- */ /** \name Transform (Resize Utils) * \{ */ -void headerResize(TransInfo *t, const float vec[3], char str[UI_MAX_DRAW_STR]) +void headerResize(TransInfo *t, const float vec[3], char *str, const int str_size) { char tvec[NUM_STR_REP_LEN * 3]; size_t ofs = 0; @@ -827,59 +823,55 @@ void headerResize(TransInfo *t, const float vec[3], char str[UI_MAX_DRAW_STR]) if (t->con.mode & CON_APPLY) { switch (t->num.idx_max) { case 0: - ofs += BLI_snprintf(str + ofs, - UI_MAX_DRAW_STR - ofs, - TIP_("Scale: %s%s %s"), - &tvec[0], - t->con.text, - t->proptext); + ofs += BLI_snprintf_rlen( + str + ofs, str_size - ofs, TIP_("Scale: %s%s %s"), &tvec[0], t->con.text, t->proptext); break; case 1: - ofs += BLI_snprintf(str + ofs, - UI_MAX_DRAW_STR - ofs, - TIP_("Scale: %s : %s%s %s"), - &tvec[0], - &tvec[NUM_STR_REP_LEN], - t->con.text, - t->proptext); + ofs += BLI_snprintf_rlen(str + ofs, + str_size - ofs, + TIP_("Scale: %s : %s%s %s"), + &tvec[0], + &tvec[NUM_STR_REP_LEN], + t->con.text, + t->proptext); break; case 2: - ofs += BLI_snprintf(str + ofs, - UI_MAX_DRAW_STR - ofs, - TIP_("Scale: %s : %s : %s%s %s"), - &tvec[0], - &tvec[NUM_STR_REP_LEN], - &tvec[NUM_STR_REP_LEN * 2], - t->con.text, - t->proptext); + ofs += BLI_snprintf_rlen(str + ofs, + str_size - ofs, + TIP_("Scale: %s : %s : %s%s %s"), + &tvec[0], + &tvec[NUM_STR_REP_LEN], + &tvec[NUM_STR_REP_LEN * 2], + t->con.text, + t->proptext); break; } } else { if (t->flag & T_2D_EDIT) { - ofs += BLI_snprintf(str + ofs, - UI_MAX_DRAW_STR - ofs, - TIP_("Scale X: %s Y: %s%s %s"), - &tvec[0], - &tvec[NUM_STR_REP_LEN], - t->con.text, - t->proptext); + ofs += BLI_snprintf_rlen(str + ofs, + str_size - ofs, + TIP_("Scale X: %s Y: %s%s %s"), + &tvec[0], + &tvec[NUM_STR_REP_LEN], + t->con.text, + t->proptext); } else { - ofs += BLI_snprintf(str + ofs, - UI_MAX_DRAW_STR - ofs, - TIP_("Scale X: %s Y: %s Z: %s%s %s"), - &tvec[0], - &tvec[NUM_STR_REP_LEN], - &tvec[NUM_STR_REP_LEN * 2], - t->con.text, - t->proptext); + ofs += BLI_snprintf_rlen(str + ofs, + str_size - ofs, + TIP_("Scale X: %s Y: %s Z: %s%s %s"), + &tvec[0], + &tvec[NUM_STR_REP_LEN], + &tvec[NUM_STR_REP_LEN * 2], + t->con.text, + t->proptext); } } if (t->flag & T_PROP_EDIT_ALL) { - ofs += BLI_snprintf( - str + ofs, UI_MAX_DRAW_STR - ofs, TIP_(" Proportional size: %.2f"), t->prop_size); + ofs += BLI_snprintf_rlen( + str + ofs, str_size - ofs, TIP_(" Proportional size: %.2f"), t->prop_size); } } @@ -1264,7 +1256,7 @@ void transform_mode_init(TransInfo *t, wmOperator *op, const int mode) if (t->data_type == TC_MESH_VERTS) { /* Init Custom Data correction. * Ideally this should be called when creating the TransData. */ - mesh_customdatacorrect_init(t); + transform_convert_mesh_customdatacorrect_init(t); } /* TODO(germano): Some of these operations change the `t->mode`. @@ -1273,7 +1265,7 @@ void transform_mode_init(TransInfo *t, wmOperator *op, const int mode) } /** - * When in modal nad not set, initializes a default orientation for the mode. + * When in modal and not set, initializes a default orientation for the mode. */ void transform_mode_default_modal_orientation_set(TransInfo *t, int type) { diff --git a/source/blender/editors/transform/transform_mode.h b/source/blender/editors/transform/transform_mode.h index 106dc68c9ee..a2b95eb3de4 100644 --- a/source/blender/editors/transform/transform_mode.h +++ b/source/blender/editors/transform/transform_mode.h @@ -47,7 +47,7 @@ void protectedTransBits(short protectflag, float vec[3]); void protectedSizeBits(short protectflag, float size[3]); void constraintTransLim(TransInfo *t, TransData *td); void constraintSizeLim(TransInfo *t, TransData *td); -void headerRotation(TransInfo *t, char *str, float final); +void headerRotation(TransInfo *t, char *str, int str_size, float final); void ElementRotation_ex(TransInfo *t, TransDataContainer *tc, TransData *td, @@ -55,7 +55,7 @@ void ElementRotation_ex(TransInfo *t, const float *center); void ElementRotation( TransInfo *t, TransDataContainer *tc, TransData *td, float mat[3][3], const short around); -void headerResize(TransInfo *t, const float vec[3], char *str); +void headerResize(TransInfo *t, const float vec[3], char *str, int str_size); void ElementResize(TransInfo *t, TransDataContainer *tc, TransData *td, float mat[3][3]); short getAnimEdit_SnapMode(TransInfo *t); void doAnimEdit_SnapFrame( diff --git a/source/blender/editors/transform/transform_mode_curveshrinkfatten.c b/source/blender/editors/transform/transform_mode_curveshrinkfatten.c index ee63bf4be6f..68416c780ef 100644 --- a/source/blender/editors/transform/transform_mode_curveshrinkfatten.c +++ b/source/blender/editors/transform/transform_mode_curveshrinkfatten.c @@ -107,7 +107,6 @@ void initCurveShrinkFatten(TransInfo *t) t->num.unit_sys = t->scene->unit.system; t->num.unit_type[0] = B_UNIT_NONE; - t->flag |= T_NO_ZERO; #ifdef USE_NUM_NO_ZERO t->num.val_flag[0] |= NUM_NO_ZERO; #endif diff --git a/source/blender/editors/transform/transform_mode_edge_rotate_normal.c b/source/blender/editors/transform/transform_mode_edge_rotate_normal.c index b7b3de69731..6f2bcc148ce 100644 --- a/source/blender/editors/transform/transform_mode_edge_rotate_normal.c +++ b/source/blender/editors/transform/transform_mode_edge_rotate_normal.c @@ -103,7 +103,7 @@ static void applyNormalRotation(TransInfo *t, const int UNUSED(mval[2])) applyNumInput(&t->num, &angle); - headerRotation(t, str, angle); + headerRotation(t, str, sizeof(str), angle); axis_angle_normalized_to_mat3(mat, axis, angle); diff --git a/source/blender/editors/transform/transform_mode_edge_seq_slide.c b/source/blender/editors/transform/transform_mode_edge_seq_slide.c index 4330d5e79be..7e7b79c9f90 100644 --- a/source/blender/editors/transform/transform_mode_edge_seq_slide.c +++ b/source/blender/editors/transform/transform_mode_edge_seq_slide.c @@ -72,7 +72,7 @@ static void headerSeqSlide(TransInfo *t, const float val[2], char str[UI_MAX_DRA BLI_snprintf(&tvec[0], NUM_STR_REP_LEN, "%.0f, %.0f", val[0], val[1]); } - ofs += BLI_snprintf( + ofs += BLI_snprintf_rlen( str + ofs, UI_MAX_DRAW_STR - ofs, TIP_("Sequence Slide: %s%s, ("), &tvec[0], t->con.text); const wmKeyMapItem *kmi = t->custom.mode.data; @@ -80,10 +80,10 @@ static void headerSeqSlide(TransInfo *t, const float val[2], char str[UI_MAX_DRA ofs += WM_keymap_item_to_string(kmi, false, str + ofs, UI_MAX_DRAW_STR - ofs); } - ofs += BLI_snprintf(str + ofs, - UI_MAX_DRAW_STR - ofs, - TIP_(" or Alt) Expand to fit %s"), - WM_bool_as_string((t->flag & T_ALT_TRANSFORM) != 0)); + ofs += BLI_snprintf_rlen(str + ofs, + UI_MAX_DRAW_STR - ofs, + TIP_(" or Alt) Expand to fit %s"), + WM_bool_as_string((t->flag & T_ALT_TRANSFORM) != 0)); } static void applySeqSlideValue(TransInfo *t, const float val[2]) diff --git a/source/blender/editors/transform/transform_mode_edge_slide.c b/source/blender/editors/transform/transform_mode_edge_slide.c index 16c1c05a6f8..d255a7d5660 100644 --- a/source/blender/editors/transform/transform_mode_edge_slide.c +++ b/source/blender/editors/transform/transform_mode_edge_slide.c @@ -1482,15 +1482,15 @@ static void applyEdgeSlide(TransInfo *t, const int UNUSED(mval[2])) ofs += BLI_strncpy_rlen(str + ofs, &c[0], sizeof(str) - ofs); } else { - ofs += BLI_snprintf(str + ofs, sizeof(str) - ofs, "%.4f ", final); + ofs += BLI_snprintf_rlen(str + ofs, sizeof(str) - ofs, "%.4f ", final); } - ofs += BLI_snprintf( + ofs += BLI_snprintf_rlen( str + ofs, sizeof(str) - ofs, TIP_("(E)ven: %s, "), WM_bool_as_string(use_even)); if (use_even) { - ofs += BLI_snprintf( + ofs += BLI_snprintf_rlen( str + ofs, sizeof(str) - ofs, TIP_("(F)lipped: %s, "), WM_bool_as_string(flipped)); } - ofs += BLI_snprintf( + ofs += BLI_snprintf_rlen( str + ofs, sizeof(str) - ofs, TIP_("Alt or (C)lamp: %s"), WM_bool_as_string(is_clamp)); /* done with header string */ diff --git a/source/blender/editors/transform/transform_mode_gpopacity.c b/source/blender/editors/transform/transform_mode_gpopacity.c index e67c6c03481..7c496d271ef 100644 --- a/source/blender/editors/transform/transform_mode_gpopacity.c +++ b/source/blender/editors/transform/transform_mode_gpopacity.c @@ -117,7 +117,6 @@ void initGPOpacity(TransInfo *t) t->num.unit_sys = t->scene->unit.system; t->num.unit_type[0] = B_UNIT_NONE; - t->flag |= T_NO_ZERO; #ifdef USE_NUM_NO_ZERO t->num.val_flag[0] |= NUM_NO_ZERO; #endif diff --git a/source/blender/editors/transform/transform_mode_gpshrinkfatten.c b/source/blender/editors/transform/transform_mode_gpshrinkfatten.c index 95e3d89d2b7..608a49f38b1 100644 --- a/source/blender/editors/transform/transform_mode_gpshrinkfatten.c +++ b/source/blender/editors/transform/transform_mode_gpshrinkfatten.c @@ -119,7 +119,6 @@ void initGPShrinkFatten(TransInfo *t) t->num.unit_sys = t->scene->unit.system; t->num.unit_type[0] = B_UNIT_NONE; - t->flag |= T_NO_ZERO; #ifdef USE_NUM_NO_ZERO t->num.val_flag[0] |= NUM_NO_ZERO; #endif diff --git a/source/blender/editors/transform/transform_mode_maskshrinkfatten.c b/source/blender/editors/transform/transform_mode_maskshrinkfatten.c index 3019984d70b..857ee37f0ad 100644 --- a/source/blender/editors/transform/transform_mode_maskshrinkfatten.c +++ b/source/blender/editors/transform/transform_mode_maskshrinkfatten.c @@ -133,7 +133,6 @@ void initMaskShrinkFatten(TransInfo *t) t->num.unit_sys = t->scene->unit.system; t->num.unit_type[0] = B_UNIT_NONE; - t->flag |= T_NO_ZERO; #ifdef USE_NUM_NO_ZERO t->num.val_flag[0] |= NUM_NO_ZERO; #endif diff --git a/source/blender/editors/transform/transform_mode_mirror.c b/source/blender/editors/transform/transform_mode_mirror.c index 9891af8b9a4..f225f1a7c06 100644 --- a/source/blender/editors/transform/transform_mode_mirror.c +++ b/source/blender/editors/transform/transform_mode_mirror.c @@ -235,8 +235,5 @@ void initMirror(TransInfo *t) initMouseInputMode(t, &t->mouse, INPUT_NONE); t->flag |= T_NULL_ONE; - if ((t->flag & T_EDIT) == 0) { - t->flag |= T_NO_ZERO; - } } /** \} */ diff --git a/source/blender/editors/transform/transform_mode_resize.c b/source/blender/editors/transform/transform_mode_resize.c index a98cee81915..1d7d1369f29 100644 --- a/source/blender/editors/transform/transform_mode_resize.c +++ b/source/blender/editors/transform/transform_mode_resize.c @@ -113,10 +113,10 @@ static void applyResize(TransInfo *t, const int UNUSED(mval[2])) pvec[j++] = t->values_final[i]; } } - headerResize(t, pvec, str); + headerResize(t, pvec, str, sizeof(str)); } else { - headerResize(t, t->values_final, str); + headerResize(t, t->values_final, str, sizeof(str)); } copy_m3_m3(t->mat, mat); /* used in gizmo */ @@ -176,7 +176,6 @@ void initResize(TransInfo *t) t->num.val_flag[2] |= NUM_NULL_ONE; t->num.flag |= NUM_AFFECT_ALL; if ((t->flag & T_EDIT) == 0) { - t->flag |= T_NO_ZERO; #ifdef USE_NUM_NO_ZERO t->num.val_flag[0] |= NUM_NO_ZERO; t->num.val_flag[1] |= NUM_NO_ZERO; diff --git a/source/blender/editors/transform/transform_mode_rotate.c b/source/blender/editors/transform/transform_mode_rotate.c index 0fdbfb25989..8350e94e0e8 100644 --- a/source/blender/editors/transform/transform_mode_rotate.c +++ b/source/blender/editors/transform/transform_mode_rotate.c @@ -217,7 +217,7 @@ static void applyRotation(TransInfo *t, const int UNUSED(mval[2])) t->values_final[0] = final; - headerRotation(t, str, final); + headerRotation(t, str, sizeof(str), final); const bool is_large_rotation = hasNumInput(&t->num); applyRotationValue(t, final, axis_final, is_large_rotation); diff --git a/source/blender/editors/transform/transform_mode_shrink_fatten.c b/source/blender/editors/transform/transform_mode_shrink_fatten.c index 6e497d85417..d2d73a14396 100644 --- a/source/blender/editors/transform/transform_mode_shrink_fatten.c +++ b/source/blender/editors/transform/transform_mode_shrink_fatten.c @@ -79,7 +79,7 @@ static void applyShrinkFatten(TransInfo *t, const int UNUSED(mval[2])) if (hasNumInput(&t->num)) { char c[NUM_STR_REP_LEN]; outputNumInput(&(t->num), c, unit); - ofs += BLI_snprintf(str + ofs, sizeof(str) - ofs, "%s", c); + ofs += BLI_snprintf_rlen(str + ofs, sizeof(str) - ofs, "%s", c); } else { /* default header print */ @@ -93,12 +93,12 @@ static void applyShrinkFatten(TransInfo *t, const int UNUSED(mval[2])) true); } else { - ofs += BLI_snprintf(str + ofs, sizeof(str) - ofs, "%.4f", distance); + ofs += BLI_snprintf_rlen(str + ofs, sizeof(str) - ofs, "%.4f", distance); } } if (t->proptext[0]) { - ofs += BLI_snprintf(str + ofs, sizeof(str) - ofs, " %s", t->proptext); + ofs += BLI_snprintf_rlen(str + ofs, sizeof(str) - ofs, " %s", t->proptext); } ofs += BLI_strncpy_rlen(str + ofs, ", (", sizeof(str) - ofs); diff --git a/source/blender/editors/transform/transform_mode_skin_resize.c b/source/blender/editors/transform/transform_mode_skin_resize.c index 8beacb844b9..75ad83b0787 100644 --- a/source/blender/editors/transform/transform_mode_skin_resize.c +++ b/source/blender/editors/transform/transform_mode_skin_resize.c @@ -64,7 +64,7 @@ static void applySkinResize(TransInfo *t, const int UNUSED(mval[2])) size_to_mat3(mat, t->values_final); - headerResize(t, t->values_final, str); + headerResize(t, t->values_final, str, sizeof(str)); FOREACH_TRANS_DATA_CONTAINER (t, tc) { TransData *td = tc->data; @@ -111,7 +111,6 @@ void initSkinResize(TransInfo *t) t->num.val_flag[2] |= NUM_NULL_ONE; t->num.flag |= NUM_AFFECT_ALL; if ((t->flag & T_EDIT) == 0) { - t->flag |= T_NO_ZERO; #ifdef USE_NUM_NO_ZERO t->num.val_flag[0] |= NUM_NO_ZERO; t->num.val_flag[1] |= NUM_NO_ZERO; diff --git a/source/blender/editors/transform/transform_mode_timetranslate.c b/source/blender/editors/transform/transform_mode_timetranslate.c index 5ad6d04b4de..948242e547f 100644 --- a/source/blender/editors/transform/transform_mode_timetranslate.c +++ b/source/blender/editors/transform/transform_mode_timetranslate.c @@ -76,10 +76,10 @@ static void headerTimeTranslate(TransInfo *t, char str[UI_MAX_DRAW_STR]) } } - ofs += BLI_snprintf(str, UI_MAX_DRAW_STR, TIP_("DeltaX: %s"), &tvec[0]); + ofs += BLI_snprintf_rlen(str, UI_MAX_DRAW_STR, TIP_("DeltaX: %s"), &tvec[0]); if (t->flag & T_PROP_EDIT_ALL) { - ofs += BLI_snprintf( + ofs += BLI_snprintf_rlen( str + ofs, UI_MAX_DRAW_STR - ofs, TIP_(" Proportional size: %.2f"), t->prop_size); } } diff --git a/source/blender/editors/transform/transform_mode_trackball.c b/source/blender/editors/transform/transform_mode_trackball.c index 5a57a69f986..d05077ef1ef 100644 --- a/source/blender/editors/transform/transform_mode_trackball.c +++ b/source/blender/editors/transform/transform_mode_trackball.c @@ -102,24 +102,24 @@ static void applyTrackball(TransInfo *t, const int UNUSED(mval[2])) outputNumInput(&(t->num), c, &t->scene->unit); - ofs += BLI_snprintf(str + ofs, - sizeof(str) - ofs, - TIP_("Trackball: %s %s %s"), - &c[0], - &c[NUM_STR_REP_LEN], - t->proptext); + ofs += BLI_snprintf_rlen(str + ofs, + sizeof(str) - ofs, + TIP_("Trackball: %s %s %s"), + &c[0], + &c[NUM_STR_REP_LEN], + t->proptext); } else { - ofs += BLI_snprintf(str + ofs, - sizeof(str) - ofs, - TIP_("Trackball: %.2f %.2f %s"), - RAD2DEGF(phi[0]), - RAD2DEGF(phi[1]), - t->proptext); + ofs += BLI_snprintf_rlen(str + ofs, + sizeof(str) - ofs, + TIP_("Trackball: %.2f %.2f %s"), + RAD2DEGF(phi[0]), + RAD2DEGF(phi[1]), + t->proptext); } if (t->flag & T_PROP_EDIT_ALL) { - ofs += BLI_snprintf( + ofs += BLI_snprintf_rlen( str + ofs, sizeof(str) - ofs, TIP_(" Proportional size: %.2f"), t->prop_size); } diff --git a/source/blender/editors/transform/transform_mode_translate.c b/source/blender/editors/transform/transform_mode_translate.c index 175b7b52a1a..3088f6a7776 100644 --- a/source/blender/editors/transform/transform_mode_translate.c +++ b/source/blender/editors/transform/transform_mode_translate.c @@ -141,67 +141,67 @@ static void headerTranslation(TransInfo *t, const float vec[3], char str[UI_MAX_ if (t->con.mode & CON_APPLY) { switch (t->num.idx_max) { case 0: - ofs += BLI_snprintf(str + ofs, - UI_MAX_DRAW_STR - ofs, - "D: %s (%s)%s %s %s", - &tvec[0], - distvec, - t->con.text, - t->proptext, - autoik); + ofs += BLI_snprintf_rlen(str + ofs, + UI_MAX_DRAW_STR - ofs, + "D: %s (%s)%s %s %s", + &tvec[0], + distvec, + t->con.text, + t->proptext, + autoik); break; case 1: - ofs += BLI_snprintf(str + ofs, - UI_MAX_DRAW_STR - ofs, - "D: %s D: %s (%s)%s %s %s", - &tvec[0], - &tvec[NUM_STR_REP_LEN], - distvec, - t->con.text, - t->proptext, - autoik); + ofs += BLI_snprintf_rlen(str + ofs, + UI_MAX_DRAW_STR - ofs, + "D: %s D: %s (%s)%s %s %s", + &tvec[0], + &tvec[NUM_STR_REP_LEN], + distvec, + t->con.text, + t->proptext, + autoik); break; case 2: - ofs += BLI_snprintf(str + ofs, - UI_MAX_DRAW_STR - ofs, - "D: %s D: %s D: %s (%s)%s %s %s", - &tvec[0], - &tvec[NUM_STR_REP_LEN], - &tvec[NUM_STR_REP_LEN * 2], - distvec, - t->con.text, - t->proptext, - autoik); + ofs += BLI_snprintf_rlen(str + ofs, + UI_MAX_DRAW_STR - ofs, + "D: %s D: %s D: %s (%s)%s %s %s", + &tvec[0], + &tvec[NUM_STR_REP_LEN], + &tvec[NUM_STR_REP_LEN * 2], + distvec, + t->con.text, + t->proptext, + autoik); break; } } else { if (t->flag & T_2D_EDIT) { - ofs += BLI_snprintf(str + ofs, - UI_MAX_DRAW_STR - ofs, - "Dx: %s Dy: %s (%s)%s %s", - &tvec[0], - &tvec[NUM_STR_REP_LEN], - distvec, - t->con.text, - t->proptext); + ofs += BLI_snprintf_rlen(str + ofs, + UI_MAX_DRAW_STR - ofs, + "Dx: %s Dy: %s (%s)%s %s", + &tvec[0], + &tvec[NUM_STR_REP_LEN], + distvec, + t->con.text, + t->proptext); } else { - ofs += BLI_snprintf(str + ofs, - UI_MAX_DRAW_STR - ofs, - "Dx: %s Dy: %s Dz: %s (%s)%s %s %s", - &tvec[0], - &tvec[NUM_STR_REP_LEN], - &tvec[NUM_STR_REP_LEN * 2], - distvec, - t->con.text, - t->proptext, - autoik); + ofs += BLI_snprintf_rlen(str + ofs, + UI_MAX_DRAW_STR - ofs, + "Dx: %s Dy: %s Dz: %s (%s)%s %s %s", + &tvec[0], + &tvec[NUM_STR_REP_LEN], + &tvec[NUM_STR_REP_LEN * 2], + distvec, + t->con.text, + t->proptext, + autoik); } } if (t->flag & T_PROP_EDIT_ALL) { - ofs += BLI_snprintf( + ofs += BLI_snprintf_rlen( str + ofs, UI_MAX_DRAW_STR - ofs, TIP_(" Proportional size: %.2f"), t->prop_size); } @@ -217,12 +217,12 @@ static void headerTranslation(TransInfo *t, const float vec[3], char str[UI_MAX_ WM_modalkeymap_items_to_string( t->keymap, TFM_MODAL_INSERTOFS_TOGGLE_DIR, true, str_km, sizeof(str_km)); - ofs += BLI_snprintf(str, - UI_MAX_DRAW_STR, - TIP_("Auto-offset set to %s - press %s to toggle direction | %s"), - str_dir, - str_km, - str_old); + ofs += BLI_snprintf_rlen(str, + UI_MAX_DRAW_STR, + TIP_("Auto-offset set to %s - press %s to toggle direction | %s"), + str_dir, + str_km, + str_old); MEM_freeN((void *)str_old); } diff --git a/source/blender/editors/transform/transform_mode_vert_slide.c b/source/blender/editors/transform/transform_mode_vert_slide.c index 1e5d027e253..e16aa636872 100644 --- a/source/blender/editors/transform/transform_mode_vert_slide.c +++ b/source/blender/editors/transform/transform_mode_vert_slide.c @@ -606,15 +606,15 @@ static void applyVertSlide(TransInfo *t, const int UNUSED(mval[2])) ofs += BLI_strncpy_rlen(str + ofs, &c[0], sizeof(str) - ofs); } else { - ofs += BLI_snprintf(str + ofs, sizeof(str) - ofs, "%.4f ", final); + ofs += BLI_snprintf_rlen(str + ofs, sizeof(str) - ofs, "%.4f ", final); } - ofs += BLI_snprintf( + ofs += BLI_snprintf_rlen( str + ofs, sizeof(str) - ofs, TIP_("(E)ven: %s, "), WM_bool_as_string(use_even)); if (use_even) { - ofs += BLI_snprintf( + ofs += BLI_snprintf_rlen( str + ofs, sizeof(str) - ofs, TIP_("(F)lipped: %s, "), WM_bool_as_string(flipped)); } - ofs += BLI_snprintf( + ofs += BLI_snprintf_rlen( str + ofs, sizeof(str) - ofs, TIP_("Alt or (C)lamp: %s"), WM_bool_as_string(is_clamp)); /* done with header string */ diff --git a/source/blender/editors/transform/transform_orientations.c b/source/blender/editors/transform/transform_orientations.c index b3ed294845d..d97bcba161f 100644 --- a/source/blender/editors/transform/transform_orientations.c +++ b/source/blender/editors/transform/transform_orientations.c @@ -445,7 +445,7 @@ int BIF_countTransformOrientation(const bContext *C) return BLI_listbase_count(transform_orientations); } -void applyTransformOrientation(const TransformOrientation *ts, float r_mat[3][3], char *r_name) +void applyTransformOrientation(const TransformOrientation *ts, float r_mat[3][3], char r_name[64]) { if (r_name) { BLI_strncpy(r_name, ts->name, MAX_NAME); diff --git a/source/blender/editors/transform/transform_snap.c b/source/blender/editors/transform/transform_snap.c index d0f91802fff..193954fffb6 100644 --- a/source/blender/editors/transform/transform_snap.c +++ b/source/blender/editors/transform/transform_snap.c @@ -345,7 +345,7 @@ void applyProject(TransInfo *t) SCE_SNAP_MODE_FACE, &(const struct SnapObjectParams){ .snap_select = t->tsnap.modeSelect, - .use_object_edit_cage = (t->flag & T_EDIT) != 0, + .edit_mode_type = (t->flag & T_EDIT) != 0 ? SNAP_GEOM_EDIT : SNAP_GEOM_FINAL, .use_occlusion_test = false, .use_backface_culling = t->tsnap.use_backface_culling, }, @@ -1167,7 +1167,7 @@ short snapObjectsTransform( t->settings->snap_mode, &(const struct SnapObjectParams){ .snap_select = t->tsnap.modeSelect, - .use_object_edit_cage = (t->flag & T_EDIT) != 0, + .edit_mode_type = (t->flag & T_EDIT) != 0 ? SNAP_GEOM_EDIT : SNAP_GEOM_FINAL, .use_occlusion_test = t->settings->snap_mode != SCE_SNAP_MODE_FACE, .use_backface_culling = t->tsnap.use_backface_culling, }, @@ -1201,7 +1201,7 @@ bool peelObjectsTransform(TransInfo *t, t->depsgraph, &(const struct SnapObjectParams){ .snap_select = t->tsnap.modeSelect, - .use_object_edit_cage = (t->flag & T_EDIT) != 0, + .edit_mode_type = (t->flag & T_EDIT) != 0 ? SNAP_GEOM_EDIT : SNAP_GEOM_FINAL, }, mval, -1.0f, diff --git a/source/blender/editors/transform/transform_snap_object.c b/source/blender/editors/transform/transform_snap_object.c index e5f6f207e3c..512f912a532 100644 --- a/source/blender/editors/transform/transform_snap_object.c +++ b/source/blender/editors/transform/transform_snap_object.c @@ -44,6 +44,7 @@ #include "BKE_duplilist.h" #include "BKE_editmesh.h" #include "BKE_geometry_set.h" +#include "BKE_global.h" #include "BKE_layer.h" #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" @@ -145,9 +146,37 @@ struct SnapObjectContext { /** \name Utilities * \{ */ -static bool editmesh_eval_final_is_bmesh(const BMEditMesh *em) +/* Mesh used for snapping. + * If NULL the BMesh should be used. */ +static Mesh *mesh_for_snap(Object *ob_eval, eSnapEditType edit_mode_type, bool *r_use_hide) { - return (em->mesh_eval_final->runtime.wrapper_type == ME_WRAPPER_TYPE_BMESH); + Mesh *me_eval = ob_eval->data; + bool use_hide = false; + if (BKE_object_is_in_editmode(ob_eval)) { + if (edit_mode_type == SNAP_GEOM_EDIT) { + return NULL; + } + + BMEditMesh *em_eval = BKE_editmesh_from_object(ob_eval); + if ((edit_mode_type == SNAP_GEOM_FINAL) && em_eval->mesh_eval_final) { + if (em_eval->mesh_eval_final->runtime.wrapper_type == ME_WRAPPER_TYPE_BMESH) { + return NULL; + } + me_eval = em_eval->mesh_eval_final; + use_hide = true; + } + else if ((edit_mode_type == SNAP_GEOM_CAGE) && em_eval->mesh_eval_cage) { + if (em_eval->mesh_eval_cage->runtime.wrapper_type == ME_WRAPPER_TYPE_BMESH) { + return NULL; + } + me_eval = em_eval->mesh_eval_cage; + use_hide = true; + } + } + if (r_use_hide) { + *r_use_hide = use_hide; + } + return me_eval; } /** \} */ @@ -209,30 +238,69 @@ static void snap_object_data_clear(SnapObjectData *sod) memset(&sod->type, 0x0, sizeof(*sod) - offsetof(SnapObjectData, type)); } -static SnapObjectData *snap_object_data_lookup(SnapObjectContext *sctx, Object *ob) +static SnapObjectData *snap_object_data_lookup(SnapObjectContext *sctx, Object *ob_eval) { - SnapObjectData *sod = BLI_ghash_lookup(sctx->cache.object_map, ob); + SnapObjectData *sod = BLI_ghash_lookup(sctx->cache.object_map, ob_eval); if (sod == NULL) { if (sctx->cache.data_to_object_map != NULL) { - ob = BLI_ghash_lookup(sctx->cache.data_to_object_map, ob->data); + ob_eval = BLI_ghash_lookup(sctx->cache.data_to_object_map, ob_eval->data); /* Could be NULl when mixing edit-mode and non edit-mode objects. */ - if (ob != NULL) { - sod = BLI_ghash_lookup(sctx->cache.object_map, ob); + if (ob_eval != NULL) { + sod = BLI_ghash_lookup(sctx->cache.object_map, ob_eval); } } } return sod; } -static SnapObjectData *snap_object_data_mesh_get(SnapObjectContext *sctx, Object *ob) +static SnapObjectData *snap_object_data_mesh_get(SnapObjectContext *sctx, + Object *ob_eval, + Mesh *me_eval, + bool use_hide) { SnapObjectData *sod; void **sod_p; bool init = false; - if (BLI_ghash_ensure_p(sctx->cache.object_map, ob, &sod_p)) { + if (BLI_ghash_ensure_p(sctx->cache.object_map, ob_eval, &sod_p)) { sod = *sod_p; + bool is_dirty = false; if (sod->type != SNAP_MESH) { + is_dirty = true; + } + else if (sod->treedata_mesh.tree && sod->treedata_mesh.cached && + !bvhcache_has_tree(me_eval->runtime.bvh_cache, sod->treedata_mesh.tree)) { + /* The tree is owned by the Mesh and may have been freed since we last used. */ + is_dirty = true; + } + else if (sod->bvhtree[0] && sod->cached[0] && + !bvhcache_has_tree(me_eval->runtime.bvh_cache, sod->bvhtree[0])) { + /* The tree is owned by the Mesh and may have been freed since we last used. */ + is_dirty = true; + } + else if (sod->bvhtree[1] && sod->cached[1] && + !bvhcache_has_tree(me_eval->runtime.bvh_cache, sod->bvhtree[1])) { + /* The tree is owned by the Mesh and may have been freed since we last used. */ + is_dirty = true; + } + else if (!sod->treedata_mesh.looptri_allocated && + sod->treedata_mesh.looptri != me_eval->runtime.looptris.array) { + is_dirty = true; + } + else if (!sod->treedata_mesh.vert_allocated && sod->treedata_mesh.vert != me_eval->mvert) { + is_dirty = true; + } + else if (!sod->treedata_mesh.loop_allocated && sod->treedata_mesh.loop != me_eval->mloop) { + is_dirty = true; + } + else if (!sod->treedata_mesh.edge_allocated && sod->treedata_mesh.edge != me_eval->medge) { + is_dirty = true; + } + else if (sod->poly != me_eval->mpoly) { + is_dirty = true; + } + + if (is_dirty) { snap_object_data_clear(sod); init = true; } @@ -244,8 +312,32 @@ static SnapObjectData *snap_object_data_mesh_get(SnapObjectContext *sctx, Object if (init) { sod->type = SNAP_MESH; - /* start assuming that it has each of these element types */ - sod->has_looptris = true; + + /* The BVHTree from looptris is always required. */ + BLI_assert(sod->treedata_mesh.tree == NULL); + BKE_bvhtree_from_mesh_get(&sod->treedata_mesh, + me_eval, + use_hide ? BVHTREE_FROM_LOOPTRI_NO_HIDDEN : BVHTREE_FROM_LOOPTRI, + 4); + + if (sod->treedata_mesh.tree == NULL) { + sod->treedata_mesh.vert = me_eval->mvert; + sod->treedata_mesh.loop = me_eval->mloop; + sod->treedata_mesh.looptri = BKE_mesh_runtime_looptri_ensure(me_eval); + BLI_assert(sod->has_looptris == false); + } + else { + BLI_assert(sod->treedata_mesh.vert != NULL); + BLI_assert(sod->treedata_mesh.loop != NULL); + BLI_assert(sod->treedata_mesh.looptri != NULL); + sod->has_looptris = true; + } + + /* Required for snapping with occlusion. */ + sod->treedata_mesh.edge = me_eval->medge; + sod->poly = me_eval->mpoly; + + /* Start assuming that it has each of these element types. */ sod->has_loose_edge = true; sod->has_loose_vert = true; } @@ -253,26 +345,26 @@ static SnapObjectData *snap_object_data_mesh_get(SnapObjectContext *sctx, Object return sod; } -static struct Mesh_Runtime *snap_object_data_editmesh_runtime_get(Object *ob) +static struct Mesh_Runtime *snap_object_data_editmesh_runtime_get(Object *ob_eval) { - BMEditMesh *em = BKE_editmesh_from_object(ob); - if (em->mesh_eval_final) { - return &em->mesh_eval_final->runtime; + BMEditMesh *em_eval = BKE_editmesh_from_object(ob_eval); + if (em_eval->mesh_eval_final) { + return &em_eval->mesh_eval_final->runtime; } - if (em->mesh_eval_cage) { - return &em->mesh_eval_cage->runtime; + if (em_eval->mesh_eval_cage) { + return &em_eval->mesh_eval_cage->runtime; } - return &((Mesh *)ob->data)->runtime; + return &((Mesh *)ob_eval->data)->runtime; } static SnapObjectData *snap_object_data_editmesh_get(SnapObjectContext *sctx, - Object *ob, + Object *ob_eval, BMEditMesh *em) { SnapObjectData *sod; void **sod_p; - bool init = false, init_min_max = true, clear_cache = false; + bool init = false; { /* Use object-data as the key in ghash since the editmesh @@ -281,48 +373,53 @@ static SnapObjectData *snap_object_data_editmesh_get(SnapObjectContext *sctx, sctx->cache.data_to_object_map = BLI_ghash_ptr_new(__func__); } void **ob_p; - if (BLI_ghash_ensure_p(sctx->cache.data_to_object_map, ob->data, &ob_p)) { - ob = *ob_p; + if (BLI_ghash_ensure_p(sctx->cache.data_to_object_map, ob_eval->data, &ob_p)) { + ob_eval = *ob_p; } else { - *ob_p = ob; + *ob_p = ob_eval; } } - if (BLI_ghash_ensure_p(sctx->cache.object_map, ob, &sod_p)) { + if (BLI_ghash_ensure_p(sctx->cache.object_map, ob_eval, &sod_p)) { sod = *sod_p; - bool clear = false; + bool is_dirty = false; /* Check if the geometry has changed. */ if (sod->type != SNAP_EDIT_MESH) { - clear = true; + is_dirty = true; } else if (sod->treedata_editmesh.em != em) { - clear_cache = true; - init = true; + is_dirty = true; } else if (sod->mesh_runtime) { - if (sod->mesh_runtime != snap_object_data_editmesh_runtime_get(ob)) { - clear_cache = true; - init = true; + if (sod->mesh_runtime != snap_object_data_editmesh_runtime_get(ob_eval)) { + if (G.moving) { + /* Hack to avoid updating while transforming. */ + BLI_assert(!sod->treedata_editmesh.cached && !sod->cached[0] && !sod->cached[1]); + sod->mesh_runtime = snap_object_data_editmesh_runtime_get(ob_eval); + } + else { + is_dirty = true; + } } else if (sod->treedata_editmesh.tree && sod->treedata_editmesh.cached && !bvhcache_has_tree(sod->mesh_runtime->bvh_cache, sod->treedata_editmesh.tree)) { /* The tree is owned by the EditMesh and may have been freed since we last used! */ - clear = true; + is_dirty = true; } else if (sod->bvhtree[0] && sod->cached[0] && !bvhcache_has_tree(sod->mesh_runtime->bvh_cache, sod->bvhtree[0])) { /* The tree is owned by the EditMesh and may have been freed since we last used! */ - clear = true; + is_dirty = true; } else if (sod->bvhtree[1] && sod->cached[1] && !bvhcache_has_tree(sod->mesh_runtime->bvh_cache, sod->bvhtree[1])) { /* The tree is owned by the EditMesh and may have been freed since we last used! */ - clear = true; + is_dirty = true; } } - if (clear) { + if (is_dirty) { snap_object_data_clear(sod); init = true; } @@ -335,27 +432,8 @@ static SnapObjectData *snap_object_data_editmesh_get(SnapObjectContext *sctx, if (init) { sod->type = SNAP_EDIT_MESH; sod->treedata_editmesh.em = em; - - if (clear_cache) { - /* Only init min and max when you have a non-custom bvhtree pending. */ - init_min_max = false; - if (sod->treedata_editmesh.cached) { - sod->treedata_editmesh.tree = NULL; - init_min_max = true; - } - for (int i = 0; i < ARRAY_SIZE(sod->bvhtree); i++) { - if (sod->cached[i]) { - sod->bvhtree[i] = NULL; - init_min_max = true; - } - } - } - - if (init_min_max) { - bm_mesh_minmax(em->bm, sod->min, sod->max); - } - - sod->mesh_runtime = snap_object_data_editmesh_runtime_get(ob); + sod->mesh_runtime = snap_object_data_editmesh_runtime_get(ob_eval); + bm_mesh_minmax(em->bm, sod->min, sod->max); } return sod; @@ -368,9 +446,9 @@ static SnapObjectData *snap_object_data_editmesh_get(SnapObjectContext *sctx, * \{ */ typedef void (*IterSnapObjsCallback)(SnapObjectContext *sctx, - Object *ob, + Object *ob_eval, float obmat[4][4], - bool use_obedit, + eSnapEditType edit_mode_type, bool use_backface_culling, bool is_object_active, void *data); @@ -387,10 +465,17 @@ static void iter_snap_objects(SnapObjectContext *sctx, ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph); const View3D *v3d = sctx->v3d_data.v3d; const eSnapSelect snap_select = params->snap_select; - const bool use_object_edit_cage = params->use_object_edit_cage; + const eSnapEditType edit_mode_type = params->edit_mode_type; const bool use_backface_culling = params->use_backface_culling; Base *base_act = view_layer->basact; + if (snap_select == SNAP_ONLY_ACTIVE) { + Object *obj_eval = DEG_get_evaluated_object(depsgraph, base_act->object); + sob_callback( + sctx, obj_eval, obj_eval->obmat, edit_mode_type, use_backface_culling, true, data); + return; + } + for (Base *base = view_layer->object_bases.first; base != NULL; base = base->next) { if (!BASE_VISIBLE(v3d, base)) { continue; @@ -425,7 +510,7 @@ static void iter_snap_objects(SnapObjectContext *sctx, sob_callback(sctx, dupli_ob->ob, dupli_ob->mat, - use_object_edit_cage, + edit_mode_type, use_backface_culling, is_object_active, data); @@ -436,7 +521,7 @@ static void iter_snap_objects(SnapObjectContext *sctx, sob_callback(sctx, obj_eval, obj_eval->obmat, - use_object_edit_cage, + edit_mode_type, use_backface_culling, is_object_active, data); @@ -464,7 +549,7 @@ struct RayCastAll_Data { float len_diff; float local_scale; - Object *ob; + Object *ob_eval; uint ob_uuid; /* output data */ @@ -476,7 +561,7 @@ static struct SnapObjectHitDepth *hit_depth_create(const float depth, const float co[3], const float no[3], int index, - Object *ob, + Object *ob_eval, const float obmat[4][4], uint ob_uuid) { @@ -487,7 +572,7 @@ static struct SnapObjectHitDepth *hit_depth_create(const float depth, copy_v3_v3(hit->no, no); hit->index = index; - hit->ob = ob; + hit->ob_eval = ob_eval; copy_m4_m4(hit->obmat, (float(*)[4])obmat); hit->ob_uuid = ob_uuid; @@ -529,7 +614,7 @@ static void raycast_all_cb(void *userdata, int index, const BVHTreeRay *ray, BVH normalize_v3(normal); struct SnapObjectHitDepth *hit_item = hit_depth_create( - depth, location, normal, hit->index, data->ob, data->obmat, data->ob_uuid); + depth, location, normal, hit->index, data->ob_eval, data->obmat, data->ob_uuid); BLI_addtail(data->hit_list, hit_item); } } @@ -601,8 +686,8 @@ static void editmesh_looptri_raycast_backface_culling_cb(void *userdata, static bool raycastMesh(SnapObjectContext *sctx, const float ray_start[3], const float ray_dir[3], - Object *ob, - Mesh *me, + Object *ob_eval, + Mesh *me_eval, const float obmat[4][4], const uint ob_index, bool use_hide, @@ -617,7 +702,7 @@ static bool raycastMesh(SnapObjectContext *sctx, { bool retval = false; - if (me->totpoly == 0) { + if (me_eval->totpoly == 0) { return retval; } @@ -641,7 +726,7 @@ static bool raycastMesh(SnapObjectContext *sctx, } /* Test BoundBox */ - BoundBox *bb = BKE_mesh_boundbox_get(ob); + BoundBox *bb = BKE_mesh_boundbox_get(ob_eval); if (bb) { /* was BKE_boundbox_ray_hit_check, see: cf6ca226fa58 */ if (!isect_ray_aabb_v3_simple( @@ -661,53 +746,18 @@ static bool raycastMesh(SnapObjectContext *sctx, len_diff = 0.0f; } - SnapObjectData *sod = snap_object_data_mesh_get(sctx, ob); + SnapObjectData *sod = snap_object_data_mesh_get(sctx, ob_eval, me_eval, use_hide); BVHTreeFromMesh *treedata = &sod->treedata_mesh; - /* The tree is owned by the Mesh and may have been freed since we last used. */ - if (treedata->tree) { - BLI_assert(treedata->cached); - if (!bvhcache_has_tree(me->runtime.bvh_cache, treedata->tree)) { - free_bvhtree_from_mesh(treedata); - } - else { - /* Update Pointers. */ - if (treedata->vert && treedata->vert_allocated == false) { - treedata->vert = me->mvert; - } - if (treedata->loop && treedata->loop_allocated == false) { - treedata->loop = me->mloop; - } - if (treedata->looptri && treedata->looptri_allocated == false) { - treedata->looptri = BKE_mesh_runtime_looptri_ensure(me); - } - /* required for snapping with occlusion. */ - treedata->edge = me->medge; - sod->poly = me->mpoly; - } - } - if (treedata->tree == NULL) { - if (use_hide) { - BKE_bvhtree_from_mesh_get(treedata, me, BVHTREE_FROM_LOOPTRI_NO_HIDDEN, 4); - } - else { - BKE_bvhtree_from_mesh_get(treedata, me, BVHTREE_FROM_LOOPTRI, 4); - } - - /* required for snapping with occlusion. */ - treedata->edge = me->medge; - sod->poly = me->mpoly; - - if (treedata->tree == NULL) { - return retval; - } + return retval; } float timat[3][3]; /* transpose inverse matrix for normals */ transpose_m3_m4(timat, imat); + BLI_assert(treedata->raycast_callback != NULL); if (r_hit_list) { struct RayCastAll_Data data; @@ -717,7 +767,7 @@ static bool raycastMesh(SnapObjectContext *sctx, data.timat = timat; data.len_diff = len_diff; data.local_scale = local_scale; - data.ob = ob; + data.ob_eval = ob_eval; data.ob_uuid = ob_index; data.hit_list = r_hit_list; data.retval = retval; @@ -776,7 +826,7 @@ static bool raycastMesh(SnapObjectContext *sctx, static bool raycastEditMesh(SnapObjectContext *sctx, const float ray_start[3], const float ray_dir[3], - Object *ob, + Object *ob_eval, BMEditMesh *em, const float obmat[4][4], const uint ob_index, @@ -813,7 +863,7 @@ static bool raycastEditMesh(SnapObjectContext *sctx, local_depth *= local_scale; } - SnapObjectData *sod = snap_object_data_editmesh_get(sctx, ob, em); + SnapObjectData *sod = snap_object_data_editmesh_get(sctx, ob_eval, em); /* Test BoundBox */ @@ -839,7 +889,8 @@ static bool raycastEditMesh(SnapObjectContext *sctx, if (treedata->tree == NULL) { /* Operators only update the editmesh looptris of the original mesh. */ - BLI_assert(sod->treedata_editmesh.em == BKE_editmesh_from_object(DEG_get_original_object(ob))); + BLI_assert(sod->treedata_editmesh.em == + BKE_editmesh_from_object(DEG_get_original_object(ob_eval))); em = sod->treedata_editmesh.em; if (sctx->callbacks.edit_mesh.test_face_fn) { @@ -886,7 +937,7 @@ static bool raycastEditMesh(SnapObjectContext *sctx, data.timat = timat; data.len_diff = len_diff; data.local_scale = local_scale; - data.ob = ob; + data.ob_eval = ob_eval; data.ob_uuid = ob_index; data.hit_list = r_hit_list; data.retval = retval; @@ -967,9 +1018,9 @@ struct RaycastObjUserData { * \note Duplicate args here are documented at #snapObjectsRay */ static void raycast_obj_fn(SnapObjectContext *sctx, - Object *ob, + Object *ob_eval, float obmat[4][4], - bool use_obedit, + eSnapEditType edit_mode_type, bool use_backface_culling, bool is_object_active, void *data) @@ -982,53 +1033,46 @@ static void raycast_obj_fn(SnapObjectContext *sctx, bool retval = false; if (use_occlusion_test) { - if (use_obedit && sctx->use_v3d && XRAY_FLAG_ENABLED(sctx->v3d_data.v3d)) { + if ((edit_mode_type == SNAP_GEOM_EDIT) && sctx->use_v3d && + XRAY_FLAG_ENABLED(sctx->v3d_data.v3d)) { /* Use of occlude geometry in editing mode disabled. */ return; } - if (ELEM(ob->dt, OB_BOUNDBOX, OB_WIRE)) { + if (ELEM(ob_eval->dt, OB_BOUNDBOX, OB_WIRE)) { /* Do not hit objects that are in wire or bounding box * display mode. */ return; } } - switch (ob->type) { + switch (ob_eval->type) { case OB_MESH: { - Mesh *me = ob->data; bool use_hide = false; - if (BKE_object_is_in_editmode(ob)) { - if (use_obedit || editmesh_eval_final_is_bmesh(me->edit_mesh)) { - /* Operators only update the editmesh looptris of the original mesh. */ - BMEditMesh *em_orig = BKE_editmesh_from_object(DEG_get_original_object(ob)); - retval = raycastEditMesh(sctx, - dt->ray_start, - dt->ray_dir, - ob, - em_orig, - obmat, - ob_index, - use_backface_culling, - ray_depth, - dt->r_loc, - dt->r_no, - dt->r_index, - dt->r_hit_list); - break; - } - - BMEditMesh *em = BKE_editmesh_from_object(ob); - if (em->mesh_eval_final) { - me = em->mesh_eval_final; - use_hide = true; - } + Mesh *me_eval = mesh_for_snap(ob_eval, edit_mode_type, &use_hide); + if (me_eval == NULL) { + /* Operators only update the editmesh looptris of the original mesh. */ + BMEditMesh *em_orig = BKE_editmesh_from_object(DEG_get_original_object(ob_eval)); + retval = raycastEditMesh(sctx, + dt->ray_start, + dt->ray_dir, + ob_eval, + em_orig, + obmat, + ob_index, + use_backface_culling, + ray_depth, + dt->r_loc, + dt->r_no, + dt->r_index, + dt->r_hit_list); + break; } retval = raycastMesh(sctx, dt->ray_start, dt->ray_dir, - ob, - me, + ob_eval, + me_eval, obmat, ob_index, use_hide, @@ -1044,12 +1088,12 @@ static void raycast_obj_fn(SnapObjectContext *sctx, case OB_SURF: case OB_FONT: { if (!is_object_active) { - Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); + Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval); if (mesh_eval) { retval = raycastMesh(sctx, dt->ray_start, dt->ray_dir, - ob, + ob_eval, mesh_eval, obmat, ob_index, @@ -1068,7 +1112,7 @@ static void raycast_obj_fn(SnapObjectContext *sctx, if (retval) { if (dt->r_ob) { - *dt->r_ob = ob; + *dt->r_ob = ob_eval; } if (dt->r_obmat) { copy_m4_m4(dt->r_obmat, obmat); @@ -1323,6 +1367,33 @@ typedef struct Nearest2dUserData { bool use_backface_culling; } Nearest2dUserData; +static void nearest2d_data_init(SnapObjectData *sod, + bool is_persp, + bool use_backface_culling, + Nearest2dUserData *r_nearest2d) +{ + if (sod->type == SNAP_MESH) { + r_nearest2d->userdata = &sod->treedata_mesh; + r_nearest2d->get_vert_co = (Nearest2DGetVertCoCallback)cb_mvert_co_get; + r_nearest2d->get_edge_verts_index = (Nearest2DGetEdgeVertsCallback)cb_medge_verts_get; + r_nearest2d->copy_vert_no = (Nearest2DCopyVertNoCallback)cb_mvert_no_copy; + r_nearest2d->get_tri_verts_index = (Nearest2DGetTriVertsCallback)cb_mlooptri_verts_get; + r_nearest2d->get_tri_edges_index = (Nearest2DGetTriEdgesCallback)cb_mlooptri_edges_get; + } + else { + BLI_assert(sod->type == SNAP_EDIT_MESH); + r_nearest2d->userdata = sod->treedata_editmesh.em; + r_nearest2d->get_vert_co = (Nearest2DGetVertCoCallback)cb_bvert_co_get; + r_nearest2d->get_edge_verts_index = (Nearest2DGetEdgeVertsCallback)cb_bedge_verts_get; + r_nearest2d->copy_vert_no = (Nearest2DCopyVertNoCallback)cb_bvert_no_copy; + r_nearest2d->get_tri_verts_index = NULL; + r_nearest2d->get_tri_edges_index = NULL; + } + + r_nearest2d->is_persp = is_persp; + r_nearest2d->use_backface_culling = use_backface_culling; +} + static void cb_snap_vert(void *userdata, int index, const struct DistProjectedAABBPrecalc *precalc, @@ -1470,7 +1541,7 @@ static void cb_snap_tri_verts(void *userdata, static short snap_mesh_polygon(SnapObjectContext *sctx, SnapData *snapdata, - Object *ob, + Object *ob_eval, const float obmat[4][4], bool use_backface_culling, /* read/write args */ @@ -1495,28 +1566,21 @@ static short snap_mesh_polygon(SnapObjectContext *sctx, mul_v4_m4v4(clip_planes_local[i], tobmat, snapdata->clip_plane[i]); } - Nearest2dUserData nearest2d = { - .is_persp = snapdata->view_proj == VIEW_PROJ_PERSP, - .use_backface_culling = use_backface_culling, - }; - BVHTreeNearest nearest = { .index = -1, .dist_sq = square_f(*dist_px), }; - SnapObjectData *sod = snap_object_data_lookup(sctx, ob); - + SnapObjectData *sod = snap_object_data_lookup(sctx, ob_eval); BLI_assert(sod != NULL); + Nearest2dUserData nearest2d; + nearest2d_data_init( + sod, snapdata->view_proj == VIEW_PROJ_PERSP, use_backface_culling, &nearest2d); + if (sod->type == SNAP_MESH) { BVHTreeFromMesh *treedata = &sod->treedata_mesh; - nearest2d.userdata = treedata; - nearest2d.get_vert_co = (Nearest2DGetVertCoCallback)cb_mvert_co_get; - nearest2d.get_edge_verts_index = (Nearest2DGetEdgeVertsCallback)cb_medge_verts_get; - nearest2d.copy_vert_no = (Nearest2DCopyVertNoCallback)cb_mvert_no_copy; - const MPoly *mp = &sod->poly[*r_index]; const MLoop *ml = &treedata->loop[mp->loopstart]; if (snapdata->snap_to_flag & SCE_SNAP_MODE_EDGE) { @@ -1547,11 +1611,6 @@ static short snap_mesh_polygon(SnapObjectContext *sctx, BLI_assert(sod->type == SNAP_EDIT_MESH); BMEditMesh *em = sod->treedata_editmesh.em; - nearest2d.userdata = em; - nearest2d.get_vert_co = (Nearest2DGetVertCoCallback)cb_bvert_co_get; - nearest2d.get_edge_verts_index = (Nearest2DGetEdgeVertsCallback)cb_bedge_verts_get; - nearest2d.copy_vert_no = (Nearest2DCopyVertNoCallback)cb_bvert_no_copy; - BM_mesh_elem_table_ensure(em->bm, BM_FACE); BMFace *f = BM_face_at_index(em->bm, *r_index); BMLoop *l_iter, *l_first; @@ -1608,7 +1667,7 @@ static short snap_mesh_polygon(SnapObjectContext *sctx, static short snap_mesh_edge_verts_mixed(SnapObjectContext *sctx, SnapData *snapdata, - Object *ob, + Object *ob_eval, const float obmat[4][4], float original_dist_px, const float prev_co[3], @@ -1622,32 +1681,16 @@ static short snap_mesh_edge_verts_mixed(SnapObjectContext *sctx, { short elem = SCE_SNAP_MODE_EDGE; - if (ob->type != OB_MESH) { + if (ob_eval->type != OB_MESH) { return elem; } - SnapObjectData *sod = snap_object_data_lookup(sctx, ob); - + SnapObjectData *sod = snap_object_data_lookup(sctx, ob_eval); BLI_assert(sod != NULL); Nearest2dUserData nearest2d; - { - nearest2d.is_persp = snapdata->view_proj == VIEW_PROJ_PERSP; - nearest2d.use_backface_culling = use_backface_culling; - if (sod->type == SNAP_MESH) { - nearest2d.userdata = &sod->treedata_mesh; - nearest2d.get_vert_co = (Nearest2DGetVertCoCallback)cb_mvert_co_get; - nearest2d.get_edge_verts_index = (Nearest2DGetEdgeVertsCallback)cb_medge_verts_get; - nearest2d.copy_vert_no = (Nearest2DCopyVertNoCallback)cb_mvert_no_copy; - } - else { - BLI_assert(sod->type == SNAP_EDIT_MESH); - nearest2d.userdata = sod->treedata_editmesh.em; - nearest2d.get_vert_co = (Nearest2DGetVertCoCallback)cb_bvert_co_get; - nearest2d.get_edge_verts_index = (Nearest2DGetEdgeVertsCallback)cb_bedge_verts_get; - nearest2d.copy_vert_no = (Nearest2DCopyVertNoCallback)cb_bvert_no_copy; - } - } + nearest2d_data_init( + sod, snapdata->view_proj == VIEW_PROJ_PERSP, use_backface_culling, &nearest2d); int vindex[2]; nearest2d.get_edge_verts_index(*r_index, vindex, nearest2d.userdata); @@ -1772,9 +1815,8 @@ static short snap_mesh_edge_verts_mixed(SnapObjectContext *sctx, } static short snapArmature(SnapData *snapdata, - Object *ob, + Object *ob_eval, const float obmat[4][4], - bool use_obedit, /* read/write args */ float *dist_px, /* return args */ @@ -1795,11 +1837,11 @@ static short snapArmature(SnapData *snapdata, dist_squared_to_projected_aabb_precalc( &neasrest_precalc, lpmat, snapdata->win_size, snapdata->mval); - use_obedit = use_obedit && BKE_object_is_in_editmode(ob); + bool use_obedit = ((bArmature *)ob_eval->data)->edbo != NULL; if (use_obedit == false) { /* Test BoundBox */ - BoundBox *bb = BKE_armature_boundbox_get(ob); + BoundBox *bb = BKE_armature_boundbox_get(ob_eval); if (bb && !snap_bound_box_check_dist( bb->vec[0], bb->vec[6], lpmat, snapdata->win_size, snapdata->mval, dist_px_sq)) { return retval; @@ -1814,7 +1856,7 @@ static short snapArmature(SnapData *snapdata, bool is_persp = snapdata->view_proj == VIEW_PROJ_PERSP; - bArmature *arm = ob->data; + bArmature *arm = ob_eval->data; if (arm->edbo) { LISTBASE_FOREACH (EditBone *, eBone, arm->edbo) { if (eBone->layer & arm->layer) { @@ -1858,8 +1900,8 @@ static short snapArmature(SnapData *snapdata, } } } - else if (ob->pose && ob->pose->chanbase.first) { - LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) { + else if (ob_eval->pose && ob_eval->pose->chanbase.first) { + LISTBASE_FOREACH (bPoseChannel *, pchan, &ob_eval->pose->chanbase) { Bone *bone = pchan->bone; /* skip hidden bones */ if (bone && !(bone->flag & (BONE_HIDDEN_P | BONE_HIDDEN_PG))) { @@ -1917,7 +1959,7 @@ static short snapArmature(SnapData *snapdata, } static short snapCurve(SnapData *snapdata, - Object *ob, + Object *ob_eval, const float obmat[4][4], bool use_obedit, /* read/write args */ @@ -1934,7 +1976,7 @@ static short snapCurve(SnapData *snapdata, return 0; } - Curve *cu = ob->data; + Curve *cu = ob_eval->data; float dist_px_sq = square_f(*dist_px); float lpmat[4][4]; @@ -1944,11 +1986,11 @@ static short snapCurve(SnapData *snapdata, dist_squared_to_projected_aabb_precalc( &neasrest_precalc, lpmat, snapdata->win_size, snapdata->mval); - use_obedit = use_obedit && BKE_object_is_in_editmode(ob); + use_obedit = use_obedit && BKE_object_is_in_editmode(ob_eval); if (use_obedit == false) { /* Test BoundBox */ - BoundBox *bb = BKE_curve_boundbox_get(ob); + BoundBox *bb = BKE_curve_boundbox_get(ob_eval); if (bb && !snap_bound_box_check_dist( bb->vec[0], bb->vec[6], lpmat, snapdata->win_size, snapdata->mval, dist_px_sq)) { return 0; @@ -2068,7 +2110,7 @@ static short snapCurve(SnapData *snapdata, /* may extend later (for now just snaps to empty center) */ static short snap_object_center(SnapData *snapdata, - Object *ob, + Object *ob_eval, const float obmat[4][4], /* read/write args */ float *dist_px, @@ -2079,7 +2121,7 @@ static short snap_object_center(SnapData *snapdata, { short retval = 0; - if (ob->transflag & OB_DUPLI) { + if (ob_eval->transflag & OB_DUPLI) { return retval; } @@ -2222,9 +2264,10 @@ static short snapCamera(const SnapObjectContext *sctx, static short snapMesh(SnapObjectContext *sctx, SnapData *snapdata, - Object *ob, - Mesh *me, + Object *ob_eval, + Mesh *me_eval, const float obmat[4][4], + bool use_hide, bool use_backface_culling, /* read/write args */ float *dist_px, @@ -2234,10 +2277,10 @@ static short snapMesh(SnapObjectContext *sctx, int *r_index) { BLI_assert(snapdata->snap_to_flag != SCE_SNAP_MODE_FACE); - if (me->totvert == 0) { + if (me_eval->totvert == 0) { return 0; } - if (me->totedge == 0 && !(snapdata->snap_to_flag & SCE_SNAP_MODE_VERTEX)) { + if (me_eval->totedge == 0 && !(snapdata->snap_to_flag & SCE_SNAP_MODE_VERTEX)) { return 0; } @@ -2247,65 +2290,46 @@ static short snapMesh(SnapObjectContext *sctx, float dist_px_sq = square_f(*dist_px); /* Test BoundBox */ - BoundBox *bb = BKE_mesh_boundbox_get(ob); + BoundBox *bb = BKE_mesh_boundbox_get(ob_eval); if (bb && !snap_bound_box_check_dist( bb->vec[0], bb->vec[6], lpmat, snapdata->win_size, snapdata->mval, dist_px_sq)) { return 0; } - SnapObjectData *sod = snap_object_data_mesh_get(sctx, ob); + SnapObjectData *sod = snap_object_data_mesh_get(sctx, ob_eval, me_eval, use_hide); - BVHTreeFromMesh *treedata, dummy_treedata; + BVHTreeFromMesh *treedata, treedata_tmp; treedata = &sod->treedata_mesh; - /* The tree is owned by the Mesh and may have been freed since we last used! */ - if (treedata->cached && treedata->tree && - !bvhcache_has_tree(me->runtime.bvh_cache, treedata->tree)) { - free_bvhtree_from_mesh(treedata); - } - if (sod->cached[0] && sod->bvhtree[0] && - !bvhcache_has_tree(me->runtime.bvh_cache, sod->bvhtree[0])) { - sod->bvhtree[0] = NULL; - } - if (sod->cached[1] && sod->bvhtree[1] && - !bvhcache_has_tree(me->runtime.bvh_cache, sod->bvhtree[1])) { - sod->bvhtree[1] = NULL; - } - - if (sod->has_looptris && treedata->tree == NULL) { - BKE_bvhtree_from_mesh_get(treedata, me, BVHTREE_FROM_LOOPTRI, 4); - sod->has_looptris = (treedata->tree != NULL); - if (sod->has_looptris) { - /* Make sure that the array of edges is referenced in the callbacks. */ - treedata->edge = me->medge; /* CustomData_get_layer(&me->edata, CD_MEDGE);? */ - } - } if (sod->has_loose_edge && sod->bvhtree[0] == NULL) { - sod->bvhtree[0] = BKE_bvhtree_from_mesh_get(&dummy_treedata, me, BVHTREE_FROM_LOOSEEDGES, 2); - sod->has_loose_edge = sod->bvhtree[0] != NULL; - sod->cached[0] = dummy_treedata.cached; - - if (sod->has_loose_edge) { - BLI_assert(treedata->vert_allocated == false); - treedata->vert = dummy_treedata.vert; - treedata->vert_allocated = dummy_treedata.vert_allocated; - - BLI_assert(treedata->edge_allocated == false); - treedata->edge = dummy_treedata.edge; - treedata->edge_allocated = dummy_treedata.edge_allocated; + sod->bvhtree[0] = BKE_bvhtree_from_mesh_get( + &treedata_tmp, me_eval, BVHTREE_FROM_LOOSEEDGES, 2); + if (sod->bvhtree[0] == NULL) { + sod->has_loose_edge = false; } + sod->cached[0] = treedata_tmp.cached; + BLI_assert(!ELEM(true, + treedata_tmp.vert_allocated, + treedata_tmp.edge_allocated, + treedata_tmp.face_allocated, + treedata_tmp.loop_allocated, + treedata_tmp.looptri_allocated)); } + if (snapdata->snap_to_flag & SCE_SNAP_MODE_VERTEX) { if (sod->has_loose_vert && sod->bvhtree[1] == NULL) { - sod->bvhtree[1] = BKE_bvhtree_from_mesh_get(&dummy_treedata, me, BVHTREE_FROM_LOOSEVERTS, 2); - sod->has_loose_vert = sod->bvhtree[1] != NULL; - sod->cached[1] = dummy_treedata.cached; - - if (sod->has_loose_vert) { - BLI_assert(treedata->vert_allocated == false); - treedata->vert = dummy_treedata.vert; - treedata->vert_allocated = dummy_treedata.vert_allocated; + sod->bvhtree[1] = BKE_bvhtree_from_mesh_get( + &treedata_tmp, me_eval, BVHTREE_FROM_LOOSEVERTS, 2); + if (sod->bvhtree[1] == NULL) { + sod->has_loose_vert = false; } + sod->cached[1] = treedata_tmp.cached; + BLI_assert(!ELEM(true, + treedata_tmp.vert_allocated, + treedata_tmp.edge_allocated, + treedata_tmp.face_allocated, + treedata_tmp.loop_allocated, + treedata_tmp.looptri_allocated)); } } else { @@ -2313,33 +2337,9 @@ static short snapMesh(SnapObjectContext *sctx, sod->has_loose_vert = false; } - /* Update pointers. */ - if (treedata->vert_allocated == false) { - treedata->vert = me->mvert; /* CustomData_get_layer(&me->vdata, CD_MVERT);? */ - } - if (treedata->tree || sod->bvhtree[0]) { - if (treedata->edge_allocated == false) { - /* If raycast has been executed before, `treedata->edge` can be NULL. */ - treedata->edge = me->medge; /* CustomData_get_layer(&me->edata, CD_MEDGE);? */ - } - if (treedata->loop && treedata->loop_allocated == false) { - treedata->loop = me->mloop; /* CustomData_get_layer(&me->edata, CD_MLOOP);? */ - } - if (treedata->looptri && treedata->looptri_allocated == false) { - treedata->looptri = BKE_mesh_runtime_looptri_ensure(me); - } - } - - Nearest2dUserData nearest2d = { - .userdata = treedata, - .get_vert_co = (Nearest2DGetVertCoCallback)cb_mvert_co_get, - .get_edge_verts_index = (Nearest2DGetEdgeVertsCallback)cb_medge_verts_get, - .get_tri_verts_index = (Nearest2DGetTriVertsCallback)cb_mlooptri_verts_get, - .get_tri_edges_index = (Nearest2DGetTriEdgesCallback)cb_mlooptri_edges_get, - .copy_vert_no = (Nearest2DCopyVertNoCallback)cb_mvert_no_copy, - .is_persp = snapdata->view_proj == VIEW_PROJ_PERSP, - .use_backface_culling = use_backface_culling, - }; + Nearest2dUserData nearest2d; + nearest2d_data_init( + sod, snapdata->view_proj == VIEW_PROJ_PERSP, use_backface_culling, &nearest2d); BVHTreeNearest nearest = { .index = -1, @@ -2455,7 +2455,7 @@ static short snapMesh(SnapObjectContext *sctx, static short snapEditMesh(SnapObjectContext *sctx, SnapData *snapdata, - Object *ob, + Object *ob_eval, BMEditMesh *em, const float obmat[4][4], bool use_backface_culling, @@ -2484,7 +2484,7 @@ static short snapEditMesh(SnapObjectContext *sctx, float dist_px_sq = square_f(*dist_px); - SnapObjectData *sod = snap_object_data_editmesh_get(sctx, ob, em); + SnapObjectData *sod = snap_object_data_editmesh_get(sctx, ob_eval, em); /* Test BoundBox */ @@ -2558,14 +2558,9 @@ static short snapEditMesh(SnapObjectContext *sctx, } } - Nearest2dUserData nearest2d = { - .userdata = em, - .get_vert_co = (Nearest2DGetVertCoCallback)cb_bvert_co_get, - .get_edge_verts_index = (Nearest2DGetEdgeVertsCallback)cb_bedge_verts_get, - .copy_vert_no = (Nearest2DCopyVertNoCallback)cb_bvert_no_copy, - .is_persp = snapdata->view_proj == VIEW_PROJ_PERSP, - .use_backface_culling = use_backface_culling, - }; + Nearest2dUserData nearest2d; + nearest2d_data_init( + sod, snapdata->view_proj == VIEW_PROJ_PERSP, use_backface_culling, &nearest2d); BVHTreeNearest nearest = { .index = -1, @@ -2659,9 +2654,9 @@ struct SnapObjUserData { * \note Duplicate args here are documented at #snapObjectsRay */ static void snap_obj_fn(SnapObjectContext *sctx, - Object *ob, + Object *ob_eval, float obmat[4][4], - bool use_obedit, + eSnapEditType edit_mode_type, bool use_backface_culling, bool UNUSED(is_object_active), void *data) @@ -2669,41 +2664,36 @@ static void snap_obj_fn(SnapObjectContext *sctx, struct SnapObjUserData *dt = data; short retval = 0; - switch (ob->type) { + switch (ob_eval->type) { case OB_MESH: { - Mesh *me = ob->data; - if (BKE_object_is_in_editmode(ob)) { - if (use_obedit || editmesh_eval_final_is_bmesh(me->edit_mesh)) { - /* Operators only update the editmesh looptris of the original mesh. */ - BMEditMesh *em_orig = BKE_editmesh_from_object(DEG_get_original_object(ob)); - retval = snapEditMesh(sctx, - dt->snapdata, - ob, - em_orig, - obmat, - use_backface_culling, - dt->dist_px, - dt->r_loc, - dt->r_no, - dt->r_index); - break; - } - - BMEditMesh *em = BKE_editmesh_from_object(ob); - if (em->mesh_eval_final) { - me = em->mesh_eval_final; - } + bool use_hide; + Mesh *me_eval = mesh_for_snap(ob_eval, edit_mode_type, &use_hide); + if (me_eval == NULL) { + /* Operators only update the editmesh looptris of the original mesh. */ + BMEditMesh *em_orig = BKE_editmesh_from_object(DEG_get_original_object(ob_eval)); + retval = snapEditMesh(sctx, + dt->snapdata, + ob_eval, + em_orig, + obmat, + use_backface_culling, + dt->dist_px, + dt->r_loc, + dt->r_no, + dt->r_index); + break; } - else if (ob->dt == OB_BOUNDBOX) { + if (ob_eval->dt == OB_BOUNDBOX) { /* Do not snap to objects that are in bounding box display mode */ return; } retval = snapMesh(sctx, dt->snapdata, - ob, - me, + ob_eval, + me_eval, obmat, + use_hide, use_backface_culling, dt->dist_px, dt->r_loc, @@ -2713,21 +2703,28 @@ static void snap_obj_fn(SnapObjectContext *sctx, } case OB_ARMATURE: retval = snapArmature( - dt->snapdata, ob, obmat, use_obedit, dt->dist_px, dt->r_loc, dt->r_no, dt->r_index); + dt->snapdata, ob_eval, obmat, dt->dist_px, dt->r_loc, dt->r_no, dt->r_index); break; case OB_CURVE: - retval = snapCurve( - dt->snapdata, ob, obmat, use_obedit, dt->dist_px, dt->r_loc, dt->r_no, dt->r_index); + retval = snapCurve(dt->snapdata, + ob_eval, + obmat, + edit_mode_type == SNAP_GEOM_EDIT, + dt->dist_px, + dt->r_loc, + dt->r_no, + dt->r_index); break; /* Use ATTR_FALLTHROUGH if we want to snap to the generated mesh. */ case OB_SURF: case OB_FONT: { - Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob); + Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval); if (mesh_eval) { retval |= snapMesh(sctx, dt->snapdata, - ob, + ob_eval, mesh_eval, obmat, + false, use_backface_culling, dt->dist_px, dt->r_loc, @@ -2740,17 +2737,17 @@ static void snap_obj_fn(SnapObjectContext *sctx, case OB_GPENCIL: case OB_LAMP: retval = snap_object_center( - dt->snapdata, ob, obmat, dt->dist_px, dt->r_loc, dt->r_no, dt->r_index); + dt->snapdata, ob_eval, obmat, dt->dist_px, dt->r_loc, dt->r_no, dt->r_index); break; case OB_CAMERA: retval = snapCamera( - sctx, dt->snapdata, ob, obmat, dt->dist_px, dt->r_loc, dt->r_no, dt->r_index); + sctx, dt->snapdata, ob_eval, obmat, dt->dist_px, dt->r_loc, dt->r_no, dt->r_index); break; } if (retval) { if (dt->r_ob) { - *dt->r_ob = ob; + *dt->r_ob = ob_eval; } if (dt->r_obmat) { copy_m4_m4(dt->r_obmat, obmat); @@ -3023,7 +3020,7 @@ static short transform_snap_context_project_view3d_mixed_impl( short retval = 0; bool has_hit = false; - Object *ob = NULL; + Object *ob_eval = NULL; float loc[3]; /* Not all snapping callbacks set the normal, * initialize this since any hit copies both the `loc` and `no`. */ @@ -3060,7 +3057,7 @@ static short transform_snap_context_project_view3d_mixed_impl( loc, no, &index, - &ob, + &ob_eval, obmat, NULL); @@ -3072,7 +3069,7 @@ static short transform_snap_context_project_view3d_mixed_impl( copy_v3_v3(r_no, no); } if (r_ob) { - *r_ob = ob; + *r_ob = ob_eval; } if (r_obmat) { copy_m4_m4(r_obmat, obmat); @@ -3108,9 +3105,10 @@ static short transform_snap_context_project_view3d_mixed_impl( snapdata.has_occlusion_plane = false; /* By convention we only snap to the original elements of a curve. */ - if (has_hit && ob->type != OB_CURVE) { + if (has_hit && ob_eval->type != OB_CURVE) { /* Compute the new clip_pane but do not add it yet. */ float new_clipplane[4]; + BLI_ASSERT_UNIT_V3(no); plane_from_point_normal_v3(new_clipplane, loc, no); if (dot_v3v3(snapdata.clip_plane[0], new_clipplane) > 0.0f) { /* The plane is facing the wrong direction. */ @@ -3121,8 +3119,15 @@ static short transform_snap_context_project_view3d_mixed_impl( new_clipplane[3] += 0.01f; /* Try to snap only to the polygon. */ - elem_test = snap_mesh_polygon( - sctx, &snapdata, ob, obmat, params->use_backface_culling, &dist_px_tmp, loc, no, &index); + elem_test = snap_mesh_polygon(sctx, + &snapdata, + ob_eval, + obmat, + params->use_backface_culling, + &dist_px_tmp, + loc, + no, + &index); if (elem_test) { elem = elem_test; } @@ -3137,7 +3142,7 @@ static short transform_snap_context_project_view3d_mixed_impl( } elem_test = snapObjectsRay( - sctx, depsgraph, &snapdata, params, &dist_px_tmp, loc, no, &index, &ob, obmat); + sctx, depsgraph, &snapdata, params, &dist_px_tmp, loc, no, &index, &ob_eval, obmat); if (elem_test) { elem = elem_test; } @@ -3148,7 +3153,7 @@ static short transform_snap_context_project_view3d_mixed_impl( snapdata.snap_to_flag = snap_to_flag; elem = snap_mesh_edge_verts_mixed(sctx, &snapdata, - ob, + ob_eval, obmat, *dist_px, prev_co, @@ -3167,7 +3172,7 @@ static short transform_snap_context_project_view3d_mixed_impl( copy_v3_v3(r_no, no); } if (r_ob) { - *r_ob = ob; + *r_ob = ob_eval; } if (r_obmat) { copy_m4_m4(r_obmat, obmat); diff --git a/source/blender/editors/util/ed_transverts.c b/source/blender/editors/util/ed_transverts.c index ad45686dc75..c1d0dcdb095 100644 --- a/source/blender/editors/util/ed_transverts.c +++ b/source/blender/editors/util/ed_transverts.c @@ -54,7 +54,7 @@ void ED_transverts_update_obedit(TransVertStore *tvs, Object *obedit) const int mode = tvs->mode; BLI_assert(ED_transverts_check_obedit(obedit) == true); - DEG_id_tag_update(obedit->data, 0); + DEG_id_tag_update(obedit->data, ID_RECALC_GEOMETRY); if (obedit->type == OB_MESH) { BMEditMesh *em = BKE_editmesh_from_object(obedit); diff --git a/source/blender/functions/FN_cpp_type.hh b/source/blender/functions/FN_cpp_type.hh index 54ea0103fe5..cd1597a742c 100644 --- a/source/blender/functions/FN_cpp_type.hh +++ b/source/blender/functions/FN_cpp_type.hh @@ -666,7 +666,7 @@ class CPPType : NonCopyable, NonMovable { template<typename T> bool is() const { - return this == &CPPType::get<T>(); + return this == &CPPType::get<std::decay_t<T>>(); } }; diff --git a/source/blender/functions/FN_generic_pointer.hh b/source/blender/functions/FN_generic_pointer.hh index 2bd66daa7fe..f88ff09f916 100644 --- a/source/blender/functions/FN_generic_pointer.hh +++ b/source/blender/functions/FN_generic_pointer.hh @@ -66,6 +66,16 @@ class GMutablePointer { return type_ != nullptr && type_->is<T>(); } + template<typename T> T relocate_out() + { + BLI_assert(this->is_type<T>()); + T value; + type_->relocate_to_initialized(data_, &value); + data_ = nullptr; + type_ = nullptr; + return value; + } + void destruct() { BLI_assert(data_ != nullptr); diff --git a/source/blender/functions/FN_generic_span.hh b/source/blender/functions/FN_generic_span.hh index 31b67dd3d70..e2c49697ba9 100644 --- a/source/blender/functions/FN_generic_span.hh +++ b/source/blender/functions/FN_generic_span.hh @@ -30,7 +30,7 @@ namespace blender::fn { * A generic span. It behaves just like a blender::Span<T>, but the type is only known at run-time. */ class GSpan { - private: + protected: const CPPType *type_; const void *data_; int64_t size_; @@ -85,6 +85,14 @@ class GSpan { BLI_assert(type_->is<T>()); return Span<T>(static_cast<const T *>(data_), size_); } + + GSpan slice(const int64_t start, int64_t size) const + { + BLI_assert(start >= 0); + BLI_assert(size >= 0); + const int64_t new_size = std::max<int64_t>(0, std::min(size, size_ - start)); + return GSpan(*type_, POINTER_OFFSET(data_, type_->size() * start), new_size); + } }; /** @@ -92,7 +100,7 @@ class GSpan { * known at run-time. */ class GMutableSpan { - private: + protected: const CPPType *type_; void *data_; int64_t size_; @@ -153,6 +161,14 @@ class GMutableSpan { BLI_assert(type_->is<T>()); return MutableSpan<T>(static_cast<T *>(data_), size_); } + + GMutableSpan slice(const int64_t start, int64_t size) const + { + BLI_assert(start >= 0); + BLI_assert(size >= 0); + const int64_t new_size = std::max<int64_t>(0, std::min(size, size_ - start)); + return GMutableSpan(*type_, POINTER_OFFSET(data_, type_->size() * start), new_size); + } }; } // namespace blender::fn diff --git a/source/blender/functions/FN_generic_value_map.hh b/source/blender/functions/FN_generic_value_map.hh index 68cb945f1af..4e7fe298874 100644 --- a/source/blender/functions/FN_generic_value_map.hh +++ b/source/blender/functions/FN_generic_value_map.hh @@ -93,6 +93,11 @@ template<typename Key> class GValueMap { return values_.pop_as(key); } + template<typename ForwardKey> GPointer lookup(const ForwardKey &key) const + { + return values_.lookup_as(key); + } + /* Remove the value for the given name from the container and remove it. */ template<typename T, typename ForwardKey> T extract(const ForwardKey &key) { diff --git a/source/blender/functions/FN_generic_vector_array.hh b/source/blender/functions/FN_generic_vector_array.hh index ae6eb8a614f..b02ed471875 100644 --- a/source/blender/functions/FN_generic_vector_array.hh +++ b/source/blender/functions/FN_generic_vector_array.hh @@ -123,7 +123,7 @@ template<typename T> class GVectorArray_TypedMutableRef { void extend(const int64_t index, const VArray<T> &values) { - GVArrayForVArray<T> array{values}; + GVArray_For_VArray<T> array{values}; this->extend(index, array); } @@ -134,12 +134,12 @@ template<typename T> class GVectorArray_TypedMutableRef { }; /* A generic virtual vector array implementation for a `GVectorArray`. */ -class GVVectorArrayForGVectorArray : public GVVectorArray { +class GVVectorArray_For_GVectorArray : public GVVectorArray { private: const GVectorArray &vector_array_; public: - GVVectorArrayForGVectorArray(const GVectorArray &vector_array) + GVVectorArray_For_GVectorArray(const GVectorArray &vector_array) : GVVectorArray(vector_array.type(), vector_array.size()), vector_array_(vector_array) { } diff --git a/source/blender/functions/FN_generic_virtual_array.hh b/source/blender/functions/FN_generic_virtual_array.hh index c6230730a8d..d530d10b3c8 100644 --- a/source/blender/functions/FN_generic_virtual_array.hh +++ b/source/blender/functions/FN_generic_virtual_array.hh @@ -23,12 +23,23 @@ * the data type is only known at runtime. */ +#include <optional> + #include "BLI_virtual_array.hh" #include "FN_generic_span.hh" namespace blender::fn { +template<typename T> class GVArray_Typed; +template<typename T> class GVMutableArray_Typed; + +class GVArray; +class GVMutableArray; + +using GVArrayPtr = std::unique_ptr<GVArray>; +using GVMutableArrayPtr = std::unique_ptr<GVMutableArray>; + /* A generically typed version of `VArray<T>`. */ class GVArray { protected: @@ -86,13 +97,13 @@ class GVArray { /* Returns the internally used span of the virtual array. This invokes undefined behavior is the * virtual array is not stored as a span internally. */ - GSpan get_span() const + GSpan get_internal_span() const { BLI_assert(this->is_span()); if (size_ == 0) { return GSpan(*type_); } - return this->get_span_impl(); + return this->get_internal_span_impl(); } /* Returns true when the virtual array returns the same value for every index. */ @@ -107,57 +118,151 @@ class GVArray { /* Copies the value that is used for every element into `r_value`, which is expected to point to * initialized memory. This invokes undefined behavior if the virtual array would not return the * same value for every index. */ - void get_single(void *r_value) const + void get_internal_single(void *r_value) const { BLI_assert(this->is_single()); if (size_ == 1) { this->get(0, r_value); + return; } - this->get_single_impl(r_value); + this->get_internal_single_impl(r_value); } - /* Same as `get_single`, but `r_value` points to initialized memory. */ + /* Same as `get_internal_single`, but `r_value` points to initialized memory. */ void get_single_to_uninitialized(void *r_value) const { type_->construct_default(r_value); - this->get_single(r_value); + this->get_internal_single(r_value); } + void materialize(void *dst) const; + void materialize(const IndexMask mask, void *dst) const; + + void materialize_to_uninitialized(void *dst) const; void materialize_to_uninitialized(const IndexMask mask, void *dst) const; + template<typename T> const VArray<T> *try_get_internal_varray() const + { + BLI_assert(type_->is<T>()); + return (const VArray<T> *)this->try_get_internal_varray_impl(); + } + + /* Create a typed virtual array for this generic virtual array. */ + template<typename T> GVArray_Typed<T> typed() const + { + return GVArray_Typed<T>(*this); + } + + GVArrayPtr shallow_copy() const; + protected: virtual void get_impl(const int64_t index, void *r_value) const; virtual void get_to_uninitialized_impl(const int64_t index, void *r_value) const = 0; virtual bool is_span_impl() const; - virtual GSpan get_span_impl() const; + virtual GSpan get_internal_span_impl() const; virtual bool is_single_impl() const; - virtual void get_single_impl(void *UNUSED(r_value)) const; + virtual void get_internal_single_impl(void *UNUSED(r_value)) const; + + virtual void materialize_impl(const IndexMask mask, void *dst) const; + virtual void materialize_to_uninitialized_impl(const IndexMask mask, void *dst) const; + + virtual const void *try_get_internal_varray_impl() const; +}; + +/* Similar to GVArray, but supports changing the elements in the virtual array. */ +class GVMutableArray : public GVArray { + public: + GVMutableArray(const CPPType &type, const int64_t size) : GVArray(type, size) + { + } + + void set_by_copy(const int64_t index, const void *value) + { + BLI_assert(index >= 0); + BLI_assert(index < size_); + this->set_by_copy_impl(index, value); + } + + void set_by_move(const int64_t index, void *value) + { + BLI_assert(index >= 0); + BLI_assert(index < size_); + this->set_by_move_impl(index, value); + } + + void set_by_relocate(const int64_t index, void *value) + { + BLI_assert(index >= 0); + BLI_assert(index < size_); + this->set_by_relocate_impl(index, value); + } + + GMutableSpan get_internal_span() + { + BLI_assert(this->is_span()); + GSpan span = static_cast<const GVArray *>(this)->get_internal_span(); + return GMutableSpan(span.type(), const_cast<void *>(span.data()), span.size()); + } + + template<typename T> VMutableArray<T> *try_get_internal_mutable_varray() + { + BLI_assert(type_->is<T>()); + return (VMutableArray<T> *)this->try_get_internal_mutable_varray_impl(); + } + + /* Create a typed virtual array for this generic virtual array. */ + template<typename T> GVMutableArray_Typed<T> typed() + { + return GVMutableArray_Typed<T>(*this); + } + + void fill(const void *value); + + /* Copy the values from the source buffer to all elements in the virtual array. */ + void set_all(const void *src) + { + this->set_all_impl(src); + } + + protected: + virtual void set_by_copy_impl(const int64_t index, const void *value); + virtual void set_by_relocate_impl(const int64_t index, void *value); + virtual void set_by_move_impl(const int64_t index, void *value) = 0; + + virtual void set_all_impl(const void *src); + + virtual void *try_get_internal_mutable_varray_impl(); }; -class GVArrayForGSpan : public GVArray { +class GVArray_For_GSpan : public GVArray { protected: - const void *data_; + const void *data_ = nullptr; const int64_t element_size_; public: - GVArrayForGSpan(const GSpan span) + GVArray_For_GSpan(const GSpan span) : GVArray(span.type(), span.size()), data_(span.data()), element_size_(span.type().size()) { } protected: + GVArray_For_GSpan(const CPPType &type, const int64_t size) + : GVArray(type, size), element_size_(type.size()) + { + } + void get_impl(const int64_t index, void *r_value) const override; void get_to_uninitialized_impl(const int64_t index, void *r_value) const override; bool is_span_impl() const override; - GSpan get_span_impl() const override; + GSpan get_internal_span_impl() const override; }; -class GVArrayForEmpty : public GVArray { +class GVArray_For_Empty : public GVArray { public: - GVArrayForEmpty(const CPPType &type) : GVArray(type, 0) + GVArray_For_Empty(const CPPType &type) : GVArray(type, 0) { } @@ -168,108 +273,642 @@ class GVArrayForEmpty : public GVArray { } }; -class GVArrayForSingleValueRef : public GVArray { - private: - const void *value_; +class GVMutableArray_For_GMutableSpan : public GVMutableArray { + protected: + void *data_ = nullptr; + const int64_t element_size_; public: - GVArrayForSingleValueRef(const CPPType &type, const int64_t size, const void *value) + GVMutableArray_For_GMutableSpan(const GMutableSpan span) + : GVMutableArray(span.type(), span.size()), + data_(span.data()), + element_size_(span.type().size()) + { + } + + protected: + GVMutableArray_For_GMutableSpan(const CPPType &type, const int64_t size) + : GVMutableArray(type, size), element_size_(type.size()) + { + } + + void get_impl(const int64_t index, void *r_value) const override; + void get_to_uninitialized_impl(const int64_t index, void *r_value) const override; + + void set_by_copy_impl(const int64_t index, const void *value) override; + void set_by_move_impl(const int64_t index, void *value) override; + void set_by_relocate_impl(const int64_t index, void *value) override; + + bool is_span_impl() const override; + GSpan get_internal_span_impl() const override; +}; + +/* Generic virtual array where each element has the same value. The value is not owned. */ +class GVArray_For_SingleValueRef : public GVArray { + protected: + const void *value_ = nullptr; + + public: + GVArray_For_SingleValueRef(const CPPType &type, const int64_t size, const void *value) : GVArray(type, size), value_(value) { } protected: + GVArray_For_SingleValueRef(const CPPType &type, const int64_t size) : GVArray(type, size) + { + } + void get_impl(const int64_t index, void *r_value) const override; void get_to_uninitialized_impl(const int64_t index, void *r_value) const override; bool is_span_impl() const override; - GSpan get_span_impl() const override; + GSpan get_internal_span_impl() const override; bool is_single_impl() const override; - void get_single_impl(void *r_value) const override; + void get_internal_single_impl(void *r_value) const override; }; -template<typename T> class GVArrayForVArray : public GVArray { - private: - const VArray<T> &array_; +/* Same as GVArray_For_SingleValueRef, but the value is owned. */ +class GVArray_For_SingleValue : public GVArray_For_SingleValueRef { + public: + GVArray_For_SingleValue(const CPPType &type, const int64_t size, const void *value); + ~GVArray_For_SingleValue(); +}; + +/* Used to convert a typed virtual array into a generic one. */ +template<typename T> class GVArray_For_VArray : public GVArray { + protected: + const VArray<T> *varray_ = nullptr; public: - GVArrayForVArray(const VArray<T> &array) - : GVArray(CPPType::get<T>(), array.size()), array_(array) + GVArray_For_VArray(const VArray<T> &varray) + : GVArray(CPPType::get<T>(), varray.size()), varray_(&varray) { } protected: + GVArray_For_VArray(const int64_t size) : GVArray(CPPType::get<T>(), size) + { + } + void get_impl(const int64_t index, void *r_value) const override { - *(T *)r_value = array_.get(index); + *(T *)r_value = varray_->get(index); } void get_to_uninitialized_impl(const int64_t index, void *r_value) const override { - new (r_value) T(array_.get(index)); + new (r_value) T(varray_->get(index)); } bool is_span_impl() const override { - return array_.is_span(); + return varray_->is_span(); } - GSpan get_span_impl() const override + GSpan get_internal_span_impl() const override { - return GSpan(array_.get_span()); + return GSpan(varray_->get_internal_span()); } bool is_single_impl() const override { - return array_.is_single(); + return varray_->is_single(); } - void get_single_impl(void *r_value) const override + void get_internal_single_impl(void *r_value) const override { - *(T *)r_value = array_.get_single(); + *(T *)r_value = varray_->get_internal_single(); + } + + void materialize_impl(const IndexMask mask, void *dst) const override + { + varray_->materialize(mask, MutableSpan((T *)dst, mask.min_array_size())); + } + + void materialize_to_uninitialized_impl(const IndexMask mask, void *dst) const override + { + varray_->materialize_to_uninitialized(mask, MutableSpan((T *)dst, mask.min_array_size())); + } + + const void *try_get_internal_varray_impl() const override + { + return varray_; } }; -template<typename T> class VArrayForGVArray : public VArray<T> { - private: - const GVArray &array_; +/* Used to convert any generic virtual array into a typed one. */ +template<typename T> class VArray_For_GVArray : public VArray<T> { + protected: + const GVArray *varray_ = nullptr; public: - VArrayForGVArray(const GVArray &array) : VArray<T>(array.size()), array_(array) + VArray_For_GVArray(const GVArray &varray) : VArray<T>(varray.size()), varray_(&varray) { - BLI_assert(array_.type().template is<T>()); + BLI_assert(varray_->type().template is<T>()); } protected: + VArray_For_GVArray(const int64_t size) : VArray<T>(size) + { + } + T get_impl(const int64_t index) const override { T value; - array_.get(index, &value); + varray_->get(index, &value); return value; } bool is_span_impl() const override { - return array_.is_span(); + return varray_->is_span(); } - Span<T> get_span_impl() const override + Span<T> get_internal_span_impl() const override { - return array_.get_span().template typed<T>(); + return varray_->get_internal_span().template typed<T>(); } bool is_single_impl() const override { - return array_.is_single(); + return varray_->is_single(); } - T get_single_impl() const override + T get_internal_single_impl() const override { T value; - array_.get_single(&value); + varray_->get_internal_single(&value); return value; } }; +/* Used to convert an generic mutable virtual array into a typed one. */ +template<typename T> class VMutableArray_For_GVMutableArray : public VMutableArray<T> { + protected: + GVMutableArray *varray_ = nullptr; + + public: + VMutableArray_For_GVMutableArray(GVMutableArray &varray) + : VMutableArray<T>(varray.size()), varray_(&varray) + { + BLI_assert(varray.type().template is<T>()); + } + + VMutableArray_For_GVMutableArray(const int64_t size) : VMutableArray<T>(size) + { + } + + private: + T get_impl(const int64_t index) const override + { + T value; + varray_->get(index, &value); + return value; + } + + void set_impl(const int64_t index, T value) override + { + varray_->set_by_relocate(index, &value); + } + + bool is_span_impl() const override + { + return varray_->is_span(); + } + + Span<T> get_internal_span_impl() const override + { + return varray_->get_internal_span().template typed<T>(); + } + + bool is_single_impl() const override + { + return varray_->is_single(); + } + + T get_internal_single_impl() const override + { + T value; + varray_->get_internal_single(&value); + return value; + } +}; + +/* Used to convert any typed virtual mutable array into a generic one. */ +template<typename T> class GVMutableArray_For_VMutableArray : public GVMutableArray { + protected: + VMutableArray<T> *varray_ = nullptr; + + public: + GVMutableArray_For_VMutableArray(VMutableArray<T> &varray) + : GVMutableArray(CPPType::get<T>(), varray.size()), varray_(&varray) + { + } + + protected: + GVMutableArray_For_VMutableArray(const int64_t size) : GVMutableArray(CPPType::get<T>(), size) + { + } + + void get_impl(const int64_t index, void *r_value) const override + { + *(T *)r_value = varray_->get(index); + } + + void get_to_uninitialized_impl(const int64_t index, void *r_value) const override + { + new (r_value) T(varray_->get(index)); + } + + bool is_span_impl() const override + { + return varray_->is_span(); + } + + GSpan get_internal_span_impl() const override + { + Span<T> span = varray_->get_internal_span(); + return span; + } + + bool is_single_impl() const override + { + return varray_->is_single(); + } + + void get_internal_single_impl(void *r_value) const override + { + *(T *)r_value = varray_->get_internal_single(); + } + + void set_by_copy_impl(const int64_t index, const void *value) override + { + const T &value_ = *(const T *)value; + varray_->set(index, value_); + } + + void set_by_relocate_impl(const int64_t index, void *value) override + { + T &value_ = *(T *)value; + varray_->set(index, std::move(value_)); + value_.~T(); + } + + void set_by_move_impl(const int64_t index, void *value) override + { + T &value_ = *(T *)value; + varray_->set(index, std::move(value_)); + } + + void set_all_impl(const void *src) override + { + varray_->set_all(Span((T *)src, size_)); + } + + void materialize_impl(const IndexMask mask, void *dst) const override + { + varray_->materialize(mask, MutableSpan((T *)dst, mask.min_array_size())); + } + + void materialize_to_uninitialized_impl(const IndexMask mask, void *dst) const override + { + varray_->materialize_to_uninitialized(mask, MutableSpan((T *)dst, mask.min_array_size())); + } + + const void *try_get_internal_varray_impl() const override + { + return (const VArray<T> *)varray_; + } + + void *try_get_internal_mutable_varray_impl() override + { + return varray_; + } +}; + +/* A generic version of VArray_Span. */ +class GVArray_GSpan : public GSpan { + private: + const GVArray &varray_; + void *owned_data_ = nullptr; + + public: + GVArray_GSpan(const GVArray &varray); + ~GVArray_GSpan(); +}; + +/* A generic version of VMutableArray_Span. */ +class GVMutableArray_GSpan : public GMutableSpan { + private: + GVMutableArray &varray_; + void *owned_data_ = nullptr; + bool save_has_been_called_ = false; + bool show_not_saved_warning_ = true; + + public: + GVMutableArray_GSpan(GVMutableArray &varray, bool copy_values_to_span = true); + ~GVMutableArray_GSpan(); + + void save(); + void disable_not_applied_warning(); +}; + +/* Similar to GVArray_GSpan, but the resulting span is typed. */ +template<typename T> class GVArray_Span : public Span<T> { + private: + GVArray_GSpan varray_gspan_; + + public: + GVArray_Span(const GVArray &varray) : varray_gspan_(varray) + { + BLI_assert(varray.type().is<T>()); + this->data_ = (const T *)varray_gspan_.data(); + this->size_ = varray_gspan_.size(); + } +}; + +template<typename T> class GVArray_For_OwnedVArray : public GVArray_For_VArray<T> { + private: + VArrayPtr<T> owned_varray_; + + public: + /* Takes ownership of varray and passes a reference to the base class. */ + GVArray_For_OwnedVArray(VArrayPtr<T> varray) + : GVArray_For_VArray<T>(*varray), owned_varray_(std::move(varray)) + { + } +}; + +template<typename T> class VArray_For_OwnedGVArray : public VArray_For_GVArray<T> { + private: + GVArrayPtr owned_varray_; + + public: + /* Takes ownership of varray and passes a reference to the base class. */ + VArray_For_OwnedGVArray(GVArrayPtr varray) + : VArray_For_GVArray<T>(*varray), owned_varray_(std::move(varray)) + { + } +}; + +template<typename T> +class GVMutableArray_For_OwnedVMutableArray : public GVMutableArray_For_VMutableArray<T> { + private: + VMutableArrayPtr<T> owned_varray_; + + public: + /* Takes ownership of varray and passes a reference to the base class. */ + GVMutableArray_For_OwnedVMutableArray(VMutableArrayPtr<T> varray) + : GVMutableArray_For_VMutableArray<T>(*varray), owned_varray_(std::move(varray)) + { + } +}; + +template<typename T> +class VMutableArray_For_OwnedGVMutableArray : public VMutableArray_For_GVMutableArray<T> { + private: + GVMutableArrayPtr owned_varray_; + + public: + /* Takes ownership of varray and passes a reference to the base class. */ + VMutableArray_For_OwnedGVMutableArray(GVMutableArrayPtr varray) + : VMutableArray_For_GVMutableArray<T>(*varray), owned_varray_(std::move(varray)) + { + } +}; + +/* Utility to embed a typed virtual array into a generic one. This avoids one allocation and give + * the compiler more opportunity to optimize the generic virtual array. */ +template<typename T, typename VArrayT> +class GVArray_For_EmbeddedVArray : public GVArray_For_VArray<T> { + private: + VArrayT embedded_varray_; + + public: + template<typename... Args> + GVArray_For_EmbeddedVArray(const int64_t size, Args &&... args) + : GVArray_For_VArray<T>(size), embedded_varray_(std::forward<Args>(args)...) + { + this->varray_ = &embedded_varray_; + } +}; + +/* Same as GVArray_For_EmbeddedVArray, but for mutable virtual arrays. */ +template<typename T, typename VMutableArrayT> +class GVMutableArray_For_EmbeddedVMutableArray : public GVMutableArray_For_VMutableArray<T> { + private: + VMutableArrayT embedded_varray_; + + public: + template<typename... Args> + GVMutableArray_For_EmbeddedVMutableArray(const int64_t size, Args &&... args) + : GVMutableArray_For_VMutableArray<T>(size), embedded_varray_(std::forward<Args>(args)...) + { + this->varray_ = &embedded_varray_; + } +}; + +/* Same as VArray_For_ArrayContainer, but for a generic virtual array. */ +template<typename Container, typename T = typename Container::value_type> +class GVArray_For_ArrayContainer + : public GVArray_For_EmbeddedVArray<T, VArray_For_ArrayContainer<Container, T>> { + public: + GVArray_For_ArrayContainer(Container container) + : GVArray_For_EmbeddedVArray<T, VArray_For_ArrayContainer<Container, T>>( + container.size(), std::move(container)) + { + } +}; + +/* Same as VArray_For_DerivedSpan, but for a generic virtual array. */ +template<typename StructT, typename ElemT, ElemT (*GetFunc)(const StructT &)> +class GVArray_For_DerivedSpan + : public GVArray_For_EmbeddedVArray<ElemT, VArray_For_DerivedSpan<StructT, ElemT, GetFunc>> { + public: + GVArray_For_DerivedSpan(const Span<StructT> data) + : GVArray_For_EmbeddedVArray<ElemT, VArray_For_DerivedSpan<StructT, ElemT, GetFunc>>( + data.size(), data) + { + } +}; + +/* Same as VMutableArray_For_DerivedSpan, but for a generic virtual array. */ +template<typename StructT, + typename ElemT, + ElemT (*GetFunc)(const StructT &), + void (*SetFunc)(StructT &, ElemT)> +class GVMutableArray_For_DerivedSpan + : public GVMutableArray_For_EmbeddedVMutableArray< + ElemT, + VMutableArray_For_DerivedSpan<StructT, ElemT, GetFunc, SetFunc>> { + public: + GVMutableArray_For_DerivedSpan(const MutableSpan<StructT> data) + : GVMutableArray_For_EmbeddedVMutableArray< + ElemT, + VMutableArray_For_DerivedSpan<StructT, ElemT, GetFunc, SetFunc>>(data.size(), data) + { + } +}; + +/* Same as VArray_For_Span, but for a generic virtual array. */ +template<typename T> +class GVArray_For_Span : public GVArray_For_EmbeddedVArray<T, VArray_For_Span<T>> { + public: + GVArray_For_Span(const Span<T> data) + : GVArray_For_EmbeddedVArray<T, VArray_For_Span<T>>(data.size(), data) + { + } +}; + +/* Same as VMutableArray_For_MutableSpan, but for a generic virtual array. */ +template<typename T> +class GVMutableArray_For_MutableSpan + : public GVMutableArray_For_EmbeddedVMutableArray<T, VMutableArray_For_MutableSpan<T>> { + public: + GVMutableArray_For_MutableSpan(const MutableSpan<T> data) + : GVMutableArray_For_EmbeddedVMutableArray<T, VMutableArray_For_MutableSpan<T>>(data.size(), + data) + { + } +}; + +/** + * Utility class to create the "best" typed virtual array for a given generic virtual array. + * In most cases we don't just want to use VArray_For_GVArray, because it adds an additional + * indirection on element-access that can be avoided in many cases (e.g. when the virtual array is + * just a span or single value). + * + * This is not a virtual array itself, but is used to get a virtual array. + */ +template<typename T> class GVArray_Typed { + private: + const VArray<T> *varray_; + /* Of these optional virtual arrays, at most one is constructed at any time. */ + std::optional<VArray_For_Span<T>> varray_span_; + std::optional<VArray_For_Single<T>> varray_single_; + std::optional<VArray_For_GVArray<T>> varray_any_; + GVArrayPtr owned_gvarray_; + + public: + explicit GVArray_Typed(const GVArray &gvarray) + { + BLI_assert(gvarray.type().is<T>()); + if (gvarray.is_span()) { + const GSpan span = gvarray.get_internal_span(); + varray_span_.emplace(span.typed<T>()); + varray_ = &*varray_span_; + } + else if (gvarray.is_single()) { + T single_value; + gvarray.get_internal_single(&single_value); + varray_single_.emplace(single_value, gvarray.size()); + varray_ = &*varray_single_; + } + else if (const VArray<T> *internal_varray = gvarray.try_get_internal_varray<T>()) { + varray_ = internal_varray; + } + else { + varray_any_.emplace(gvarray); + varray_ = &*varray_any_; + } + } + + /* Same as the constructor above, but also takes ownership of the passed in virtual array. */ + explicit GVArray_Typed(GVArrayPtr gvarray) : GVArray_Typed(*gvarray) + { + owned_gvarray_ = std::move(gvarray); + } + + const VArray<T> &operator*() const + { + return *varray_; + } + + const VArray<T> *operator->() const + { + return varray_; + } + + /* Support implicit cast to the typed virtual array for convenience when `varray->typed<T>()` is + * used within an expression. */ + operator const VArray<T> &() const + { + return *varray_; + } + + T operator[](const int64_t index) const + { + return varray_->get(index); + } + + int64_t size() const + { + return varray_->size(); + } + + IndexRange index_range() const + { + return IndexRange(this->size()); + } +}; + +/* Same as GVArray_Typed, but for mutable virtual arrays. */ +template<typename T> class GVMutableArray_Typed { + private: + VMutableArray<T> *varray_; + std::optional<VMutableArray_For_MutableSpan<T>> varray_span_; + std::optional<VMutableArray_For_GVMutableArray<T>> varray_any_; + GVMutableArrayPtr owned_gvarray_; + + public: + explicit GVMutableArray_Typed(GVMutableArray &gvarray) + { + BLI_assert(gvarray.type().is<T>()); + if (gvarray.is_span()) { + const GMutableSpan span = gvarray.get_internal_span(); + varray_span_.emplace(span.typed<T>()); + varray_ = &*varray_span_; + } + else if (VMutableArray<T> *internal_varray = gvarray.try_get_internal_mutable_varray<T>()) { + varray_ = internal_varray; + } + else { + varray_any_.emplace(gvarray); + varray_ = &*varray_any_; + } + } + + explicit GVMutableArray_Typed(GVMutableArrayPtr gvarray) : GVMutableArray_Typed(*gvarray) + { + owned_gvarray_ = std::move(gvarray); + } + + VMutableArray<T> &operator*() + { + return *varray_; + } + + VMutableArray<T> *operator->() + { + return varray_; + } + + operator VMutableArray<T> &() + { + return *varray_; + } + + T operator[](const int64_t index) const + { + return varray_->get(index); + } + + int64_t size() const + { + return varray_->size(); + } +}; + } // namespace blender::fn diff --git a/source/blender/functions/FN_generic_virtual_vector_array.hh b/source/blender/functions/FN_generic_virtual_vector_array.hh index ef3f53b5c25..4155a55a801 100644 --- a/source/blender/functions/FN_generic_virtual_vector_array.hh +++ b/source/blender/functions/FN_generic_virtual_vector_array.hh @@ -100,13 +100,13 @@ class GVVectorArray { } }; -class GVArrayForGVVectorArrayIndex : public GVArray { +class GVArray_For_GVVectorArrayIndex : public GVArray { private: const GVVectorArray &vector_array_; const int64_t index_; public: - GVArrayForGVVectorArrayIndex(const GVVectorArray &vector_array, const int64_t index) + GVArray_For_GVVectorArrayIndex(const GVVectorArray &vector_array, const int64_t index) : GVArray(vector_array.type(), vector_array.get_vector_size(index)), vector_array_(vector_array), index_(index) @@ -118,12 +118,12 @@ class GVArrayForGVVectorArrayIndex : public GVArray { void get_to_uninitialized_impl(const int64_t index_in_vector, void *r_value) const override; }; -class GVVectorArrayForSingleGVArray : public GVVectorArray { +class GVVectorArray_For_SingleGVArray : public GVVectorArray { private: const GVArray &array_; public: - GVVectorArrayForSingleGVArray(const GVArray &array, const int64_t size) + GVVectorArray_For_SingleGVArray(const GVArray &array, const int64_t size) : GVVectorArray(array.type(), size), array_(array) { } @@ -137,12 +137,12 @@ class GVVectorArrayForSingleGVArray : public GVVectorArray { bool is_single_vector_impl() const override; }; -class GVVectorArrayForSingleGSpan : public GVVectorArray { +class GVVectorArray_For_SingleGSpan : public GVVectorArray { private: const GSpan span_; public: - GVVectorArrayForSingleGSpan(const GSpan span, const int64_t size) + GVVectorArray_For_SingleGSpan(const GSpan span, const int64_t size) : GVVectorArray(span.type(), size), span_(span) { } @@ -156,12 +156,12 @@ class GVVectorArrayForSingleGSpan : public GVVectorArray { bool is_single_vector_impl() const override; }; -template<typename T> class VVectorArrayForGVVectorArray : public VVectorArray<T> { +template<typename T> class VVectorArray_For_GVVectorArray : public VVectorArray<T> { private: const GVVectorArray &vector_array_; public: - VVectorArrayForGVVectorArray(const GVVectorArray &vector_array) + VVectorArray_For_GVVectorArray(const GVVectorArray &vector_array) : VVectorArray<T>(vector_array.size()), vector_array_(vector_array) { } diff --git a/source/blender/functions/FN_multi_function_params.hh b/source/blender/functions/FN_multi_function_params.hh index 72ebc0d9b94..e292d11def7 100644 --- a/source/blender/functions/FN_multi_function_params.hh +++ b/source/blender/functions/FN_multi_function_params.hh @@ -27,6 +27,7 @@ #include "BLI_resource_scope.hh" +#include "FN_generic_pointer.hh" #include "FN_generic_vector_array.hh" #include "FN_generic_virtual_vector_array.hh" #include "FN_multi_function_signature.hh" @@ -55,13 +56,19 @@ class MFParamsBuilder { template<typename T> void add_readonly_single_input(const T *value, StringRef expected_name = "") { - this->add_readonly_single_input(scope_.construct<GVArrayForSingleValueRef>( + this->add_readonly_single_input(scope_.construct<GVArray_For_SingleValueRef>( __func__, CPPType::get<T>(), min_array_size_, value), expected_name); } void add_readonly_single_input(const GSpan span, StringRef expected_name = "") { - this->add_readonly_single_input(scope_.construct<GVArrayForGSpan>(__func__, span), + this->add_readonly_single_input(scope_.construct<GVArray_For_GSpan>(__func__, span), + expected_name); + } + void add_readonly_single_input(GPointer value, StringRef expected_name = "") + { + this->add_readonly_single_input(scope_.construct<GVArray_For_SingleValueRef>( + __func__, *value.type(), min_array_size_, value.get()), expected_name); } void add_readonly_single_input(const GVArray &ref, StringRef expected_name = "") @@ -74,7 +81,7 @@ class MFParamsBuilder { void add_readonly_vector_input(const GVectorArray &vector_array, StringRef expected_name = "") { this->add_readonly_vector_input( - scope_.construct<GVVectorArrayForGVectorArray>(__func__, vector_array), expected_name); + scope_.construct<GVVectorArray_For_GVectorArray>(__func__, vector_array), expected_name); } void add_readonly_vector_input(const GVVectorArray &ref, StringRef expected_name = "") { @@ -177,7 +184,7 @@ class MFParams { template<typename T> const VArray<T> &readonly_single_input(int param_index, StringRef name = "") { const GVArray &array = this->readonly_single_input(param_index, name); - return builder_->scope_.construct<VArrayForGVArray<T>>(__func__, array); + return builder_->scope_.construct<VArray_For_GVArray<T>>(__func__, array); } const GVArray &readonly_single_input(int param_index, StringRef name = "") { @@ -202,7 +209,7 @@ class MFParams { const VVectorArray<T> &readonly_vector_input(int param_index, StringRef name = "") { const GVVectorArray &vector_array = this->readonly_vector_input(param_index, name); - return builder_->scope_.construct<VVectorArrayForGVVectorArray<T>>(__func__, vector_array); + return builder_->scope_.construct<VVectorArray_For_GVVectorArray<T>>(__func__, vector_array); } const GVVectorArray &readonly_vector_input(int param_index, StringRef name = "") { diff --git a/source/blender/functions/intern/cpp_types.cc b/source/blender/functions/intern/cpp_types.cc index 53c5def57e9..9c2c1621e23 100644 --- a/source/blender/functions/intern/cpp_types.cc +++ b/source/blender/functions/intern/cpp_types.cc @@ -34,8 +34,8 @@ MAKE_CPP_TYPE(int32, int32_t) MAKE_CPP_TYPE(uint32, uint32_t) MAKE_CPP_TYPE(uint8, uint8_t) -MAKE_CPP_TYPE(Color4f, blender::Color4f) -MAKE_CPP_TYPE(Color4b, blender::Color4b) +MAKE_CPP_TYPE(ColorGeometry4f, blender::ColorGeometry4f) +MAKE_CPP_TYPE(ColorGeometry4b, blender::ColorGeometry4b) MAKE_CPP_TYPE(string, std::string) diff --git a/source/blender/functions/intern/generic_vector_array.cc b/source/blender/functions/intern/generic_vector_array.cc index b3c5517cc43..3335b07e559 100644 --- a/source/blender/functions/intern/generic_vector_array.cc +++ b/source/blender/functions/intern/generic_vector_array.cc @@ -60,21 +60,21 @@ void GVectorArray::extend(const int64_t index, const GVArray &values) void GVectorArray::extend(const int64_t index, const GSpan values) { - GVArrayForGSpan varray{values}; + GVArray_For_GSpan varray{values}; this->extend(index, varray); } void GVectorArray::extend(IndexMask mask, const GVVectorArray &values) { for (const int i : mask) { - GVArrayForGVVectorArrayIndex array{values, i}; + GVArray_For_GVVectorArrayIndex array{values, i}; this->extend(i, array); } } void GVectorArray::extend(IndexMask mask, const GVectorArray &values) { - GVVectorArrayForGVectorArray virtual_values{values}; + GVVectorArray_For_GVectorArray virtual_values{values}; this->extend(mask, virtual_values); } diff --git a/source/blender/functions/intern/generic_virtual_array.cc b/source/blender/functions/intern/generic_virtual_array.cc index 9380eb257b2..87dae06ccdc 100644 --- a/source/blender/functions/intern/generic_virtual_array.cc +++ b/source/blender/functions/intern/generic_virtual_array.cc @@ -18,8 +18,72 @@ namespace blender::fn { +/* -------------------------------------------------------------------- + * GVArray_For_ShallowCopy. + */ + +class GVArray_For_ShallowCopy : public GVArray { + private: + const GVArray &varray_; + + public: + GVArray_For_ShallowCopy(const GVArray &varray) + : GVArray(varray.type(), varray.size()), varray_(varray) + { + } + + private: + void get_impl(const int64_t index, void *r_value) const override + { + varray_.get(index, r_value); + } + + void get_to_uninitialized_impl(const int64_t index, void *r_value) const override + { + varray_.get_to_uninitialized(index, r_value); + } + + void materialize_to_uninitialized_impl(const IndexMask mask, void *dst) const override + { + varray_.materialize_to_uninitialized(mask, dst); + } +}; + +/* -------------------------------------------------------------------- + * GVArray. + */ + +void GVArray::materialize(void *dst) const +{ + this->materialize(IndexMask(size_), dst); +} + +void GVArray::materialize(const IndexMask mask, void *dst) const +{ + this->materialize_impl(mask, dst); +} + +void GVArray::materialize_impl(const IndexMask mask, void *dst) const +{ + for (const int64_t i : mask) { + void *elem_dst = POINTER_OFFSET(dst, type_->size() * i); + this->get(i, elem_dst); + } +} + +void GVArray::materialize_to_uninitialized(void *dst) const +{ + this->materialize_to_uninitialized(IndexMask(size_), dst); +} + void GVArray::materialize_to_uninitialized(const IndexMask mask, void *dst) const { + BLI_assert(mask.min_array_size() <= size_); + this->materialize_to_uninitialized_impl(mask, dst); +} + +void GVArray::materialize_to_uninitialized_impl(const IndexMask mask, void *dst) const +{ for (const int64_t i : mask) { void *elem_dst = POINTER_OFFSET(dst, type_->size() * i); this->get_to_uninitialized(i, elem_dst); @@ -37,7 +101,7 @@ bool GVArray::is_span_impl() const return false; } -GSpan GVArray::get_span_impl() const +GSpan GVArray::get_internal_span_impl() const { BLI_assert(false); return GSpan(*type_); @@ -48,60 +112,279 @@ bool GVArray::is_single_impl() const return false; } -void GVArray::get_single_impl(void *UNUSED(r_value)) const +void GVArray::get_internal_single_impl(void *UNUSED(r_value)) const { BLI_assert(false); } -void GVArrayForGSpan::get_impl(const int64_t index, void *r_value) const +const void *GVArray::try_get_internal_varray_impl() const +{ + return nullptr; +} + +/** + * Creates a new `std::unique_ptr<GVArray>` based on this `GVArray`. + * The lifetime of the returned virtual array must not be longer than the lifetime of this virtual + * array. + */ +GVArrayPtr GVArray::shallow_copy() const +{ + if (this->is_span()) { + return std::make_unique<GVArray_For_GSpan>(this->get_internal_span()); + } + if (this->is_single()) { + BUFFER_FOR_CPP_TYPE_VALUE(*type_, buffer); + this->get_internal_single(buffer); + std::unique_ptr new_varray = std::make_unique<GVArray_For_SingleValue>(*type_, size_, buffer); + type_->destruct(buffer); + return new_varray; + } + return std::make_unique<GVArray_For_ShallowCopy>(*this); +} + +/* -------------------------------------------------------------------- + * GVMutableArray. + */ + +void GVMutableArray::set_by_copy_impl(const int64_t index, const void *value) +{ + BUFFER_FOR_CPP_TYPE_VALUE(*type_, buffer); + type_->copy_to_uninitialized(value, buffer); + this->set_by_move_impl(index, buffer); + type_->destruct(buffer); +} + +void GVMutableArray::set_by_relocate_impl(const int64_t index, void *value) +{ + this->set_by_move_impl(index, value); + type_->destruct(value); +} + +void GVMutableArray::set_all_impl(const void *src) +{ + if (this->is_span()) { + const GMutableSpan span = this->get_internal_span(); + type_->copy_to_initialized_n(src, span.data(), size_); + } + else { + for (int64_t i : IndexRange(size_)) { + this->set_by_copy(i, POINTER_OFFSET(src, type_->size() * i)); + } + } +} + +void *GVMutableArray::try_get_internal_mutable_varray_impl() +{ + return nullptr; +} + +void GVMutableArray::fill(const void *value) +{ + if (this->is_span()) { + const GMutableSpan span = this->get_internal_span(); + type_->fill_initialized(value, span.data(), size_); + } + else { + for (int64_t i : IndexRange(size_)) { + this->set_by_copy(i, value); + } + } +} + +/* -------------------------------------------------------------------- + * GVArray_For_GSpan. + */ + +void GVArray_For_GSpan::get_impl(const int64_t index, void *r_value) const { type_->copy_to_initialized(POINTER_OFFSET(data_, element_size_ * index), r_value); } -void GVArrayForGSpan::get_to_uninitialized_impl(const int64_t index, void *r_value) const +void GVArray_For_GSpan::get_to_uninitialized_impl(const int64_t index, void *r_value) const { type_->copy_to_uninitialized(POINTER_OFFSET(data_, element_size_ * index), r_value); } -bool GVArrayForGSpan::is_span_impl() const +bool GVArray_For_GSpan::is_span_impl() const { return true; } -GSpan GVArrayForGSpan::get_span_impl() const +GSpan GVArray_For_GSpan::get_internal_span_impl() const { return GSpan(*type_, data_, size_); } -void GVArrayForSingleValueRef::get_impl(const int64_t UNUSED(index), void *r_value) const +/* -------------------------------------------------------------------- + * GVMutableArray_For_GMutableSpan. + */ + +void GVMutableArray_For_GMutableSpan::get_impl(const int64_t index, void *r_value) const +{ + type_->copy_to_initialized(POINTER_OFFSET(data_, element_size_ * index), r_value); +} + +void GVMutableArray_For_GMutableSpan::get_to_uninitialized_impl(const int64_t index, + void *r_value) const +{ + type_->copy_to_uninitialized(POINTER_OFFSET(data_, element_size_ * index), r_value); +} + +void GVMutableArray_For_GMutableSpan::set_by_copy_impl(const int64_t index, const void *value) +{ + type_->copy_to_initialized(value, POINTER_OFFSET(data_, element_size_ * index)); +} + +void GVMutableArray_For_GMutableSpan::set_by_move_impl(const int64_t index, void *value) +{ + type_->move_to_initialized(value, POINTER_OFFSET(data_, element_size_ * index)); +} + +void GVMutableArray_For_GMutableSpan::set_by_relocate_impl(const int64_t index, void *value) +{ + type_->relocate_to_initialized(value, POINTER_OFFSET(data_, element_size_ * index)); +} + +bool GVMutableArray_For_GMutableSpan::is_span_impl() const +{ + return true; +} + +GSpan GVMutableArray_For_GMutableSpan::get_internal_span_impl() const +{ + return GSpan(*type_, data_, size_); +} + +/* -------------------------------------------------------------------- + * GVArray_For_SingleValueRef. + */ + +void GVArray_For_SingleValueRef::get_impl(const int64_t UNUSED(index), void *r_value) const { type_->copy_to_initialized(value_, r_value); } -void GVArrayForSingleValueRef::get_to_uninitialized_impl(const int64_t UNUSED(index), - void *r_value) const +void GVArray_For_SingleValueRef::get_to_uninitialized_impl(const int64_t UNUSED(index), + void *r_value) const { type_->copy_to_uninitialized(value_, r_value); } -bool GVArrayForSingleValueRef::is_span_impl() const +bool GVArray_For_SingleValueRef::is_span_impl() const { return size_ == 1; } -GSpan GVArrayForSingleValueRef::get_span_impl() const +GSpan GVArray_For_SingleValueRef::get_internal_span_impl() const { return GSpan{*type_, value_, 1}; } -bool GVArrayForSingleValueRef::is_single_impl() const +bool GVArray_For_SingleValueRef::is_single_impl() const { return true; } -void GVArrayForSingleValueRef::get_single_impl(void *r_value) const +void GVArray_For_SingleValueRef::get_internal_single_impl(void *r_value) const { type_->copy_to_initialized(value_, r_value); } +/* -------------------------------------------------------------------- + * GVArray_For_SingleValue. + */ + +GVArray_For_SingleValue::GVArray_For_SingleValue(const CPPType &type, + const int64_t size, + const void *value) + : GVArray_For_SingleValueRef(type, size) +{ + value_ = MEM_mallocN_aligned(type.size(), type.alignment(), __func__); + type.copy_to_uninitialized(value, (void *)value_); +} + +GVArray_For_SingleValue::~GVArray_For_SingleValue() +{ + type_->destruct((void *)value_); + MEM_freeN((void *)value_); +} + +/* -------------------------------------------------------------------- + * GVArray_GSpan. + */ + +GVArray_GSpan::GVArray_GSpan(const GVArray &varray) : GSpan(varray.type()), varray_(varray) +{ + size_ = varray_.size(); + if (varray_.is_span()) { + data_ = varray_.get_internal_span().data(); + } + else { + owned_data_ = MEM_mallocN_aligned(type_->size() * size_, type_->alignment(), __func__); + varray_.materialize_to_uninitialized(IndexRange(size_), owned_data_); + data_ = owned_data_; + } +} + +GVArray_GSpan::~GVArray_GSpan() +{ + if (owned_data_ != nullptr) { + type_->destruct_n(owned_data_, size_); + MEM_freeN(owned_data_); + } +} + +/* -------------------------------------------------------------------- + * GVMutableArray_GSpan. + */ + +GVMutableArray_GSpan::GVMutableArray_GSpan(GVMutableArray &varray, const bool copy_values_to_span) + : GMutableSpan(varray.type()), varray_(varray) +{ + size_ = varray_.size(); + if (varray_.is_span()) { + data_ = varray_.get_internal_span().data(); + } + else { + owned_data_ = MEM_mallocN_aligned(type_->size() * size_, type_->alignment(), __func__); + if (copy_values_to_span) { + varray_.materialize_to_uninitialized(IndexRange(size_), owned_data_); + } + else { + type_->construct_default_n(owned_data_, size_); + } + data_ = owned_data_; + } +} + +GVMutableArray_GSpan::~GVMutableArray_GSpan() +{ + if (show_not_saved_warning_) { + if (!save_has_been_called_) { + std::cout << "Warning: Call `apply()` to make sure that changes persist in all cases.\n"; + } + } + if (owned_data_ != nullptr) { + type_->destruct_n(owned_data_, size_); + MEM_freeN(owned_data_); + } +} + +void GVMutableArray_GSpan::save() +{ + save_has_been_called_ = true; + if (data_ != owned_data_) { + return; + } + const int64_t element_size = type_->size(); + for (int64_t i : IndexRange(size_)) { + varray_.set_by_copy(i, POINTER_OFFSET(owned_data_, element_size * i)); + } +} + +void GVMutableArray_GSpan::disable_not_applied_warning() +{ + show_not_saved_warning_ = false; +} + } // namespace blender::fn diff --git a/source/blender/functions/intern/generic_virtual_vector_array.cc b/source/blender/functions/intern/generic_virtual_vector_array.cc index f6504cee41e..aa3d90883c6 100644 --- a/source/blender/functions/intern/generic_virtual_vector_array.cc +++ b/source/blender/functions/intern/generic_virtual_vector_array.cc @@ -18,48 +18,48 @@ namespace blender::fn { -void GVArrayForGVVectorArrayIndex::get_impl(const int64_t index_in_vector, void *r_value) const +void GVArray_For_GVVectorArrayIndex::get_impl(const int64_t index_in_vector, void *r_value) const { vector_array_.get_vector_element(index_, index_in_vector, r_value); } -void GVArrayForGVVectorArrayIndex::get_to_uninitialized_impl(const int64_t index_in_vector, - void *r_value) const +void GVArray_For_GVVectorArrayIndex::get_to_uninitialized_impl(const int64_t index_in_vector, + void *r_value) const { type_->construct_default(r_value); vector_array_.get_vector_element(index_, index_in_vector, r_value); } -int64_t GVVectorArrayForSingleGVArray::get_vector_size_impl(const int64_t UNUSED(index)) const +int64_t GVVectorArray_For_SingleGVArray::get_vector_size_impl(const int64_t UNUSED(index)) const { return array_.size(); } -void GVVectorArrayForSingleGVArray::get_vector_element_impl(const int64_t UNUSED(index), - const int64_t index_in_vector, - void *r_value) const +void GVVectorArray_For_SingleGVArray::get_vector_element_impl(const int64_t UNUSED(index), + const int64_t index_in_vector, + void *r_value) const { array_.get(index_in_vector, r_value); } -bool GVVectorArrayForSingleGVArray::is_single_vector_impl() const +bool GVVectorArray_For_SingleGVArray::is_single_vector_impl() const { return true; } -int64_t GVVectorArrayForSingleGSpan::get_vector_size_impl(const int64_t UNUSED(index)) const +int64_t GVVectorArray_For_SingleGSpan::get_vector_size_impl(const int64_t UNUSED(index)) const { return span_.size(); } -void GVVectorArrayForSingleGSpan::get_vector_element_impl(const int64_t UNUSED(index), - const int64_t index_in_vector, - void *r_value) const +void GVVectorArray_For_SingleGSpan::get_vector_element_impl(const int64_t UNUSED(index), + const int64_t index_in_vector, + void *r_value) const { type_->copy_to_initialized(span_[index_in_vector], r_value); } -bool GVVectorArrayForSingleGSpan::is_single_vector_impl() const +bool GVVectorArray_For_SingleGSpan::is_single_vector_impl() const { return true; } diff --git a/source/blender/functions/intern/multi_function_network_evaluation.cc b/source/blender/functions/intern/multi_function_network_evaluation.cc index 86ac4f6a179..9a0cb0c35ce 100644 --- a/source/blender/functions/intern/multi_function_network_evaluation.cc +++ b/source/blender/functions/intern/multi_function_network_evaluation.cc @@ -974,11 +974,11 @@ const GVArray &MFNetworkEvaluationStorage::get_single_input__full(const MFInputS if (any_value->type == ValueType::OwnSingle) { OwnSingleValue *value = static_cast<OwnSingleValue *>(any_value); if (value->is_single_allocated) { - return scope.construct<GVArrayForSingleValueRef>( + return scope.construct<GVArray_For_SingleValueRef>( __func__, value->span.type(), min_array_size_, value->span.data()); } - return scope.construct<GVArrayForGSpan>(__func__, value->span); + return scope.construct<GVArray_For_GSpan>(__func__, value->span); } if (any_value->type == ValueType::InputSingle) { InputSingleValue *value = static_cast<InputSingleValue *>(any_value); @@ -987,11 +987,11 @@ const GVArray &MFNetworkEvaluationStorage::get_single_input__full(const MFInputS if (any_value->type == ValueType::OutputSingle) { OutputSingleValue *value = static_cast<OutputSingleValue *>(any_value); BLI_assert(value->is_computed); - return scope.construct<GVArrayForGSpan>(__func__, value->span); + return scope.construct<GVArray_For_GSpan>(__func__, value->span); } BLI_assert(false); - return scope.construct<GVArrayForEmpty>(__func__, CPPType::get<float>()); + return scope.construct<GVArray_For_Empty>(__func__, CPPType::get<float>()); } const GVArray &MFNetworkEvaluationStorage::get_single_input__single(const MFInputSocket &socket, @@ -1004,7 +1004,7 @@ const GVArray &MFNetworkEvaluationStorage::get_single_input__single(const MFInpu if (any_value->type == ValueType::OwnSingle) { OwnSingleValue *value = static_cast<OwnSingleValue *>(any_value); BLI_assert(value->span.size() == 1); - return scope.construct<GVArrayForGSpan>(__func__, value->span); + return scope.construct<GVArray_For_GSpan>(__func__, value->span); } if (any_value->type == ValueType::InputSingle) { InputSingleValue *value = static_cast<InputSingleValue *>(any_value); @@ -1015,11 +1015,11 @@ const GVArray &MFNetworkEvaluationStorage::get_single_input__single(const MFInpu OutputSingleValue *value = static_cast<OutputSingleValue *>(any_value); BLI_assert(value->is_computed); BLI_assert(value->span.size() == 1); - return scope.construct<GVArrayForGSpan>(__func__, value->span); + return scope.construct<GVArray_For_GSpan>(__func__, value->span); } BLI_assert(false); - return scope.construct<GVArrayForEmpty>(__func__, CPPType::get<float>()); + return scope.construct<GVArray_For_Empty>(__func__, CPPType::get<float>()); } const GVVectorArray &MFNetworkEvaluationStorage::get_vector_input__full( @@ -1033,10 +1033,10 @@ const GVVectorArray &MFNetworkEvaluationStorage::get_vector_input__full( OwnVectorValue *value = static_cast<OwnVectorValue *>(any_value); if (value->vector_array->size() == 1) { GSpan span = (*value->vector_array)[0]; - return scope.construct<GVVectorArrayForSingleGSpan>(__func__, span, min_array_size_); + return scope.construct<GVVectorArray_For_SingleGSpan>(__func__, span, min_array_size_); } - return scope.construct<GVVectorArrayForGVectorArray>(__func__, *value->vector_array); + return scope.construct<GVVectorArray_For_GVectorArray>(__func__, *value->vector_array); } if (any_value->type == ValueType::InputVector) { InputVectorValue *value = static_cast<InputVectorValue *>(any_value); @@ -1044,11 +1044,11 @@ const GVVectorArray &MFNetworkEvaluationStorage::get_vector_input__full( } if (any_value->type == ValueType::OutputVector) { OutputVectorValue *value = static_cast<OutputVectorValue *>(any_value); - return scope.construct<GVVectorArrayForGVectorArray>(__func__, *value->vector_array); + return scope.construct<GVVectorArray_For_GVectorArray>(__func__, *value->vector_array); } BLI_assert(false); - return scope.construct<GVVectorArrayForSingleGSpan>(__func__, GSpan(CPPType::get<float>()), 0); + return scope.construct<GVVectorArray_For_SingleGSpan>(__func__, GSpan(CPPType::get<float>()), 0); } const GVVectorArray &MFNetworkEvaluationStorage::get_vector_input__single( @@ -1061,7 +1061,7 @@ const GVVectorArray &MFNetworkEvaluationStorage::get_vector_input__single( if (any_value->type == ValueType::OwnVector) { OwnVectorValue *value = static_cast<OwnVectorValue *>(any_value); BLI_assert(value->vector_array->size() == 1); - return scope.construct<GVVectorArrayForGVectorArray>(__func__, *value->vector_array); + return scope.construct<GVVectorArray_For_GVectorArray>(__func__, *value->vector_array); } if (any_value->type == ValueType::InputVector) { InputVectorValue *value = static_cast<InputVectorValue *>(any_value); @@ -1071,11 +1071,11 @@ const GVVectorArray &MFNetworkEvaluationStorage::get_vector_input__single( if (any_value->type == ValueType::OutputVector) { OutputVectorValue *value = static_cast<OutputVectorValue *>(any_value); BLI_assert(value->vector_array->size() == 1); - return scope.construct<GVVectorArrayForGVectorArray>(__func__, *value->vector_array); + return scope.construct<GVVectorArray_For_GVectorArray>(__func__, *value->vector_array); } BLI_assert(false); - return scope.construct<GVVectorArrayForSingleGSpan>(__func__, GSpan(CPPType::get<float>()), 0); + return scope.construct<GVVectorArray_For_SingleGSpan>(__func__, GSpan(CPPType::get<float>()), 0); } /** \} */ diff --git a/source/blender/functions/tests/FN_multi_function_network_test.cc b/source/blender/functions/tests/FN_multi_function_network_test.cc index 51e116b5983..7b9738e5ca4 100644 --- a/source/blender/functions/tests/FN_multi_function_network_test.cc +++ b/source/blender/functions/tests/FN_multi_function_network_test.cc @@ -223,7 +223,7 @@ TEST(multi_function_network, Test2) Array<int> output_value_2(5, -1); MFParamsBuilder params(network_fn, 5); - GVVectorArrayForSingleGSpan inputs_1{input_value_1.as_span(), 5}; + GVVectorArray_For_SingleGSpan inputs_1{input_value_1.as_span(), 5}; params.add_readonly_vector_input(inputs_1); params.add_readonly_single_input(&input_value_2); params.add_vector_output(output_value_1); diff --git a/source/blender/gpencil_modifiers/CMakeLists.txt b/source/blender/gpencil_modifiers/CMakeLists.txt index 9690f47c862..f39306ac9d0 100644 --- a/source/blender/gpencil_modifiers/CMakeLists.txt +++ b/source/blender/gpencil_modifiers/CMakeLists.txt @@ -54,6 +54,7 @@ set(SRC intern/MOD_gpencilcolor.c intern/MOD_gpencilhook.c intern/MOD_gpencillattice.c + intern/MOD_gpencillength.c intern/MOD_gpencillineart.c intern/MOD_gpencilmirror.c intern/MOD_gpencilmultiply.c diff --git a/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h b/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h index e6ce7983a0f..f8a28f2e5cb 100644 --- a/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h +++ b/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h @@ -35,6 +35,7 @@ extern GpencilModifierTypeInfo modifierType_Gpencil_Array; extern GpencilModifierTypeInfo modifierType_Gpencil_Build; extern GpencilModifierTypeInfo modifierType_Gpencil_Opacity; extern GpencilModifierTypeInfo modifierType_Gpencil_Lattice; +extern GpencilModifierTypeInfo modifierType_Gpencil_Length; extern GpencilModifierTypeInfo modifierType_Gpencil_Mirror; extern GpencilModifierTypeInfo modifierType_Gpencil_Smooth; extern GpencilModifierTypeInfo modifierType_Gpencil_Hook; diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencil_ui_common.c b/source/blender/gpencil_modifiers/intern/MOD_gpencil_ui_common.c index a156fca5b7b..94285b5032e 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencil_ui_common.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencil_ui_common.c @@ -203,6 +203,20 @@ void gpencil_modifier_curve_panel_draw(const bContext *UNUSED(C), Panel *panel) uiTemplateCurveMapping(layout, ptr, "curve", 0, false, false, false, false); } +void gpencil_modifier_fading_draw(const bContext *UNUSED(C), Panel *panel) +{ + PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL); + + uiLayout *layout = panel->layout; + uiLayoutSetPropSep(layout, true); + + uiItemR(layout, ptr, "object", 0, NULL, ICON_CUBE); + uiLayout *sub = uiLayoutColumn(layout, true); + uiItemR(sub, ptr, "fading_start", 0, NULL, ICON_NONE); + uiItemR(sub, ptr, "fading_end", 0, IFACE_("End"), ICON_NONE); + uiItemR(layout, ptr, "fading_end_factor", 0, NULL, ICON_NONE); +} + /** * Draw modifier error message. */ diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencil_ui_common.h b/source/blender/gpencil_modifiers/intern/MOD_gpencil_ui_common.h index 782b36d47ed..75907aaa781 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencil_ui_common.h +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencil_ui_common.h @@ -37,6 +37,8 @@ void gpencil_modifier_masking_panel_draw(Panel *panel, bool use_material, bool u void gpencil_modifier_curve_header_draw(const bContext *C, Panel *panel); void gpencil_modifier_curve_panel_draw(const bContext *C, Panel *panel); +void gpencil_modifier_fading_draw(const bContext *UNUSED(C), Panel *panel); + void gpencil_modifier_panel_end(struct uiLayout *layout, PointerRNA *ptr); struct PointerRNA *gpencil_modifier_panel_get_property_pointers(struct Panel *panel, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c index 2dc00079f91..b28a44a0521 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c @@ -54,6 +54,7 @@ void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[]) INIT_GP_TYPE(Build); INIT_GP_TYPE(Opacity); INIT_GP_TYPE(Lattice); + INIT_GP_TYPE(Length); INIT_GP_TYPE(Mirror); INIT_GP_TYPE(Smooth); INIT_GP_TYPE(Hook); diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilbuild.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilbuild.c index d7fc08e38f3..d9f0fc9bddd 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilbuild.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilbuild.c @@ -455,8 +455,10 @@ static void generate_geometry( /* Compute start and end frames for the animation effect * By default, the upper bound is given by the "maximum length" setting */ - float start_frame = gpf->framenum + mmd->start_delay; - float end_frame = start_frame + mmd->length; + float start_frame = is_percentage ? gpf->framenum : gpf->framenum + mmd->start_delay; + /* When use percentage don't need a limit in the upper bound, so use a maximum value for the last + * frame. */ + float end_frame = is_percentage ? start_frame + 9999 : start_frame + mmd->length; if (gpf->next) { /* Use the next frame or upper bound as end frame, whichever is lower/closer */ @@ -547,6 +549,7 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel) PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, &ob_ptr); int mode = RNA_enum_get(ptr, "mode"); + const bool use_percentage = RNA_boolean_get(ptr, "use_percentage"); uiLayoutSetPropSep(layout, true); @@ -558,8 +561,12 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel) uiItemS(layout); uiItemR(layout, ptr, "transition", 0, NULL, ICON_NONE); - uiItemR(layout, ptr, "start_delay", 0, NULL, ICON_NONE); - uiItemR(layout, ptr, "length", 0, IFACE_("Frames"), ICON_NONE); + row = uiLayoutRow(layout, true); + uiLayoutSetActive(row, !use_percentage); + uiItemR(row, ptr, "start_delay", 0, NULL, ICON_NONE); + row = uiLayoutRow(layout, true); + uiLayoutSetActive(row, !use_percentage); + uiItemR(row, ptr, "length", 0, IFACE_("Frames"), ICON_NONE); uiItemS(layout); @@ -567,7 +574,7 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel) uiLayoutSetPropDecorate(row, false); uiItemR(row, ptr, "use_percentage", 0, "", ICON_NONE); sub = uiLayoutRow(row, true); - uiLayoutSetActive(sub, RNA_boolean_get(ptr, "use_percentage")); + uiLayoutSetActive(sub, use_percentage); uiItemR(sub, ptr, "percentage_factor", 0, "", ICON_NONE); uiItemDecoratorR(row, ptr, "percentage_factor", 0); diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c new file mode 100644 index 00000000000..fd94ac92bc3 --- /dev/null +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c @@ -0,0 +1,223 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * 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) 2017, Blender Foundation + * This is a new part of Blender + * + * ***** END GPL LICENSE BLOCK ***** + * + */ + +/** \file + * \ingroup modifiers + */ + +#include <stdio.h> + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "DNA_defaults.h" +#include "DNA_gpencil_modifier_types.h" +#include "DNA_gpencil_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" + +#include "BKE_context.h" +#include "BKE_gpencil_geom.h" +#include "BKE_gpencil_modifier.h" +#include "BKE_lib_query.h" +#include "BKE_main.h" +#include "BKE_modifier.h" +#include "BKE_screen.h" + +#include "MEM_guardedalloc.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "RNA_access.h" + +#include "MOD_gpencil_modifiertypes.h" +#include "MOD_gpencil_ui_common.h" +#include "MOD_gpencil_util.h" + +#include "DEG_depsgraph.h" + +static void initData(GpencilModifierData *md) +{ + LengthGpencilModifierData *gpmd = (LengthGpencilModifierData *)md; + + BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(gpmd, modifier)); + + MEMCPY_STRUCT_AFTER(gpmd, DNA_struct_default_get(LengthGpencilModifierData), modifier); +} + +static void copyData(const GpencilModifierData *md, GpencilModifierData *target) +{ + BKE_gpencil_modifier_copydata_generic(md, target); +} + +static bool gpencil_modify_stroke(bGPDstroke *gps, + float length, + const float overshoot_fac, + const short len_mode) +{ + bool changed = false; + if (length == 0.0f) { + return changed; + } + + if (length > 0.0f) { + BKE_gpencil_stroke_stretch(gps, length, overshoot_fac, len_mode); + } + else { + changed |= BKE_gpencil_stroke_shrink(gps, fabs(length), len_mode); + } + + return changed; +} + +static void applyLength(LengthGpencilModifierData *lmd, bGPdata *gpd, bGPDstroke *gps) +{ + bool changed = false; + const float len = (lmd->mode == GP_LENGTH_ABSOLUTE) ? 1.0f : + BKE_gpencil_stroke_length(gps, true); + if (len < FLT_EPSILON) { + return; + } + + changed |= gpencil_modify_stroke(gps, len * lmd->start_fac, lmd->overshoot_fac, 1); + changed |= gpencil_modify_stroke(gps, len * lmd->end_fac, lmd->overshoot_fac, 2); + + if (changed) { + BKE_gpencil_stroke_geometry_update(gpd, gps); + } +} + +static void bakeModifier(Main *UNUSED(bmain), + Depsgraph *UNUSED(depsgraph), + GpencilModifierData *md, + Object *ob) +{ + + bGPdata *gpd = ob->data; + + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LengthGpencilModifierData *lmd = (LengthGpencilModifierData *)md; + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + applyLength(lmd, gpd, gps); + } + } + } +} + +/* -------------------------------- */ + +/* Generic "generateStrokes" callback */ +static void deformStroke(GpencilModifierData *md, + Depsgraph *UNUSED(depsgraph), + Object *ob, + bGPDlayer *gpl, + bGPDframe *UNUSED(gpf), + bGPDstroke *gps) +{ + bGPdata *gpd = ob->data; + LengthGpencilModifierData *lmd = (LengthGpencilModifierData *)md; + if (is_stroke_affected_by_modifier(ob, + lmd->layername, + lmd->material, + lmd->pass_index, + lmd->layer_pass, + 1, + gpl, + gps, + lmd->flag & GP_LENGTH_INVERT_LAYER, + lmd->flag & GP_LENGTH_INVERT_PASS, + lmd->flag & GP_LENGTH_INVERT_LAYERPASS, + lmd->flag & GP_LENGTH_INVERT_MATERIAL)) { + applyLength(lmd, gpd, gps); + } +} + +static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk, void *userData) +{ + LengthGpencilModifierData *mmd = (LengthGpencilModifierData *)md; + + walk(userData, ob, (ID **)&mmd->material, IDWALK_CB_USER); +} + +static void panel_draw(const bContext *UNUSED(C), Panel *panel) +{ + uiLayout *layout = panel->layout; + + PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL); + + uiLayoutSetPropSep(layout, true); + uiItemR(layout, ptr, "mode", 0, NULL, ICON_NONE); + + uiLayout *col = uiLayoutColumn(layout, true); + + uiItemR(col, ptr, "start_factor", 0, IFACE_("Start"), ICON_NONE); + uiItemR(col, ptr, "end_factor", 0, IFACE_("End"), ICON_NONE); + + uiItemR(layout, ptr, "overshoot_factor", UI_ITEM_R_SLIDER, IFACE_("Overshoot"), ICON_NONE); + + gpencil_modifier_panel_end(layout, ptr); +} + +static void mask_panel_draw(const bContext *UNUSED(C), Panel *panel) +{ + gpencil_modifier_masking_panel_draw(panel, true, false); +} + +static void panelRegister(ARegionType *region_type) +{ + PanelType *panel_type = gpencil_modifier_panel_register( + region_type, eGpencilModifierType_Length, panel_draw); + gpencil_modifier_subpanel_register( + region_type, "mask", "Influence", NULL, mask_panel_draw, panel_type); +} + +GpencilModifierTypeInfo modifierType_Gpencil_Length = { + /* name */ "Length", + /* structName */ "LengthGpencilModifierData", + /* structSize */ sizeof(LengthGpencilModifierData), + /* type */ eGpencilModifierTypeType_Gpencil, + /* flags */ eGpencilModifierTypeFlag_SupportsEditmode, + + /* copyData */ copyData, + + /* deformStroke */ deformStroke, + /* generateStrokes */ NULL, + /* bakeModifier */ bakeModifier, + /* remapTime */ NULL, + + /* initData */ initData, + /* freeData */ NULL, + /* isDisabled */ NULL, + /* updateDepsgraph */ NULL, + /* dependsOnTime */ NULL, + /* foreachIDLink */ foreachIDLink, + /* foreachTexLink */ NULL, + /* panelRegister */ panelRegister, +}; diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c index c1a791d460b..cc79810d2a2 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c @@ -345,7 +345,7 @@ static void occlusion_panel_draw(const bContext *UNUSED(C), Panel *panel) if (use_multiple_levels) { uiLayout *col = uiLayoutColumn(layout, true); uiItemR(col, ptr, "level_start", 0, NULL, ICON_NONE); - uiItemR(col, ptr, "level_end", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "level_end", 0, IFACE_("End"), ICON_NONE); } else { uiItemR(layout, ptr, "level_start", 0, IFACE_("Level"), ICON_NONE); @@ -377,7 +377,7 @@ static void transparency_panel_draw(const bContext *UNUSED(C), Panel *panel) uiLayout *row = uiLayoutRow(layout, true); uiLayoutSetPropDecorate(row, false); - uiLayout *sub = uiLayoutRow(row, true); + uiLayout *sub = uiLayoutRowWithHeading(row, true, IFACE_("Masks")); char text[2] = "0"; PropertyRNA *prop = RNA_struct_find_property(ptr, "use_transparency_mask"); diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpenciloffset.c b/source/blender/gpencil_modifiers/intern/MOD_gpenciloffset.c index bffb324f07f..cd29a006aae 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpenciloffset.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpenciloffset.c @@ -26,7 +26,11 @@ #include "BLI_listbase.h" #include "BLI_utildefines.h" +#include "BLT_translation.h" + +#include "BLI_hash.h" #include "BLI_math.h" +#include "BLI_rand.h" #include "DNA_defaults.h" #include "DNA_gpencil_modifier_types.h" @@ -71,7 +75,7 @@ static void deformStroke(GpencilModifierData *md, Depsgraph *UNUSED(depsgraph), Object *ob, bGPDlayer *gpl, - bGPDframe *UNUSED(gpf), + bGPDframe *gpf, bGPDstroke *gps) { OffsetGpencilModifierData *mmd = (OffsetGpencilModifierData *)md; @@ -94,6 +98,46 @@ static void deformStroke(GpencilModifierData *md, mmd->flag & GP_OFFSET_INVERT_MATERIAL)) { return; } + + int seed = mmd->seed; + /* Make sure different modifiers get different seeds. */ + seed += BLI_hash_string(ob->id.name + 2); + seed += BLI_hash_string(md->name); + + float rand[3][3]; + float rand_offset = BLI_hash_int_01(seed); + + /* Get stroke index for random offset. */ + int rnd_index = BLI_findindex(&gpf->strokes, gps); + for (int j = 0; j < 3; j++) { + const uint primes[3] = {2, 3, 7}; + double offset[3] = {0.0f, 0.0f, 0.0f}; + double r[3]; + /* To ensure a nice distribution, we use halton sequence and offset using the seed. */ + BLI_halton_3d(primes, offset, rnd_index, r); + + if ((mmd->flag & GP_OFFSET_UNIFORM_RANDOM_SCALE) && j == 2) { + float rand_value; + rand_value = fmodf(r[0] * 2.0f - 1.0f + rand_offset, 1.0f); + rand_value = fmodf(sin(rand_value * 12.9898f + j * 78.233f) * 43758.5453f, 1.0f); + copy_v3_fl(rand[j], rand_value); + } + else { + for (int i = 0; i < 3; i++) { + rand[j][i] = fmodf(r[i] * 2.0f - 1.0f + rand_offset, 1.0f); + rand[j][i] = fmodf(sin(rand[j][i] * 12.9898f + j * 78.233f) * 43758.5453f, 1.0f); + } + } + } + /* Calculate Random matrix. */ + float mat_rnd[4][4]; + float rnd_loc[3], rnd_rot[3]; + float rnd_scale[3] = {1.0f, 1.0f, 1.0f}; + mul_v3_v3v3(rnd_loc, mmd->rnd_offset, rand[0]); + mul_v3_v3v3(rnd_rot, mmd->rnd_rot, rand[1]); + madd_v3_v3v3(rnd_scale, mmd->rnd_scale, rand[2]); + loc_eul_size_to_mat4(mat_rnd, rnd_loc, rnd_rot, rnd_scale); + bGPdata *gpd = ob->data; for (int i = 0; i < gps->totpoints; i++) { @@ -106,6 +150,9 @@ static void deformStroke(GpencilModifierData *md, if (weight < 0.0f) { continue; } + /* Apply randomness matrix. */ + mul_m4_v3(mat_rnd, &pt->x); + /* Calculate matrix. */ mul_v3_v3fl(loc, mmd->loc, weight); mul_v3_v3fl(rot, mmd->rot, weight); @@ -161,6 +208,21 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel) gpencil_modifier_panel_end(layout, ptr); } +static void random_panel_draw(const bContext *UNUSED(C), Panel *panel) +{ + uiLayout *layout = panel->layout; + + PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL); + + uiLayoutSetPropSep(layout, true); + + uiItemR(layout, ptr, "random_offset", 0, IFACE_("Offset"), ICON_NONE); + uiItemR(layout, ptr, "random_rotation", 0, IFACE_("Rotation"), ICON_NONE); + uiItemR(layout, ptr, "random_scale", 0, IFACE_("Scale"), ICON_NONE); + uiItemR(layout, ptr, "use_uniform_random_scale", 0, NULL, ICON_NONE); + uiItemR(layout, ptr, "seed", 0, NULL, ICON_NONE); +} + static void mask_panel_draw(const bContext *UNUSED(C), Panel *panel) { gpencil_modifier_masking_panel_draw(panel, true, true); @@ -171,6 +233,8 @@ static void panelRegister(ARegionType *region_type) PanelType *panel_type = gpencil_modifier_panel_register( region_type, eGpencilModifierType_Offset, panel_draw); gpencil_modifier_subpanel_register( + region_type, "randomize", "Randomize", NULL, random_panel_draw, panel_type); + gpencil_modifier_subpanel_register( region_type, "mask", "Influence", NULL, mask_panel_draw, panel_type); } diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilopacity.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilopacity.c index c193fc49362..ea37558fa7f 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilopacity.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilopacity.c @@ -47,6 +47,8 @@ #include "BKE_screen.h" #include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" +#include "DEG_depsgraph_query.h" #include "UI_interface.h" #include "UI_resources.h" @@ -84,6 +86,39 @@ static void copyData(const GpencilModifierData *md, GpencilModifierData *target) tgmd->curve_intensity = BKE_curvemapping_copy(gmd->curve_intensity); } +static float give_opacity_fading_factor(OpacityGpencilModifierData *mmd, + Object *ob_this, + float *pos, + bool apply_obmat) +{ + float factor_depth = 1.0f; + + if (((mmd->flag & GP_OPACITY_FADING) == 0) || ((mmd->object) == NULL)) { + return factor_depth; + } + + float gvert[3]; + if (apply_obmat) { + mul_v3_m4v3(gvert, ob_this->obmat, pos); + } + float dist = len_v3v3(mmd->object->obmat[3], gvert); + float fading_max = MAX2(mmd->fading_start, mmd->fading_end); + float fading_min = MIN2(mmd->fading_start, mmd->fading_end); + + /* Better with ratiof() function from line art. */ + if (dist > fading_max) { + factor_depth = 0.0f; + } + else if (dist <= fading_max && dist > fading_min) { + factor_depth = (fading_max - dist) / (fading_max - fading_min); + } + else { + factor_depth = 1.0f; + } + + return factor_depth; +} + /* opacity strokes */ static void deformStroke(GpencilModifierData *md, Depsgraph *UNUSED(depsgraph), @@ -138,6 +173,9 @@ static void deformStroke(GpencilModifierData *md, factor_curve *= BKE_curvemapping_evaluateF(mmd->curve_intensity, 0, value); } + float factor_depth = give_opacity_fading_factor(mmd, ob, &pt->x, true); + factor_curve = interpf(factor_curve, mmd->fading_end_factor, factor_depth); + if (def_nr < 0) { if (mmd->flag & GP_OPACITY_NORMALIZE) { pt->strength = factor_curve; @@ -167,6 +205,10 @@ static void deformStroke(GpencilModifierData *md, /* Fill using opacity factor. */ if (mmd->modify_color != GP_MODIFY_COLOR_STROKE) { gps->fill_opacity_fac = mmd->factor; + + float factor_depth = give_opacity_fading_factor(mmd, ob, ob->obmat[3], true); + gps->fill_opacity_fac = interpf(mmd->factor, mmd->fading_end_factor, factor_depth); + CLAMP(gps->fill_opacity_fac, 0.0f, 1.0f); } } @@ -201,6 +243,18 @@ static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk, OpacityGpencilModifierData *mmd = (OpacityGpencilModifierData *)md; walk(userData, ob, (ID **)&mmd->material, IDWALK_CB_USER); + walk(userData, ob, (ID **)&mmd->object, IDWALK_CB_NOP); +} + +static void updateDepsgraph(GpencilModifierData *md, + const ModifierUpdateDepsgraphContext *ctx, + const int UNUSED(mode)) +{ + OpacityGpencilModifierData *mmd = (OpacityGpencilModifierData *)md; + if (mmd->object != NULL) { + DEG_add_object_relation(ctx->node, mmd->object, DEG_OB_COMP_TRANSFORM, "Opacity Modifier"); + } + DEG_add_object_relation(ctx->node, ctx->object, DEG_OB_COMP_TRANSFORM, "Opacity Modifier"); } static void panel_draw(const bContext *UNUSED(C), Panel *panel) @@ -228,6 +282,20 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel) gpencil_modifier_panel_end(layout, ptr); } +static void fading_header_draw(const bContext *UNUSED(C), Panel *panel) +{ + uiLayout *layout = panel->layout; + + PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL); + + uiItemR(layout, ptr, "use_fading", 0, NULL, ICON_NONE); +} + +static void fading_panel_draw(const bContext *C, Panel *panel) +{ + gpencil_modifier_fading_draw(C, panel); +} + static void mask_panel_draw(const bContext *UNUSED(C), Panel *panel) { PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL); @@ -266,6 +334,9 @@ static void panelRegister(ARegionType *region_type) { PanelType *panel_type = gpencil_modifier_panel_register( region_type, eGpencilModifierType_Opacity, panel_draw); + + gpencil_modifier_subpanel_register( + region_type, "fading", "", fading_header_draw, fading_panel_draw, panel_type); PanelType *mask_panel_type = gpencil_modifier_subpanel_register( region_type, "mask", "Influence", NULL, mask_panel_draw, panel_type); gpencil_modifier_subpanel_register( @@ -289,7 +360,7 @@ GpencilModifierTypeInfo modifierType_Gpencil_Opacity = { /* initData */ initData, /* freeData */ freeData, /* isDisabled */ NULL, - /* updateDepsgraph */ NULL, + /* updateDepsgraph */ updateDepsgraph, /* dependsOnTime */ NULL, /* foreachIDLink */ foreachIDLink, /* foreachTexLink */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilthick.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilthick.c index a13f8d64755..512e3af063a 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilthick.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilthick.c @@ -43,6 +43,8 @@ #include "BKE_screen.h" #include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" +#include "DEG_depsgraph_query.h" #include "UI_interface.h" #include "UI_resources.h" @@ -128,6 +130,30 @@ static void deformStroke(GpencilModifierData *md, } float curvef = 1.0f; + + float factor_depth = 1.0f; + + if (mmd->flag & GP_THICK_FADING) { + if (mmd->object) { + float gvert[3]; + mul_v3_m4v3(gvert, ob->obmat, &pt->x); + float dist = len_v3v3(mmd->object->obmat[3], gvert); + float fading_max = MAX2(mmd->fading_start, mmd->fading_end); + float fading_min = MIN2(mmd->fading_start, mmd->fading_end); + + /* Better with ratiof() function from line art. */ + if (dist > fading_max) { + factor_depth = 0.0f; + } + else if (dist <= fading_max && dist > fading_min) { + factor_depth = (fading_max - dist) / (fading_max - fading_min); + } + else { + factor_depth = 1.0f; + } + } + } + if ((mmd->flag & GP_THICK_CUSTOM_CURVE) && (mmd->curve_thickness)) { /* Normalize value to evaluate curve. */ float value = (float)i / (gps->totpoints - 1); @@ -144,6 +170,9 @@ static void deformStroke(GpencilModifierData *md, weight *= curvef; } + float fac_begin = mmd->flag & GP_THICK_NORMALIZE ? 1 : mmd->thickness_fac; + target *= interpf(fac_begin, mmd->fading_end_factor, factor_depth); + pt->pressure = interpf(target, pt->pressure, weight); CLAMP_MIN(pt->pressure, 0.0f); @@ -171,6 +200,32 @@ static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk, ThickGpencilModifierData *mmd = (ThickGpencilModifierData *)md; walk(userData, ob, (ID **)&mmd->material, IDWALK_CB_USER); + walk(userData, ob, (ID **)&mmd->object, IDWALK_CB_NOP); +} + +static void updateDepsgraph(GpencilModifierData *md, + const ModifierUpdateDepsgraphContext *ctx, + const int UNUSED(mode)) +{ + ThickGpencilModifierData *mmd = (ThickGpencilModifierData *)md; + if (mmd->object != NULL) { + DEG_add_object_relation(ctx->node, mmd->object, DEG_OB_COMP_TRANSFORM, "Thickness Modifier"); + } + DEG_add_object_relation(ctx->node, ctx->object, DEG_OB_COMP_TRANSFORM, "Thickness Modifier"); +} + +static void fading_header_draw(const bContext *UNUSED(C), Panel *panel) +{ + uiLayout *layout = panel->layout; + + PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL); + + uiItemR(layout, ptr, "use_fading", 0, NULL, ICON_NONE); +} + +static void fading_panel_draw(const bContext *C, Panel *panel) +{ + gpencil_modifier_fading_draw(C, panel); } static void panel_draw(const bContext *UNUSED(C), Panel *panel) @@ -202,6 +257,8 @@ static void panelRegister(ARegionType *region_type) { PanelType *panel_type = gpencil_modifier_panel_register( region_type, eGpencilModifierType_Thick, panel_draw); + gpencil_modifier_subpanel_register( + region_type, "fading", "", fading_header_draw, fading_panel_draw, panel_type); PanelType *mask_panel_type = gpencil_modifier_subpanel_register( region_type, "mask", "Influence", NULL, mask_panel_draw, panel_type); gpencil_modifier_subpanel_register(region_type, @@ -229,7 +286,7 @@ GpencilModifierTypeInfo modifierType_Gpencil_Thick = { /* initData */ initData, /* freeData */ freeData, /* isDisabled */ NULL, - /* updateDepsgraph */ NULL, + /* updateDepsgraph */ updateDepsgraph, /* dependsOnTime */ NULL, /* foreachIDLink */ foreachIDLink, /* foreachTexLink */ NULL, diff --git a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h index 56cd7fa1456..712e92d017d 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h +++ b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h @@ -93,8 +93,8 @@ typedef struct LineartElementLinkNode { float crease_threshold; } LineartElementLinkNode; -typedef struct LineartLineSegment { - struct LineartLineSegment *next, *prev; +typedef struct LineartEdgeSegment { + struct LineartEdgeSegment *next, *prev; /** at==0: left at==1: right (this is in 2D projected space) */ double at; /** Occlusion level after "at" point */ @@ -107,7 +107,7 @@ typedef struct LineartLineSegment { * enough for most cases. */ unsigned char transparency_mask; -} LineartLineSegment; +} LineartEdgeSegment; typedef struct LineartVert { double gloc[3]; @@ -155,7 +155,7 @@ typedef struct LineartEdge { /** * Still need this entry because culled lines will not add to object - * #LineartElementLinkNode node (known as `reln` internally). + * #LineartElementLinkNode node (known as `eln` internally). * * TODO: If really need more savings, we can allocate this in a "extended" way too, but we need * another bit in flags to be able to show the difference. @@ -163,8 +163,8 @@ typedef struct LineartEdge { struct Object *object_ref; } LineartEdge; -typedef struct LineartLineChain { - struct LineartLineChain *next, *prev; +typedef struct LineartEdgeChain { + struct LineartEdgeChain *next, *prev; ListBase chain; /** Calculated before draw command. */ @@ -179,10 +179,10 @@ typedef struct LineartLineChain { unsigned char transparency_mask; struct Object *object_ref; -} LineartLineChain; +} LineartEdgeChain; -typedef struct LineartLineChainItem { - struct LineartLineChainItem *next, *prev; +typedef struct LineartEdgeChainItem { + struct LineartEdgeChainItem *next, *prev; /** Need z value for fading */ float pos[3]; /** For restoring position to 3d space */ @@ -192,12 +192,12 @@ typedef struct LineartLineChainItem { char occlusion; unsigned char transparency_mask; size_t index; -} LineartLineChainItem; +} LineartEdgeChainItem; typedef struct LineartChainRegisterEntry { struct LineartChainRegisterEntry *next, *prev; - LineartLineChain *rlc; - LineartLineChainItem *rlci; + LineartEdgeChain *ec; + LineartEdgeChainItem *eci; char picked; /* left/right mark. @@ -205,6 +205,17 @@ typedef struct LineartChainRegisterEntry { char is_left; } LineartChainRegisterEntry; +enum eLineArtTileRecursiveLimit { + /* If tile gets this small, it's already much smaller than a pixel. No need to continue + * splitting. */ + LRT_TILE_RECURSIVE_PERSPECTIVE = 30, + /* This is a tried-and-true safe value for high poly models that also needed ortho rendering. */ + LRT_TILE_RECURSIVE_ORTHO = 10, +}; + +#define LRT_TILE_SPLITTING_TRIANGLE_LIMIT 100 +#define LRT_TILE_EDGE_COUNT_INITIAL 32 + typedef struct LineartRenderBuffer { struct LineartRenderBuffer *prev, *next; @@ -219,6 +230,11 @@ typedef struct LineartRenderBuffer { struct LineartBoundingArea *initial_bounding_areas; unsigned int bounding_area_count; + /* When splitting bounding areas, if there's an ortho camera placed at a straight angle, there + * will be a lot of triangles aligned in line which can not be separated by continue subdividing + * the tile. So we set a strict limit when using ortho camera. See eLineArtTileRecursiveLimit. */ + int tile_recursive_level; + ListBase vertex_buffer_pointers; ListBase line_buffer_pointers; ListBase triangle_buffer_pointers; @@ -237,31 +253,14 @@ typedef struct LineartRenderBuffer { int triangle_size; - unsigned int contour_count; - unsigned int contour_processed; - LineartEdge *contour_managed; - /** A single linked list (cast to #LinkNode). */ - LineartEdge *contours; - - unsigned int intersection_count; - unsigned int intersection_processed; - LineartEdge *intersection_managed; - LineartEdge *intersection_lines; - - unsigned int crease_count; - unsigned int crease_processed; - LineartEdge *crease_managed; - LineartEdge *crease_lines; - - unsigned int material_line_count; - unsigned int material_processed; - LineartEdge *material_managed; - LineartEdge *material_lines; - - unsigned int edge_mark_count; - unsigned int edge_mark_processed; - LineartEdge *edge_mark_managed; - LineartEdge *edge_marks; + /* Although using ListBase here, LineartEdge is single linked list. + * list.last is used to store worker progress along the list. + * See lineart_main_occlusion_begin() for more info. */ + ListBase contour; + ListBase intersection; + ListBase crease; + ListBase material; + ListBase edge_mark; ListBase chains; @@ -322,9 +321,11 @@ typedef enum eLineartTriangleFlags { LRT_TRIANGLE_NO_INTERSECTION = (1 << 4), } eLineartTriangleFlags; -/** Controls how many edges a worker thread is processing at one request. +/** + * Controls how many edges a worker thread is processing at one request. * There's no significant performance impact on choosing different values. - * Don't make it too small so that the worker thread won't request too many times. */ + * Don't make it too small so that the worker thread won't request too many times. + */ #define LRT_THREAD_EDGE_COUNT 1000 typedef struct LineartRenderTaskInfo { @@ -332,20 +333,13 @@ typedef struct LineartRenderTaskInfo { int thread_id; - LineartEdge *contour; - LineartEdge *contour_end; - - LineartEdge *intersection; - LineartEdge *intersection_end; - - LineartEdge *crease; - LineartEdge *crease_end; - - LineartEdge *material; - LineartEdge *material_end; - - LineartEdge *edge_mark; - LineartEdge *edge_mark_end; + /* These lists only denote the part of the main edge list that the thread should iterate over. + * Be careful to not iterate outside of these bounds as it is not thread safe to do so. */ + ListBase contour; + ListBase intersection; + ListBase crease; + ListBase material; + ListBase edge_mark; } LineartRenderTaskInfo; @@ -385,10 +379,14 @@ typedef struct LineartBoundingArea { ListBase up; ListBase bp; - short triangle_count; + int16_t triangle_count; + int16_t max_triangle_count; + int16_t line_count; + int16_t max_line_count; - ListBase linked_triangles; - ListBase linked_lines; + /* Use array for speeding up multiple accesses. */ + struct LineartTriangle **linked_triangles; + struct LineartEdge **linked_lines; /** Reserved for image space reduction && multi-thread chaining. */ ListBase linked_chains; @@ -527,7 +525,7 @@ void MOD_lineart_chain_connect(LineartRenderBuffer *rb); void MOD_lineart_chain_discard_short(LineartRenderBuffer *rb, const float threshold); void MOD_lineart_chain_split_angle(LineartRenderBuffer *rb, float angle_threshold_rad); -int MOD_lineart_chain_count(const LineartLineChain *rlc); +int MOD_lineart_chain_count(const LineartEdgeChain *ec); void MOD_lineart_chain_clear_picked_flag(struct LineartRenderBuffer *rb); bool MOD_lineart_compute_feature_lines(struct Depsgraph *depsgraph, @@ -563,6 +561,6 @@ void MOD_lineart_gpencil_generate(LineartRenderBuffer *rb, const char *vgname, int modifier_flags); -float MOD_lineart_chain_compute_length(LineartLineChain *rlc); +float MOD_lineart_chain_compute_length(LineartEdgeChain *ec); void ED_operatortypes_lineart(void); diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c index 464316b6a10..23928b4ccda 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c @@ -31,17 +31,17 @@ #include <math.h> -#define LRT_OTHER_RV(e, rv) ((rv) == (e)->v1 ? (e)->v2 : ((rv) == (e)->v2 ? (e)->v1 : NULL)) +#define LRT_OTHER_VERT(e, vt) ((vt) == (e)->v1 ? (e)->v2 : ((vt) == (e)->v2 ? (e)->v1 : NULL)) /* Get a connected line, only for lines who has the exact given vert, or (in the case of * intersection lines) who has a vert that has the exact same position. */ static LineartEdge *lineart_line_get_connected(LineartBoundingArea *ba, - LineartVert *rv, - LineartVert **new_rv, + LineartVert *vt, + LineartVert **new_vt, int match_flag) { - LISTBASE_FOREACH (LinkData *, lip, &ba->linked_lines) { - LineartEdge *n_e = lip->data; + for (int i = 0; i < ba->line_count; i++) { + LineartEdge *n_e = ba->linked_lines[i]; if ((!(n_e->flags & LRT_EDGE_FLAG_ALL_TYPE)) || (n_e->flags & LRT_EDGE_FLAG_CHAIN_PICKED)) { continue; @@ -51,18 +51,18 @@ static LineartEdge *lineart_line_get_connected(LineartBoundingArea *ba, continue; } - *new_rv = LRT_OTHER_RV(n_e, rv); - if (*new_rv) { + *new_vt = LRT_OTHER_VERT(n_e, vt); + if (*new_vt) { return n_e; } if (n_e->flags & LRT_EDGE_FLAG_INTERSECTION) { - if (rv->fbcoord[0] == n_e->v1->fbcoord[0] && rv->fbcoord[1] == n_e->v1->fbcoord[1]) { - *new_rv = LRT_OTHER_RV(n_e, n_e->v1); + if (vt->fbcoord[0] == n_e->v1->fbcoord[0] && vt->fbcoord[1] == n_e->v1->fbcoord[1]) { + *new_vt = LRT_OTHER_VERT(n_e, n_e->v1); return n_e; } - if (rv->fbcoord[0] == n_e->v2->fbcoord[0] && rv->fbcoord[1] == n_e->v2->fbcoord[1]) { - *new_rv = LRT_OTHER_RV(n_e, n_e->v2); + if (vt->fbcoord[0] == n_e->v2->fbcoord[0] && vt->fbcoord[1] == n_e->v2->fbcoord[1]) { + *new_vt = LRT_OTHER_VERT(n_e, n_e->v2); return n_e; } } @@ -71,33 +71,33 @@ static LineartEdge *lineart_line_get_connected(LineartBoundingArea *ba, return NULL; } -static LineartLineChain *lineart_chain_create(LineartRenderBuffer *rb) +static LineartEdgeChain *lineart_chain_create(LineartRenderBuffer *rb) { - LineartLineChain *rlc; - rlc = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartLineChain)); + LineartEdgeChain *ec; + ec = lineart_mem_acquire(&rb->render_data_pool, sizeof(LineartEdgeChain)); - BLI_addtail(&rb->chains, rlc); + BLI_addtail(&rb->chains, ec); - return rlc; + return ec; } -static bool lineart_point_overlapping(LineartLineChainItem *rlci, +static bool lineart_point_overlapping(LineartEdgeChainItem *eci, float x, float y, double threshold) { - if (!rlci) { + if (!eci) { return false; } - if (((rlci->pos[0] + threshold) >= x) && ((rlci->pos[0] - threshold) <= x) && - ((rlci->pos[1] + threshold) >= y) && ((rlci->pos[1] - threshold) <= y)) { + if (((eci->pos[0] + threshold) >= x) && ((eci->pos[0] - threshold) <= x) && + ((eci->pos[1] + threshold) >= y) && ((eci->pos[1] - threshold) <= y)) { return true; } return false; } -static LineartLineChainItem *lineart_chain_append_point(LineartRenderBuffer *rb, - LineartLineChain *rlc, +static LineartEdgeChainItem *lineart_chain_append_point(LineartRenderBuffer *rb, + LineartEdgeChain *ec, float *fbcoord, float *gpos, float *normal, @@ -106,35 +106,35 @@ static LineartLineChainItem *lineart_chain_append_point(LineartRenderBuffer *rb, unsigned char transparency_mask, size_t index) { - LineartLineChainItem *rlci; + LineartEdgeChainItem *eci; - if (lineart_point_overlapping(rlc->chain.last, fbcoord[0], fbcoord[1], 1e-5)) { + if (lineart_point_overlapping(ec->chain.last, fbcoord[0], fbcoord[1], 1e-5)) { /* Because the new chain point is overlapping, just replace the type and occlusion level of the * current point. This makes it so that the line to the point after this one has the correct * type and level. */ - LineartLineChainItem *old_rlci = rlc->chain.last; + LineartEdgeChainItem *old_rlci = ec->chain.last; old_rlci->line_type = type; old_rlci->occlusion = level; old_rlci->transparency_mask = transparency_mask; return old_rlci; } - rlci = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartLineChainItem)); + eci = lineart_mem_acquire(&rb->render_data_pool, sizeof(LineartEdgeChainItem)); - copy_v2_v2(rlci->pos, fbcoord); - copy_v3_v3(rlci->gpos, gpos); - rlci->index = index; - copy_v3_v3(rlci->normal, normal); - rlci->line_type = type & LRT_EDGE_FLAG_ALL_TYPE; - rlci->occlusion = level; - rlci->transparency_mask = transparency_mask; - BLI_addtail(&rlc->chain, rlci); + copy_v2_v2(eci->pos, fbcoord); + copy_v3_v3(eci->gpos, gpos); + eci->index = index; + copy_v3_v3(eci->normal, normal); + eci->line_type = type & LRT_EDGE_FLAG_ALL_TYPE; + eci->occlusion = level; + eci->transparency_mask = transparency_mask; + BLI_addtail(&ec->chain, eci); - return rlci; + return eci; } -static LineartLineChainItem *lineart_chain_prepend_point(LineartRenderBuffer *rb, - LineartLineChain *rlc, +static LineartEdgeChainItem *lineart_chain_prepend_point(LineartRenderBuffer *rb, + LineartEdgeChain *ec, float *fbcoord, float *gpos, float *normal, @@ -143,32 +143,32 @@ static LineartLineChainItem *lineart_chain_prepend_point(LineartRenderBuffer *rb unsigned char transparency_mask, size_t index) { - LineartLineChainItem *rlci; + LineartEdgeChainItem *eci; - if (lineart_point_overlapping(rlc->chain.first, fbcoord[0], fbcoord[1], 1e-5)) { - return rlc->chain.first; + if (lineart_point_overlapping(ec->chain.first, fbcoord[0], fbcoord[1], 1e-5)) { + return ec->chain.first; } - rlci = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartLineChainItem)); + eci = lineart_mem_acquire(&rb->render_data_pool, sizeof(LineartEdgeChainItem)); - copy_v2_v2(rlci->pos, fbcoord); - copy_v3_v3(rlci->gpos, gpos); - rlci->index = index; - copy_v3_v3(rlci->normal, normal); - rlci->line_type = type & LRT_EDGE_FLAG_ALL_TYPE; - rlci->occlusion = level; - rlci->transparency_mask = transparency_mask; - BLI_addhead(&rlc->chain, rlci); + copy_v2_v2(eci->pos, fbcoord); + copy_v3_v3(eci->gpos, gpos); + eci->index = index; + copy_v3_v3(eci->normal, normal); + eci->line_type = type & LRT_EDGE_FLAG_ALL_TYPE; + eci->occlusion = level; + eci->transparency_mask = transparency_mask; + BLI_addhead(&ec->chain, eci); - return rlci; + return eci; } void MOD_lineart_chain_feature_lines(LineartRenderBuffer *rb) { - LineartLineChain *rlc; - LineartLineChainItem *rlci; + LineartEdgeChain *ec; + LineartEdgeChainItem *eci; LineartBoundingArea *ba; - LineartLineSegment *rls; + LineartEdgeSegment *es; int last_occlusion; unsigned char last_transparency; /* Used when converting from double. */ @@ -192,14 +192,14 @@ void MOD_lineart_chain_feature_lines(LineartRenderBuffer *rb) e->flags |= LRT_EDGE_FLAG_CHAIN_PICKED; - rlc = lineart_chain_create(rb); + ec = lineart_chain_create(rb); /* One chain can only have one object_ref, * so we assign it based on the first segment we found. */ - rlc->object_ref = e->object_ref; + ec->object_ref = e->object_ref; LineartEdge *new_e = e; - LineartVert *new_rv; + LineartVert *new_vt; float N[3] = {0}; if (e->t1) { @@ -218,19 +218,19 @@ void MOD_lineart_chain_feature_lines(LineartRenderBuffer *rb) /* Step 1: grow left. */ ba = MOD_lineart_get_bounding_area(rb, e->v1->fbcoord[0], e->v1->fbcoord[1]); - new_rv = e->v1; - rls = e->segments.first; - VERT_COORD_TO_FLOAT(new_rv); + new_vt = e->v1; + es = e->segments.first; + VERT_COORD_TO_FLOAT(new_vt); lineart_chain_prepend_point(rb, - rlc, + ec, use_fbcoord, use_gpos, N, e->flags, - rls->occlusion, - rls->transparency_mask, + es->occlusion, + es->transparency_mask, e->v1_obindex); - while (ba && (new_e = lineart_line_get_connected(ba, new_rv, &new_rv, e->flags))) { + while (ba && (new_e = lineart_line_get_connected(ba, new_vt, &new_vt, e->flags))) { new_e->flags |= LRT_EDGE_FLAG_CHAIN_PICKED; if (new_e->t1 || new_e->t2) { @@ -248,41 +248,41 @@ void MOD_lineart_chain_feature_lines(LineartRenderBuffer *rb) normalize_v3(N); } - if (new_rv == new_e->v1) { - for (rls = new_e->segments.last; rls; rls = rls->prev) { + if (new_vt == new_e->v1) { + for (es = new_e->segments.last; es; es = es->prev) { double gpos[3], lpos[3]; double *lfb = new_e->v1->fbcoord, *rfb = new_e->v2->fbcoord; - double global_at = lfb[3] * rls->at / (rls->at * lfb[3] + (1 - rls->at) * rfb[3]); - interp_v3_v3v3_db(lpos, new_e->v1->fbcoord, new_e->v2->fbcoord, rls->at); + double global_at = lfb[3] * es->at / (es->at * lfb[3] + (1 - es->at) * rfb[3]); + interp_v3_v3v3_db(lpos, new_e->v1->fbcoord, new_e->v2->fbcoord, es->at); interp_v3_v3v3_db(gpos, new_e->v1->gloc, new_e->v2->gloc, global_at); POS_TO_FLOAT(lpos, gpos) lineart_chain_prepend_point(rb, - rlc, + ec, use_fbcoord, use_gpos, N, new_e->flags, - rls->occlusion, - rls->transparency_mask, + es->occlusion, + es->transparency_mask, new_e->v1_obindex); - last_occlusion = rls->occlusion; - last_transparency = rls->transparency_mask; + last_occlusion = es->occlusion; + last_transparency = es->transparency_mask; } } - else if (new_rv == new_e->v2) { - rls = new_e->segments.first; - last_occlusion = rls->occlusion; - last_transparency = rls->transparency_mask; - rls = rls->next; - for (; rls; rls = rls->next) { + else if (new_vt == new_e->v2) { + es = new_e->segments.first; + last_occlusion = es->occlusion; + last_transparency = es->transparency_mask; + es = es->next; + for (; es; es = es->next) { double gpos[3], lpos[3]; double *lfb = new_e->v1->fbcoord, *rfb = new_e->v2->fbcoord; - double global_at = lfb[3] * rls->at / (rls->at * lfb[3] + (1 - rls->at) * rfb[3]); - interp_v3_v3v3_db(lpos, new_e->v1->fbcoord, new_e->v2->fbcoord, rls->at); + double global_at = lfb[3] * es->at / (es->at * lfb[3] + (1 - es->at) * rfb[3]); + interp_v3_v3v3_db(lpos, new_e->v1->fbcoord, new_e->v2->fbcoord, es->at); interp_v3_v3v3_db(gpos, new_e->v1->gloc, new_e->v2->gloc, global_at); POS_TO_FLOAT(lpos, gpos) lineart_chain_prepend_point(rb, - rlc, + ec, use_fbcoord, use_gpos, N, @@ -290,12 +290,12 @@ void MOD_lineart_chain_feature_lines(LineartRenderBuffer *rb) last_occlusion, last_transparency, new_e->v2_obindex); - last_occlusion = rls->occlusion; - last_transparency = rls->transparency_mask; + last_occlusion = es->occlusion; + last_transparency = es->transparency_mask; } VERT_COORD_TO_FLOAT(new_e->v2); lineart_chain_prepend_point(rb, - rlc, + ec, use_fbcoord, use_gpos, N, @@ -304,7 +304,7 @@ void MOD_lineart_chain_feature_lines(LineartRenderBuffer *rb) last_transparency, new_e->v2_obindex); } - ba = MOD_lineart_get_bounding_area(rb, new_rv->fbcoord[0], new_rv->fbcoord[1]); + ba = MOD_lineart_get_bounding_area(rb, new_vt->fbcoord[0], new_vt->fbcoord[1]); } /* Restore normal value. */ @@ -324,31 +324,31 @@ void MOD_lineart_chain_feature_lines(LineartRenderBuffer *rb) } /* Step 2: Adding all cuts from the given line, so we can continue connecting the right side * of the line. */ - rls = e->segments.first; - last_occlusion = ((LineartLineSegment *)rls)->occlusion; - last_transparency = ((LineartLineSegment *)rls)->transparency_mask; - for (rls = rls->next; rls; rls = rls->next) { + es = e->segments.first; + last_occlusion = ((LineartEdgeSegment *)es)->occlusion; + last_transparency = ((LineartEdgeSegment *)es)->transparency_mask; + for (es = es->next; es; es = es->next) { double gpos[3], lpos[3]; double *lfb = e->v1->fbcoord, *rfb = e->v2->fbcoord; - double global_at = lfb[3] * rls->at / (rls->at * lfb[3] + (1 - rls->at) * rfb[3]); - interp_v3_v3v3_db(lpos, e->v1->fbcoord, e->v2->fbcoord, rls->at); + double global_at = lfb[3] * es->at / (es->at * lfb[3] + (1 - es->at) * rfb[3]); + interp_v3_v3v3_db(lpos, e->v1->fbcoord, e->v2->fbcoord, es->at); interp_v3_v3v3_db(gpos, e->v1->gloc, e->v2->gloc, global_at); POS_TO_FLOAT(lpos, gpos) lineart_chain_append_point(rb, - rlc, + ec, use_fbcoord, use_gpos, N, e->flags, - rls->occlusion, - rls->transparency_mask, + es->occlusion, + es->transparency_mask, e->v1_obindex); - last_occlusion = rls->occlusion; - last_transparency = rls->transparency_mask; + last_occlusion = es->occlusion; + last_transparency = es->transparency_mask; } VERT_COORD_TO_FLOAT(e->v2) lineart_chain_append_point(rb, - rlc, + ec, use_fbcoord, use_gpos, N, @@ -359,8 +359,8 @@ void MOD_lineart_chain_feature_lines(LineartRenderBuffer *rb) /* Step 3: grow right. */ ba = MOD_lineart_get_bounding_area(rb, e->v2->fbcoord[0], e->v2->fbcoord[1]); - new_rv = e->v2; - while (ba && (new_e = lineart_line_get_connected(ba, new_rv, &new_rv, e->flags))) { + new_vt = e->v2; + while (ba && (new_e = lineart_line_get_connected(ba, new_vt, &new_vt, e->flags))) { new_e->flags |= LRT_EDGE_FLAG_CHAIN_PICKED; if (new_e->t1 || new_e->t2) { @@ -379,27 +379,27 @@ void MOD_lineart_chain_feature_lines(LineartRenderBuffer *rb) } /* Fix leading vertex type. */ - rlci = rlc->chain.last; - rlci->line_type = new_e->flags & LRT_EDGE_FLAG_ALL_TYPE; + eci = ec->chain.last; + eci->line_type = new_e->flags & LRT_EDGE_FLAG_ALL_TYPE; - if (new_rv == new_e->v1) { - rls = new_e->segments.last; - last_occlusion = rls->occlusion; - last_transparency = rls->transparency_mask; + if (new_vt == new_e->v1) { + es = new_e->segments.last; + last_occlusion = es->occlusion; + last_transparency = es->transparency_mask; /* Fix leading vertex occlusion. */ - rlci->occlusion = last_occlusion; - rlci->transparency_mask = last_transparency; - for (rls = new_e->segments.last; rls; rls = rls->prev) { + eci->occlusion = last_occlusion; + eci->transparency_mask = last_transparency; + for (es = new_e->segments.last; es; es = es->prev) { double gpos[3], lpos[3]; double *lfb = new_e->v1->fbcoord, *rfb = new_e->v2->fbcoord; - double global_at = lfb[3] * rls->at / (rls->at * lfb[3] + (1 - rls->at) * rfb[3]); - interp_v3_v3v3_db(lpos, new_e->v1->fbcoord, new_e->v2->fbcoord, rls->at); + double global_at = lfb[3] * es->at / (es->at * lfb[3] + (1 - es->at) * rfb[3]); + interp_v3_v3v3_db(lpos, new_e->v1->fbcoord, new_e->v2->fbcoord, es->at); interp_v3_v3v3_db(gpos, new_e->v1->gloc, new_e->v2->gloc, global_at); - last_occlusion = rls->prev ? rls->prev->occlusion : last_occlusion; - last_transparency = rls->prev ? rls->prev->transparency_mask : last_transparency; + last_occlusion = es->prev ? es->prev->occlusion : last_occlusion; + last_transparency = es->prev ? es->prev->transparency_mask : last_transparency; POS_TO_FLOAT(lpos, gpos) lineart_chain_append_point(rb, - rlc, + ec, use_fbcoord, use_gpos, N, @@ -409,35 +409,35 @@ void MOD_lineart_chain_feature_lines(LineartRenderBuffer *rb) new_e->v1_obindex); } } - else if (new_rv == new_e->v2) { - rls = new_e->segments.first; - last_occlusion = rls->occlusion; - last_transparency = rls->transparency_mask; - rlci->occlusion = last_occlusion; - rlci->transparency_mask = last_transparency; - rls = rls->next; - for (; rls; rls = rls->next) { + else if (new_vt == new_e->v2) { + es = new_e->segments.first; + last_occlusion = es->occlusion; + last_transparency = es->transparency_mask; + eci->occlusion = last_occlusion; + eci->transparency_mask = last_transparency; + es = es->next; + for (; es; es = es->next) { double gpos[3], lpos[3]; double *lfb = new_e->v1->fbcoord, *rfb = new_e->v2->fbcoord; - double global_at = lfb[3] * rls->at / (rls->at * lfb[3] + (1 - rls->at) * rfb[3]); - interp_v3_v3v3_db(lpos, new_e->v1->fbcoord, new_e->v2->fbcoord, rls->at); + double global_at = lfb[3] * es->at / (es->at * lfb[3] + (1 - es->at) * rfb[3]); + interp_v3_v3v3_db(lpos, new_e->v1->fbcoord, new_e->v2->fbcoord, es->at); interp_v3_v3v3_db(gpos, new_e->v1->gloc, new_e->v2->gloc, global_at); POS_TO_FLOAT(lpos, gpos) lineart_chain_append_point(rb, - rlc, + ec, use_fbcoord, use_gpos, N, new_e->flags, - rls->occlusion, - rls->transparency_mask, + es->occlusion, + es->transparency_mask, new_e->v2_obindex); - last_occlusion = rls->occlusion; - last_transparency = rls->transparency_mask; + last_occlusion = es->occlusion; + last_transparency = es->transparency_mask; } VERT_COORD_TO_FLOAT(new_e->v2) lineart_chain_append_point(rb, - rlc, + ec, use_fbcoord, use_gpos, N, @@ -446,57 +446,57 @@ void MOD_lineart_chain_feature_lines(LineartRenderBuffer *rb) last_transparency, new_e->v2_obindex); } - ba = MOD_lineart_get_bounding_area(rb, new_rv->fbcoord[0], new_rv->fbcoord[1]); + ba = MOD_lineart_get_bounding_area(rb, new_vt->fbcoord[0], new_vt->fbcoord[1]); } if (rb->fuzzy_everything) { - rlc->type = LRT_EDGE_FLAG_CONTOUR; + ec->type = LRT_EDGE_FLAG_CONTOUR; } else { - rlc->type = (e->flags & LRT_EDGE_FLAG_ALL_TYPE); + ec->type = (e->flags & LRT_EDGE_FLAG_ALL_TYPE); } } LRT_ITER_ALL_LINES_END } -static LineartBoundingArea *lineart_bounding_area_get_rlci_recursive(LineartRenderBuffer *rb, - LineartBoundingArea *root, - LineartLineChainItem *rlci) +static LineartBoundingArea *lineart_bounding_area_get_eci_recursive(LineartRenderBuffer *rb, + LineartBoundingArea *root, + LineartEdgeChainItem *eci) { if (root->child == NULL) { return root; } LineartBoundingArea *ch = root->child; -#define IN_BOUND(ba, rlci) \ - ba.l <= rlci->pos[0] && ba.r >= rlci->pos[0] && ba.b <= rlci->pos[1] && ba.u >= rlci->pos[1] +#define IN_BOUND(ba, eci) \ + ba.l <= eci->pos[0] && ba.r >= eci->pos[0] && ba.b <= eci->pos[1] && ba.u >= eci->pos[1] - if (IN_BOUND(ch[0], rlci)) { - return lineart_bounding_area_get_rlci_recursive(rb, &ch[0], rlci); + if (IN_BOUND(ch[0], eci)) { + return lineart_bounding_area_get_eci_recursive(rb, &ch[0], eci); } - if (IN_BOUND(ch[1], rlci)) { - return lineart_bounding_area_get_rlci_recursive(rb, &ch[1], rlci); + if (IN_BOUND(ch[1], eci)) { + return lineart_bounding_area_get_eci_recursive(rb, &ch[1], eci); } - if (IN_BOUND(ch[2], rlci)) { - return lineart_bounding_area_get_rlci_recursive(rb, &ch[2], rlci); + if (IN_BOUND(ch[2], eci)) { + return lineart_bounding_area_get_eci_recursive(rb, &ch[2], eci); } - if (IN_BOUND(ch[3], rlci)) { - return lineart_bounding_area_get_rlci_recursive(rb, &ch[3], rlci); + if (IN_BOUND(ch[3], eci)) { + return lineart_bounding_area_get_eci_recursive(rb, &ch[3], eci); } #undef IN_BOUND return NULL; } static LineartBoundingArea *lineart_bounding_area_get_end_point(LineartRenderBuffer *rb, - LineartLineChainItem *rlci) + LineartEdgeChainItem *eci) { - if (!rlci) { + if (!eci) { return NULL; } - LineartBoundingArea *root = MOD_lineart_get_parent_bounding_area(rb, rlci->pos[0], rlci->pos[1]); + LineartBoundingArea *root = MOD_lineart_get_parent_bounding_area(rb, eci->pos[0], eci->pos[1]); if (root == NULL) { return NULL; } - return lineart_bounding_area_get_rlci_recursive(rb, root, rlci); + return lineart_bounding_area_get_eci_recursive(rb, root, eci); } /** @@ -507,61 +507,61 @@ static LineartBoundingArea *lineart_bounding_area_get_end_point(LineartRenderBuf */ static void lineart_bounding_area_link_point_recursive(LineartRenderBuffer *rb, LineartBoundingArea *root, - LineartLineChain *rlc, - LineartLineChainItem *rlci) + LineartEdgeChain *ec, + LineartEdgeChainItem *eci) { if (root->child == NULL) { LineartChainRegisterEntry *cre = lineart_list_append_pointer_pool_sized( - &root->linked_chains, &rb->render_data_pool, rlc, sizeof(LineartChainRegisterEntry)); + &root->linked_chains, &rb->render_data_pool, ec, sizeof(LineartChainRegisterEntry)); - cre->rlci = rlci; + cre->eci = eci; - if (rlci == rlc->chain.first) { + if (eci == ec->chain.first) { cre->is_left = 1; } } else { LineartBoundingArea *ch = root->child; -#define IN_BOUND(ba, rlci) \ - ba.l <= rlci->pos[0] && ba.r >= rlci->pos[0] && ba.b <= rlci->pos[1] && ba.u >= rlci->pos[1] +#define IN_BOUND(ba, eci) \ + ba.l <= eci->pos[0] && ba.r >= eci->pos[0] && ba.b <= eci->pos[1] && ba.u >= eci->pos[1] - if (IN_BOUND(ch[0], rlci)) { - lineart_bounding_area_link_point_recursive(rb, &ch[0], rlc, rlci); + if (IN_BOUND(ch[0], eci)) { + lineart_bounding_area_link_point_recursive(rb, &ch[0], ec, eci); } - else if (IN_BOUND(ch[1], rlci)) { - lineart_bounding_area_link_point_recursive(rb, &ch[1], rlc, rlci); + else if (IN_BOUND(ch[1], eci)) { + lineart_bounding_area_link_point_recursive(rb, &ch[1], ec, eci); } - else if (IN_BOUND(ch[2], rlci)) { - lineart_bounding_area_link_point_recursive(rb, &ch[2], rlc, rlci); + else if (IN_BOUND(ch[2], eci)) { + lineart_bounding_area_link_point_recursive(rb, &ch[2], ec, eci); } - else if (IN_BOUND(ch[3], rlci)) { - lineart_bounding_area_link_point_recursive(rb, &ch[3], rlc, rlci); + else if (IN_BOUND(ch[3], eci)) { + lineart_bounding_area_link_point_recursive(rb, &ch[3], ec, eci); } #undef IN_BOUND } } -static void lineart_bounding_area_link_chain(LineartRenderBuffer *rb, LineartLineChain *rlc) +static void lineart_bounding_area_link_chain(LineartRenderBuffer *rb, LineartEdgeChain *ec) { - LineartLineChainItem *pl = rlc->chain.first; - LineartLineChainItem *pr = rlc->chain.last; + LineartEdgeChainItem *pl = ec->chain.first; + LineartEdgeChainItem *pr = ec->chain.last; LineartBoundingArea *ba1 = MOD_lineart_get_parent_bounding_area(rb, pl->pos[0], pl->pos[1]); LineartBoundingArea *ba2 = MOD_lineart_get_parent_bounding_area(rb, pr->pos[0], pr->pos[1]); if (ba1) { - lineart_bounding_area_link_point_recursive(rb, ba1, rlc, pl); + lineart_bounding_area_link_point_recursive(rb, ba1, ec, pl); } if (ba2) { - lineart_bounding_area_link_point_recursive(rb, ba2, rlc, pr); + lineart_bounding_area_link_point_recursive(rb, ba2, ec, pr); } } void MOD_lineart_chain_split_for_fixed_occlusion(LineartRenderBuffer *rb) { - LineartLineChain *rlc, *new_rlc; - LineartLineChainItem *rlci, *next_rlci; + LineartEdgeChain *ec, *new_rlc; + LineartEdgeChainItem *eci, *next_rlci; ListBase swap = {0}; swap.first = rb->chains.first; @@ -569,59 +569,59 @@ void MOD_lineart_chain_split_for_fixed_occlusion(LineartRenderBuffer *rb) rb->chains.last = rb->chains.first = NULL; - while ((rlc = BLI_pophead(&swap)) != NULL) { - rlc->next = rlc->prev = NULL; - BLI_addtail(&rb->chains, rlc); - LineartLineChainItem *first_rlci = (LineartLineChainItem *)rlc->chain.first; + while ((ec = BLI_pophead(&swap)) != NULL) { + ec->next = ec->prev = NULL; + BLI_addtail(&rb->chains, ec); + LineartEdgeChainItem *first_rlci = (LineartEdgeChainItem *)ec->chain.first; int fixed_occ = first_rlci->occlusion; unsigned char fixed_mask = first_rlci->transparency_mask; - rlc->level = fixed_occ; - rlc->transparency_mask = fixed_mask; - for (rlci = first_rlci->next; rlci; rlci = next_rlci) { - next_rlci = rlci->next; - if (rlci->occlusion != fixed_occ || rlci->transparency_mask != fixed_mask) { + ec->level = fixed_occ; + ec->transparency_mask = fixed_mask; + for (eci = first_rlci->next; eci; eci = next_rlci) { + next_rlci = eci->next; + if (eci->occlusion != fixed_occ || eci->transparency_mask != fixed_mask) { if (next_rlci) { - if (lineart_point_overlapping(next_rlci, rlci->pos[0], rlci->pos[1], 1e-5)) { + if (lineart_point_overlapping(next_rlci, eci->pos[0], eci->pos[1], 1e-5)) { continue; } } else { /* Set the same occlusion level for the end vertex, so when further connection is needed * the backwards occlusion info is also correct. */ - rlci->occlusion = fixed_occ; - rlci->transparency_mask = fixed_mask; + eci->occlusion = fixed_occ; + eci->transparency_mask = fixed_mask; /* No need to split at the last point anyway. */ break; } new_rlc = lineart_chain_create(rb); - new_rlc->chain.first = rlci; - new_rlc->chain.last = rlc->chain.last; - rlc->chain.last = rlci->prev; - ((LineartLineChainItem *)rlc->chain.last)->next = 0; - rlci->prev = 0; + new_rlc->chain.first = eci; + new_rlc->chain.last = ec->chain.last; + ec->chain.last = eci->prev; + ((LineartEdgeChainItem *)ec->chain.last)->next = 0; + eci->prev = 0; /* End the previous one. */ lineart_chain_append_point(rb, - rlc, - rlci->pos, - rlci->gpos, - rlci->normal, - rlci->line_type, + ec, + eci->pos, + eci->gpos, + eci->normal, + eci->line_type, fixed_occ, fixed_mask, - rlci->index); - new_rlc->object_ref = rlc->object_ref; - new_rlc->type = rlc->type; - rlc = new_rlc; - fixed_occ = rlci->occlusion; - fixed_mask = rlci->transparency_mask; - rlc->level = fixed_occ; - rlc->transparency_mask = fixed_mask; + eci->index); + new_rlc->object_ref = ec->object_ref; + new_rlc->type = ec->type; + ec = new_rlc; + fixed_occ = eci->occlusion; + fixed_mask = eci->transparency_mask; + ec->level = fixed_occ; + ec->transparency_mask = fixed_mask; } } } - LISTBASE_FOREACH (LineartLineChain *, irlc, &rb->chains) { - lineart_bounding_area_link_chain(rb, irlc); + LISTBASE_FOREACH (LineartEdgeChain *, iec, &rb->chains) { + lineart_bounding_area_link_chain(rb, iec); } } @@ -629,12 +629,12 @@ void MOD_lineart_chain_split_for_fixed_occlusion(LineartRenderBuffer *rb) * Note: segment type (crease/material/contour...) is ambiguous after this. */ static void lineart_chain_connect(LineartRenderBuffer *UNUSED(rb), - LineartLineChain *onto, - LineartLineChain *sub, + LineartEdgeChain *onto, + LineartEdgeChain *sub, int reverse_1, int reverse_2) { - LineartLineChainItem *rlci; + LineartEdgeChainItem *eci; if (onto->type == LRT_EDGE_FLAG_INTERSECTION) { if (sub->object_ref) { onto->object_ref = sub->object_ref; @@ -650,38 +650,38 @@ static void lineart_chain_connect(LineartRenderBuffer *UNUSED(rb), if (reverse_2) { /* L--R R-L. */ BLI_listbase_reverse(&sub->chain); } - rlci = sub->chain.first; - if (lineart_point_overlapping(onto->chain.last, rlci->pos[0], rlci->pos[1], 1e-5)) { + eci = sub->chain.first; + if (lineart_point_overlapping(onto->chain.last, eci->pos[0], eci->pos[1], 1e-5)) { BLI_pophead(&sub->chain); if (sub->chain.first == NULL) { return; } } - ((LineartLineChainItem *)onto->chain.last)->next = sub->chain.first; - ((LineartLineChainItem *)sub->chain.first)->prev = onto->chain.last; + ((LineartEdgeChainItem *)onto->chain.last)->next = sub->chain.first; + ((LineartEdgeChainItem *)sub->chain.first)->prev = onto->chain.last; onto->chain.last = sub->chain.last; } else { /* L-R L--R. */ if (!reverse_2) { /* R-L L--R. */ BLI_listbase_reverse(&sub->chain); } - rlci = onto->chain.first; - if (lineart_point_overlapping(sub->chain.last, rlci->pos[0], rlci->pos[1], 1e-5)) { + eci = onto->chain.first; + if (lineart_point_overlapping(sub->chain.last, eci->pos[0], eci->pos[1], 1e-5)) { BLI_pophead(&onto->chain); if (onto->chain.first == NULL) { return; } } - ((LineartLineChainItem *)sub->chain.last)->next = onto->chain.first; - ((LineartLineChainItem *)onto->chain.first)->prev = sub->chain.last; + ((LineartEdgeChainItem *)sub->chain.last)->next = onto->chain.first; + ((LineartEdgeChainItem *)onto->chain.first)->prev = sub->chain.last; onto->chain.first = sub->chain.first; } } static LineartChainRegisterEntry *lineart_chain_get_closest_cre(LineartRenderBuffer *rb, LineartBoundingArea *ba, - LineartLineChain *rlc, - LineartLineChainItem *rlci, + LineartEdgeChain *ec, + LineartEdgeChainItem *eci, int occlusion, unsigned char transparency_mask, float dist, @@ -694,12 +694,12 @@ static LineartChainRegisterEntry *lineart_chain_get_closest_cre(LineartRenderBuf /* Keep using for loop because `cre` could be removed from the iteration before getting to the * next one. */ LISTBASE_FOREACH_MUTABLE (LineartChainRegisterEntry *, cre, &ba->linked_chains) { - if (cre->rlc->object_ref != rlc->object_ref) { + if (cre->ec->object_ref != ec->object_ref) { if (!rb->fuzzy_everything) { if (rb->fuzzy_intersections) { /* If none of those are intersection lines... */ - if ((!(cre->rlc->type & LRT_EDGE_FLAG_INTERSECTION)) && - (!(rlc->type & LRT_EDGE_FLAG_INTERSECTION))) { + if ((!(cre->ec->type & LRT_EDGE_FLAG_INTERSECTION)) && + (!(ec->type & LRT_EDGE_FLAG_INTERSECTION))) { continue; /* We don't want to chain along different objects at the moment. */ } } @@ -708,18 +708,18 @@ static LineartChainRegisterEntry *lineart_chain_get_closest_cre(LineartRenderBuf } } } - if (cre->rlc->picked || cre->picked) { + if (cre->ec->picked || cre->picked) { continue; } - if (cre->rlc == rlc || (!cre->rlc->chain.first) || (cre->rlc->level != occlusion) || - (cre->rlc->transparency_mask != transparency_mask)) { + if (cre->ec == ec || (!cre->ec->chain.first) || (cre->ec->level != occlusion) || + (cre->ec->transparency_mask != transparency_mask)) { continue; } if (!rb->fuzzy_everything) { - if (cre->rlc->type != rlc->type) { + if (cre->ec->type != ec->type) { if (rb->fuzzy_intersections) { - if (!(cre->rlc->type == LRT_EDGE_FLAG_INTERSECTION || - rlc->type == LRT_EDGE_FLAG_INTERSECTION)) { + if (!(cre->ec->type == LRT_EDGE_FLAG_INTERSECTION || + ec->type == LRT_EDGE_FLAG_INTERSECTION)) { continue; /* Fuzzy intersections but no intersection line found. */ } } @@ -729,7 +729,7 @@ static LineartChainRegisterEntry *lineart_chain_get_closest_cre(LineartRenderBuf } } - float new_len = len_v2v2(cre->rlci->pos, rlci->pos); + float new_len = len_v2v2(cre->eci->pos, eci->pos); if (new_len < dist) { closest_cre = cre; dist = new_len; @@ -748,7 +748,7 @@ static LineartChainRegisterEntry *lineart_chain_get_closest_cre(LineartRenderBuf LISTBASE_FOREACH (LinkData *, ld, list) { \ LineartBoundingArea *sba = (LineartBoundingArea *)ld->data; \ adjacent_closest = lineart_chain_get_closest_cre( \ - rb, sba, rlc, rlci, occlusion, transparency_mask, dist, &adjacent_new_len, ba); \ + rb, sba, ec, eci, occlusion, transparency_mask, dist, &adjacent_new_len, ba); \ if (adjacent_new_len < dist) { \ dist = adjacent_new_len; \ closest_cre = adjacent_closest; \ @@ -756,10 +756,10 @@ static LineartChainRegisterEntry *lineart_chain_get_closest_cre(LineartRenderBuf } \ } if (!caller_ba) { - LRT_TEST_ADJACENT_AREAS(rlci->pos[0] - ba->l, &ba->lp); - LRT_TEST_ADJACENT_AREAS(ba->r - rlci->pos[0], &ba->rp); - LRT_TEST_ADJACENT_AREAS(ba->u - rlci->pos[1], &ba->up); - LRT_TEST_ADJACENT_AREAS(rlci->pos[1] - ba->b, &ba->bp); + LRT_TEST_ADJACENT_AREAS(eci->pos[0] - ba->l, &ba->lp); + LRT_TEST_ADJACENT_AREAS(ba->r - eci->pos[0], &ba->rp); + LRT_TEST_ADJACENT_AREAS(ba->u - eci->pos[1], &ba->up); + LRT_TEST_ADJACENT_AREAS(eci->pos[1] - ba->b, &ba->bp); } if (result_new_len) { (*result_new_len) = dist; @@ -774,8 +774,8 @@ static LineartChainRegisterEntry *lineart_chain_get_closest_cre(LineartRenderBuf */ void MOD_lineart_chain_connect(LineartRenderBuffer *rb) { - LineartLineChain *rlc; - LineartLineChainItem *rlci_l, *rlci_r; + LineartEdgeChain *ec; + LineartEdgeChainItem *rlci_l, *rlci_r; LineartBoundingArea *ba_l, *ba_r; LineartChainRegisterEntry *closest_cre_l, *closest_cre_r, *closest_cre; float dist = rb->chaining_image_threshold; @@ -793,24 +793,24 @@ void MOD_lineart_chain_connect(LineartRenderBuffer *rb) rb->chains.last = rb->chains.first = NULL; - while ((rlc = BLI_pophead(&swap)) != NULL) { - rlc->next = rlc->prev = NULL; - if (rlc->picked) { + while ((ec = BLI_pophead(&swap)) != NULL) { + ec->next = ec->prev = NULL; + if (ec->picked) { continue; } - BLI_addtail(&rb->chains, rlc); + BLI_addtail(&rb->chains, ec); - occlusion = rlc->level; - transparency_mask = rlc->transparency_mask; + occlusion = ec->level; + transparency_mask = ec->transparency_mask; - rlci_l = rlc->chain.first; - rlci_r = rlc->chain.last; + rlci_l = ec->chain.first; + rlci_r = ec->chain.last; while ((ba_l = lineart_bounding_area_get_end_point(rb, rlci_l)) && (ba_r = lineart_bounding_area_get_end_point(rb, rlci_r))) { closest_cre_l = lineart_chain_get_closest_cre( - rb, ba_l, rlc, rlci_l, occlusion, transparency_mask, dist, &dist_l, NULL); + rb, ba_l, ec, rlci_l, occlusion, transparency_mask, dist, &dist_l, NULL); closest_cre_r = lineart_chain_get_closest_cre( - rb, ba_r, rlc, rlci_r, occlusion, transparency_mask, dist, &dist_r, NULL); + rb, ba_r, ec, rlci_r, occlusion, transparency_mask, dist, &dist_r, NULL); if (closest_cre_l && closest_cre_r) { if (dist_l < dist_r) { closest_cre = closest_cre_l; @@ -834,56 +834,56 @@ void MOD_lineart_chain_connect(LineartRenderBuffer *rb) break; } closest_cre->picked = 1; - closest_cre->rlc->picked = 1; + closest_cre->ec->picked = 1; if (closest_cre->is_left) { - lineart_chain_connect(rb, rlc, closest_cre->rlc, reverse_main, 0); + lineart_chain_connect(rb, ec, closest_cre->ec, reverse_main, 0); } else { - lineart_chain_connect(rb, rlc, closest_cre->rlc, reverse_main, 1); + lineart_chain_connect(rb, ec, closest_cre->ec, reverse_main, 1); } - BLI_remlink(&swap, closest_cre->rlc); - rlci_l = rlc->chain.first; - rlci_r = rlc->chain.last; + BLI_remlink(&swap, closest_cre->ec); + rlci_l = ec->chain.first; + rlci_r = ec->chain.last; } - rlc->picked = 1; + ec->picked = 1; } } /** * Length is in image space. */ -float MOD_lineart_chain_compute_length(LineartLineChain *rlc) +float MOD_lineart_chain_compute_length(LineartEdgeChain *ec) { - LineartLineChainItem *rlci; + LineartEdgeChainItem *eci; float offset_accum = 0; float dist; float last_point[2]; - rlci = rlc->chain.first; - copy_v2_v2(last_point, rlci->pos); - for (rlci = rlc->chain.first; rlci; rlci = rlci->next) { - dist = len_v2v2(rlci->pos, last_point); + eci = ec->chain.first; + copy_v2_v2(last_point, eci->pos); + for (eci = ec->chain.first; eci; eci = eci->next) { + dist = len_v2v2(eci->pos, last_point); offset_accum += dist; - copy_v2_v2(last_point, rlci->pos); + copy_v2_v2(last_point, eci->pos); } return offset_accum; } void MOD_lineart_chain_discard_short(LineartRenderBuffer *rb, const float threshold) { - LineartLineChain *rlc, *next_rlc; - for (rlc = rb->chains.first; rlc; rlc = next_rlc) { - next_rlc = rlc->next; - if (MOD_lineart_chain_compute_length(rlc) < threshold) { - BLI_remlink(&rb->chains, rlc); + LineartEdgeChain *ec, *next_rlc; + for (ec = rb->chains.first; ec; ec = next_rlc) { + next_rlc = ec->next; + if (MOD_lineart_chain_compute_length(ec) < threshold) { + BLI_remlink(&rb->chains, ec); } } } -int MOD_lineart_chain_count(const LineartLineChain *rlc) +int MOD_lineart_chain_count(const LineartEdgeChain *ec) { int count = 0; - LISTBASE_FOREACH (LineartLineChainItem *, rlci, &rlc->chain) { + LISTBASE_FOREACH (LineartEdgeChainItem *, eci, &ec->chain) { count++; } return count; @@ -894,8 +894,8 @@ void MOD_lineart_chain_clear_picked_flag(LineartRenderBuffer *rb) if (rb == NULL) { return; } - LISTBASE_FOREACH (LineartLineChain *, rlc, &rb->chains) { - rlc->picked = 0; + LISTBASE_FOREACH (LineartEdgeChain *, ec, &rb->chains) { + ec->picked = 0; } } @@ -905,8 +905,8 @@ void MOD_lineart_chain_clear_picked_flag(LineartRenderBuffer *rb) */ void MOD_lineart_chain_split_angle(LineartRenderBuffer *rb, float angle_threshold_rad) { - LineartLineChain *rlc, *new_rlc; - LineartLineChainItem *rlci, *next_rlci, *prev_rlci; + LineartEdgeChain *ec, *new_rlc; + LineartEdgeChainItem *eci, *next_rlci, *prev_rlci; ListBase swap = {0}; swap.first = rb->chains.first; @@ -914,43 +914,43 @@ void MOD_lineart_chain_split_angle(LineartRenderBuffer *rb, float angle_threshol rb->chains.last = rb->chains.first = NULL; - while ((rlc = BLI_pophead(&swap)) != NULL) { - rlc->next = rlc->prev = NULL; - BLI_addtail(&rb->chains, rlc); - LineartLineChainItem *first_rlci = (LineartLineChainItem *)rlc->chain.first; - for (rlci = first_rlci->next; rlci; rlci = next_rlci) { - next_rlci = rlci->next; - prev_rlci = rlci->prev; + while ((ec = BLI_pophead(&swap)) != NULL) { + ec->next = ec->prev = NULL; + BLI_addtail(&rb->chains, ec); + LineartEdgeChainItem *first_rlci = (LineartEdgeChainItem *)ec->chain.first; + for (eci = first_rlci->next; eci; eci = next_rlci) { + next_rlci = eci->next; + prev_rlci = eci->prev; float angle = M_PI; if (next_rlci && prev_rlci) { - angle = angle_v2v2v2(prev_rlci->pos, rlci->pos, next_rlci->pos); + angle = angle_v2v2v2(prev_rlci->pos, eci->pos, next_rlci->pos); } else { break; /* No need to split at the last point anyway.*/ } if (angle < angle_threshold_rad) { new_rlc = lineart_chain_create(rb); - new_rlc->chain.first = rlci; - new_rlc->chain.last = rlc->chain.last; - rlc->chain.last = rlci->prev; - ((LineartLineChainItem *)rlc->chain.last)->next = 0; - rlci->prev = 0; + new_rlc->chain.first = eci; + new_rlc->chain.last = ec->chain.last; + ec->chain.last = eci->prev; + ((LineartEdgeChainItem *)ec->chain.last)->next = 0; + eci->prev = 0; /* End the previous one. */ lineart_chain_append_point(rb, - rlc, - rlci->pos, - rlci->gpos, - rlci->normal, - rlci->line_type, - rlc->level, - rlci->transparency_mask, - rlci->index); - new_rlc->object_ref = rlc->object_ref; - new_rlc->type = rlc->type; - new_rlc->level = rlc->level; - new_rlc->transparency_mask = rlc->transparency_mask; - rlc = new_rlc; + ec, + eci->pos, + eci->gpos, + eci->normal, + eci->line_type, + ec->level, + eci->transparency_mask, + eci->index); + new_rlc->object_ref = ec->object_ref; + new_rlc->type = ec->type; + new_rlc->level = ec->level; + new_rlc->transparency_mask = ec->transparency_mask; + ec = new_rlc; } } } diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c index e9cb8453f43..0b439c20d65 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c @@ -63,7 +63,7 @@ static LineartBoundingArea *lineart_edge_first_bounding_area(LineartRenderBuffer *rb, LineartEdge *e); -static void lineart_bounding_area_link_line(LineartRenderBuffer *rb, +static void lineart_bounding_area_link_edge(LineartRenderBuffer *rb, LineartBoundingArea *root_ba, LineartEdge *e); @@ -86,14 +86,14 @@ static bool lineart_get_edge_bounding_areas(LineartRenderBuffer *rb, static void lineart_bounding_area_link_triangle(LineartRenderBuffer *rb, LineartBoundingArea *root_ba, - LineartTriangle *rt, + LineartTriangle *tri, double *LRUB, int recursive, int recursive_level, bool do_intersection); static bool lineart_triangle_edge_image_space_occlusion(SpinLock *spl, - const LineartTriangle *rt, + const LineartTriangle *tri, const LineartEdge *e, const double *override_camera_loc, const bool override_cam_is_persp, @@ -107,35 +107,35 @@ static bool lineart_triangle_edge_image_space_occlusion(SpinLock *spl, static void lineart_add_edge_to_list(LineartRenderBuffer *rb, LineartEdge *e); -static void lineart_discard_segment(LineartRenderBuffer *rb, LineartLineSegment *rls) +static void lineart_discard_segment(LineartRenderBuffer *rb, LineartEdgeSegment *es) { BLI_spin_lock(&rb->lock_cuts); - memset(rls, 0, sizeof(LineartLineSegment)); + memset(es, 0, sizeof(LineartEdgeSegment)); /* Storing the node for potentially reuse the memory for new segment data. * Line Art data is not freed after all calculations are done. */ - BLI_addtail(&rb->wasted_cuts, rls); + BLI_addtail(&rb->wasted_cuts, es); BLI_spin_unlock(&rb->lock_cuts); } -static LineartLineSegment *lineart_give_segment(LineartRenderBuffer *rb) +static LineartEdgeSegment *lineart_give_segment(LineartRenderBuffer *rb) { BLI_spin_lock(&rb->lock_cuts); /* See if there is any already allocated memory we can reuse. */ if (rb->wasted_cuts.first) { - LineartLineSegment *rls = (LineartLineSegment *)BLI_pophead(&rb->wasted_cuts); + LineartEdgeSegment *es = (LineartEdgeSegment *)BLI_pophead(&rb->wasted_cuts); BLI_spin_unlock(&rb->lock_cuts); - memset(rls, 0, sizeof(LineartLineSegment)); - return rls; + memset(es, 0, sizeof(LineartEdgeSegment)); + return es; } BLI_spin_unlock(&rb->lock_cuts); /* Otherwise allocate some new memory. */ - return (LineartLineSegment *)lineart_mem_aquire_thread(&rb->render_data_pool, - sizeof(LineartLineSegment)); + return (LineartEdgeSegment *)lineart_mem_acquire_thread(&rb->render_data_pool, + sizeof(LineartEdgeSegment)); } /** @@ -144,9 +144,9 @@ static LineartLineSegment *lineart_give_segment(LineartRenderBuffer *rb) static void lineart_edge_cut( LineartRenderBuffer *rb, LineartEdge *e, double start, double end, uchar transparency_mask) { - LineartLineSegment *rls, *irls, *next_rls, *prev_rls; - LineartLineSegment *cut_start_before = 0, *cut_end_before = 0; - LineartLineSegment *ns = 0, *ns2 = 0; + LineartEdgeSegment *es, *ies, *next_es, *prev_es; + LineartEdgeSegment *cut_start_before = 0, *cut_end_before = 0; + LineartEdgeSegment *ns = 0, *ns2 = 0; int untouched = 0; /* If for some reason the occlusion function may give a result that has zero length, or reversed @@ -173,18 +173,18 @@ static void lineart_edge_cut( /* Begin looking for starting position of the segment. */ /* Not using a list iteration macro because of it more clear when using for loops to iterate * through the segments. */ - for (rls = e->segments.first; rls; rls = rls->next) { - if (LRT_DOUBLE_CLOSE_ENOUGH(rls->at, start)) { - cut_start_before = rls; + for (es = e->segments.first; es; es = es->next) { + if (LRT_DOUBLE_CLOSE_ENOUGH(es->at, start)) { + cut_start_before = es; ns = cut_start_before; break; } - if (rls->next == NULL) { + if (es->next == NULL) { break; } - irls = rls->next; - if (irls->at > start + 1e-09 && start > rls->at) { - cut_start_before = irls; + ies = es->next; + if (ies->at > start + 1e-09 && start > es->at) { + cut_start_before = ies; ns = lineart_give_segment(rb); break; } @@ -192,25 +192,25 @@ static void lineart_edge_cut( if (!cut_start_before && LRT_DOUBLE_CLOSE_ENOUGH(1, end)) { untouched = 1; } - for (rls = cut_start_before; rls; rls = rls->next) { + for (es = cut_start_before; es; es = es->next) { /* We tried to cut at existing cutting point (e.g. where the line's occluded by a triangle * strip). */ - if (LRT_DOUBLE_CLOSE_ENOUGH(rls->at, end)) { - cut_end_before = rls; + if (LRT_DOUBLE_CLOSE_ENOUGH(es->at, end)) { + cut_end_before = es; ns2 = cut_end_before; break; } - /* This check is to prevent `rls->at == 1.0` (where we don't need to cut because we are at the + /* This check is to prevent `es->at == 1.0` (where we don't need to cut because we are at the * end point). */ - if (!rls->next && LRT_DOUBLE_CLOSE_ENOUGH(1, end)) { - cut_end_before = rls; + if (!es->next && LRT_DOUBLE_CLOSE_ENOUGH(1, end)) { + cut_end_before = es; ns2 = cut_end_before; untouched = 1; break; } /* When an actual cut is needed in the line. */ - if (rls->at > end) { - cut_end_before = rls; + if (es->at > end) { + cut_end_before = es; ns2 = lineart_give_segment(rb); break; } @@ -233,9 +233,9 @@ static void lineart_edge_cut( if (cut_start_before) { if (cut_start_before != ns) { /* Insert cutting points for when a new cut is needed. */ - irls = cut_start_before->prev ? cut_start_before->prev : NULL; - ns->occlusion = irls ? irls->occlusion : 0; - ns->transparency_mask = irls->transparency_mask; + ies = cut_start_before->prev ? cut_start_before->prev : NULL; + ns->occlusion = ies ? ies->occlusion : 0; + ns->transparency_mask = ies->transparency_mask; BLI_insertlinkbefore(&e->segments, cut_start_before, ns); } /* Otherwise we already found a existing cutting point, no need to insert a new one. */ @@ -243,24 +243,24 @@ static void lineart_edge_cut( else { /* We have yet to reach a existing cutting point even after we searched the whole line, so we * append the new cut to the end. */ - irls = e->segments.last; - ns->occlusion = irls->occlusion; - ns->transparency_mask = irls->transparency_mask; + ies = e->segments.last; + ns->occlusion = ies->occlusion; + ns->transparency_mask = ies->transparency_mask; BLI_addtail(&e->segments, ns); } if (cut_end_before) { /* The same manipulation as on "cut_start_before". */ if (cut_end_before != ns2) { - irls = cut_end_before->prev ? cut_end_before->prev : NULL; - ns2->occlusion = irls ? irls->occlusion : 0; - ns2->transparency_mask = irls ? irls->transparency_mask : 0; + ies = cut_end_before->prev ? cut_end_before->prev : NULL; + ns2->occlusion = ies ? ies->occlusion : 0; + ns2->transparency_mask = ies ? ies->transparency_mask : 0; BLI_insertlinkbefore(&e->segments, cut_end_before, ns2); } } else { - irls = e->segments.last; - ns2->occlusion = irls->occlusion; - ns2->transparency_mask = irls->transparency_mask; + ies = e->segments.last; + ns2->occlusion = ies->occlusion; + ns2->transparency_mask = ies->transparency_mask; BLI_addtail(&e->segments, ns2); } @@ -276,29 +276,29 @@ static void lineart_edge_cut( } /* Register 1 level of occlusion for all touched segments. */ - for (rls = ns; rls && rls != ns2; rls = rls->next) { - rls->occlusion++; - rls->transparency_mask |= transparency_mask; + for (es = ns; es && es != ns2; es = es->next) { + es->occlusion++; + es->transparency_mask |= transparency_mask; } /* Reduce adjacent cutting points of the same level, which saves memory. */ char min_occ = 127; - prev_rls = NULL; - for (rls = e->segments.first; rls; rls = next_rls) { - next_rls = rls->next; + prev_es = NULL; + for (es = e->segments.first; es; es = next_es) { + next_es = es->next; - if (prev_rls && prev_rls->occlusion == rls->occlusion && - prev_rls->transparency_mask == rls->transparency_mask) { - BLI_remlink(&e->segments, rls); + if (prev_es && prev_es->occlusion == es->occlusion && + prev_es->transparency_mask == es->transparency_mask) { + BLI_remlink(&e->segments, es); /* This puts the node back to the render buffer, if more cut happens, these unused nodes get * picked first. */ - lineart_discard_segment(rb, rls); + lineart_discard_segment(rb, es); continue; } - min_occ = MIN2(min_occ, rls->occlusion); + min_occ = MIN2(min_occ, es->occlusion); - prev_rls = rls; + prev_es = es; } e->min_occ = min_occ; } @@ -306,12 +306,42 @@ static void lineart_edge_cut( /** * To see if given line is connected to an adjacent intersection line. */ -BLI_INLINE bool lineart_occlusion_is_adjacent_intersection(LineartEdge *e, LineartTriangle *rt) +BLI_INLINE bool lineart_occlusion_is_adjacent_intersection(LineartEdge *e, LineartTriangle *tri) { LineartVertIntersection *v1 = (void *)e->v1; LineartVertIntersection *v2 = (void *)e->v2; - return ((v1->base.flag && v1->intersecting_with == rt) || - (v2->base.flag && v2->intersecting_with == rt)); + return ((v1->base.flag && v1->intersecting_with == tri) || + (v2->base.flag && v2->intersecting_with == tri)); +} + +static void lineart_bounding_area_triangle_add(LineartRenderBuffer *rb, + LineartBoundingArea *ba, + LineartTriangle *tri) +{ + if (ba->triangle_count >= ba->max_triangle_count) { + LineartTriangle **new_array = lineart_mem_acquire( + &rb->render_data_pool, sizeof(LineartTriangle *) * ba->max_triangle_count * 2); + memcpy(new_array, ba->linked_triangles, sizeof(LineartTriangle *) * ba->max_triangle_count); + ba->max_triangle_count *= 2; + ba->linked_triangles = new_array; + } + ba->linked_triangles[ba->triangle_count] = tri; + ba->triangle_count++; +} + +static void lineart_bounding_area_line_add(LineartRenderBuffer *rb, + LineartBoundingArea *ba, + LineartEdge *e) +{ + if (ba->line_count >= ba->max_line_count) { + LineartEdge **new_array = lineart_mem_acquire(&rb->render_data_pool, + sizeof(LineartEdge *) * ba->max_line_count * 2); + memcpy(new_array, ba->linked_lines, sizeof(LineartEdge *) * ba->max_line_count); + ba->max_line_count *= 2; + ba->linked_lines = new_array; + } + ba->linked_lines[ba->line_count] = e; + ba->line_count++; } static void lineart_occlusion_single_line(LineartRenderBuffer *rb, LineartEdge *e, int thread_id) @@ -319,7 +349,7 @@ static void lineart_occlusion_single_line(LineartRenderBuffer *rb, LineartEdge * double x = e->v1->fbcoord[0], y = e->v1->fbcoord[1]; LineartBoundingArea *ba = lineart_edge_first_bounding_area(rb, e); LineartBoundingArea *nba = ba; - LineartTriangleThread *rt; + LineartTriangleThread *tri; /* These values are used for marching along the line. */ double l, r; @@ -334,16 +364,16 @@ static void lineart_occlusion_single_line(LineartRenderBuffer *rb, LineartEdge * while (nba) { - LISTBASE_FOREACH (LinkData *, lip, &nba->linked_triangles) { - rt = lip->data; + for (int i = 0; i < nba->triangle_count; i++) { + tri = (LineartTriangleThread *)nba->linked_triangles[i]; /* If we are already testing the line in this thread, then don't do it. */ - if (rt->testing_e[thread_id] == e || (rt->base.flags & LRT_TRIANGLE_INTERSECTION_ONLY) || - lineart_occlusion_is_adjacent_intersection(e, (LineartTriangle *)rt)) { + if (tri->testing_e[thread_id] == e || (tri->base.flags & LRT_TRIANGLE_INTERSECTION_ONLY) || + lineart_occlusion_is_adjacent_intersection(e, (LineartTriangle *)tri)) { continue; } - rt->testing_e[thread_id] = e; + tri->testing_e[thread_id] = e; if (lineart_triangle_edge_image_space_occlusion(&rb->lock_task, - (const LineartTriangle *)rt, + (const LineartTriangle *)tri, e, rb->camera_pos, rb->cam_is_persp, @@ -354,7 +384,7 @@ static void lineart_occlusion_single_line(LineartRenderBuffer *rb, LineartEdge * rb->shift_y, &l, &r)) { - lineart_edge_cut(rb, e, l, r, rt->base.transparency_mask); + lineart_edge_cut(rb, e, l, r, tri->base.transparency_mask); if (e->min_occ > rb->max_occlusion_level) { /* No need to calculate any longer on this line because no level more than set value is * going to show up in the rendered result. */ @@ -376,18 +406,18 @@ static int lineart_occlusion_make_task_info(LineartRenderBuffer *rb, LineartRend BLI_spin_lock(&rb->lock_task); #define LRT_ASSIGN_OCCLUSION_TASK(name) \ - if (rb->name##_managed) { \ - data = rb->name##_managed; \ - rti->name = (void *)data; \ + if (rb->name.last) { \ + data = rb->name.last; \ + rti->name.first = (void *)data; \ for (i = 0; i < LRT_THREAD_EDGE_COUNT && data; i++) { \ data = data->next; \ } \ - rti->name##_end = data; \ - rb->name##_managed = data; \ + rti->name.last = data; \ + rb->name.last = data; \ res = 1; \ } \ else { \ - rti->name = NULL; \ + rti->name.first = rti->name.last = NULL; \ } LRT_ASSIGN_OCCLUSION_TASK(contour); @@ -410,23 +440,23 @@ static void lineart_occlusion_worker(TaskPool *__restrict UNUSED(pool), LineartR while (lineart_occlusion_make_task_info(rb, rti)) { - for (eip = rti->contour; eip && eip != rti->contour_end; eip = eip->next) { + for (eip = rti->contour.first; eip && eip != rti->contour.last; eip = eip->next) { lineart_occlusion_single_line(rb, eip, rti->thread_id); } - for (eip = rti->crease; eip && eip != rti->crease_end; eip = eip->next) { + for (eip = rti->crease.first; eip && eip != rti->crease.last; eip = eip->next) { lineart_occlusion_single_line(rb, eip, rti->thread_id); } - for (eip = rti->intersection; eip && eip != rti->intersection_end; eip = eip->next) { + for (eip = rti->intersection.first; eip && eip != rti->intersection.last; eip = eip->next) { lineart_occlusion_single_line(rb, eip, rti->thread_id); } - for (eip = rti->material; eip && eip != rti->material_end; eip = eip->next) { + for (eip = rti->material.first; eip && eip != rti->material.last; eip = eip->next) { lineart_occlusion_single_line(rb, eip, rti->thread_id); } - for (eip = rti->edge_mark; eip && eip != rti->edge_mark_end; eip = eip->next) { + for (eip = rti->edge_mark.first; eip && eip != rti->edge_mark.last; eip = eip->next) { lineart_occlusion_single_line(rb, eip, rti->thread_id); } } @@ -444,11 +474,13 @@ static void lineart_main_occlusion_begin(LineartRenderBuffer *rb) "Task Pool"); int i; - rb->contour_managed = rb->contours; - rb->crease_managed = rb->crease_lines; - rb->intersection_managed = rb->intersection_lines; - rb->material_managed = rb->material_lines; - rb->edge_mark_managed = rb->edge_marks; + /* The "last" entry is used to store worker progress in the whole list. + * These list themselves are single-direction linked, with list.first being the head. */ + rb->contour.last = rb->contour.first; + rb->crease.last = rb->crease.first; + rb->intersection.last = rb->intersection.first; + rb->material.last = rb->material.first; + rb->edge_mark.last = rb->edge_mark.first; TaskPool *tp = BLI_task_pool_create(NULL, TASK_PRIORITY_HIGH); @@ -627,75 +659,75 @@ static bool lineart_point_inside_triangle3d(double v[3], double v0[3], double v1 */ static LineartElementLinkNode *lineart_memory_get_triangle_space(LineartRenderBuffer *rb) { - LineartElementLinkNode *reln; + LineartElementLinkNode *eln; /* We don't need to allocate a whole bunch of triangles because the amount of clipped triangles * are relatively small. */ - LineartTriangle *render_triangles = lineart_mem_aquire(&rb->render_data_pool, - 64 * rb->triangle_size); + LineartTriangle *render_triangles = lineart_mem_acquire(&rb->render_data_pool, + 64 * rb->triangle_size); - reln = lineart_list_append_pointer_pool_sized(&rb->triangle_buffer_pointers, - &rb->render_data_pool, - render_triangles, - sizeof(LineartElementLinkNode)); - reln->element_count = 64; - reln->flags |= LRT_ELEMENT_IS_ADDITIONAL; + eln = lineart_list_append_pointer_pool_sized(&rb->triangle_buffer_pointers, + &rb->render_data_pool, + render_triangles, + sizeof(LineartElementLinkNode)); + eln->element_count = 64; + eln->flags |= LRT_ELEMENT_IS_ADDITIONAL; - return reln; + return eln; } static LineartElementLinkNode *lineart_memory_get_vert_space(LineartRenderBuffer *rb) { - LineartElementLinkNode *reln; + LineartElementLinkNode *eln; - LineartVert *render_vertices = lineart_mem_aquire(&rb->render_data_pool, - sizeof(LineartVert) * 64); + LineartVert *render_vertices = lineart_mem_acquire(&rb->render_data_pool, + sizeof(LineartVert) * 64); - reln = lineart_list_append_pointer_pool_sized(&rb->vertex_buffer_pointers, - &rb->render_data_pool, - render_vertices, - sizeof(LineartElementLinkNode)); - reln->element_count = 64; - reln->flags |= LRT_ELEMENT_IS_ADDITIONAL; + eln = lineart_list_append_pointer_pool_sized(&rb->vertex_buffer_pointers, + &rb->render_data_pool, + render_vertices, + sizeof(LineartElementLinkNode)); + eln->element_count = 64; + eln->flags |= LRT_ELEMENT_IS_ADDITIONAL; - return reln; + return eln; } static LineartElementLinkNode *lineart_memory_get_edge_space(LineartRenderBuffer *rb) { - LineartElementLinkNode *reln; + LineartElementLinkNode *eln; - LineartEdge *render_edges = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartEdge) * 64); + LineartEdge *render_edges = lineart_mem_acquire(&rb->render_data_pool, sizeof(LineartEdge) * 64); - reln = lineart_list_append_pointer_pool_sized(&rb->line_buffer_pointers, - &rb->render_data_pool, - render_edges, - sizeof(LineartElementLinkNode)); - reln->element_count = 64; - reln->crease_threshold = rb->crease_threshold; - reln->flags |= LRT_ELEMENT_IS_ADDITIONAL; + eln = lineart_list_append_pointer_pool_sized(&rb->line_buffer_pointers, + &rb->render_data_pool, + render_edges, + sizeof(LineartElementLinkNode)); + eln->element_count = 64; + eln->crease_threshold = rb->crease_threshold; + eln->flags |= LRT_ELEMENT_IS_ADDITIONAL; - return reln; + return eln; } -static void lineart_triangle_post(LineartTriangle *rt, LineartTriangle *orig) +static void lineart_triangle_post(LineartTriangle *tri, LineartTriangle *orig) { /* Just re-assign normal and set cull flag. */ - copy_v3_v3_db(rt->gn, orig->gn); - rt->flags = LRT_CULL_GENERATED; + copy_v3_v3_db(tri->gn, orig->gn); + tri->flags = LRT_CULL_GENERATED; } -static void lineart_triangle_set_cull_flag(LineartTriangle *rt, uchar flag) +static void lineart_triangle_set_cull_flag(LineartTriangle *tri, uchar flag) { - uchar intersection_only = (rt->flags & LRT_TRIANGLE_INTERSECTION_ONLY); - rt->flags = flag; - rt->flags |= intersection_only; + uchar intersection_only = (tri->flags & LRT_TRIANGLE_INTERSECTION_ONLY); + tri->flags = flag; + tri->flags |= intersection_only; } -static bool lineart_edge_match(LineartTriangle *rt, LineartEdge *e, int v1, int v2) +static bool lineart_edge_match(LineartTriangle *tri, LineartEdge *e, int v1, int v2) { - return ((rt->v[v1] == e->v1 && rt->v[v2] == e->v2) || - (rt->v[v2] == e->v1 && rt->v[v1] == e->v2)); + return ((tri->v[v1] == e->v1 && tri->v[v2] == e->v2) || + (tri->v[v2] == e->v1 && tri->v[v1] == e->v2)); } /** @@ -703,7 +735,7 @@ static bool lineart_edge_match(LineartTriangle *rt, LineartEdge *e, int v1, int * reversed by the caller so don't need to implement one in a different direction. */ static void lineart_triangle_cull_single(LineartRenderBuffer *rb, - LineartTriangle *rt, + LineartTriangle *tri, int in0, int in1, int in2, @@ -728,26 +760,26 @@ static void lineart_triangle_cull_single(LineartRenderBuffer *rb, char new_flag = 0; LineartEdge *new_e, *e, *old_e; - LineartLineSegment *rls; - LineartTriangleAdjacent *rta; + LineartEdgeSegment *es; + LineartTriangleAdjacent *ta; - if (rt->flags & (LRT_CULL_USED | LRT_CULL_GENERATED | LRT_CULL_DISCARD)) { + if (tri->flags & (LRT_CULL_USED | LRT_CULL_GENERATED | LRT_CULL_DISCARD)) { return; } - /* See definition of rt->intersecting_verts and the usage in + /* See definition of tri->intersecting_verts and the usage in * lineart_geometry_object_load() for details. */ - rta = (void *)rt->intersecting_verts; + ta = (void *)tri->intersecting_verts; - LineartVert *rv = &((LineartVert *)v_eln->pointer)[v_count]; - LineartTriangle *rt1 = (void *)(((uchar *)t_eln->pointer) + rb->triangle_size * t_count); - LineartTriangle *rt2 = (void *)(((uchar *)t_eln->pointer) + rb->triangle_size * (t_count + 1)); + LineartVert *vt = &((LineartVert *)v_eln->pointer)[v_count]; + LineartTriangle *tri1 = (void *)(((uchar *)t_eln->pointer) + rb->triangle_size * t_count); + LineartTriangle *tri2 = (void *)(((uchar *)t_eln->pointer) + rb->triangle_size * (t_count + 1)); new_e = &((LineartEdge *)e_eln->pointer)[e_count]; - /* Init `rl` to the last `rl` entry. */ + /* Init `edge` to the last `edge` entry. */ e = new_e; -#define INCREASE_RL \ +#define INCREASE_EDGE \ e_count++; \ v1_obi = e->v1_obindex; \ v2_obi = e->v2_obindex; \ @@ -755,40 +787,40 @@ static void lineart_triangle_cull_single(LineartRenderBuffer *rb, e = new_e; \ e->v1_obindex = v1_obi; \ e->v2_obindex = v2_obi; \ - rls = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartLineSegment)); \ - BLI_addtail(&e->segments, rls); + es = lineart_mem_acquire(&rb->render_data_pool, sizeof(LineartEdgeSegment)); \ + BLI_addtail(&e->segments, es); -#define SELECT_RL(e_num, v1_link, v2_link, newrt) \ - if (rta->e[e_num]) { \ - old_e = rta->e[e_num]; \ +#define SELECT_EDGE(e_num, v1_link, v2_link, new_tri) \ + if (ta->e[e_num]) { \ + old_e = ta->e[e_num]; \ new_flag = old_e->flags; \ old_e->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ - INCREASE_RL \ + INCREASE_EDGE \ e->v1 = (v1_link); \ e->v2 = (v2_link); \ e->flags = new_flag; \ e->object_ref = ob; \ - e->t1 = ((old_e->t1 == rt) ? (newrt) : (old_e->t1)); \ - e->t2 = ((old_e->t2 == rt) ? (newrt) : (old_e->t2)); \ + e->t1 = ((old_e->t1 == tri) ? (new_tri) : (old_e->t1)); \ + e->t2 = ((old_e->t2 == tri) ? (new_tri) : (old_e->t2)); \ lineart_add_edge_to_list(rb, e); \ } -#define RELINK_RL(e_num, newrt) \ - if (rta->e[e_num]) { \ - old_e = rta->e[e_num]; \ - old_e->t1 = ((old_e->t1 == rt) ? (newrt) : (old_e->t1)); \ - old_e->t2 = ((old_e->t2 == rt) ? (newrt) : (old_e->t2)); \ +#define RELINK_EDGE(e_num, new_tri) \ + if (ta->e[e_num]) { \ + old_e = ta->e[e_num]; \ + old_e->t1 = ((old_e->t1 == tri) ? (new_tri) : (old_e->t1)); \ + old_e->t2 = ((old_e->t2 == tri) ? (new_tri) : (old_e->t2)); \ } -#define REMOVE_TRIANGLE_RL \ - if (rta->e[0]) { \ - rta->e[0]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ +#define REMOVE_TRIANGLE_EDGE \ + if (ta->e[0]) { \ + ta->e[0]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ } \ - if (rta->e[1]) { \ - rta->e[1]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ + if (ta->e[1]) { \ + ta->e[1]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ } \ - if (rta->e[2]) { \ - rta->e[2]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ + if (ta->e[2]) { \ + ta->e[2]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ } switch (in0 + in1 + in2) { @@ -797,13 +829,13 @@ static void lineart_triangle_cull_single(LineartRenderBuffer *rb, case 3: /* Triangle completely behind near plane, throw it away * also remove render lines form being computed. */ - lineart_triangle_set_cull_flag(rt, LRT_CULL_DISCARD); - REMOVE_TRIANGLE_RL + lineart_triangle_set_cull_flag(tri, LRT_CULL_DISCARD); + REMOVE_TRIANGLE_EDGE return; case 2: /* Two points behind near plane, cut those and * generate 2 new points, 3 lines and 1 triangle. */ - lineart_triangle_set_cull_flag(rt, LRT_CULL_USED); + lineart_triangle_set_cull_flag(tri, LRT_CULL_USED); /** * (!in0) means "when point 0 is visible". @@ -828,136 +860,136 @@ static void lineart_triangle_cull_single(LineartRenderBuffer *rb, if (!in0) { /* Cut point for line 2---|-----0. */ - sub_v3_v3v3_db(vv1, rt->v[0]->gloc, cam_pos); - sub_v3_v3v3_db(vv2, cam_pos, rt->v[2]->gloc); + sub_v3_v3v3_db(vv1, tri->v[0]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, tri->v[2]->gloc); dot1 = dot_v3v3_db(vv1, view_dir); dot2 = dot_v3v3_db(vv2, view_dir); a = dot1 / (dot1 + dot2); /* Assign it to a new point. */ - interp_v3_v3v3_db(rv[0].gloc, rt->v[0]->gloc, rt->v[2]->gloc, a); - mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc); - rv[0].index = rt->v[2]->index; + interp_v3_v3v3_db(vt[0].gloc, tri->v[0]->gloc, tri->v[2]->gloc, a); + mul_v4_m4v3_db(vt[0].fbcoord, vp, vt[0].gloc); + vt[0].index = tri->v[2]->index; /* Cut point for line 1---|-----0. */ - sub_v3_v3v3_db(vv1, rt->v[0]->gloc, cam_pos); - sub_v3_v3v3_db(vv2, cam_pos, rt->v[1]->gloc); + sub_v3_v3v3_db(vv1, tri->v[0]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, tri->v[1]->gloc); dot1 = dot_v3v3_db(vv1, view_dir); dot2 = dot_v3v3_db(vv2, view_dir); a = dot1 / (dot1 + dot2); /* Assign it to another new point. */ - interp_v3_v3v3_db(rv[1].gloc, rt->v[0]->gloc, rt->v[1]->gloc, a); - mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc); - rv[1].index = rt->v[1]->index; + interp_v3_v3v3_db(vt[1].gloc, tri->v[0]->gloc, tri->v[1]->gloc, a); + mul_v4_m4v3_db(vt[1].fbcoord, vp, vt[1].gloc); + vt[1].index = tri->v[1]->index; /* New line connecting two new points. */ - INCREASE_RL + INCREASE_EDGE if (allow_boundaries) { e->flags = LRT_EDGE_FLAG_CONTOUR; - lineart_prepend_edge_direct(&rb->contours, e); + lineart_prepend_edge_direct(&rb->contour.first, e); } /* NOTE: inverting `e->v1/v2` (left/right point) doesn't matter as long as - * `rt->rl` and `rt->v` has the same sequence. and the winding direction + * `tri->edge` and `tri->v` has the same sequence. and the winding direction * can be either CW or CCW but needs to be consistent throughout the calculation. */ - e->v1 = &rv[1]; - e->v2 = &rv[0]; + e->v1 = &vt[1]; + e->v2 = &vt[0]; /* Only one adjacent triangle, because the other side is the near plane. */ /* Use `tl` or `tr` doesn't matter. */ - e->t1 = rt1; + e->t1 = tri1; e->object_ref = ob; /* New line connecting original point 0 and a new point, only when it's a selected line. */ - SELECT_RL(2, rt->v[0], &rv[0], rt1) + SELECT_EDGE(2, tri->v[0], &vt[0], tri1) /* New line connecting original point 0 and another new point. */ - SELECT_RL(0, rt->v[0], &rv[1], rt1) + SELECT_EDGE(0, tri->v[0], &vt[1], tri1) /* Re-assign triangle point array to two new points. */ - rt1->v[0] = rt->v[0]; - rt1->v[1] = &rv[1]; - rt1->v[2] = &rv[0]; + tri1->v[0] = tri->v[0]; + tri1->v[1] = &vt[1]; + tri1->v[2] = &vt[0]; - lineart_triangle_post(rt1, rt); + lineart_triangle_post(tri1, tri); v_count += 2; t_count += 1; } else if (!in2) { - sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos); - sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc); + sub_v3_v3v3_db(vv1, tri->v[2]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, tri->v[0]->gloc); dot1 = dot_v3v3_db(vv1, view_dir); dot2 = dot_v3v3_db(vv2, view_dir); a = dot1 / (dot1 + dot2); - interp_v3_v3v3_db(rv[0].gloc, rt->v[2]->gloc, rt->v[0]->gloc, a); - mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc); - rv[0].index = rt->v[0]->index; + interp_v3_v3v3_db(vt[0].gloc, tri->v[2]->gloc, tri->v[0]->gloc, a); + mul_v4_m4v3_db(vt[0].fbcoord, vp, vt[0].gloc); + vt[0].index = tri->v[0]->index; - sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos); - sub_v3_v3v3_db(vv2, cam_pos, rt->v[1]->gloc); + sub_v3_v3v3_db(vv1, tri->v[2]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, tri->v[1]->gloc); dot1 = dot_v3v3_db(vv1, view_dir); dot2 = dot_v3v3_db(vv2, view_dir); a = dot1 / (dot1 + dot2); - interp_v3_v3v3_db(rv[1].gloc, rt->v[2]->gloc, rt->v[1]->gloc, a); - mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc); - rv[1].index = rt->v[1]->index; + interp_v3_v3v3_db(vt[1].gloc, tri->v[2]->gloc, tri->v[1]->gloc, a); + mul_v4_m4v3_db(vt[1].fbcoord, vp, vt[1].gloc); + vt[1].index = tri->v[1]->index; - INCREASE_RL + INCREASE_EDGE if (allow_boundaries) { e->flags = LRT_EDGE_FLAG_CONTOUR; - lineart_prepend_edge_direct(&rb->contours, e); + lineart_prepend_edge_direct(&rb->contour.first, e); } - e->v1 = &rv[0]; - e->v2 = &rv[1]; - e->t1 = rt1; + e->v1 = &vt[0]; + e->v2 = &vt[1]; + e->t1 = tri1; e->object_ref = ob; - SELECT_RL(2, rt->v[2], &rv[0], rt1) - SELECT_RL(1, rt->v[2], &rv[1], rt1) + SELECT_EDGE(2, tri->v[2], &vt[0], tri1) + SELECT_EDGE(1, tri->v[2], &vt[1], tri1) - rt1->v[0] = &rv[0]; - rt1->v[1] = &rv[1]; - rt1->v[2] = rt->v[2]; + tri1->v[0] = &vt[0]; + tri1->v[1] = &vt[1]; + tri1->v[2] = tri->v[2]; - lineart_triangle_post(rt1, rt); + lineart_triangle_post(tri1, tri); v_count += 2; t_count += 1; } else if (!in1) { - sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos); - sub_v3_v3v3_db(vv2, cam_pos, rt->v[2]->gloc); + sub_v3_v3v3_db(vv1, tri->v[1]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, tri->v[2]->gloc); dot1 = dot_v3v3_db(vv1, view_dir); dot2 = dot_v3v3_db(vv2, view_dir); a = dot1 / (dot1 + dot2); - interp_v3_v3v3_db(rv[0].gloc, rt->v[1]->gloc, rt->v[2]->gloc, a); - mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc); - rv[0].index = rt->v[2]->index; + interp_v3_v3v3_db(vt[0].gloc, tri->v[1]->gloc, tri->v[2]->gloc, a); + mul_v4_m4v3_db(vt[0].fbcoord, vp, vt[0].gloc); + vt[0].index = tri->v[2]->index; - sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos); - sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc); + sub_v3_v3v3_db(vv1, tri->v[1]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, tri->v[0]->gloc); dot1 = dot_v3v3_db(vv1, view_dir); dot2 = dot_v3v3_db(vv2, view_dir); a = dot1 / (dot1 + dot2); - interp_v3_v3v3_db(rv[1].gloc, rt->v[1]->gloc, rt->v[0]->gloc, a); - mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc); - rv[1].index = rt->v[0]->index; + interp_v3_v3v3_db(vt[1].gloc, tri->v[1]->gloc, tri->v[0]->gloc, a); + mul_v4_m4v3_db(vt[1].fbcoord, vp, vt[1].gloc); + vt[1].index = tri->v[0]->index; - INCREASE_RL + INCREASE_EDGE if (allow_boundaries) { e->flags = LRT_EDGE_FLAG_CONTOUR; - lineart_prepend_edge_direct(&rb->contours, e); + lineart_prepend_edge_direct(&rb->contour.first, e); } - e->v1 = &rv[1]; - e->v2 = &rv[0]; - e->t1 = rt1; + e->v1 = &vt[1]; + e->v2 = &vt[0]; + e->t1 = tri1; e->object_ref = ob; - SELECT_RL(1, rt->v[1], &rv[0], rt1) - SELECT_RL(0, rt->v[1], &rv[1], rt1) + SELECT_EDGE(1, tri->v[1], &vt[0], tri1) + SELECT_EDGE(0, tri->v[1], &vt[1], tri1) - rt1->v[0] = &rv[0]; - rt1->v[1] = rt->v[1]; - rt1->v[2] = &rv[1]; + tri1->v[0] = &vt[0]; + tri1->v[1] = tri->v[1]; + tri1->v[2] = &vt[1]; - lineart_triangle_post(rt1, rt); + lineart_triangle_post(tri1, tri); v_count += 2; t_count += 1; @@ -966,7 +998,7 @@ static void lineart_triangle_cull_single(LineartRenderBuffer *rb, case 1: /* One point behind near plane, cut those and * generate 2 new points, 4 lines and 2 triangles. */ - lineart_triangle_set_cull_flag(rt, LRT_CULL_USED); + lineart_triangle_set_cull_flag(tri, LRT_CULL_USED); /** * (in0) means "when point 0 is invisible". @@ -993,152 +1025,152 @@ static void lineart_triangle_cull_single(LineartRenderBuffer *rb, */ if (in0) { /* Cut point for line 0---|------1. */ - sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos); - sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc); + sub_v3_v3v3_db(vv1, tri->v[1]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, tri->v[0]->gloc); dot1 = dot_v3v3_db(vv1, view_dir); dot2 = dot_v3v3_db(vv2, view_dir); a = dot2 / (dot1 + dot2); /* Assign to a new point. */ - interp_v3_v3v3_db(rv[0].gloc, rt->v[0]->gloc, rt->v[1]->gloc, a); - mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc); - rv[0].index = rt->v[0]->index; + interp_v3_v3v3_db(vt[0].gloc, tri->v[0]->gloc, tri->v[1]->gloc, a); + mul_v4_m4v3_db(vt[0].fbcoord, vp, vt[0].gloc); + vt[0].index = tri->v[0]->index; /* Cut point for line 0---|------2. */ - sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos); - sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc); + sub_v3_v3v3_db(vv1, tri->v[2]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, tri->v[0]->gloc); dot1 = dot_v3v3_db(vv1, view_dir); dot2 = dot_v3v3_db(vv2, view_dir); a = dot2 / (dot1 + dot2); /* Assign to other new point. */ - interp_v3_v3v3_db(rv[1].gloc, rt->v[0]->gloc, rt->v[2]->gloc, a); - mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc); - rv[1].index = rt->v[0]->index; + interp_v3_v3v3_db(vt[1].gloc, tri->v[0]->gloc, tri->v[2]->gloc, a); + mul_v4_m4v3_db(vt[1].fbcoord, vp, vt[1].gloc); + vt[1].index = tri->v[0]->index; /* New line connects two new points. */ - INCREASE_RL + INCREASE_EDGE if (allow_boundaries) { e->flags = LRT_EDGE_FLAG_CONTOUR; - lineart_prepend_edge_direct(&rb->contours, e); + lineart_prepend_edge_direct(&rb->contour.first, e); } - e->v1 = &rv[1]; - e->v2 = &rv[0]; - e->t1 = rt1; + e->v1 = &vt[1]; + e->v2 = &vt[0]; + e->t1 = tri1; e->object_ref = ob; /* New line connects new point 0 and old point 1, * this is a border line. */ - SELECT_RL(0, rt->v[1], &rv[0], rt1) - SELECT_RL(2, rt->v[2], &rv[1], rt2) - RELINK_RL(1, rt2) + SELECT_EDGE(0, tri->v[1], &vt[0], tri1) + SELECT_EDGE(2, tri->v[2], &vt[1], tri2) + RELINK_EDGE(1, tri2) /* We now have one triangle closed. */ - rt1->v[0] = rt->v[1]; - rt1->v[1] = &rv[1]; - rt1->v[2] = &rv[0]; + tri1->v[0] = tri->v[1]; + tri1->v[1] = &vt[1]; + tri1->v[2] = &vt[0]; /* Close the second triangle. */ - rt2->v[0] = &rv[1]; - rt2->v[1] = rt->v[1]; - rt2->v[2] = rt->v[2]; + tri2->v[0] = &vt[1]; + tri2->v[1] = tri->v[1]; + tri2->v[2] = tri->v[2]; - lineart_triangle_post(rt1, rt); - lineart_triangle_post(rt2, rt); + lineart_triangle_post(tri1, tri); + lineart_triangle_post(tri2, tri); v_count += 2; t_count += 2; } else if (in1) { - sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos); - sub_v3_v3v3_db(vv2, cam_pos, rt->v[2]->gloc); + sub_v3_v3v3_db(vv1, tri->v[1]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, tri->v[2]->gloc); dot1 = dot_v3v3_db(vv1, view_dir); dot2 = dot_v3v3_db(vv2, view_dir); a = dot1 / (dot1 + dot2); - interp_v3_v3v3_db(rv[0].gloc, rt->v[1]->gloc, rt->v[2]->gloc, a); - mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc); - rv[0].index = rt->v[1]->index; + interp_v3_v3v3_db(vt[0].gloc, tri->v[1]->gloc, tri->v[2]->gloc, a); + mul_v4_m4v3_db(vt[0].fbcoord, vp, vt[0].gloc); + vt[0].index = tri->v[1]->index; - sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos); - sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc); + sub_v3_v3v3_db(vv1, tri->v[1]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, tri->v[0]->gloc); dot1 = dot_v3v3_db(vv1, view_dir); dot2 = dot_v3v3_db(vv2, view_dir); a = dot1 / (dot1 + dot2); - interp_v3_v3v3_db(rv[1].gloc, rt->v[1]->gloc, rt->v[0]->gloc, a); - mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc); - rv[1].index = rt->v[1]->index; + interp_v3_v3v3_db(vt[1].gloc, tri->v[1]->gloc, tri->v[0]->gloc, a); + mul_v4_m4v3_db(vt[1].fbcoord, vp, vt[1].gloc); + vt[1].index = tri->v[1]->index; - INCREASE_RL + INCREASE_EDGE if (allow_boundaries) { e->flags = LRT_EDGE_FLAG_CONTOUR; - lineart_prepend_edge_direct(&rb->contours, e); + lineart_prepend_edge_direct(&rb->contour.first, e); } - e->v1 = &rv[1]; - e->v2 = &rv[0]; - e->t1 = rt1; + e->v1 = &vt[1]; + e->v2 = &vt[0]; + e->t1 = tri1; e->object_ref = ob; - SELECT_RL(1, rt->v[2], &rv[0], rt1) - SELECT_RL(0, rt->v[0], &rv[1], rt2) - RELINK_RL(2, rt2) + SELECT_EDGE(1, tri->v[2], &vt[0], tri1) + SELECT_EDGE(0, tri->v[0], &vt[1], tri2) + RELINK_EDGE(2, tri2) - rt1->v[0] = rt->v[2]; - rt1->v[1] = &rv[1]; - rt1->v[2] = &rv[0]; + tri1->v[0] = tri->v[2]; + tri1->v[1] = &vt[1]; + tri1->v[2] = &vt[0]; - rt2->v[0] = &rv[1]; - rt2->v[1] = rt->v[2]; - rt2->v[2] = rt->v[0]; + tri2->v[0] = &vt[1]; + tri2->v[1] = tri->v[2]; + tri2->v[2] = tri->v[0]; - lineart_triangle_post(rt1, rt); - lineart_triangle_post(rt2, rt); + lineart_triangle_post(tri1, tri); + lineart_triangle_post(tri2, tri); v_count += 2; t_count += 2; } else if (in2) { - sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos); - sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc); + sub_v3_v3v3_db(vv1, tri->v[2]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, tri->v[0]->gloc); dot1 = dot_v3v3_db(vv1, view_dir); dot2 = dot_v3v3_db(vv2, view_dir); a = dot1 / (dot1 + dot2); - interp_v3_v3v3_db(rv[0].gloc, rt->v[2]->gloc, rt->v[0]->gloc, a); - mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc); - rv[0].index = rt->v[2]->index; + interp_v3_v3v3_db(vt[0].gloc, tri->v[2]->gloc, tri->v[0]->gloc, a); + mul_v4_m4v3_db(vt[0].fbcoord, vp, vt[0].gloc); + vt[0].index = tri->v[2]->index; - sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos); - sub_v3_v3v3_db(vv2, cam_pos, rt->v[1]->gloc); + sub_v3_v3v3_db(vv1, tri->v[2]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, tri->v[1]->gloc); dot1 = dot_v3v3_db(vv1, view_dir); dot2 = dot_v3v3_db(vv2, view_dir); a = dot1 / (dot1 + dot2); - interp_v3_v3v3_db(rv[1].gloc, rt->v[2]->gloc, rt->v[1]->gloc, a); - mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc); - rv[1].index = rt->v[2]->index; + interp_v3_v3v3_db(vt[1].gloc, tri->v[2]->gloc, tri->v[1]->gloc, a); + mul_v4_m4v3_db(vt[1].fbcoord, vp, vt[1].gloc); + vt[1].index = tri->v[2]->index; - INCREASE_RL + INCREASE_EDGE if (allow_boundaries) { e->flags = LRT_EDGE_FLAG_CONTOUR; - lineart_prepend_edge_direct(&rb->contours, e); + lineart_prepend_edge_direct(&rb->contour.first, e); } - e->v1 = &rv[1]; - e->v2 = &rv[0]; - e->t1 = rt1; + e->v1 = &vt[1]; + e->v2 = &vt[0]; + e->t1 = tri1; e->object_ref = ob; - SELECT_RL(2, rt->v[0], &rv[0], rt1) - SELECT_RL(1, rt->v[1], &rv[1], rt2) - RELINK_RL(0, rt2) + SELECT_EDGE(2, tri->v[0], &vt[0], tri1) + SELECT_EDGE(1, tri->v[1], &vt[1], tri2) + RELINK_EDGE(0, tri2) - rt1->v[0] = rt->v[0]; - rt1->v[1] = &rv[1]; - rt1->v[2] = &rv[0]; + tri1->v[0] = tri->v[0]; + tri1->v[1] = &vt[1]; + tri1->v[2] = &vt[0]; - rt2->v[0] = &rv[1]; - rt2->v[1] = rt->v[0]; - rt2->v[2] = rt->v[1]; + tri2->v[0] = &vt[1]; + tri2->v[1] = tri->v[0]; + tri2->v[2] = tri->v[1]; - lineart_triangle_post(rt1, rt); - lineart_triangle_post(rt2, rt); + lineart_triangle_post(tri1, tri); + lineart_triangle_post(tri2, tri); v_count += 2; t_count += 2; @@ -1149,10 +1181,10 @@ static void lineart_triangle_cull_single(LineartRenderBuffer *rb, *r_e_count = e_count; *r_t_count = t_count; -#undef INCREASE_RL -#undef SELECT_RL -#undef RELINK_RL -#undef REMOVE_TRIANGLE_RL +#undef INCREASE_EDGE +#undef SELECT_EDGE +#undef RELINK_EDGE +#undef REMOVE_TRIANGLE_EDGE } /** @@ -1163,7 +1195,7 @@ static void lineart_triangle_cull_single(LineartRenderBuffer *rb, */ static void lineart_main_cull_triangles(LineartRenderBuffer *rb, bool clip_far) { - LineartTriangle *rt; + LineartTriangle *tri; LineartElementLinkNode *v_eln, *t_eln, *e_eln; double(*vp)[4] = rb->view_projection; int i; @@ -1219,25 +1251,25 @@ static void lineart_main_cull_triangles(LineartRenderBuffer *rb, bool clip_far) in0 = 0, in1 = 0, in2 = 0; \ if (clip_far) { \ /* Point outside far plane. */ \ - if (rt->v[0]->fbcoord[use_w] > clip_end) { \ + if (tri->v[0]->fbcoord[use_w] > clip_end) { \ in0 = 1; \ } \ - if (rt->v[1]->fbcoord[use_w] > clip_end) { \ + if (tri->v[1]->fbcoord[use_w] > clip_end) { \ in1 = 1; \ } \ - if (rt->v[2]->fbcoord[use_w] > clip_end) { \ + if (tri->v[2]->fbcoord[use_w] > clip_end) { \ in2 = 1; \ } \ } \ else { \ /* Point inside near plane. */ \ - if (rt->v[0]->fbcoord[use_w] < clip_start) { \ + if (tri->v[0]->fbcoord[use_w] < clip_start) { \ in0 = 1; \ } \ - if (rt->v[1]->fbcoord[use_w] < clip_start) { \ + if (tri->v[1]->fbcoord[use_w] < clip_start) { \ in1 = 1; \ } \ - if (rt->v[2]->fbcoord[use_w] < clip_start) { \ + if (tri->v[2]->fbcoord[use_w] < clip_start) { \ in2 = 1; \ } \ } @@ -1252,19 +1284,19 @@ static void lineart_main_cull_triangles(LineartRenderBuffer *rb, bool clip_far) } /* Then go through all the other triangles. */ - LISTBASE_FOREACH (LineartElementLinkNode *, reln, &rb->triangle_buffer_pointers) { - if (reln->flags & LRT_ELEMENT_IS_ADDITIONAL) { + LISTBASE_FOREACH (LineartElementLinkNode *, eln, &rb->triangle_buffer_pointers) { + if (eln->flags & LRT_ELEMENT_IS_ADDITIONAL) { continue; } - ob = reln->object_ref; - for (i = 0; i < reln->element_count; i++) { + ob = eln->object_ref; + for (i = 0; i < eln->element_count; i++) { /* Select the triangle in the array. */ - rt = (void *)(((uchar *)reln->pointer) + rb->triangle_size * i); + tri = (void *)(((uchar *)eln->pointer) + rb->triangle_size * i); LRT_CULL_DECIDE_INSIDE LRT_CULL_ENSURE_MEMORY lineart_triangle_cull_single(rb, - rt, + tri, in0, in1, in2, @@ -1298,40 +1330,40 @@ static void lineart_main_free_adjacent_data(LineartRenderBuffer *rb) while ((ld = BLI_pophead(&rb->triangle_adjacent_pointers)) != NULL) { MEM_freeN(ld->data); } - LISTBASE_FOREACH (LineartElementLinkNode *, reln, &rb->triangle_buffer_pointers) { - LineartTriangle *rt = reln->pointer; + LISTBASE_FOREACH (LineartElementLinkNode *, eln, &rb->triangle_buffer_pointers) { + LineartTriangle *tri = eln->pointer; int i; - for (i = 0; i < reln->element_count; i++) { - /* See definition of rt->intersecting_verts and the usage in + for (i = 0; i < eln->element_count; i++) { + /* See definition of tri->intersecting_verts and the usage in * lineart_geometry_object_load() for detailed. */ - rt->intersecting_verts = NULL; - rt = (LineartTriangle *)(((uchar *)rt) + rb->triangle_size); + tri->intersecting_verts = NULL; + tri = (LineartTriangle *)(((uchar *)tri) + rb->triangle_size); } } } static void lineart_main_perspective_division(LineartRenderBuffer *rb) { - LineartVert *rv; + LineartVert *vt; int i; if (!rb->cam_is_persp) { return; } - LISTBASE_FOREACH (LineartElementLinkNode *, reln, &rb->vertex_buffer_pointers) { - rv = reln->pointer; - for (i = 0; i < reln->element_count; i++) { + LISTBASE_FOREACH (LineartElementLinkNode *, eln, &rb->vertex_buffer_pointers) { + vt = eln->pointer; + for (i = 0; i < eln->element_count; i++) { /* Do not divide Z, we use Z to back transform cut points in later chaining process. */ - rv[i].fbcoord[0] /= rv[i].fbcoord[3]; - rv[i].fbcoord[1] /= rv[i].fbcoord[3]; + vt[i].fbcoord[0] /= vt[i].fbcoord[3]; + vt[i].fbcoord[1] /= vt[i].fbcoord[3]; /* Re-map z into (0-1) range, because we no longer need NDC (Normalized Device Coordinates) * at the moment. * The algorithm currently doesn't need Z for operation, we use W instead. If Z is needed in * the future, the line below correctly transforms it to view space coordinates. */ - // `rv[i].fbcoord[2] = -2 * rv[i].fbcoord[2] / (far - near) - (far + near) / (far - near); - rv[i].fbcoord[0] -= rb->shift_x * 2; - rv[i].fbcoord[1] -= rb->shift_y * 2; + // `vt[i].fbcoord[2] = -2 * vt[i].fbcoord[2] / (far - near) - (far + near) / (far - near); + vt[i].fbcoord[0] -= rb->shift_x * 2; + vt[i].fbcoord[1] -= rb->shift_y * 2; } } } @@ -1343,10 +1375,10 @@ static void lineart_vert_transform( BMVert *v, int index, LineartVert *RvBuf, double (*mv_mat)[4], double (*mvp_mat)[4]) { double co[4]; - LineartVert *rv = &RvBuf[index]; + LineartVert *vt = &RvBuf[index]; copy_v3db_v3fl(co, v->co); - mul_v3_m4v3_db(rv->gloc, mv_mat, co); - mul_v4_m4v3_db(rv->fbcoord, mvp_mat, co); + mul_v3_m4v3_db(vt->gloc, mv_mat, co); + mul_v4_m4v3_db(vt->fbcoord, mvp_mat, co); } /** @@ -1381,12 +1413,12 @@ static char lineart_identify_feature_line(LineartRenderBuffer *rb, return LRT_EDGE_FLAG_CONTOUR; } - LineartTriangle *rt1, *rt2; + LineartTriangle *tri1, *tri2; LineartVert *l; /* The mesh should already be triangulated now, so we can assume each face is a triangle. */ - rt1 = lineart_triangle_from_index(rb, rt_array, BM_elem_index_get(ll->f)); - rt2 = lineart_triangle_from_index(rb, rt_array, BM_elem_index_get(lr->f)); + tri1 = lineart_triangle_from_index(rb, rt_array, BM_elem_index_get(ll->f)); + tri2 = lineart_triangle_from_index(rb, rt_array, BM_elem_index_get(lr->f)); l = &rv_array[BM_elem_index_get(e->v1)]; @@ -1403,14 +1435,14 @@ static char lineart_identify_feature_line(LineartRenderBuffer *rb, view_vector = rb->view_vector; } - dot_1 = dot_v3v3_db(view_vector, rt1->gn); - dot_2 = dot_v3v3_db(view_vector, rt2->gn); + dot_1 = dot_v3v3_db(view_vector, tri1->gn); + dot_2 = dot_v3v3_db(view_vector, tri2->gn); if ((result = dot_1 * dot_2) <= 0 && (dot_1 + dot_2)) { return LRT_EDGE_FLAG_CONTOUR; } - if (rb->use_crease && (dot_v3v3_db(rt1->gn, rt2->gn) < crease_threshold)) { + if (rb->use_crease && (dot_v3v3_db(tri1->gn, tri2->gn) < crease_threshold)) { if (!no_crease) { return LRT_EDGE_FLAG_CREASE; } @@ -1431,35 +1463,35 @@ static void lineart_add_edge_to_list(LineartRenderBuffer *rb, LineartEdge *e) { switch (e->flags) { case LRT_EDGE_FLAG_CONTOUR: - lineart_prepend_edge_direct(&rb->contours, e); + lineart_prepend_edge_direct(&rb->contour.first, e); break; case LRT_EDGE_FLAG_CREASE: - lineart_prepend_edge_direct(&rb->crease_lines, e); + lineart_prepend_edge_direct(&rb->crease.first, e); break; case LRT_EDGE_FLAG_MATERIAL: - lineart_prepend_edge_direct(&rb->material_lines, e); + lineart_prepend_edge_direct(&rb->material.first, e); break; case LRT_EDGE_FLAG_EDGE_MARK: - lineart_prepend_edge_direct(&rb->edge_marks, e); + lineart_prepend_edge_direct(&rb->edge_mark.first, e); break; case LRT_EDGE_FLAG_INTERSECTION: - lineart_prepend_edge_direct(&rb->intersection_lines, e); + lineart_prepend_edge_direct(&rb->intersection.first, e); break; } } -static void lineart_triangle_adjacent_assign(LineartTriangle *rt, - LineartTriangleAdjacent *rta, +static void lineart_triangle_adjacent_assign(LineartTriangle *tri, + LineartTriangleAdjacent *ta, LineartEdge *e) { - if (lineart_edge_match(rt, e, 0, 1)) { - rta->e[0] = e; + if (lineart_edge_match(tri, e, 0, 1)) { + ta->e[0] = e; } - else if (lineart_edge_match(rt, e, 1, 2)) { - rta->e[1] = e; + else if (lineart_edge_match(tri, e, 1, 2)) { + ta->e[1] = e; } - else if (lineart_edge_match(rt, e, 2, 0)) { - rta->e[2] = e; + else if (lineart_edge_match(tri, e, 2, 0)) { + ta->e[2] = e; } } @@ -1477,11 +1509,11 @@ static void lineart_geometry_object_load(Depsgraph *dg, BMEdge *e; BMLoop *loop; LineartEdge *la_e; - LineartTriangle *rt; + LineartTriangle *tri; LineartTriangleAdjacent *orta; double new_mvp[4][4], new_mv[4][4], normal[4][4]; float imat[4][4]; - LineartElementLinkNode *reln; + LineartElementLinkNode *eln; LineartVert *orv; LineartEdge *o_la_e; LineartTriangle *ort; @@ -1576,15 +1608,15 @@ static void lineart_geometry_object_load(Depsgraph *dg, /* Only allocate memory for verts and tris as we don't know how many lines we will generate * yet. */ - orv = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartVert) * bm->totvert); - ort = lineart_mem_aquire(&rb->render_data_pool, bm->totface * rb->triangle_size); + orv = lineart_mem_acquire(&rb->render_data_pool, sizeof(LineartVert) * bm->totvert); + ort = lineart_mem_acquire(&rb->render_data_pool, bm->totface * rb->triangle_size); orig_ob = ob->id.orig_id ? (Object *)ob->id.orig_id : ob; - reln = lineart_list_append_pointer_pool_sized( + eln = lineart_list_append_pointer_pool_sized( &rb->vertex_buffer_pointers, &rb->render_data_pool, orv, sizeof(LineartElementLinkNode)); - reln->element_count = bm->totvert; - reln->object_ref = orig_ob; + eln->element_count = bm->totvert; + eln->object_ref = orig_ob; if (ob->lineart.flags & OBJECT_LRT_OWN_CREASE) { use_crease = cosf(M_PI - ob->lineart.crease_threshold); @@ -1596,14 +1628,14 @@ static void lineart_geometry_object_load(Depsgraph *dg, /* FIXME(Yiming): Hack for getting clean 3D text, the seam that extruded text object creates * erroneous detection on creases. Future configuration should allow options. */ if (ob->type == OB_FONT) { - reln->flags |= LRT_ELEMENT_BORDER_ONLY; + eln->flags |= LRT_ELEMENT_BORDER_ONLY; } - reln = lineart_list_append_pointer_pool_sized( + eln = lineart_list_append_pointer_pool_sized( &rb->triangle_buffer_pointers, &rb->render_data_pool, ort, sizeof(LineartElementLinkNode)); - reln->element_count = bm->totface; - reln->object_ref = orig_ob; - reln->flags |= (usage == OBJECT_LRT_NO_INTERSECTION ? LRT_ELEMENT_NO_INTERSECTION : 0); + eln->element_count = bm->totface; + eln->object_ref = orig_ob; + eln->flags |= (usage == OBJECT_LRT_NO_INTERSECTION ? LRT_ELEMENT_NO_INTERSECTION : 0); /* Note this memory is not from pool, will be deleted after culling. */ orta = MEM_callocN(sizeof(LineartTriangleAdjacent) * bm->totface, "LineartTriangleAdjacent"); @@ -1621,39 +1653,39 @@ static void lineart_geometry_object_load(Depsgraph *dg, * index to come close together. */ (*global_vindex) += bm->totvert; - rt = ort; + tri = ort; for (i = 0; i < bm->totface; i++) { f = BM_face_at_index(bm, i); loop = f->l_first; - rt->v[0] = &orv[BM_elem_index_get(loop->v)]; + tri->v[0] = &orv[BM_elem_index_get(loop->v)]; loop = loop->next; - rt->v[1] = &orv[BM_elem_index_get(loop->v)]; + tri->v[1] = &orv[BM_elem_index_get(loop->v)]; loop = loop->next; - rt->v[2] = &orv[BM_elem_index_get(loop->v)]; + tri->v[2] = &orv[BM_elem_index_get(loop->v)]; /* Transparency bit assignment. */ Material *mat = BKE_object_material_get(ob, f->mat_nr + 1); - rt->transparency_mask = ((mat && (mat->lineart.flags & LRT_MATERIAL_TRANSPARENCY_ENABLED)) ? - mat->lineart.transparency_mask : - 0); + tri->transparency_mask = ((mat && (mat->lineart.flags & LRT_MATERIAL_TRANSPARENCY_ENABLED)) ? + mat->lineart.transparency_mask : + 0); double gn[3]; copy_v3db_v3fl(gn, f->no); - mul_v3_mat3_m4v3_db(rt->gn, normal, gn); - normalize_v3_db(rt->gn); + mul_v3_mat3_m4v3_db(tri->gn, normal, gn); + normalize_v3_db(tri->gn); if (usage == OBJECT_LRT_INTERSECTION_ONLY) { - rt->flags |= LRT_TRIANGLE_INTERSECTION_ONLY; + tri->flags |= LRT_TRIANGLE_INTERSECTION_ONLY; } else if (ELEM(usage, OBJECT_LRT_NO_INTERSECTION, OBJECT_LRT_OCCLUSION_ONLY)) { - rt->flags |= LRT_TRIANGLE_NO_INTERSECTION; + tri->flags |= LRT_TRIANGLE_NO_INTERSECTION; } /* Re-use this field to refer to adjacent info, will be cleared after culling stage. */ - rt->intersecting_verts = (void *)&orta[i]; + tri->intersecting_verts = (void *)&orta[i]; - rt = (LineartTriangle *)(((uchar *)rt) + rb->triangle_size); + tri = (LineartTriangle *)(((uchar *)tri) + rb->triangle_size); } /* Use BM_ELEM_TAG in f->head.hflag to store needed faces in the first iteration. */ @@ -1675,11 +1707,11 @@ static void lineart_geometry_object_load(Depsgraph *dg, e->head.hflag = eflag; } - o_la_e = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartEdge) * allocate_la_e); - reln = lineart_list_append_pointer_pool_sized( + o_la_e = lineart_mem_acquire(&rb->render_data_pool, sizeof(LineartEdge) * allocate_la_e); + eln = lineart_list_append_pointer_pool_sized( &rb->line_buffer_pointers, &rb->render_data_pool, o_la_e, sizeof(LineartElementLinkNode)); - reln->element_count = allocate_la_e; - reln->object_ref = orig_ob; + eln->element_count = allocate_la_e; + eln->object_ref = orig_ob; la_e = o_la_e; for (i = 0; i < bm->totedge; i++) { @@ -1707,9 +1739,9 @@ static void lineart_geometry_object_load(Depsgraph *dg, la_e->flags = e->head.hflag; la_e->object_ref = orig_ob; - LineartLineSegment *rls = lineart_mem_aquire(&rb->render_data_pool, - sizeof(LineartLineSegment)); - BLI_addtail(&la_e->segments, rls); + LineartEdgeSegment *es = lineart_mem_acquire(&rb->render_data_pool, + sizeof(LineartEdgeSegment)); + BLI_addtail(&la_e->segments, es); if (ELEM(usage, OBJECT_LRT_INHERIT, OBJECT_LRT_INCLUDE, OBJECT_LRT_NO_INTERSECTION)) { lineart_add_edge_to_list(rb, la_e); } @@ -1875,51 +1907,51 @@ static void lineart_main_load_geometries( * Returns the two other verts of the triangle given a vertex. Returns false if the given vertex * doesn't belong to this triangle. */ -static bool lineart_triangle_get_other_verts(const LineartTriangle *rt, - const LineartVert *rv, +static bool lineart_triangle_get_other_verts(const LineartTriangle *tri, + const LineartVert *vt, LineartVert **l, LineartVert **r) { - if (rt->v[0] == rv) { - *l = rt->v[1]; - *r = rt->v[2]; + if (tri->v[0] == vt) { + *l = tri->v[1]; + *r = tri->v[2]; return true; } - if (rt->v[1] == rv) { - *l = rt->v[2]; - *r = rt->v[0]; + if (tri->v[1] == vt) { + *l = tri->v[2]; + *r = tri->v[0]; return true; } - if (rt->v[2] == rv) { - *l = rt->v[0]; - *r = rt->v[1]; + if (tri->v[2] == vt) { + *l = tri->v[0]; + *r = tri->v[1]; return true; } return false; } -static bool lineart_edge_from_triangle(const LineartTriangle *rt, +static bool lineart_edge_from_triangle(const LineartTriangle *tri, const LineartEdge *e, bool allow_overlapping_edges) { /* Normally we just determine from the pointer address. */ - if (e->t1 == rt || e->t2 == rt) { + if (e->t1 == tri || e->t2 == tri) { return true; } /* If allows overlapping, then we compare the vertex coordinates one by one to determine if one * edge is from specific triangle. This is slower but can handle edge split cases very well. */ if (allow_overlapping_edges) { -#define LRT_TRI_SAME_POINT(rt, i, pt) \ - ((LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[0], pt->gloc[0]) && \ - LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[1], pt->gloc[1]) && \ - LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[2], pt->gloc[2])) || \ - (LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[0], pt->gloc[0]) && \ - LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[1], pt->gloc[1]) && \ - LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[2], pt->gloc[2]))) - if ((LRT_TRI_SAME_POINT(rt, 0, e->v1) || LRT_TRI_SAME_POINT(rt, 1, e->v1) || - LRT_TRI_SAME_POINT(rt, 2, e->v1)) && - (LRT_TRI_SAME_POINT(rt, 0, e->v2) || LRT_TRI_SAME_POINT(rt, 1, e->v2) || - LRT_TRI_SAME_POINT(rt, 2, e->v2))) { +#define LRT_TRI_SAME_POINT(tri, i, pt) \ + ((LRT_DOUBLE_CLOSE_ENOUGH(tri->v[i]->gloc[0], pt->gloc[0]) && \ + LRT_DOUBLE_CLOSE_ENOUGH(tri->v[i]->gloc[1], pt->gloc[1]) && \ + LRT_DOUBLE_CLOSE_ENOUGH(tri->v[i]->gloc[2], pt->gloc[2])) || \ + (LRT_DOUBLE_CLOSE_ENOUGH(tri->v[i]->gloc[0], pt->gloc[0]) && \ + LRT_DOUBLE_CLOSE_ENOUGH(tri->v[i]->gloc[1], pt->gloc[1]) && \ + LRT_DOUBLE_CLOSE_ENOUGH(tri->v[i]->gloc[2], pt->gloc[2]))) + if ((LRT_TRI_SAME_POINT(tri, 0, e->v1) || LRT_TRI_SAME_POINT(tri, 1, e->v1) || + LRT_TRI_SAME_POINT(tri, 2, e->v1)) && + (LRT_TRI_SAME_POINT(tri, 0, e->v2) || LRT_TRI_SAME_POINT(tri, 1, e->v2) || + LRT_TRI_SAME_POINT(tri, 2, e->v2))) { return true; } #undef LRT_TRI_SAME_POINT @@ -1961,7 +1993,7 @@ static bool lineart_edge_from_triangle(const LineartTriangle *rt, * in ratio from `e->v1` to `e->v2`. The line is later cut with these two values. */ static bool lineart_triangle_edge_image_space_occlusion(SpinLock *UNUSED(spl), - const LineartTriangle *rt, + const LineartTriangle *tri, const LineartEdge *e, const double *override_camera_loc, const bool override_cam_is_persp, @@ -1988,8 +2020,8 @@ static bool lineart_triangle_edge_image_space_occlusion(SpinLock *UNUSED(spl), double gloc[4], trans[4]; double cut = -1; - double *LFBC = e->v1->fbcoord, *RFBC = e->v2->fbcoord, *FBC0 = rt->v[0]->fbcoord, - *FBC1 = rt->v[1]->fbcoord, *FBC2 = rt->v[2]->fbcoord; + double *LFBC = e->v1->fbcoord, *RFBC = e->v2->fbcoord, *FBC0 = tri->v[0]->fbcoord, + *FBC1 = tri->v[1]->fbcoord, *FBC2 = tri->v[2]->fbcoord; /* Overlapping not possible, return early. */ if ((MAX3(FBC0[0], FBC1[0], FBC2[0]) < MIN2(LFBC[0], RFBC[0])) || @@ -2001,7 +2033,7 @@ static bool lineart_triangle_edge_image_space_occlusion(SpinLock *UNUSED(spl), } /* If the the line is one of the edge in the triangle, then it's not occluded. */ - if (lineart_edge_from_triangle(rt, e, allow_overlapping_edges)) { + if (lineart_edge_from_triangle(tri, e, allow_overlapping_edges)) { return false; } @@ -2013,8 +2045,8 @@ static bool lineart_triangle_edge_image_space_occlusion(SpinLock *UNUSED(spl), /* Sort the intersection distance. */ INTERSECT_SORT_MIN_TO_MAX_3(is[0], is[1], is[2], order); - sub_v3_v3v3_db(Lv, e->v1->gloc, rt->v[0]->gloc); - sub_v3_v3v3_db(Rv, e->v2->gloc, rt->v[0]->gloc); + sub_v3_v3v3_db(Lv, e->v1->gloc, tri->v[0]->gloc); + sub_v3_v3v3_db(Rv, e->v2->gloc, tri->v[0]->gloc); copy_v3_v3_db(Cv, camera_dir); @@ -2025,12 +2057,12 @@ static bool lineart_triangle_edge_image_space_occlusion(SpinLock *UNUSED(spl), copy_v4_v4_db(vd4, override_camera_loc); } if (override_cam_is_persp) { - sub_v3_v3v3_db(Cv, vd4, rt->v[0]->gloc); + sub_v3_v3v3_db(Cv, vd4, tri->v[0]->gloc); } - dot_l = dot_v3v3_db(Lv, rt->gn); - dot_r = dot_v3v3_db(Rv, rt->gn); - dot_f = dot_v3v3_db(Cv, rt->gn); + dot_l = dot_v3v3_db(Lv, tri->gn); + dot_r = dot_v3v3_db(Rv, tri->gn); + dot_f = dot_v3v3_db(Cv, tri->gn); if (!dot_f) { return false; @@ -2271,17 +2303,17 @@ static LineartVert *lineart_triangle_share_point(const LineartTriangle *l, /** * To save time and prevent overlapping lines when computing intersection lines. */ -static bool lineart_vert_already_intersected_2v(LineartVertIntersection *rv, +static bool lineart_vert_already_intersected_2v(LineartVertIntersection *vt, LineartVertIntersection *v1, LineartVertIntersection *v2) { - return ((rv->isec1 == v1->base.index && rv->isec2 == v2->base.index) || - (rv->isec2 == v2->base.index && rv->isec1 == v1->base.index)); + return ((vt->isec1 == v1->base.index && vt->isec2 == v2->base.index) || + (vt->isec2 == v2->base.index && vt->isec1 == v1->base.index)); } -static void lineart_vert_set_intersection_2v(LineartVert *rv, LineartVert *v1, LineartVert *v2) +static void lineart_vert_set_intersection_2v(LineartVert *vt, LineartVert *v1, LineartVert *v2) { - LineartVertIntersection *irv = (LineartVertIntersection *)rv; + LineartVertIntersection *irv = (LineartVertIntersection *)vt; irv->isec1 = v1->index; irv->isec2 = v2->index; } @@ -2294,7 +2326,7 @@ static void lineart_vert_set_intersection_2v(LineartVert *rv, LineartVert *v1, L static LineartVert *lineart_triangle_2v_intersection_test(LineartRenderBuffer *rb, LineartVert *v1, LineartVert *v2, - LineartTriangle *rt, + LineartTriangle *tri, LineartTriangle *testing, LineartVert *last) { @@ -2306,11 +2338,11 @@ static LineartVert *lineart_triangle_2v_intersection_test(LineartRenderBuffer *r LineartVert *l = v1, *r = v2; for (LinkNode *ln = (void *)testing->intersecting_verts; ln; ln = ln->next) { - LineartVertIntersection *rv = ln->link; - if (rv->intersecting_with == rt && + LineartVertIntersection *vt = ln->link; + if (vt->intersecting_with == tri && lineart_vert_already_intersected_2v( - rv, (LineartVertIntersection *)l, (LineartVertIntersection *)r)) { - return (LineartVert *)rv; + vt, (LineartVertIntersection *)l, (LineartVertIntersection *)r)) { + return (LineartVert *)vt; } } @@ -2344,7 +2376,7 @@ static LineartVert *lineart_triangle_2v_intersection_test(LineartRenderBuffer *r /* This is an intersection vert, the size is bigger than LineartVert, * allocated separately. */ - result = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartVertIntersection)); + result = lineart_mem_acquire(&rb->render_data_pool, sizeof(LineartVertIntersection)); /* Indicate the data structure difference. */ result->flag = LRT_VERT_HAS_INTERSECTION_DATA; @@ -2360,7 +2392,7 @@ static LineartVert *lineart_triangle_2v_intersection_test(LineartRenderBuffer *r * Test if two triangles intersect. Generates one intersection line if the check succeeds. */ static LineartEdge *lineart_triangle_intersect(LineartRenderBuffer *rb, - LineartTriangle *rt, + LineartTriangle *tri, LineartTriangle *testing) { LineartVert *v1 = 0, *v2 = 0; @@ -2379,62 +2411,62 @@ static LineartEdge *lineart_triangle_intersect(LineartRenderBuffer *rb, ZMax = rb->far_clip; ZMin = rb->near_clip; copy_v3_v3_db(cl, rb->camera_pos); - LineartVert *share = lineart_triangle_share_point(testing, rt); + LineartVert *share = lineart_triangle_share_point(testing, tri); if (share) { /* If triangles have sharing points like `abc` and `acd`, then we only need to detect `bc` * against `acd` or `cd` against `abc`. */ LineartVert *new_share; - lineart_triangle_get_other_verts(rt, share, &sv1, &sv2); + lineart_triangle_get_other_verts(tri, share, &sv1, &sv2); - v1 = new_share = lineart_mem_aquire(&rb->render_data_pool, (sizeof(LineartVertIntersection))); + v1 = new_share = lineart_mem_acquire(&rb->render_data_pool, (sizeof(LineartVertIntersection))); new_share->flag = LRT_VERT_HAS_INTERSECTION_DATA; copy_v3_v3_db(new_share->gloc, share->gloc); - v2 = lineart_triangle_2v_intersection_test(rb, sv1, sv2, rt, testing, 0); + v2 = lineart_triangle_2v_intersection_test(rb, sv1, sv2, tri, testing, 0); if (v2 == NULL) { lineart_triangle_get_other_verts(testing, share, &sv1, &sv2); - v2 = lineart_triangle_2v_intersection_test(rb, sv1, sv2, testing, rt, 0); + v2 = lineart_triangle_2v_intersection_test(rb, sv1, sv2, testing, tri, 0); if (v2 == NULL) { return 0; } lineart_prepend_pool(&testing->intersecting_verts, &rb->render_data_pool, new_share); } else { - lineart_prepend_pool(&rt->intersecting_verts, &rb->render_data_pool, new_share); + lineart_prepend_pool(&tri->intersecting_verts, &rb->render_data_pool, new_share); } } else { /* If not sharing any points, then we need to try all the possibilities. */ - E0T = lineart_triangle_2v_intersection_test(rb, rt->v[0], rt->v[1], rt, testing, 0); + E0T = lineart_triangle_2v_intersection_test(rb, tri->v[0], tri->v[1], tri, testing, 0); if (E0T && (!(*next))) { (*next) = E0T; - lineart_vert_set_intersection_2v((*next), rt->v[0], rt->v[1]); + lineart_vert_set_intersection_2v((*next), tri->v[0], tri->v[1]); next = &v2; } - E1T = lineart_triangle_2v_intersection_test(rb, rt->v[1], rt->v[2], rt, testing, v1); + E1T = lineart_triangle_2v_intersection_test(rb, tri->v[1], tri->v[2], tri, testing, v1); if (E1T && (!(*next))) { (*next) = E1T; - lineart_vert_set_intersection_2v((*next), rt->v[1], rt->v[2]); + lineart_vert_set_intersection_2v((*next), tri->v[1], tri->v[2]); next = &v2; } if (!(*next)) { - E2T = lineart_triangle_2v_intersection_test(rb, rt->v[2], rt->v[0], rt, testing, v1); + E2T = lineart_triangle_2v_intersection_test(rb, tri->v[2], tri->v[0], tri, testing, v1); } if (E2T && (!(*next))) { (*next) = E2T; - lineart_vert_set_intersection_2v((*next), rt->v[2], rt->v[0]); + lineart_vert_set_intersection_2v((*next), tri->v[2], tri->v[0]); next = &v2; } if (!(*next)) { TE0 = lineart_triangle_2v_intersection_test( - rb, testing->v[0], testing->v[1], testing, rt, v1); + rb, testing->v[0], testing->v[1], testing, tri, v1); } if (TE0 && (!(*next))) { (*next) = TE0; @@ -2443,7 +2475,7 @@ static LineartEdge *lineart_triangle_intersect(LineartRenderBuffer *rb, } if (!(*next)) { TE1 = lineart_triangle_2v_intersection_test( - rb, testing->v[1], testing->v[2], testing, rt, v1); + rb, testing->v[1], testing->v[2], testing, tri, v1); } if (TE1 && (!(*next))) { (*next) = TE1; @@ -2452,7 +2484,7 @@ static LineartEdge *lineart_triangle_intersect(LineartRenderBuffer *rb, } if (!(*next)) { TE2 = lineart_triangle_2v_intersection_test( - rb, testing->v[2], testing->v[0], testing, rt, v1); + rb, testing->v[2], testing->v[0], testing, tri, v1); } if (TE2 && (!(*next))) { (*next) = TE2; @@ -2484,70 +2516,67 @@ static LineartEdge *lineart_triangle_intersect(LineartRenderBuffer *rb, v1->fbcoord[2] = ZMin * ZMax / (ZMax - fabs(v1->fbcoord[2]) * (ZMax - ZMin)); v2->fbcoord[2] = ZMin * ZMax / (ZMax - fabs(v2->fbcoord[2]) * (ZMax - ZMin)); - ((LineartVertIntersection *)v1)->intersecting_with = rt; + ((LineartVertIntersection *)v1)->intersecting_with = tri; ((LineartVertIntersection *)v2)->intersecting_with = testing; - result = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartEdge)); + result = lineart_mem_acquire(&rb->render_data_pool, sizeof(LineartEdge)); result->v1 = v1; result->v2 = v2; - result->t1 = rt; + result->t1 = tri; result->t2 = testing; - LineartLineSegment *rls = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartLineSegment)); - BLI_addtail(&result->segments, rls); + LineartEdgeSegment *es = lineart_mem_acquire(&rb->render_data_pool, sizeof(LineartEdgeSegment)); + BLI_addtail(&result->segments, es); /* Don't need to OR flags right now, just a type mark. */ result->flags = LRT_EDGE_FLAG_INTERSECTION; - lineart_prepend_edge_direct(&rb->intersection_lines, result); + + lineart_prepend_edge_direct(&rb->intersection.first, result); int r1, r2, c1, c2, row, col; if (lineart_get_edge_bounding_areas(rb, result, &r1, &r2, &c1, &c2)) { for (row = r1; row != r2 + 1; row++) { for (col = c1; col != c2 + 1; col++) { - lineart_bounding_area_link_line( + lineart_bounding_area_link_edge( rb, &rb->initial_bounding_areas[row * LRT_BA_ROWS + col], result); } } } - rb->intersection_count++; - return result; } static void lineart_triangle_intersect_in_bounding_area(LineartRenderBuffer *rb, - LineartTriangle *rt, + LineartTriangle *tri, LineartBoundingArea *ba) { /* Testing_triangle->testing[0] is used to store pairing triangle reference. * See definition of LineartTriangleThread for more info. */ LineartTriangle *testing_triangle; - LineartTriangleThread *rtt; - LinkData *lip, *next_lip; + LineartTriangleThread *tt; - double *G0 = rt->v[0]->gloc, *G1 = rt->v[1]->gloc, *G2 = rt->v[2]->gloc; + double *G0 = tri->v[0]->gloc, *G1 = tri->v[1]->gloc, *G2 = tri->v[2]->gloc; /* If this is not the smallest subdiv bounding area.*/ if (ba->child) { - lineart_triangle_intersect_in_bounding_area(rb, rt, &ba->child[0]); - lineart_triangle_intersect_in_bounding_area(rb, rt, &ba->child[1]); - lineart_triangle_intersect_in_bounding_area(rb, rt, &ba->child[2]); - lineart_triangle_intersect_in_bounding_area(rb, rt, &ba->child[3]); + lineart_triangle_intersect_in_bounding_area(rb, tri, &ba->child[0]); + lineart_triangle_intersect_in_bounding_area(rb, tri, &ba->child[1]); + lineart_triangle_intersect_in_bounding_area(rb, tri, &ba->child[2]); + lineart_triangle_intersect_in_bounding_area(rb, tri, &ba->child[3]); return; } /* If this _is_ the smallest subdiv bounding area, then do the intersections there. */ - for (lip = ba->linked_triangles.first; lip; lip = next_lip) { - next_lip = lip->next; - testing_triangle = lip->data; - rtt = (LineartTriangleThread *)testing_triangle; + for (int i = 0; i < ba->triangle_count; i++) { + testing_triangle = ba->linked_triangles[i]; + tt = (LineartTriangleThread *)testing_triangle; - if (testing_triangle == rt || rtt->testing_e[0] == (LineartEdge *)rt) { + if (testing_triangle == tri || tt->testing_e[0] == (LineartEdge *)tri) { continue; } - rtt->testing_e[0] = (LineartEdge *)rt; + tt->testing_e[0] = (LineartEdge *)tri; if ((testing_triangle->flags & LRT_TRIANGLE_NO_INTERSECTION) || ((testing_triangle->flags & LRT_TRIANGLE_INTERSECTION_ONLY) && - (rt->flags & LRT_TRIANGLE_INTERSECTION_ONLY))) { + (tri->flags & LRT_TRIANGLE_INTERSECTION_ONLY))) { continue; } @@ -2561,12 +2590,12 @@ static void lineart_triangle_intersect_in_bounding_area(LineartRenderBuffer *rb, (MAX3(G0[0], G1[0], G2[0]) < MIN3(RG0[0], RG1[0], RG2[0])) || (MIN3(G0[1], G1[1], G2[1]) > MAX3(RG0[1], RG1[1], RG2[1])) || (MAX3(G0[1], G1[1], G2[1]) < MIN3(RG0[1], RG1[1], RG2[1])) || - lineart_triangle_share_edge(rt, testing_triangle)) { + lineart_triangle_share_edge(tri, testing_triangle)) { continue; } /* If we do need to compute intersection, then finally do it. */ - lineart_triangle_intersect(rb, rt, testing_triangle); + lineart_triangle_intersect(rb, tri, testing_triangle); } } @@ -2598,22 +2627,11 @@ static void lineart_destroy_render_data(LineartRenderBuffer *rb) return; } - rb->contour_count = 0; - rb->contour_managed = NULL; - rb->intersection_count = 0; - rb->intersection_managed = NULL; - rb->material_line_count = 0; - rb->material_managed = NULL; - rb->crease_count = 0; - rb->crease_managed = NULL; - rb->edge_mark_count = 0; - rb->edge_mark_managed = NULL; - - rb->contours = NULL; - rb->intersection_lines = NULL; - rb->crease_lines = NULL; - rb->material_lines = NULL; - rb->edge_marks = NULL; + memset(&rb->contour, 0, sizeof(ListBase)); + memset(&rb->crease, 0, sizeof(ListBase)); + memset(&rb->intersection, 0, sizeof(ListBase)); + memset(&rb->edge_mark, 0, sizeof(ListBase)); + memset(&rb->material, 0, sizeof(ListBase)); BLI_listbase_clear(&rb->chains); BLI_listbase_clear(&rb->wasted_cuts); @@ -2671,6 +2689,13 @@ static LineartRenderBuffer *lineart_create_render_buffer(Scene *scene, rb->w = scene->r.xsch; rb->h = scene->r.ysch; + if (rb->cam_is_persp) { + rb->tile_recursive_level = LRT_TILE_RECURSIVE_PERSPECTIVE; + } + else { + rb->tile_recursive_level = LRT_TILE_RECURSIVE_ORTHO; + } + double asp = ((double)rb->w / (double)rb->h); rb->shift_x = (asp >= 1) ? c->shiftx : c->shiftx * asp; rb->shift_y = (asp <= 1) ? c->shifty : c->shifty * asp; @@ -2729,7 +2754,7 @@ static void lineart_main_bounding_area_make_initial(LineartRenderBuffer *rb) rb->height_per_tile = span_h; rb->bounding_area_count = sp_w * sp_h; - rb->initial_bounding_areas = lineart_mem_aquire( + rb->initial_bounding_areas = lineart_mem_acquire( &rb->render_data_pool, sizeof(LineartBoundingArea) * rb->bounding_area_count); /* Initialize tiles. */ @@ -2746,6 +2771,14 @@ static void lineart_main_bounding_area_make_initial(LineartRenderBuffer *rb) ba->cx = (ba->l + ba->r) / 2; ba->cy = (ba->u + ba->b) / 2; + /* Init linked_triangles array. */ + ba->max_triangle_count = LRT_TILE_SPLITTING_TRIANGLE_LIMIT; + ba->max_line_count = LRT_TILE_EDGE_COUNT_INITIAL; + ba->linked_triangles = lineart_mem_acquire( + &rb->render_data_pool, sizeof(LineartTriangle *) * ba->max_triangle_count); + ba->linked_lines = lineart_mem_acquire(&rb->render_data_pool, + sizeof(LineartEdge *) * ba->max_line_count); + /* Link adjacent ones. */ if (row) { lineart_list_append_pointer_pool( @@ -2923,9 +2956,9 @@ static void lineart_bounding_area_split(LineartRenderBuffer *rb, LineartBoundingArea *root, int recursive_level) { - LineartBoundingArea *ba = lineart_mem_aquire(&rb->render_data_pool, - sizeof(LineartBoundingArea) * 4); - LineartTriangle *rt; + LineartBoundingArea *ba = lineart_mem_acquire(&rb->render_data_pool, + sizeof(LineartBoundingArea) * 4); + LineartTriangle *tri; LineartEdge *e; ba[0].l = root->cx; @@ -2960,35 +2993,47 @@ static void lineart_bounding_area_split(LineartRenderBuffer *rb, lineart_bounding_areas_connect_new(rb, root); - while ((rt = lineart_list_pop_pointer_no_free(&root->linked_triangles)) != NULL) { + /* Init linked_triangles array. */ + for (int i = 0; i < 4; i++) { + ba[i].max_triangle_count = LRT_TILE_SPLITTING_TRIANGLE_LIMIT; + ba[i].max_line_count = LRT_TILE_EDGE_COUNT_INITIAL; + ba[i].linked_triangles = lineart_mem_acquire( + &rb->render_data_pool, sizeof(LineartTriangle *) * LRT_TILE_SPLITTING_TRIANGLE_LIMIT); + ba[i].linked_lines = lineart_mem_acquire(&rb->render_data_pool, + sizeof(LineartEdge *) * LRT_TILE_EDGE_COUNT_INITIAL); + } + + for (int i = 0; i < root->triangle_count; i++) { + tri = root->linked_triangles[i]; LineartBoundingArea *cba = root->child; double b[4]; - b[0] = MIN3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]); - b[1] = MAX3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]); - b[2] = MAX3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]); - b[3] = MIN3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]); + b[0] = MIN3(tri->v[0]->fbcoord[0], tri->v[1]->fbcoord[0], tri->v[2]->fbcoord[0]); + b[1] = MAX3(tri->v[0]->fbcoord[0], tri->v[1]->fbcoord[0], tri->v[2]->fbcoord[0]); + b[2] = MAX3(tri->v[0]->fbcoord[1], tri->v[1]->fbcoord[1], tri->v[2]->fbcoord[1]); + b[3] = MIN3(tri->v[0]->fbcoord[1], tri->v[1]->fbcoord[1], tri->v[2]->fbcoord[1]); if (LRT_BOUND_AREA_CROSSES(b, &cba[0].l)) { - lineart_bounding_area_link_triangle(rb, &cba[0], rt, b, 0, recursive_level + 1, false); + lineart_bounding_area_link_triangle(rb, &cba[0], tri, b, 0, recursive_level + 1, false); } if (LRT_BOUND_AREA_CROSSES(b, &cba[1].l)) { - lineart_bounding_area_link_triangle(rb, &cba[1], rt, b, 0, recursive_level + 1, false); + lineart_bounding_area_link_triangle(rb, &cba[1], tri, b, 0, recursive_level + 1, false); } if (LRT_BOUND_AREA_CROSSES(b, &cba[2].l)) { - lineart_bounding_area_link_triangle(rb, &cba[2], rt, b, 0, recursive_level + 1, false); + lineart_bounding_area_link_triangle(rb, &cba[2], tri, b, 0, recursive_level + 1, false); } if (LRT_BOUND_AREA_CROSSES(b, &cba[3].l)) { - lineart_bounding_area_link_triangle(rb, &cba[3], rt, b, 0, recursive_level + 1, false); + lineart_bounding_area_link_triangle(rb, &cba[3], tri, b, 0, recursive_level + 1, false); } } - while ((e = lineart_list_pop_pointer_no_free(&root->linked_lines)) != NULL) { - lineart_bounding_area_link_line(rb, root, e); + for (int i = 0; i < root->line_count; i++) { + e = root->linked_lines[i]; + lineart_bounding_area_link_edge(rb, root, e); } rb->bounding_area_count += 3; } -static bool lineart_bounding_area_line_intersect(LineartRenderBuffer *UNUSED(fb), +static bool lineart_bounding_area_edge_intersect(LineartRenderBuffer *UNUSED(fb), const double l[2], const double r[2], LineartBoundingArea *ba) @@ -3032,11 +3077,11 @@ static bool lineart_bounding_area_line_intersect(LineartRenderBuffer *UNUSED(fb) } static bool lineart_bounding_area_triangle_intersect(LineartRenderBuffer *fb, - LineartTriangle *rt, + LineartTriangle *tri, LineartBoundingArea *ba) { double p1[2], p2[2], p3[2], p4[2]; - double *FBC1 = rt->v[0]->fbcoord, *FBC2 = rt->v[1]->fbcoord, *FBC3 = rt->v[2]->fbcoord; + double *FBC1 = tri->v[0]->fbcoord, *FBC2 = tri->v[1]->fbcoord, *FBC3 = tri->v[2]->fbcoord; p3[0] = p1[0] = (double)ba->l; p2[1] = p1[1] = (double)ba->b; @@ -3056,9 +3101,9 @@ static bool lineart_bounding_area_triangle_intersect(LineartRenderBuffer *fb, return true; } - if ((lineart_bounding_area_line_intersect(fb, FBC1, FBC2, ba)) || - (lineart_bounding_area_line_intersect(fb, FBC2, FBC3, ba)) || - (lineart_bounding_area_line_intersect(fb, FBC3, FBC1, ba))) { + if ((lineart_bounding_area_edge_intersect(fb, FBC1, FBC2, ba)) || + (lineart_bounding_area_edge_intersect(fb, FBC2, FBC3, ba)) || + (lineart_bounding_area_edge_intersect(fb, FBC3, FBC1, ba))) { return true; } @@ -3071,27 +3116,27 @@ static bool lineart_bounding_area_triangle_intersect(LineartRenderBuffer *fb, */ static void lineart_bounding_area_link_triangle(LineartRenderBuffer *rb, LineartBoundingArea *root_ba, - LineartTriangle *rt, + LineartTriangle *tri, double *LRUB, int recursive, int recursive_level, bool do_intersection) { - if (!lineart_bounding_area_triangle_intersect(rb, rt, root_ba)) { + if (!lineart_bounding_area_triangle_intersect(rb, tri, root_ba)) { return; } if (root_ba->child == NULL) { - lineart_list_append_pointer_pool(&root_ba->linked_triangles, &rb->render_data_pool, rt); - root_ba->triangle_count++; + lineart_bounding_area_triangle_add(rb, root_ba, tri); /* If splitting doesn't improve triangle separation, then shouldn't allow splitting anymore. * Here we use recursive limit. This is especially useful in orthographic render, * where a lot of faces could easily line up perfectly in image space, * which can not be separated by simply slicing the image tile. */ - if (root_ba->triangle_count > 200 && recursive && recursive_level < 10) { + if (root_ba->triangle_count >= LRT_TILE_SPLITTING_TRIANGLE_LIMIT && recursive && + recursive_level < rb->tile_recursive_level) { lineart_bounding_area_split(rb, root_ba, recursive_level); } if (recursive && do_intersection && rb->use_intersections) { - lineart_triangle_intersect_in_bounding_area(rb, rt, root_ba); + lineart_triangle_intersect_in_bounding_area(rb, tri, root_ba); } } else { @@ -3099,54 +3144,54 @@ static void lineart_bounding_area_link_triangle(LineartRenderBuffer *rb, double *B1 = LRUB; double b[4]; if (!LRUB) { - b[0] = MIN3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]); - b[1] = MAX3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]); - b[2] = MAX3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]); - b[3] = MIN3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]); + b[0] = MIN3(tri->v[0]->fbcoord[0], tri->v[1]->fbcoord[0], tri->v[2]->fbcoord[0]); + b[1] = MAX3(tri->v[0]->fbcoord[0], tri->v[1]->fbcoord[0], tri->v[2]->fbcoord[0]); + b[2] = MAX3(tri->v[0]->fbcoord[1], tri->v[1]->fbcoord[1], tri->v[2]->fbcoord[1]); + b[3] = MIN3(tri->v[0]->fbcoord[1], tri->v[1]->fbcoord[1], tri->v[2]->fbcoord[1]); B1 = b; } if (LRT_BOUND_AREA_CROSSES(B1, &ba[0].l)) { lineart_bounding_area_link_triangle( - rb, &ba[0], rt, B1, recursive, recursive_level + 1, do_intersection); + rb, &ba[0], tri, B1, recursive, recursive_level + 1, do_intersection); } if (LRT_BOUND_AREA_CROSSES(B1, &ba[1].l)) { lineart_bounding_area_link_triangle( - rb, &ba[1], rt, B1, recursive, recursive_level + 1, do_intersection); + rb, &ba[1], tri, B1, recursive, recursive_level + 1, do_intersection); } if (LRT_BOUND_AREA_CROSSES(B1, &ba[2].l)) { lineart_bounding_area_link_triangle( - rb, &ba[2], rt, B1, recursive, recursive_level + 1, do_intersection); + rb, &ba[2], tri, B1, recursive, recursive_level + 1, do_intersection); } if (LRT_BOUND_AREA_CROSSES(B1, &ba[3].l)) { lineart_bounding_area_link_triangle( - rb, &ba[3], rt, B1, recursive, recursive_level + 1, do_intersection); + rb, &ba[3], tri, B1, recursive, recursive_level + 1, do_intersection); } } } -static void lineart_bounding_area_link_line(LineartRenderBuffer *rb, +static void lineart_bounding_area_link_edge(LineartRenderBuffer *rb, LineartBoundingArea *root_ba, LineartEdge *e) { if (root_ba->child == NULL) { - lineart_list_append_pointer_pool(&root_ba->linked_lines, &rb->render_data_pool, e); + lineart_bounding_area_line_add(rb, root_ba, e); } else { - if (lineart_bounding_area_line_intersect( + if (lineart_bounding_area_edge_intersect( rb, e->v1->fbcoord, e->v2->fbcoord, &root_ba->child[0])) { - lineart_bounding_area_link_line(rb, &root_ba->child[0], e); + lineart_bounding_area_link_edge(rb, &root_ba->child[0], e); } - if (lineart_bounding_area_line_intersect( + if (lineart_bounding_area_edge_intersect( rb, e->v1->fbcoord, e->v2->fbcoord, &root_ba->child[1])) { - lineart_bounding_area_link_line(rb, &root_ba->child[1], e); + lineart_bounding_area_link_edge(rb, &root_ba->child[1], e); } - if (lineart_bounding_area_line_intersect( + if (lineart_bounding_area_edge_intersect( rb, e->v1->fbcoord, e->v2->fbcoord, &root_ba->child[2])) { - lineart_bounding_area_link_line(rb, &root_ba->child[2], e); + lineart_bounding_area_link_edge(rb, &root_ba->child[2], e); } - if (lineart_bounding_area_line_intersect( + if (lineart_bounding_area_edge_intersect( rb, e->v1->fbcoord, e->v2->fbcoord, &root_ba->child[3])) { - lineart_bounding_area_link_line(rb, &root_ba->child[3], e); + lineart_bounding_area_link_edge(rb, &root_ba->child[3], e); } } } @@ -3162,7 +3207,7 @@ static void lineart_main_link_lines(LineartRenderBuffer *rb) if (lineart_get_edge_bounding_areas(rb, e, &r1, &r2, &c1, &c2)) { for (row = r1; row != r2 + 1; row++) { for (col = c1; col != c2 + 1; col++) { - lineart_bounding_area_link_line( + lineart_bounding_area_link_edge( rb, &rb->initial_bounding_areas[row * LRT_BA_ROWS + col], e); } } @@ -3172,7 +3217,7 @@ static void lineart_main_link_lines(LineartRenderBuffer *rb) } static bool lineart_get_triangle_bounding_areas(LineartRenderBuffer *rb, - LineartTriangle *rt, + LineartTriangle *tri, int *rowbegin, int *rowend, int *colbegin, @@ -3181,14 +3226,14 @@ static bool lineart_get_triangle_bounding_areas(LineartRenderBuffer *rb, double sp_w = rb->width_per_tile, sp_h = rb->height_per_tile; double b[4]; - if (!rt->v[0] || !rt->v[1] || !rt->v[2]) { + if (!tri->v[0] || !tri->v[1] || !tri->v[2]) { return false; } - b[0] = MIN3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]); - b[1] = MAX3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]); - b[2] = MIN3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]); - b[3] = MAX3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]); + b[0] = MIN3(tri->v[0]->fbcoord[0], tri->v[1]->fbcoord[0], tri->v[2]->fbcoord[0]); + b[1] = MAX3(tri->v[0]->fbcoord[0], tri->v[1]->fbcoord[0], tri->v[2]->fbcoord[0]); + b[2] = MIN3(tri->v[0]->fbcoord[1], tri->v[1]->fbcoord[1], tri->v[2]->fbcoord[1]); + b[3] = MAX3(tri->v[0]->fbcoord[1], tri->v[1]->fbcoord[1], tri->v[2]->fbcoord[1]); if (b[0] > 1 || b[1] < -1 || b[2] > 1 || b[3] < -1) { return false; @@ -3355,33 +3400,33 @@ LineartBoundingArea *MOD_lineart_get_bounding_area(LineartRenderBuffer *rb, doub */ static void lineart_main_add_triangles(LineartRenderBuffer *rb) { - LineartTriangle *rt; + LineartTriangle *tri; int i, lim; int x1, x2, y1, y2; int r, co; - LISTBASE_FOREACH (LineartElementLinkNode *, reln, &rb->triangle_buffer_pointers) { - rt = reln->pointer; - lim = reln->element_count; + LISTBASE_FOREACH (LineartElementLinkNode *, eln, &rb->triangle_buffer_pointers) { + tri = eln->pointer; + lim = eln->element_count; for (i = 0; i < lim; i++) { - if ((rt->flags & LRT_CULL_USED) || (rt->flags & LRT_CULL_DISCARD)) { - rt = (void *)(((uchar *)rt) + rb->triangle_size); + if ((tri->flags & LRT_CULL_USED) || (tri->flags & LRT_CULL_DISCARD)) { + tri = (void *)(((uchar *)tri) + rb->triangle_size); continue; } - if (lineart_get_triangle_bounding_areas(rb, rt, &y1, &y2, &x1, &x2)) { + if (lineart_get_triangle_bounding_areas(rb, tri, &y1, &y2, &x1, &x2)) { for (co = x1; co <= x2; co++) { for (r = y1; r <= y2; r++) { lineart_bounding_area_link_triangle(rb, &rb->initial_bounding_areas[r * LRT_BA_ROWS + co], - rt, + tri, 0, 1, 0, - (!(rt->flags & LRT_TRIANGLE_NO_INTERSECTION))); + (!(tri->flags & LRT_TRIANGLE_NO_INTERSECTION))); } } } /* Else throw away. */ - rt = (void *)(((uchar *)rt) + rb->triangle_size); + tri = (void *)(((uchar *)tri) + rb->triangle_size); } } } @@ -3656,6 +3701,8 @@ bool MOD_lineart_compute_feature_lines(Depsgraph *depsgraph, LineartGpencilModif Scene *scene = DEG_get_evaluated_scene(depsgraph); int intersections_only = 0; /* Not used right now, but preserve for future. */ + BKE_scene_camera_switch_update(scene); + if (!scene->camera) { return false; } @@ -3815,52 +3862,52 @@ static void lineart_gpencil_generate(LineartRenderBuffer *rb, bool invert_input = modifier_flags & LRT_GPENCIL_INVERT_SOURCE_VGROUP; bool match_output = modifier_flags & LRT_GPENCIL_MATCH_OUTPUT_VGROUP; - LISTBASE_FOREACH (LineartLineChain *, rlc, &rb->chains) { + LISTBASE_FOREACH (LineartEdgeChain *, ec, &rb->chains) { - if (rlc->picked) { + if (ec->picked) { continue; } - if (!(rlc->type & (types & enabled_types))) { + if (!(ec->type & (types & enabled_types))) { continue; } - if (rlc->level > level_end || rlc->level < level_start) { + if (ec->level > level_end || ec->level < level_start) { continue; } - if (orig_ob && orig_ob != rlc->object_ref) { + if (orig_ob && orig_ob != ec->object_ref) { continue; } - if (orig_col && rlc->object_ref) { - if (!BKE_collection_has_object_recursive_instanced(orig_col, (Object *)rlc->object_ref)) { + if (orig_col && ec->object_ref) { + if (!BKE_collection_has_object_recursive_instanced(orig_col, (Object *)ec->object_ref)) { continue; } } if (transparency_flags & LRT_GPENCIL_TRANSPARENCY_ENABLE) { if (transparency_flags & LRT_GPENCIL_TRANSPARENCY_MATCH) { - if (rlc->transparency_mask != transparency_mask) { + if (ec->transparency_mask != transparency_mask) { continue; } } else { - if (!(rlc->transparency_mask & transparency_mask)) { + if (!(ec->transparency_mask & transparency_mask)) { continue; } } } /* Preserved: If we ever do asynchronous generation, this picked flag should be set here. */ - // rlc->picked = 1; + // ec->picked = 1; int array_idx = 0; - int count = MOD_lineart_chain_count(rlc); + int count = MOD_lineart_chain_count(ec); bGPDstroke *gps = BKE_gpencil_stroke_add(gpf, color_idx, count, thickness, false); float *stroke_data = MEM_callocN(sizeof(float) * count * GP_PRIM_DATABUF_SIZE, "line art add stroke"); - LISTBASE_FOREACH (LineartLineChainItem *, rlci, &rlc->chain) { - stroke_data[array_idx] = rlci->gpos[0]; - stroke_data[array_idx + 1] = rlci->gpos[1]; - stroke_data[array_idx + 2] = rlci->gpos[2]; + LISTBASE_FOREACH (LineartEdgeChainItem *, eci, &ec->chain) { + stroke_data[array_idx] = eci->gpos[0]; + stroke_data[array_idx + 1] = eci->gpos[1]; + stroke_data[array_idx + 2] = eci->gpos[2]; mul_m4_v3(gp_obmat_inverse, &stroke_data[array_idx]); stroke_data[array_idx + 3] = 1; /* thickness. */ stroke_data[array_idx + 4] = opacity; /* hardness?. */ @@ -3874,7 +3921,7 @@ static void lineart_gpencil_generate(LineartRenderBuffer *rb, MEM_freeN(stroke_data); if (source_vgname && vgname) { - Object *eval_ob = DEG_get_evaluated_object(depsgraph, rlc->object_ref); + Object *eval_ob = DEG_get_evaluated_object(depsgraph, ec->object_ref); int gpdg = -1; if ((match_output || (gpdg = BKE_object_defgroup_name_index(gpencil_object, vgname)) >= 0)) { if (eval_ob && eval_ob->type == OB_MESH) { @@ -3890,8 +3937,8 @@ static void lineart_gpencil_generate(LineartRenderBuffer *rb, } } int sindex = 0, vindex; - LISTBASE_FOREACH (LineartLineChainItem *, rlci, &rlc->chain) { - vindex = rlci->index; + LISTBASE_FOREACH (LineartEdgeChainItem *, eci, &ec->chain) { + vindex = eci->index; if (vindex >= me->totvert) { break; } diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_intern.h b/source/blender/gpencil_modifiers/intern/lineart/lineart_intern.h index 2c3130b46c9..9ed98b38f07 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_intern.h +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_intern.h @@ -54,11 +54,11 @@ void lineart_list_remove_pointer_item_no_free(ListBase *h, LinkData *lip); struct LineartStaticMemPoolNode *lineart_mem_new_static_pool(struct LineartStaticMemPool *smp, size_t size); -void *lineart_mem_aquire(struct LineartStaticMemPool *smp, size_t size); -void *lineart_mem_aquire_thread(struct LineartStaticMemPool *smp, size_t size); +void *lineart_mem_acquire(struct LineartStaticMemPool *smp, size_t size); +void *lineart_mem_acquire_thread(struct LineartStaticMemPool *smp, size_t size); void lineart_mem_destroy(struct LineartStaticMemPool *smp); -void lineart_prepend_edge_direct(struct LineartEdge **first, void *node); +void lineart_prepend_edge_direct(void **list_head, void *node); void lineart_prepend_pool(LinkNode **first, struct LineartStaticMemPool *smp, void *link); void lineart_matrix_ortho_44d(double (*mProjection)[4], @@ -76,29 +76,42 @@ int lineart_count_intersection_segment_count(struct LineartRenderBuffer *rb); void lineart_count_and_print_render_buffer_memory(struct LineartRenderBuffer *rb); #define LRT_ITER_ALL_LINES_BEGIN \ - LineartEdge *e, *next_e, **current_list; \ - e = rb->contours; \ - for (current_list = &rb->contours; e; e = next_e) { \ + LineartEdge *e, *next_e; \ + void **current_head; \ + e = rb->contour.first; \ + if (!e) { \ + e = rb->crease.first; \ + } \ + if (!e) { \ + e = rb->material.first; \ + } \ + if (!e) { \ + e = rb->edge_mark.first; \ + } \ + if (!e) { \ + e = rb->intersection.first; \ + } \ + for (current_head = &rb->contour.first; e; e = next_e) { \ next_e = e->next; #define LRT_ITER_ALL_LINES_NEXT \ while (!next_e) { \ - if (current_list == &rb->contours) { \ - current_list = &rb->crease_lines; \ + if (current_head == &rb->contour.first) { \ + current_head = &rb->crease.first; \ } \ - else if (current_list == &rb->crease_lines) { \ - current_list = &rb->material_lines; \ + else if (current_head == &rb->crease.first) { \ + current_head = &rb->material.first; \ } \ - else if (current_list == &rb->material_lines) { \ - current_list = &rb->edge_marks; \ + else if (current_head == &rb->material.first) { \ + current_head = &rb->edge_mark.first; \ } \ - else if (current_list == &rb->edge_marks) { \ - current_list = &rb->intersection_lines; \ + else if (current_head == &rb->edge_mark.first) { \ + current_head = &rb->intersection.first; \ } \ else { \ break; \ } \ - next_e = *current_list; \ + next_e = *current_head; \ } #define LRT_ITER_ALL_LINES_END \ diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_util.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_util.c index 4d136fe0d0e..d05f931f75d 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_util.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_util.c @@ -43,7 +43,7 @@ void *lineart_list_append_pointer_pool(ListBase *h, LineartStaticMemPool *smp, v if (h == NULL) { return 0; } - lip = lineart_mem_aquire(smp, sizeof(LinkData)); + lip = lineart_mem_acquire(smp, sizeof(LinkData)); lip->data = data; BLI_addtail(h, lip); return lip; @@ -57,7 +57,7 @@ void *lineart_list_append_pointer_pool_sized(ListBase *h, if (h == NULL) { return 0; } - lip = lineart_mem_aquire(smp, size); + lip = lineart_mem_acquire(smp, size); lip->data = data; BLI_addtail(h, lip); return lip; @@ -92,7 +92,7 @@ LineartStaticMemPoolNode *lineart_mem_new_static_pool(LineartStaticMemPool *smp, BLI_addhead(&smp->pools, smpn); return smpn; } -void *lineart_mem_aquire(LineartStaticMemPool *smp, size_t size) +void *lineart_mem_acquire(LineartStaticMemPool *smp, size_t size) { LineartStaticMemPoolNode *smpn = smp->pools.first; void *ret; @@ -107,7 +107,7 @@ void *lineart_mem_aquire(LineartStaticMemPool *smp, size_t size) return ret; } -void *lineart_mem_aquire_thread(LineartStaticMemPool *smp, size_t size) +void *lineart_mem_acquire_thread(LineartStaticMemPool *smp, size_t size) { void *ret; @@ -135,16 +135,16 @@ void lineart_mem_destroy(LineartStaticMemPool *smp) } } -void lineart_prepend_edge_direct(LineartEdge **first, void *node) +void lineart_prepend_edge_direct(void **list_head, void *node) { LineartEdge *e_n = (LineartEdge *)node; - e_n->next = (*first); - (*first) = e_n; + e_n->next = (*list_head); + (*list_head) = e_n; } void lineart_prepend_pool(LinkNode **first, LineartStaticMemPool *smp, void *link) { - LinkNode *ln = lineart_mem_aquire_thread(smp, sizeof(LinkNode)); + LinkNode *ln = lineart_mem_acquire_thread(smp, sizeof(LinkNode)); ln->next = (*first); ln->link = link; (*first) = ln; diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index 2ff72266a64..cf6009c2881 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -62,6 +62,7 @@ set(SRC intern/gpu_buffers.c intern/gpu_capabilities.cc intern/gpu_codegen.c + intern/gpu_compute.cc intern/gpu_context.cc intern/gpu_debug.cc intern/gpu_drawlist.cc @@ -91,6 +92,7 @@ set(SRC opengl/gl_backend.cc opengl/gl_batch.cc + opengl/gl_compute.cc opengl/gl_context.cc opengl/gl_debug.cc opengl/gl_debug_layer.cc @@ -113,6 +115,7 @@ set(SRC GPU_buffers.h GPU_capabilities.h GPU_common.h + GPU_compute.h GPU_context.h GPU_debug.h GPU_drawlist.h @@ -163,6 +166,7 @@ set(SRC opengl/gl_backend.hh opengl/gl_batch.hh + opengl/gl_compute.hh opengl/gl_context.hh opengl/gl_debug.hh opengl/gl_drawlist.hh @@ -275,6 +279,7 @@ data_to_c_simple(shaders/material/gpu_shader_material_anisotropic.glsl SRC) data_to_c_simple(shaders/material/gpu_shader_material_attribute.glsl SRC) data_to_c_simple(shaders/material/gpu_shader_material_background.glsl SRC) data_to_c_simple(shaders/material/gpu_shader_material_bevel.glsl SRC) +data_to_c_simple(shaders/material/gpu_shader_material_wavelength.glsl SRC) data_to_c_simple(shaders/material/gpu_shader_material_blackbody.glsl SRC) data_to_c_simple(shaders/material/gpu_shader_material_bright_contrast.glsl SRC) data_to_c_simple(shaders/material/gpu_shader_material_bump.glsl SRC) @@ -389,6 +394,7 @@ if(WITH_GTESTS) if(WITH_OPENGL_DRAW_TESTS) set(TEST_SRC tests/gpu_testing.cc + tests/gpu_shader_test.cc tests/gpu_testing.hh ) diff --git a/source/blender/gpu/GPU_capabilities.h b/source/blender/gpu/GPU_capabilities.h index b95053a3715..45c656b49be 100644 --- a/source/blender/gpu/GPU_capabilities.h +++ b/source/blender/gpu/GPU_capabilities.h @@ -37,6 +37,15 @@ int GPU_max_textures(void); int GPU_max_textures_vert(void); int GPU_max_textures_geom(void); int GPU_max_textures_frag(void); +int GPU_max_uniforms_vert(void); +int GPU_max_uniforms_frag(void); +int GPU_max_batch_indices(void); +int GPU_max_batch_vertices(void); +int GPU_max_vertex_attribs(void); +int GPU_max_varying_floats(void); + +int GPU_extensions_len(void); +const char *GPU_extension_get(int i); int GPU_texture_size_with_limit(int res, bool limit_gl_texture_size); @@ -46,6 +55,8 @@ bool GPU_use_main_context_workaround(void); bool GPU_use_hq_normals_workaround(void); bool GPU_crappy_amd_driver(void); +bool GPU_compute_shader_support(void); +bool GPU_shader_storage_buffer_objects_support(void); bool GPU_shader_image_load_store_support(void); bool GPU_mem_stats_supported(void); diff --git a/source/blender/gpu/GPU_common.h b/source/blender/gpu/GPU_common.h index 1be74701176..c00bf581e8c 100644 --- a/source/blender/gpu/GPU_common.h +++ b/source/blender/gpu/GPU_common.h @@ -24,6 +24,7 @@ #pragma once #define PROGRAM_NO_OPTI 0 +//#define GPU_NO_USE_PY_REFERENCES #if defined(NDEBUG) # define TRUST_NO_ONE 0 diff --git a/source/blender/gpu/GPU_compute.h b/source/blender/gpu/GPU_compute.h new file mode 100644 index 00000000000..a048f72c0a0 --- /dev/null +++ b/source/blender/gpu/GPU_compute.h @@ -0,0 +1,38 @@ +/* + * 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. + */ + +/** \file + * \ingroup gpu + */ + +#pragma once + +#include "BLI_sys_types.h" + +#include "GPU_shader.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void GPU_compute_dispatch(GPUShader *shader, + uint groups_x_len, + uint groups_y_len, + uint groups_z_len); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/gpu/GPU_framebuffer.h b/source/blender/gpu/GPU_framebuffer.h index af94c1fb0e6..6482d9a9d3e 100644 --- a/source/blender/gpu/GPU_framebuffer.h +++ b/source/blender/gpu/GPU_framebuffer.h @@ -209,6 +209,11 @@ void GPU_framebuffer_recursive_downsample(GPUFrameBuffer *fb, void (*callback)(void *userData, int level), void *userData); +#ifndef GPU_NO_USE_PY_REFERENCES +void **GPU_framebuffer_py_reference_get(GPUFrameBuffer *gpu_fb); +void GPU_framebuffer_py_reference_set(GPUFrameBuffer *gpu_fb, void **py_ref); +#endif + void GPU_framebuffer_push(GPUFrameBuffer *fb); GPUFrameBuffer *GPU_framebuffer_pop(void); uint GPU_framebuffer_stack_level_get(void); diff --git a/source/blender/gpu/GPU_index_buffer.h b/source/blender/gpu/GPU_index_buffer.h index 76aab3c196b..8362dbcaccc 100644 --- a/source/blender/gpu/GPU_index_buffer.h +++ b/source/blender/gpu/GPU_index_buffer.h @@ -49,6 +49,7 @@ void GPU_indexbuf_init_ex(GPUIndexBufBuilder *, GPUPrimType, uint index_len, uin /* supports only GPU_PRIM_POINTS, GPU_PRIM_LINES and GPU_PRIM_TRIS. */ void GPU_indexbuf_init(GPUIndexBufBuilder *, GPUPrimType, uint prim_len, uint vertex_len); +GPUIndexBuf *GPU_indexbuf_build_on_device(uint index_len); void GPU_indexbuf_add_generic_vert(GPUIndexBufBuilder *, uint v); void GPU_indexbuf_add_primitive_restart(GPUIndexBufBuilder *); @@ -70,6 +71,8 @@ void GPU_indexbuf_set_tri_restart(GPUIndexBufBuilder *builder, uint elem); GPUIndexBuf *GPU_indexbuf_build(GPUIndexBufBuilder *); void GPU_indexbuf_build_in_place(GPUIndexBufBuilder *, GPUIndexBuf *); +void GPU_indexbuf_bind_as_ssbo(GPUIndexBuf *elem, int binding); + /* Create a sub-range of an existing index-buffer. */ GPUIndexBuf *GPU_indexbuf_create_subrange(GPUIndexBuf *elem_src, uint start, uint length); void GPU_indexbuf_create_subrange_in_place(GPUIndexBuf *elem, @@ -77,6 +80,15 @@ void GPU_indexbuf_create_subrange_in_place(GPUIndexBuf *elem, uint start, uint length); +/** + * (Download and) return a pointer containing the data of an index buffer. + * + * Note that the returned pointer is still owned by the driver. To get an + * local copy, use `GPU_indexbuf_unmap` after calling `GPU_indexbuf_read`. + */ +const uint32_t *GPU_indexbuf_read(GPUIndexBuf *elem); +uint32_t *GPU_indexbuf_unmap(const GPUIndexBuf *elem, const uint32_t *mapped_buffer); + void GPU_indexbuf_discard(GPUIndexBuf *elem); bool GPU_indexbuf_is_init(GPUIndexBuf *elem); diff --git a/source/blender/gpu/GPU_platform.h b/source/blender/gpu/GPU_platform.h index c457b829bf7..fa7d5d7fba8 100644 --- a/source/blender/gpu/GPU_platform.h +++ b/source/blender/gpu/GPU_platform.h @@ -68,6 +68,9 @@ extern "C" { bool GPU_type_matches(eGPUDeviceType device, eGPUOSType os, eGPUDriverType driver); eGPUSupportLevel GPU_platform_support_level(void); +const char *GPU_platform_vendor(void); +const char *GPU_platform_renderer(void); +const char *GPU_platform_version(void); const char *GPU_platform_support_level_key(void); const char *GPU_platform_gpu_name(void); diff --git a/source/blender/gpu/GPU_shader.h b/source/blender/gpu/GPU_shader.h index 9824c7016dc..3923c920c9e 100644 --- a/source/blender/gpu/GPU_shader.h +++ b/source/blender/gpu/GPU_shader.h @@ -27,6 +27,7 @@ extern "C" { #endif +struct GPUIndexBuf; struct GPUVertBuf; /** Opaque type hiding #blender::gpu::Shader */ @@ -45,6 +46,10 @@ GPUShader *GPU_shader_create(const char *vertcode, const char *libcode, const char *defines, const char *shname); +GPUShader *GPU_shader_create_compute(const char *computecode, + const char *libcode, + const char *defines, + const char *shname); GPUShader *GPU_shader_create_from_python(const char *vertcode, const char *fragcode, const char *geomcode, @@ -53,6 +58,7 @@ GPUShader *GPU_shader_create_from_python(const char *vertcode, GPUShader *GPU_shader_create_ex(const char *vertcode, const char *fragcode, const char *geomcode, + const char *computecode, const char *libcode, const char *defines, const eGPUShaderTFBType tf_type, @@ -126,6 +132,7 @@ int GPU_shader_get_uniform(GPUShader *shader, const char *name); int GPU_shader_get_builtin_uniform(GPUShader *shader, int builtin); int GPU_shader_get_builtin_block(GPUShader *shader, int builtin); int GPU_shader_get_uniform_block(GPUShader *shader, const char *name); +int GPU_shader_get_ssbo(GPUShader *shader, const char *name); int GPU_shader_get_uniform_block_binding(GPUShader *shader, const char *name); int GPU_shader_get_texture_binding(GPUShader *shader, const char *name); diff --git a/source/blender/gpu/GPU_state.h b/source/blender/gpu/GPU_state.h index 0687f271670..a338728804c 100644 --- a/source/blender/gpu/GPU_state.h +++ b/source/blender/gpu/GPU_state.h @@ -39,6 +39,7 @@ typedef enum eGPUBarrier { GPU_BARRIER_NONE = 0, GPU_BARRIER_SHADER_IMAGE_ACCESS = (1 << 0), GPU_BARRIER_TEXTURE_FETCH = (1 << 1), + GPU_BARRIER_SHADER_STORAGE = (1 << 2), } eGPUBarrier; ENUM_OPERATORS(eGPUBarrier, GPU_BARRIER_TEXTURE_FETCH) diff --git a/source/blender/gpu/GPU_texture.h b/source/blender/gpu/GPU_texture.h index d9a01663de0..f980c8fdcd7 100644 --- a/source/blender/gpu/GPU_texture.h +++ b/source/blender/gpu/GPU_texture.h @@ -269,6 +269,12 @@ bool GPU_texture_cube(const GPUTexture *tex); bool GPU_texture_depth(const GPUTexture *tex); bool GPU_texture_stencil(const GPUTexture *tex); bool GPU_texture_integer(const GPUTexture *tex); + +#ifndef GPU_NO_USE_PY_REFERENCES +void **GPU_texture_py_reference_get(GPUTexture *tex); +void GPU_texture_py_reference_set(GPUTexture *tex, void **py_ref); +#endif + int GPU_texture_opengl_bindcode(const GPUTexture *tex); void GPU_texture_get_mipmap_size(GPUTexture *tex, int lvl, int *size); diff --git a/source/blender/gpu/GPU_vertex_buffer.h b/source/blender/gpu/GPU_vertex_buffer.h index aae58de533b..2c54016daa7 100644 --- a/source/blender/gpu/GPU_vertex_buffer.h +++ b/source/blender/gpu/GPU_vertex_buffer.h @@ -59,6 +59,7 @@ typedef enum { GPU_USAGE_STREAM, GPU_USAGE_STATIC, /* do not keep data in memory */ GPU_USAGE_DYNAMIC, + GPU_USAGE_DEVICE_ONLY, /* Do not do host->device data transfers. */ } GPUUsageType; /** Opaque type hiding blender::gpu::VertBuf. */ @@ -70,6 +71,14 @@ GPUVertBuf *GPU_vertbuf_create_with_format_ex(const GPUVertFormat *, GPUUsageTyp #define GPU_vertbuf_create_with_format(format) \ GPU_vertbuf_create_with_format_ex(format, GPU_USAGE_STATIC) +/** + * (Download and) return a pointer containing the data of a vertex buffer. + * + * Note that the returned pointer is still owned by the driver. To get an + * local copy, use `GPU_vertbuf_unmap` after calling `GPU_vertbuf_read`. + */ +const void *GPU_vertbuf_read(GPUVertBuf *verts); +void *GPU_vertbuf_unmap(const GPUVertBuf *verts, const void *mapped_data); void GPU_vertbuf_clear(GPUVertBuf *verts); void GPU_vertbuf_discard(GPUVertBuf *); @@ -138,6 +147,7 @@ uint GPU_vertbuf_get_vertex_len(const GPUVertBuf *verts); GPUVertBufStatus GPU_vertbuf_get_status(const GPUVertBuf *verts); void GPU_vertbuf_use(GPUVertBuf *); +void GPU_vertbuf_bind_as_ssbo(struct GPUVertBuf *verts, int binding); /* XXX do not use. */ void GPU_vertbuf_update_sub(GPUVertBuf *verts, uint start, uint len, void *data); diff --git a/source/blender/gpu/intern/gpu_backend.hh b/source/blender/gpu/intern/gpu_backend.hh index 04ec82a9213..73792215569 100644 --- a/source/blender/gpu/intern/gpu_backend.hh +++ b/source/blender/gpu/intern/gpu_backend.hh @@ -47,6 +47,7 @@ class GPUBackend { static GPUBackend *get(void); virtual void samplers_update(void) = 0; + virtual void compute_dispatch(int groups_x_len, int groups_y_len, int groups_z_len) = 0; virtual Context *context_alloc(void *ghost_window) = 0; diff --git a/source/blender/gpu/intern/gpu_capabilities.cc b/source/blender/gpu/intern/gpu_capabilities.cc index 6d9182dcf17..bedc9ad3092 100644 --- a/source/blender/gpu/intern/gpu_capabilities.cc +++ b/source/blender/gpu/intern/gpu_capabilities.cc @@ -82,6 +82,46 @@ int GPU_max_textures(void) return GCaps.max_textures; } +int GPU_max_uniforms_vert(void) +{ + return GCaps.max_uniforms_vert; +} + +int GPU_max_uniforms_frag(void) +{ + return GCaps.max_uniforms_frag; +} + +int GPU_max_batch_indices(void) +{ + return GCaps.max_batch_indices; +} + +int GPU_max_batch_vertices(void) +{ + return GCaps.max_batch_vertices; +} + +int GPU_max_vertex_attribs(void) +{ + return GCaps.max_vertex_attribs; +} + +int GPU_max_varying_floats(void) +{ + return GCaps.max_varying_floats; +} + +int GPU_extensions_len(void) +{ + return GCaps.extensions_len; +} + +const char *GPU_extension_get(int i) +{ + return GCaps.extension_get ? GCaps.extension_get(i) : "\0"; +} + bool GPU_mip_render_workaround(void) { return GCaps.mip_render_workaround; @@ -108,6 +148,16 @@ bool GPU_use_hq_normals_workaround(void) return GCaps.use_hq_normals_workaround; } +bool GPU_compute_shader_support(void) +{ + return GCaps.compute_shader_support; +} + +bool GPU_shader_storage_buffer_objects_support(void) +{ + return GCaps.shader_storage_buffer_objects_support; +} + bool GPU_shader_image_load_store_support(void) { return GCaps.shader_image_load_store_support; diff --git a/source/blender/gpu/intern/gpu_capabilities_private.hh b/source/blender/gpu/intern/gpu_capabilities_private.hh index 2b3292749f8..ee7ef1e69e6 100644 --- a/source/blender/gpu/intern/gpu_capabilities_private.hh +++ b/source/blender/gpu/intern/gpu_capabilities_private.hh @@ -41,7 +41,18 @@ struct GPUCapabilities { int max_textures_vert = 0; int max_textures_geom = 0; int max_textures_frag = 0; + int max_uniforms_vert = 0; + int max_uniforms_frag = 0; + int max_batch_indices = 0; + int max_batch_vertices = 0; + int max_vertex_attribs = 0; + int max_varying_floats = 0; + int extensions_len = 0; + const char *(*extension_get)(int); + bool mem_stats_support = false; + bool compute_shader_support = false; + bool shader_storage_buffer_objects_support = false; bool shader_image_load_store_support = false; /* OpenGL related workarounds. */ bool mip_render_workaround = false; diff --git a/source/blender/gpu/intern/gpu_compute.cc b/source/blender/gpu/intern/gpu_compute.cc new file mode 100644 index 00000000000..7a8ae2acf9a --- /dev/null +++ b/source/blender/gpu/intern/gpu_compute.cc @@ -0,0 +1,41 @@ +/* + * 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. + */ + +/** \file + * \ingroup gpu + */ + +#include "GPU_compute.h" + +#include "gpu_backend.hh" + +#ifdef __cplusplus +extern "C" { +#endif + +void GPU_compute_dispatch(GPUShader *shader, + uint groups_x_len, + uint groups_y_len, + uint groups_z_len) +{ + blender::gpu::GPUBackend &gpu_backend = *blender::gpu::GPUBackend::get(); + GPU_shader_bind(shader); + gpu_backend.compute_dispatch(groups_x_len, groups_y_len, groups_z_len); +} + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/gpu/intern/gpu_context.cc b/source/blender/gpu/intern/gpu_context.cc index a9d32dcf297..b5a437b46f7 100644 --- a/source/blender/gpu/intern/gpu_context.cc +++ b/source/blender/gpu/intern/gpu_context.cc @@ -24,7 +24,7 @@ * Use these instead of glGenBuffers & its friends * - alloc must be called from a thread that is bound * to the context that will be used for drawing with - * this vao. + * this VAO. * - free can be called from any thread */ diff --git a/source/blender/gpu/intern/gpu_debug.cc b/source/blender/gpu/intern/gpu_debug.cc index 63e7024b74b..2d9fb7822ae 100644 --- a/source/blender/gpu/intern/gpu_debug.cc +++ b/source/blender/gpu/intern/gpu_debug.cc @@ -86,8 +86,8 @@ bool GPU_debug_group_match(const char *ref) if (ctx == nullptr) { return false; } - DebugStack &stack = ctx->debug_stack; - for (StringRef &name : stack) { + const DebugStack &stack = ctx->debug_stack; + for (const StringRef &name : stack) { if (name == ref) { return true; } diff --git a/source/blender/gpu/intern/gpu_framebuffer.cc b/source/blender/gpu/intern/gpu_framebuffer.cc index 1e3cf479462..1293cc0953d 100644 --- a/source/blender/gpu/intern/gpu_framebuffer.cc +++ b/source/blender/gpu/intern/gpu_framebuffer.cc @@ -71,6 +71,12 @@ FrameBuffer::~FrameBuffer() reinterpret_cast<Texture *>(attachment.tex)->detach_from(this); } } + +#ifndef GPU_NO_USE_PY_REFERENCES + if (this->py_ref) { + *this->py_ref = nullptr; + } +#endif } /** \} */ @@ -473,6 +479,19 @@ void GPU_framebuffer_recursive_downsample(GPUFrameBuffer *gpu_fb, unwrap(gpu_fb)->recursive_downsample(max_lvl, callback, userData); } +#ifndef GPU_NO_USE_PY_REFERENCES +void **GPU_framebuffer_py_reference_get(GPUFrameBuffer *gpu_fb) +{ + return unwrap(gpu_fb)->py_ref; +} + +void GPU_framebuffer_py_reference_set(GPUFrameBuffer *gpu_fb, void **py_ref) +{ + BLI_assert(py_ref == nullptr || unwrap(gpu_fb)->py_ref == nullptr); + unwrap(gpu_fb)->py_ref = py_ref; +} +#endif + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/gpu/intern/gpu_framebuffer_private.hh b/source/blender/gpu/intern/gpu_framebuffer_private.hh index d63d72cf4f7..2b00c239af4 100644 --- a/source/blender/gpu/intern/gpu_framebuffer_private.hh +++ b/source/blender/gpu/intern/gpu_framebuffer_private.hh @@ -100,6 +100,20 @@ class FrameBuffer { bool scissor_test_ = false; bool dirty_state_ = true; +#ifndef GPU_NO_USE_PY_REFERENCES + public: + /** + * Reference of a pointer that needs to be cleaned when deallocating the frame-buffer. + * Points to #BPyGPUFrameBuffer.fb + */ + void **py_ref = nullptr; +#endif + + public: + /* Reference of a pointer that needs to be cleaned when deallocating the frame-buffer. + * Points to #BPyGPUFrameBuffer::fb */ + void **ref = nullptr; + public: FrameBuffer(const char *name); virtual ~FrameBuffer(); diff --git a/source/blender/gpu/intern/gpu_index_buffer.cc b/source/blender/gpu/intern/gpu_index_buffer.cc index 65932d2dbf4..20a26c0fe9d 100644 --- a/source/blender/gpu/intern/gpu_index_buffer.cc +++ b/source/blender/gpu/intern/gpu_index_buffer.cc @@ -31,6 +31,8 @@ #include "gpu_index_buffer_private.hh" +#include <cstring> + #define KEEP_SINGLE_COPY 1 #define RESTART_INDEX 0xFFFFFFFF @@ -66,6 +68,14 @@ void GPU_indexbuf_init(GPUIndexBufBuilder *builder, GPU_indexbuf_init_ex(builder, prim_type, prim_len * (uint)verts_per_prim, vertex_len); } +GPUIndexBuf *GPU_indexbuf_build_on_device(uint index_len) +{ + GPUIndexBuf *elem_ = GPU_indexbuf_calloc(); + IndexBuf *elem = unwrap(elem_); + elem->init_build_on_device(index_len); + return elem_; +} + void GPU_indexbuf_add_generic_vert(GPUIndexBufBuilder *builder, uint v) { #if TRUST_NO_ONE @@ -241,6 +251,15 @@ void IndexBuf::init(uint indices_len, uint32_t *indices) #endif } +void IndexBuf::init_build_on_device(uint index_len) +{ + is_init_ = true; + index_start_ = 0; + index_len_ = index_len; + index_type_ = GPU_INDEX_U32; + data_ = nullptr; +} + void IndexBuf::init_subrange(IndexBuf *elem_src, uint start, uint length) { /* We don't support nested subranges. */ @@ -307,6 +326,14 @@ void IndexBuf::squeeze_indices_short(uint min_idx, uint max_idx) } } +uint32_t *IndexBuf::unmap(const uint32_t *mapped_memory) const +{ + size_t size = size_get(); + uint32_t *result = static_cast<uint32_t *>(MEM_mallocN(size, __func__)); + memcpy(result, mapped_memory, size); + return result; +} + } // namespace blender::gpu /** \} */ @@ -351,6 +378,16 @@ void GPU_indexbuf_create_subrange_in_place(GPUIndexBuf *elem, unwrap(elem)->init_subrange(unwrap(elem_src), start, length); } +const uint32_t *GPU_indexbuf_read(GPUIndexBuf *elem) +{ + return unwrap(elem)->read(); +} + +uint32_t *GPU_indexbuf_unmap(const GPUIndexBuf *elem, const uint32_t *mapped_buffer) +{ + return unwrap(elem)->unmap(mapped_buffer); +} + void GPU_indexbuf_discard(GPUIndexBuf *elem) { delete unwrap(elem); @@ -366,4 +403,9 @@ int GPU_indexbuf_primitive_len(GPUPrimType prim_type) return indices_per_primitive(prim_type); } +void GPU_indexbuf_bind_as_ssbo(GPUIndexBuf *elem, int binding) +{ + unwrap(elem)->bind_as_ssbo(binding); +} + /** \} */ diff --git a/source/blender/gpu/intern/gpu_index_buffer_private.hh b/source/blender/gpu/intern/gpu_index_buffer_private.hh index 2405db8664a..358258604bf 100644 --- a/source/blender/gpu/intern/gpu_index_buffer_private.hh +++ b/source/blender/gpu/intern/gpu_index_buffer_private.hh @@ -75,13 +75,14 @@ class IndexBuf { void init(uint indices_len, uint32_t *indices); void init_subrange(IndexBuf *elem_src, uint start, uint length); + void init_build_on_device(uint index_len); uint32_t index_len_get(void) const { return index_len_; } /* Return size in byte of the drawable data buffer range. Actual buffer size might be bigger. */ - size_t size_get(void) + size_t size_get(void) const { return index_len_ * to_bytesize(index_type_); }; @@ -91,6 +92,11 @@ class IndexBuf { return is_init_; }; + virtual void bind_as_ssbo(uint binding) = 0; + + virtual const uint32_t *read() const = 0; + uint32_t *unmap(const uint32_t *mapped_memory) const; + private: inline void squeeze_indices_short(uint min_idx, uint max_idx); inline uint index_range(uint *r_min, uint *r_max); @@ -105,6 +111,10 @@ static inline IndexBuf *unwrap(GPUIndexBuf *indexbuf) { return reinterpret_cast<IndexBuf *>(indexbuf); } +static inline const IndexBuf *unwrap(const GPUIndexBuf *indexbuf) +{ + return reinterpret_cast<const IndexBuf *>(indexbuf); +} static inline int indices_per_primitive(GPUPrimType prim_type) { diff --git a/source/blender/gpu/intern/gpu_material_library.c b/source/blender/gpu/intern/gpu_material_library.c index 175facc0a8d..3c216c1a991 100644 --- a/source/blender/gpu/intern/gpu_material_library.c +++ b/source/blender/gpu/intern/gpu_material_library.c @@ -47,6 +47,7 @@ extern char datatoc_gpu_shader_material_anisotropic_glsl[]; extern char datatoc_gpu_shader_material_attribute_glsl[]; extern char datatoc_gpu_shader_material_background_glsl[]; extern char datatoc_gpu_shader_material_bevel_glsl[]; +extern char datatoc_gpu_shader_material_wavelength_glsl[]; extern char datatoc_gpu_shader_material_blackbody_glsl[]; extern char datatoc_gpu_shader_material_bright_contrast_glsl[]; extern char datatoc_gpu_shader_material_bump_glsl[]; @@ -191,6 +192,11 @@ static GPUMaterialLibrary gpu_shader_material_bevel_library = { .dependencies = {NULL}, }; +static GPUMaterialLibrary gpu_shader_material_wavelength_library = { + .code = datatoc_gpu_shader_material_wavelength_glsl, + .dependencies = {NULL}, +}; + static GPUMaterialLibrary gpu_shader_material_blackbody_library = { .code = datatoc_gpu_shader_material_blackbody_glsl, .dependencies = {NULL}, @@ -593,6 +599,7 @@ static GPUMaterialLibrary *gpu_material_libraries[] = { &gpu_shader_material_attribute_library, &gpu_shader_material_background_library, &gpu_shader_material_bevel_library, + &gpu_shader_material_wavelength_library, &gpu_shader_material_blackbody_library, &gpu_shader_material_bright_contrast_library, &gpu_shader_material_bump_library, diff --git a/source/blender/gpu/intern/gpu_matrix.cc b/source/blender/gpu/intern/gpu_matrix.cc index 2ae50d913da..569b51a407a 100644 --- a/source/blender/gpu/intern/gpu_matrix.cc +++ b/source/blender/gpu/intern/gpu_matrix.cc @@ -543,7 +543,7 @@ bool GPU_matrix_unproject_precalc(struct GPUMatrixUnproject_Precalc *precalc, &precalc->dims.zmax); if (isinf(precalc->dims.zmax)) { /* We cannot retrieve the actual value of the clip_end. - * Use `FLT_MAX` to avoid nans. */ + * Use `FLT_MAX` to avoid NAN's. */ precalc->dims.zmax = FLT_MAX; } for (int i = 0; i < 4; i++) { diff --git a/source/blender/gpu/intern/gpu_platform.cc b/source/blender/gpu/intern/gpu_platform.cc index 6b9878f2ba4..49dde473300 100644 --- a/source/blender/gpu/intern/gpu_platform.cc +++ b/source/blender/gpu/intern/gpu_platform.cc @@ -41,10 +41,10 @@ namespace blender::gpu { GPUPlatformGlobal GPG; -void GPUPlatformGlobal::create_key(eGPUSupportLevel support_level, - const char *vendor, - const char *renderer, - const char *version) +static char *create_key(eGPUSupportLevel support_level, + const char *vendor, + const char *renderer, + const char *version) { DynStr *ds = BLI_dynstr_new(); BLI_dynstr_appendf(ds, "{%s/%s/%s}=", vendor, renderer, version); @@ -58,29 +58,56 @@ void GPUPlatformGlobal::create_key(eGPUSupportLevel support_level, BLI_dynstr_append(ds, "UNSUPPORTED"); } - support_key = BLI_dynstr_get_cstring(ds); + char *support_key = BLI_dynstr_get_cstring(ds); BLI_dynstr_free(ds); BLI_str_replace_char(support_key, '\n', ' '); BLI_str_replace_char(support_key, '\r', ' '); + return support_key; } -void GPUPlatformGlobal::create_gpu_name(const char *vendor, - const char *renderer, - const char *version) +static char *create_gpu_name(const char *vendor, const char *renderer, const char *version) { DynStr *ds = BLI_dynstr_new(); BLI_dynstr_appendf(ds, "%s %s %s", vendor, renderer, version); - gpu_name = BLI_dynstr_get_cstring(ds); + char *gpu_name = BLI_dynstr_get_cstring(ds); BLI_dynstr_free(ds); BLI_str_replace_char(gpu_name, '\n', ' '); BLI_str_replace_char(gpu_name, '\r', ' '); + return gpu_name; +} + +void GPUPlatformGlobal::init(eGPUDeviceType gpu_device, + eGPUOSType os_type, + eGPUDriverType driver_type, + eGPUSupportLevel gpu_support_level, + const char *vendor_str, + const char *renderer_str, + const char *version_str) +{ + this->clear(); + + this->initialized = true; + + this->device = gpu_device; + this->os = os_type; + this->driver = driver_type; + this->support_level = gpu_support_level; + + this->vendor = BLI_strdup(vendor_str); + this->renderer = BLI_strdup(renderer_str); + this->version = BLI_strdup(version_str); + this->support_key = create_key(gpu_support_level, vendor_str, renderer_str, version_str); + this->gpu_name = create_gpu_name(vendor_str, renderer_str, version_str); } void GPUPlatformGlobal::clear() { - MEM_SAFE_FREE(GPG.support_key); - MEM_SAFE_FREE(GPG.gpu_name); + MEM_SAFE_FREE(vendor); + MEM_SAFE_FREE(renderer); + MEM_SAFE_FREE(version); + MEM_SAFE_FREE(support_key); + MEM_SAFE_FREE(gpu_name); initialized = false; } @@ -96,22 +123,44 @@ using namespace blender::gpu; eGPUSupportLevel GPU_platform_support_level() { + BLI_assert(GPG.initialized); return GPG.support_level; } -const char *GPU_platform_support_level_key() +const char *GPU_platform_vendor(void) +{ + BLI_assert(GPG.initialized); + return GPG.vendor; +} + +const char *GPU_platform_renderer(void) +{ + BLI_assert(GPG.initialized); + return GPG.renderer; +} + +const char *GPU_platform_version(void) +{ + BLI_assert(GPG.initialized); + return GPG.version; +} + +const char *GPU_platform_support_level_key(void) { + BLI_assert(GPG.initialized); return GPG.support_key; } const char *GPU_platform_gpu_name(void) { + BLI_assert(GPG.initialized); return GPG.gpu_name; } /* GPU Types */ bool GPU_type_matches(eGPUDeviceType device, eGPUOSType os, eGPUDriverType driver) { + BLI_assert(GPG.initialized); return (GPG.device & device) && (GPG.os & os) && (GPG.driver & driver); } diff --git a/source/blender/gpu/intern/gpu_platform_private.hh b/source/blender/gpu/intern/gpu_platform_private.hh index 02d99efa4a9..f823269ab54 100644 --- a/source/blender/gpu/intern/gpu_platform_private.hh +++ b/source/blender/gpu/intern/gpu_platform_private.hh @@ -34,16 +34,20 @@ class GPUPlatformGlobal { eGPUOSType os; eGPUDriverType driver; eGPUSupportLevel support_level; + char *vendor = nullptr; + char *renderer = nullptr; + char *version = nullptr; char *support_key = nullptr; char *gpu_name = nullptr; public: - void create_key(eGPUSupportLevel support_level, - const char *vendor, - const char *renderer, - const char *version); - - void create_gpu_name(const char *vendor, const char *renderer, const char *version); + void init(eGPUDeviceType gpu_device, + eGPUOSType os_type, + eGPUDriverType driver_type, + eGPUSupportLevel gpu_support_level, + const char *vendor_str, + const char *renderer_str, + const char *version_str); void clear(void); }; diff --git a/source/blender/gpu/intern/gpu_shader.cc b/source/blender/gpu/intern/gpu_shader.cc index 75fe7652715..265dec7c56a 100644 --- a/source/blender/gpu/intern/gpu_shader.cc +++ b/source/blender/gpu/intern/gpu_shader.cc @@ -290,6 +290,7 @@ static void standard_defines(Vector<const char *> &sources) GPUShader *GPU_shader_create_ex(const char *vertcode, const char *fragcode, const char *geomcode, + const char *computecode, const char *libcode, const char *defines, const eGPUShaderTFBType tf_type, @@ -297,8 +298,10 @@ GPUShader *GPU_shader_create_ex(const char *vertcode, const int tf_count, const char *shname) { - /* At least a vertex shader and a fragment shader are required. */ - BLI_assert((fragcode != nullptr) && (vertcode != nullptr)); + /* At least a vertex shader and a fragment shader are required, or only a compute shader. */ + BLI_assert(((fragcode != nullptr) && (vertcode != nullptr) && (computecode == nullptr)) || + ((fragcode == nullptr) && (vertcode == nullptr) && (geomcode == nullptr) && + (computecode != nullptr))); Shader *shader = GPUBackend::get()->shader_alloc(shname); @@ -349,6 +352,21 @@ GPUShader *GPU_shader_create_ex(const char *vertcode, shader->geometry_shader_from_glsl(sources); } + if (computecode) { + Vector<const char *> sources; + standard_defines(sources); + sources.append("#define GPU_COMPUTE_SHADER\n"); + if (defines) { + sources.append(defines); + } + if (libcode) { + sources.append(libcode); + } + sources.append(computecode); + + shader->compute_shader_from_glsl(sources); + } + if (tf_names != nullptr && tf_count > 0) { BLI_assert(tf_type != GPU_SHADER_TFB_NONE); shader->transform_feedback_names_set(Span<const char *>(tf_names, tf_count), tf_type); @@ -380,8 +398,33 @@ GPUShader *GPU_shader_create(const char *vertcode, const char *defines, const char *shname) { - return GPU_shader_create_ex( - vertcode, fragcode, geomcode, libcode, defines, GPU_SHADER_TFB_NONE, nullptr, 0, shname); + return GPU_shader_create_ex(vertcode, + fragcode, + geomcode, + nullptr, + libcode, + defines, + GPU_SHADER_TFB_NONE, + nullptr, + 0, + shname); +} + +GPUShader *GPU_shader_create_compute(const char *computecode, + const char *libcode, + const char *defines, + const char *shname) +{ + return GPU_shader_create_ex(nullptr, + nullptr, + nullptr, + computecode, + libcode, + defines, + GPU_SHADER_TFB_NONE, + nullptr, + 0, + shname); } GPUShader *GPU_shader_create_from_python(const char *vertcode, @@ -402,6 +445,7 @@ GPUShader *GPU_shader_create_from_python(const char *vertcode, GPUShader *sh = GPU_shader_create_ex(vertcode, fragcode, geomcode, + nullptr, libcode, defines, GPU_SHADER_TFB_NONE, @@ -567,6 +611,13 @@ int GPU_shader_get_builtin_block(GPUShader *shader, int builtin) return interface->ubo_builtin((GPUUniformBlockBuiltin)builtin); } +int GPU_shader_get_ssbo(GPUShader *shader, const char *name) +{ + ShaderInterface *interface = unwrap(shader)->interface; + const ShaderInput *ssbo = interface->ssbo_get(name); + return ssbo ? ssbo->location : -1; +} + /* DEPRECATED. */ int GPU_shader_get_uniform_block(GPUShader *shader, const char *name) { @@ -723,7 +774,7 @@ void GPU_shader_uniform_4fv_array(GPUShader *sh, const char *name, int len, cons static int g_shader_builtin_srgb_transform = 0; static bool g_shader_builtin_srgb_is_dirty = false; -static bool gpu_shader_srgb_uniform_dirty_get(void) +static bool gpu_shader_srgb_uniform_dirty_get() { return g_shader_builtin_srgb_is_dirty; } diff --git a/source/blender/gpu/intern/gpu_shader_interface.cc b/source/blender/gpu/intern/gpu_shader_interface.cc index c584c40eca8..ae94112b17b 100644 --- a/source/blender/gpu/intern/gpu_shader_interface.cc +++ b/source/blender/gpu/intern/gpu_shader_interface.cc @@ -80,6 +80,8 @@ void ShaderInterface::debug_print() Span<ShaderInput> attrs = Span<ShaderInput>(inputs_, attr_len_); Span<ShaderInput> ubos = Span<ShaderInput>(inputs_ + attr_len_, ubo_len_); Span<ShaderInput> uniforms = Span<ShaderInput>(inputs_ + attr_len_ + ubo_len_, uniform_len_); + Span<ShaderInput> ssbos = Span<ShaderInput>(inputs_ + attr_len_ + ubo_len_ + uniform_len_, + ssbo_len_); char *name_buf = name_buffer_; const char format[] = " | %.8x : %4d : %s\n"; @@ -117,6 +119,13 @@ void ShaderInterface::debug_print() } } + if (ssbos.size() > 0) { + printf("\n Shader Storage Objects :\n"); + } + for (const ShaderInput &ssbo : ssbos) { + printf(format, ssbo.name_hash, ssbo.binding, name_buf + ssbo.name_offset); + } + printf("\n"); } diff --git a/source/blender/gpu/intern/gpu_shader_interface.hh b/source/blender/gpu/intern/gpu_shader_interface.hh index aec58544111..ebed7b15170 100644 --- a/source/blender/gpu/intern/gpu_shader_interface.hh +++ b/source/blender/gpu/intern/gpu_shader_interface.hh @@ -60,6 +60,7 @@ class ShaderInterface { uint attr_len_ = 0; uint ubo_len_ = 0; uint uniform_len_ = 0; + uint ssbo_len_ = 0; /** Enabled bind-points that needs to be fed with data. */ uint16_t enabled_attr_mask_ = 0; uint16_t enabled_ubo_mask_ = 0; @@ -99,6 +100,11 @@ class ShaderInterface { return input_lookup(inputs_ + attr_len_ + ubo_len_, uniform_len_, binding); } + inline const ShaderInput *ssbo_get(const char *name) const + { + return input_lookup(inputs_ + attr_len_ + ubo_len_ + uniform_len_, ssbo_len_, name); + } + inline const char *input_name_get(const ShaderInput *input) const { return name_buffer_ + input->name_offset; diff --git a/source/blender/gpu/intern/gpu_shader_private.hh b/source/blender/gpu/intern/gpu_shader_private.hh index d9327bbc0f4..281f01dbc22 100644 --- a/source/blender/gpu/intern/gpu_shader_private.hh +++ b/source/blender/gpu/intern/gpu_shader_private.hh @@ -49,6 +49,7 @@ class Shader { virtual void vertex_shader_from_glsl(MutableSpan<const char *> sources) = 0; virtual void geometry_shader_from_glsl(MutableSpan<const char *> sources) = 0; virtual void fragment_shader_from_glsl(MutableSpan<const char *> sources) = 0; + virtual void compute_shader_from_glsl(MutableSpan<const char *> sources) = 0; virtual bool finalize(void) = 0; virtual void transform_feedback_names_set(Span<const char *> name_list, diff --git a/source/blender/gpu/intern/gpu_state.cc b/source/blender/gpu/intern/gpu_state.cc index 782d25747bb..5c33066c720 100644 --- a/source/blender/gpu/intern/gpu_state.cc +++ b/source/blender/gpu/intern/gpu_state.cc @@ -248,7 +248,7 @@ eGPUWriteMask GPU_write_mask_get() uint GPU_stencil_mask_get() { - GPUStateMutable &state = Context::get()->state_manager->mutable_state; + const GPUStateMutable &state = Context::get()->state_manager->mutable_state; return state.stencil_write_mask; } @@ -267,7 +267,7 @@ eGPUStencilTest GPU_stencil_test_get() /* NOTE: Already premultiplied by U.pixelsize. */ float GPU_line_width_get() { - GPUStateMutable &state = Context::get()->state_manager->mutable_state; + const GPUStateMutable &state = Context::get()->state_manager->mutable_state; return state.line_width; } @@ -292,7 +292,7 @@ void GPU_viewport_size_get_i(int coords[4]) bool GPU_depth_mask_get() { - GPUState &state = Context::get()->state_manager->state; + const GPUState &state = Context::get()->state_manager->state; return (state.write_mask & GPU_WRITE_DEPTH) != 0; } diff --git a/source/blender/gpu/intern/gpu_texture.cc b/source/blender/gpu/intern/gpu_texture.cc index c3e9058c6c7..997064e82a2 100644 --- a/source/blender/gpu/intern/gpu_texture.cc +++ b/source/blender/gpu/intern/gpu_texture.cc @@ -60,6 +60,12 @@ Texture::~Texture() fb_[i]->attachment_remove(fb_attachment_[i]); } } + +#ifndef GPU_NO_USE_PY_REFERENCES + if (this->py_ref) { + *this->py_ref = nullptr; + } +#endif } bool Texture::init_1D(int w, int layers, eGPUTextureFormat format) @@ -581,6 +587,19 @@ bool GPU_texture_array(const GPUTexture *tex) return (reinterpret_cast<const Texture *>(tex)->type_get() & GPU_TEXTURE_ARRAY) != 0; } +#ifndef GPU_NO_USE_PY_REFERENCES +void **GPU_texture_py_reference_get(GPUTexture *tex) +{ + return unwrap(tex)->py_ref; +} + +void GPU_texture_py_reference_set(GPUTexture *tex, void **py_ref) +{ + BLI_assert(py_ref == nullptr || unwrap(tex)->py_ref == nullptr); + unwrap(tex)->py_ref = py_ref; +} +#endif + /* TODO remove */ int GPU_texture_opengl_bindcode(const GPUTexture *tex) { diff --git a/source/blender/gpu/intern/gpu_texture_private.hh b/source/blender/gpu/intern/gpu_texture_private.hh index 3d808bce152..a8f2e482bdd 100644 --- a/source/blender/gpu/intern/gpu_texture_private.hh +++ b/source/blender/gpu/intern/gpu_texture_private.hh @@ -80,6 +80,13 @@ class Texture { int refcount = 1; /** Width & Height (of source data), optional. */ int src_w = 0, src_h = 0; +#ifndef GPU_NO_USE_PY_REFERENCES + /** + * Reference of a pointer that needs to be cleaned when deallocating the texture. + * Points to #BPyGPUTexture.tex + */ + void **py_ref = nullptr; +#endif protected: /* ---- Texture format (immutable after init). ---- */ diff --git a/source/blender/gpu/intern/gpu_vertex_buffer.cc b/source/blender/gpu/intern/gpu_vertex_buffer.cc index 09b9eba9f95..3ecbb740a0c 100644 --- a/source/blender/gpu/intern/gpu_vertex_buffer.cc +++ b/source/blender/gpu/intern/gpu_vertex_buffer.cc @@ -149,6 +149,16 @@ GPUVertBuf *GPU_vertbuf_duplicate(GPUVertBuf *verts_) return wrap(unwrap(verts_)->duplicate()); } +const void *GPU_vertbuf_read(GPUVertBuf *verts) +{ + return unwrap(verts)->read(); +} + +void *GPU_vertbuf_unmap(const GPUVertBuf *verts, const void *mapped_data) +{ + return unwrap(verts)->unmap(mapped_data); +} + /** Same as discard but does not free. */ void GPU_vertbuf_clear(GPUVertBuf *verts) { @@ -324,6 +334,11 @@ void GPU_vertbuf_use(GPUVertBuf *verts) unwrap(verts)->upload(); } +void GPU_vertbuf_bind_as_ssbo(struct GPUVertBuf *verts, int binding) +{ + unwrap(verts)->bind_as_ssbo(binding); +} + /* XXX this is just a wrapper for the use of the Hair refine workaround. * To be used with GPU_vertbuf_use(). */ void GPU_vertbuf_update_sub(GPUVertBuf *verts, uint start, uint len, void *data) diff --git a/source/blender/gpu/intern/gpu_vertex_buffer_private.hh b/source/blender/gpu/intern/gpu_vertex_buffer_private.hh index 67a09f6f83c..9531c2c1a5f 100644 --- a/source/blender/gpu/intern/gpu_vertex_buffer_private.hh +++ b/source/blender/gpu/intern/gpu_vertex_buffer_private.hh @@ -66,6 +66,7 @@ class VertBuf { void allocate(uint vert_len); void resize(uint vert_len); void upload(void); + virtual void bind_as_ssbo(uint binding) = 0; VertBuf *duplicate(void); @@ -96,6 +97,8 @@ class VertBuf { } virtual void update_sub(uint start, uint len, void *data) = 0; + virtual const void *read() const = 0; + virtual void *unmap(const void *mapped_data) const = 0; protected: virtual void acquire_data(void) = 0; diff --git a/source/blender/gpu/opengl/gl_backend.cc b/source/blender/gpu/opengl/gl_backend.cc index f31e0e05a44..fb03a2c2d2a 100644 --- a/source/blender/gpu/opengl/gl_backend.cc +++ b/source/blender/gpu/opengl/gl_backend.cc @@ -41,39 +41,42 @@ namespace blender::gpu { void GLBackend::platform_init() { BLI_assert(!GPG.initialized); - GPG.initialized = true; + + const char *vendor = (const char *)glGetString(GL_VENDOR); + const char *renderer = (const char *)glGetString(GL_RENDERER); + const char *version = (const char *)glGetString(GL_VERSION); + eGPUDeviceType device = GPU_DEVICE_ANY; + eGPUOSType os = GPU_OS_ANY; + eGPUDriverType driver = GPU_DRIVER_ANY; + eGPUSupportLevel support_level = GPU_SUPPORT_LEVEL_SUPPORTED; #ifdef _WIN32 - GPG.os = GPU_OS_WIN; + os = GPU_OS_WIN; #elif defined(__APPLE__) - GPG.os = GPU_OS_MAC; + os = GPU_OS_MAC; #else - GPG.os = GPU_OS_UNIX; + os = GPU_OS_UNIX; #endif - const char *vendor = (const char *)glGetString(GL_VENDOR); - const char *renderer = (const char *)glGetString(GL_RENDERER); - const char *version = (const char *)glGetString(GL_VERSION); - if (strstr(vendor, "ATI") || strstr(vendor, "AMD")) { - GPG.device = GPU_DEVICE_ATI; - GPG.driver = GPU_DRIVER_OFFICIAL; + device = GPU_DEVICE_ATI; + driver = GPU_DRIVER_OFFICIAL; } else if (strstr(vendor, "NVIDIA")) { - GPG.device = GPU_DEVICE_NVIDIA; - GPG.driver = GPU_DRIVER_OFFICIAL; + device = GPU_DEVICE_NVIDIA; + driver = GPU_DRIVER_OFFICIAL; } else if (strstr(vendor, "Intel") || /* src/mesa/drivers/dri/intel/intel_context.c */ strstr(renderer, "Mesa DRI Intel") || strstr(renderer, "Mesa DRI Mobile Intel")) { - GPG.device = GPU_DEVICE_INTEL; - GPG.driver = GPU_DRIVER_OFFICIAL; + device = GPU_DEVICE_INTEL; + driver = GPU_DRIVER_OFFICIAL; if (strstr(renderer, "UHD Graphics") || /* Not UHD but affected by the same bugs. */ strstr(renderer, "HD Graphics 530") || strstr(renderer, "Kaby Lake GT2") || strstr(renderer, "Whiskey Lake")) { - GPG.device |= GPU_DEVICE_INTEL_UHD; + device |= GPU_DEVICE_INTEL_UHD; } } else if ((strstr(renderer, "Mesa DRI R")) || @@ -81,49 +84,47 @@ void GLBackend::platform_init() (strstr(renderer, "AMD") && strstr(vendor, "X.Org")) || (strstr(renderer, "Gallium ") && strstr(renderer, " on ATI ")) || (strstr(renderer, "Gallium ") && strstr(renderer, " on AMD "))) { - GPG.device = GPU_DEVICE_ATI; - GPG.driver = GPU_DRIVER_OPENSOURCE; + device = GPU_DEVICE_ATI; + driver = GPU_DRIVER_OPENSOURCE; } else if (strstr(renderer, "Nouveau") || strstr(vendor, "nouveau")) { - GPG.device = GPU_DEVICE_NVIDIA; - GPG.driver = GPU_DRIVER_OPENSOURCE; + device = GPU_DEVICE_NVIDIA; + driver = GPU_DRIVER_OPENSOURCE; } else if (strstr(vendor, "Mesa")) { - GPG.device = GPU_DEVICE_SOFTWARE; - GPG.driver = GPU_DRIVER_SOFTWARE; + device = GPU_DEVICE_SOFTWARE; + driver = GPU_DRIVER_SOFTWARE; } else if (strstr(vendor, "Microsoft")) { - GPG.device = GPU_DEVICE_SOFTWARE; - GPG.driver = GPU_DRIVER_SOFTWARE; + device = GPU_DEVICE_SOFTWARE; + driver = GPU_DRIVER_SOFTWARE; } else if (strstr(vendor, "Apple")) { /* Apple Silicon. */ - GPG.device = GPU_DEVICE_APPLE; - GPG.driver = GPU_DRIVER_OFFICIAL; + device = GPU_DEVICE_APPLE; + driver = GPU_DRIVER_OFFICIAL; } else if (strstr(renderer, "Apple Software Renderer")) { - GPG.device = GPU_DEVICE_SOFTWARE; - GPG.driver = GPU_DRIVER_SOFTWARE; + device = GPU_DEVICE_SOFTWARE; + driver = GPU_DRIVER_SOFTWARE; } else if (strstr(renderer, "llvmpipe") || strstr(renderer, "softpipe")) { - GPG.device = GPU_DEVICE_SOFTWARE; - GPG.driver = GPU_DRIVER_SOFTWARE; + device = GPU_DEVICE_SOFTWARE; + driver = GPU_DRIVER_SOFTWARE; } else { printf("Warning: Could not find a matching GPU name. Things may not behave as expected.\n"); printf("Detected OpenGL configuration:\n"); printf("Vendor: %s\n", vendor); printf("Renderer: %s\n", renderer); - GPG.device = GPU_DEVICE_ANY; - GPG.driver = GPU_DRIVER_ANY; } /* Detect support level */ if (!GLEW_VERSION_3_3) { - GPG.support_level = GPU_SUPPORT_LEVEL_UNSUPPORTED; + support_level = GPU_SUPPORT_LEVEL_UNSUPPORTED; } else { - if (GPU_type_matches(GPU_DEVICE_INTEL, GPU_OS_WIN, GPU_DRIVER_ANY)) { + if ((device & GPU_DEVICE_INTEL) && (os & GPU_OS_WIN)) { /* Old Intel drivers with known bugs that cause material properties to crash. * Version Build 10.18.14.5067 is the latest available and appears to be working * ok with our workarounds, so excluded from this list. */ @@ -132,19 +133,19 @@ void GLBackend::platform_init() strstr(version, "Build 9.18") || strstr(version, "Build 10.18.10.3") || strstr(version, "Build 10.18.10.4") || strstr(version, "Build 10.18.10.5") || strstr(version, "Build 10.18.14.4")) { - GPG.support_level = GPU_SUPPORT_LEVEL_LIMITED; + support_level = GPU_SUPPORT_LEVEL_LIMITED; } } - if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_UNIX, GPU_DRIVER_ANY)) { + if ((device & GPU_DEVICE_ATI) && (os & GPU_OS_UNIX)) { /* Platform seems to work when SB backend is disabled. This can be done * by adding the environment variable `R600_DEBUG=nosb`. */ if (strstr(renderer, "AMD CEDAR")) { - GPG.support_level = GPU_SUPPORT_LEVEL_LIMITED; + support_level = GPU_SUPPORT_LEVEL_LIMITED; } } } - GPG.create_key(GPG.support_level, vendor, renderer, version); - GPG.create_gpu_name(vendor, renderer, version); + + GPG.init(device, os, driver, support_level, vendor, renderer, version); } void GLBackend::platform_exit() @@ -204,6 +205,11 @@ static bool detect_mip_render_workaround() return enable_workaround; } +static const char *gl_extension_get(int i) +{ + return (char *)glGetStringi(GL_EXTENSIONS, i); +} + static void detect_workarounds() { const char *vendor = (const char *)glGetString(GL_VENDOR); @@ -419,8 +425,20 @@ void GLBackend::capabilities_init() glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &GCaps.max_textures_vert); glGetIntegerv(GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS, &GCaps.max_textures_geom); glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &GCaps.max_textures); + glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &GCaps.max_uniforms_vert); + glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &GCaps.max_uniforms_frag); + glGetIntegerv(GL_MAX_ELEMENTS_INDICES, &GCaps.max_batch_indices); + glGetIntegerv(GL_MAX_ELEMENTS_VERTICES, &GCaps.max_batch_vertices); + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &GCaps.max_vertex_attribs); + glGetIntegerv(GL_MAX_VARYING_FLOATS, &GCaps.max_varying_floats); + + glGetIntegerv(GL_NUM_EXTENSIONS, &GCaps.extensions_len); + GCaps.extension_get = gl_extension_get; + GCaps.mem_stats_support = GLEW_NVX_gpu_memory_info || GLEW_ATI_meminfo; GCaps.shader_image_load_store_support = GLEW_ARB_shader_image_load_store; + GCaps.compute_shader_support = GLEW_ARB_compute_shader; + GCaps.shader_storage_buffer_objects_support = GLEW_ARB_shader_storage_buffer_object; /* GL specific capabilities. */ glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &GLContext::max_texture_3d_size); glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &GLContext::max_cubemap_size); diff --git a/source/blender/gpu/opengl/gl_backend.hh b/source/blender/gpu/opengl/gl_backend.hh index 231e5811b45..e9dcdffced0 100644 --- a/source/blender/gpu/opengl/gl_backend.hh +++ b/source/blender/gpu/opengl/gl_backend.hh @@ -28,6 +28,7 @@ #include "BLI_vector.hh" #include "gl_batch.hh" +#include "gl_compute.hh" #include "gl_context.hh" #include "gl_drawlist.hh" #include "gl_framebuffer.hh" @@ -126,6 +127,12 @@ class GLBackend : public GPUBackend { return shared_orphan_list_; }; + void compute_dispatch(int groups_x_len, int groups_y_len, int groups_z_len) override + { + GLContext::get()->state_manager_active_get()->apply_state(); + GLCompute::dispatch(groups_x_len, groups_y_len, groups_z_len); + } + private: static void platform_init(void); static void platform_exit(void); diff --git a/source/blender/gpu/opengl/gl_batch.cc b/source/blender/gpu/opengl/gl_batch.cc index 976a31ab5cb..fc1d1006d0d 100644 --- a/source/blender/gpu/opengl/gl_batch.cc +++ b/source/blender/gpu/opengl/gl_batch.cc @@ -44,7 +44,7 @@ using namespace blender::gpu; /* -------------------------------------------------------------------- */ -/** \name Vao cache +/** \name VAO Cache * * Each #GLBatch has a small cache of VAO objects that are used to avoid VAO reconfiguration. * TODO(fclem): Could be revisited to avoid so much cross references. diff --git a/source/blender/gpu/opengl/gl_batch.hh b/source/blender/gpu/opengl/gl_batch.hh index 444ae1c0ab1..704b6471dd5 100644 --- a/source/blender/gpu/opengl/gl_batch.hh +++ b/source/blender/gpu/opengl/gl_batch.hh @@ -43,7 +43,7 @@ class GLShaderInterface; #define GPU_VAO_STATIC_LEN 3 -/* Vao management: remembers all geometry state (vertex attribute bindings & element buffer) +/* VAO management: remembers all geometry state (vertex attribute bindings & element buffer) * for each shader interface. Start with a static number of vaos and fallback to dynamic count * if necessary. Once a batch goes dynamic it does not go back. */ class GLVaoCache { diff --git a/source/blender/gpu/opengl/gl_compute.cc b/source/blender/gpu/opengl/gl_compute.cc new file mode 100644 index 00000000000..fa8317dde4a --- /dev/null +++ b/source/blender/gpu/opengl/gl_compute.cc @@ -0,0 +1,35 @@ +/* + * 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. + */ + +/** \file + * \ingroup gpu + */ + +#include "gl_compute.hh" + +#include "gl_debug.hh" + +#include "glew-mx.h" + +namespace blender::gpu { + +void GLCompute::dispatch(int group_x_len, int group_y_len, int group_z_len) +{ + glDispatchCompute(group_x_len, group_y_len, group_z_len); + debug::check_gl_error("Dispatch Compute"); +} + +} // namespace blender::gpu diff --git a/source/blender/gpu/opengl/gl_compute.hh b/source/blender/gpu/opengl/gl_compute.hh new file mode 100644 index 00000000000..2fd918ddd10 --- /dev/null +++ b/source/blender/gpu/opengl/gl_compute.hh @@ -0,0 +1,30 @@ +/* + * 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. + */ + +/** \file + * \ingroup gpu + */ + +#pragma once + +namespace blender::gpu { + +class GLCompute { + public: + static void dispatch(int group_x_len, int group_y_len, int group_z_len); +}; + +} // namespace blender::gpu diff --git a/source/blender/gpu/opengl/gl_immediate.hh b/source/blender/gpu/opengl/gl_immediate.hh index d0bf0fcdf1f..b42f439f9ec 100644 --- a/source/blender/gpu/opengl/gl_immediate.hh +++ b/source/blender/gpu/opengl/gl_immediate.hh @@ -38,7 +38,7 @@ namespace blender::gpu { class GLImmediate : public Immediate { private: - /* Use two buffers for strict and unstrict vertex count to + /* Use two buffers for strict and non-strict vertex count to * avoid some huge driver slowdown (see T70922). * Use accessor functions to get / modify. */ struct { diff --git a/source/blender/gpu/opengl/gl_index_buffer.cc b/source/blender/gpu/opengl/gl_index_buffer.cc index e2c18c5d0b9..e305f765ad9 100644 --- a/source/blender/gpu/opengl/gl_index_buffer.cc +++ b/source/blender/gpu/opengl/gl_index_buffer.cc @@ -40,17 +40,14 @@ void GLIndexBuf::bind() return; } - if (ibo_id_ == 0) { + const bool allocate_on_device = ibo_id_ == 0; + if (allocate_on_device) { glGenBuffers(1, &ibo_id_); - - if (data_ == nullptr) { - debug::raise_gl_error("Trying to use Index Buffer but the buffer contains no data"); - } } glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_id_); - if (data_ != nullptr) { + if (data_ != nullptr || allocate_on_device) { size_t size = this->size_get(); /* Sends data to GPU. */ glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, data_, GL_STATIC_DRAW); @@ -59,4 +56,29 @@ void GLIndexBuf::bind() } } +void GLIndexBuf::bind_as_ssbo(uint binding) +{ + bind(); + BLI_assert(ibo_id_ != 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, binding, ibo_id_); +} + +const uint32_t *GLIndexBuf::read() const +{ + BLI_assert(is_active()); + void *data = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_READ_ONLY); + uint32_t *result = static_cast<uint32_t *>(data); + return result; +} + +bool GLIndexBuf::is_active() const +{ + if (!ibo_id_) { + return false; + } + int active_ibo_id = 0; + glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &active_ibo_id); + return ibo_id_ == active_ibo_id; +} + } // namespace blender::gpu diff --git a/source/blender/gpu/opengl/gl_index_buffer.hh b/source/blender/gpu/opengl/gl_index_buffer.hh index b84934bb77f..0dbdaa6d398 100644 --- a/source/blender/gpu/opengl/gl_index_buffer.hh +++ b/source/blender/gpu/opengl/gl_index_buffer.hh @@ -34,6 +34,7 @@ namespace blender::gpu { class GLIndexBuf : public IndexBuf { friend class GLBatch; friend class GLDrawList; + friend class GLShader; /* For compute shaders. */ private: GLuint ibo_id_ = 0; @@ -42,6 +43,9 @@ class GLIndexBuf : public IndexBuf { ~GLIndexBuf(); void bind(void); + void bind_as_ssbo(uint binding) override; + + const uint32_t *read() const override; void *offset_ptr(uint additional_vertex_offset) const { @@ -57,6 +61,9 @@ class GLIndexBuf : public IndexBuf { return (index_type_ == GPU_INDEX_U16) ? 0xFFFFu : 0xFFFFFFFFu; } + private: + bool is_active() const; + MEM_CXX_CLASS_ALLOC_FUNCS("GLIndexBuf") }; diff --git a/source/blender/gpu/opengl/gl_shader.cc b/source/blender/gpu/opengl/gl_shader.cc index dd08a67517e..e77347d99eb 100644 --- a/source/blender/gpu/opengl/gl_shader.cc +++ b/source/blender/gpu/opengl/gl_shader.cc @@ -26,6 +26,7 @@ #include "BLI_string.h" #include "BLI_vector.hh" +#include "GPU_capabilities.h" #include "GPU_platform.h" #include "gl_backend.hh" @@ -63,6 +64,7 @@ GLShader::~GLShader() glDeleteShader(vert_shader_); glDeleteShader(geom_shader_); glDeleteShader(frag_shader_); + glDeleteShader(compute_shader_); glDeleteProgram(shader_program_); } @@ -72,7 +74,7 @@ GLShader::~GLShader() /** \name Shader stage creation * \{ */ -char *GLShader::glsl_patch_get() +static char *glsl_patch_default_get() { /** Used for shader patching. Init once. */ static char patch[512] = "\0"; @@ -111,6 +113,30 @@ char *GLShader::glsl_patch_get() return patch; } +static char *glsl_patch_compute_get() +{ + /** Used for shader patching. Init once. */ + static char patch[512] = "\0"; + if (patch[0] != '\0') { + return patch; + } + + size_t slen = 0; + /* Version need to go first. */ + STR_CONCAT(patch, slen, "#version 430\n"); + STR_CONCAT(patch, slen, "#extension GL_ARB_compute_shader :enable\n"); + BLI_assert(slen < sizeof(patch)); + return patch; +} + +char *GLShader::glsl_patch_get(GLenum gl_stage) +{ + if (gl_stage == GL_COMPUTE_SHADER) { + return glsl_patch_compute_get(); + } + return glsl_patch_default_get(); +} + /* Create, compile and attach the shader stage to the shader program. */ GLuint GLShader::create_shader_stage(GLenum gl_stage, MutableSpan<const char *> sources) { @@ -121,7 +147,7 @@ GLuint GLShader::create_shader_stage(GLenum gl_stage, MutableSpan<const char *> } /* Patch the shader code using the first source slot. */ - sources[0] = glsl_patch_get(); + sources[0] = glsl_patch_get(gl_stage); glShaderSource(shader, sources.size(), sources.data(), nullptr); glCompileShader(shader); @@ -142,6 +168,9 @@ GLuint GLShader::create_shader_stage(GLenum gl_stage, MutableSpan<const char *> case GL_FRAGMENT_SHADER: this->print_log(sources, log, "FragShader", !status); break; + case GL_COMPUTE_SHADER: + this->print_log(sources, log, "ComputeShader", !status); + break; } } } @@ -172,6 +201,11 @@ void GLShader::fragment_shader_from_glsl(MutableSpan<const char *> sources) frag_shader_ = this->create_shader_stage(GL_FRAGMENT_SHADER, sources); } +void GLShader::compute_shader_from_glsl(MutableSpan<const char *> sources) +{ + compute_shader_ = this->create_shader_stage(GL_COMPUTE_SHADER, sources); +} + bool GLShader::finalize() { if (compilation_failed_) { diff --git a/source/blender/gpu/opengl/gl_shader.hh b/source/blender/gpu/opengl/gl_shader.hh index 152eb2f068a..48aaaf2283d 100644 --- a/source/blender/gpu/opengl/gl_shader.hh +++ b/source/blender/gpu/opengl/gl_shader.hh @@ -43,6 +43,7 @@ class GLShader : public Shader { GLuint vert_shader_ = 0; GLuint geom_shader_ = 0; GLuint frag_shader_ = 0; + GLuint compute_shader_ = 0; /** True if any shader failed to compile. */ bool compilation_failed_ = false; @@ -56,6 +57,7 @@ class GLShader : public Shader { void vertex_shader_from_glsl(MutableSpan<const char *> sources) override; void geometry_shader_from_glsl(MutableSpan<const char *> sources) override; void fragment_shader_from_glsl(MutableSpan<const char *> sources) override; + void compute_shader_from_glsl(MutableSpan<const char *> sources) override; bool finalize(void) override; void transform_feedback_names_set(Span<const char *> name_list, @@ -75,7 +77,7 @@ class GLShader : public Shader { int program_handle_get(void) const override; private: - char *glsl_patch_get(void); + char *glsl_patch_get(GLenum gl_stage); GLuint create_shader_stage(GLenum gl_stage, MutableSpan<const char *> sources); diff --git a/source/blender/gpu/opengl/gl_shader_interface.cc b/source/blender/gpu/opengl/gl_shader_interface.cc index 5870c645bf4..9cf072b2e8a 100644 --- a/source/blender/gpu/opengl/gl_shader_interface.cc +++ b/source/blender/gpu/opengl/gl_shader_interface.cc @@ -29,6 +29,8 @@ #include "gl_shader_interface.hh" +#include "GPU_capabilities.h" + namespace blender::gpu { /* -------------------------------------------------------------------- */ @@ -125,6 +127,18 @@ static inline int image_binding(int32_t program, return -1; } } + +static inline int ssbo_binding(int32_t program, uint32_t ssbo_index) +{ + GLint binding = -1; + GLenum property = GL_BUFFER_BINDING; + GLint values_written = 0; + glGetProgramResourceiv( + program, GL_SHADER_STORAGE_BLOCK, ssbo_index, 1, &property, 1, &values_written, &binding); + + return binding; +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -149,6 +163,13 @@ GLShaderInterface::GLShaderInterface(GLuint program) glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &active_uniform_len); uniform_len = active_uniform_len; + GLint max_ssbo_name_len = 0, ssbo_len = 0; + if (GPU_shader_storage_buffer_objects_support()) { + glGetProgramInterfaceiv(program, GL_SHADER_STORAGE_BLOCK, GL_ACTIVE_RESOURCES, &ssbo_len); + glGetProgramInterfaceiv( + program, GL_SHADER_STORAGE_BLOCK, GL_MAX_NAME_LENGTH, &max_ssbo_name_len); + } + BLI_assert(ubo_len <= 16 && "enabled_ubo_mask_ is uint16_t"); /* Work around driver bug with Intel HD 4600 on Windows 7/8, where @@ -162,6 +183,9 @@ GLShaderInterface::GLShaderInterface(GLuint program) if (uniform_len > 0 && max_uniform_name_len == 0) { max_uniform_name_len = 256; } + if (ssbo_len > 0 && max_ssbo_name_len == 0) { + max_ssbo_name_len = 256; + } /* GL_ACTIVE_UNIFORMS lied to us! Remove the UBO uniforms from the total before * allocating the uniform array. */ @@ -186,11 +210,12 @@ GLShaderInterface::GLShaderInterface(GLuint program) } MEM_freeN(ubo_uni_ids); - int input_tot_len = attr_len + ubo_len + uniform_len; + int input_tot_len = attr_len + ubo_len + uniform_len + ssbo_len; inputs_ = (ShaderInput *)MEM_callocN(sizeof(ShaderInput) * input_tot_len, __func__); const uint32_t name_buffer_len = attr_len * max_attr_name_len + ubo_len * max_ubo_name_len + - uniform_len * max_uniform_name_len; + uniform_len * max_uniform_name_len + + ssbo_len * max_ssbo_name_len; name_buffer_ = (char *)MEM_mallocN(name_buffer_len, "name_buffer"); uint32_t name_buffer_offset = 0; @@ -257,6 +282,22 @@ GLShaderInterface::GLShaderInterface(GLuint program) } } + /* SSBOs */ + for (int i = 0; i < ssbo_len; i++) { + char *name = name_buffer_ + name_buffer_offset; + GLsizei remaining_buffer = name_buffer_len - name_buffer_offset; + GLsizei name_len = 0; + glGetProgramResourceName( + program, GL_SHADER_STORAGE_BLOCK, i, remaining_buffer, &name_len, name); + + const GLint binding = ssbo_binding(program, i); + + ShaderInput *input = &inputs_[attr_len_ + ubo_len_ + uniform_len_ + ssbo_len_++]; + input->binding = input->location = binding; + + name_buffer_offset += this->set_input_name(input, name, name_len); + } + /* Builtin Uniforms */ for (int32_t u_int = 0; u_int < GPU_NUM_UNIFORMS; u_int++) { GPUUniformBuiltin u = static_cast<GPUUniformBuiltin>(u_int); diff --git a/source/blender/gpu/opengl/gl_state.hh b/source/blender/gpu/opengl/gl_state.hh index 651c3c22afa..3b4b40b1d10 100644 --- a/source/blender/gpu/opengl/gl_state.hh +++ b/source/blender/gpu/opengl/gl_state.hh @@ -121,6 +121,9 @@ static inline GLbitfield to_gl(eGPUBarrier barrier_bits) if (barrier_bits & GPU_BARRIER_TEXTURE_FETCH) { barrier |= GL_TEXTURE_FETCH_BARRIER_BIT; } + if (barrier_bits & GPU_BARRIER_SHADER_STORAGE) { + barrier |= GL_SHADER_STORAGE_BARRIER_BIT; + } return barrier; } diff --git a/source/blender/gpu/opengl/gl_texture.cc b/source/blender/gpu/opengl/gl_texture.cc index b65686165d9..e2478a9976c 100644 --- a/source/blender/gpu/opengl/gl_texture.cc +++ b/source/blender/gpu/opengl/gl_texture.cc @@ -368,7 +368,7 @@ void GLTexture::copy_to(Texture *dst_) void *GLTexture::read(int mip, eGPUDataFormat type) { BLI_assert(!(format_flag_ & GPU_FORMAT_COMPRESSED)); - BLI_assert(mip <= mipmaps_); + BLI_assert(mip <= mipmaps_ || mip == 0); BLI_assert(validate_data_format(format_, type)); /* NOTE: mip_size_get() won't override any dimension that is equal to 0. */ diff --git a/source/blender/gpu/opengl/gl_vertex_buffer.cc b/source/blender/gpu/opengl/gl_vertex_buffer.cc index a56d5269fde..ce16a491528 100644 --- a/source/blender/gpu/opengl/gl_vertex_buffer.cc +++ b/source/blender/gpu/opengl/gl_vertex_buffer.cc @@ -29,6 +29,10 @@ namespace blender::gpu { void GLVertBuf::acquire_data() { + if (usage_ == GPU_USAGE_DEVICE_ONLY) { + return; + } + /* Discard previous data if any. */ MEM_SAFE_FREE(data); data = (uchar *)MEM_mallocN(sizeof(uchar) * this->size_alloc_get(), __func__); @@ -36,6 +40,10 @@ void GLVertBuf::acquire_data() void GLVertBuf::resize_data() { + if (usage_ == GPU_USAGE_DEVICE_ONLY) { + return; + } + data = (uchar *)MEM_reallocN(data, sizeof(uchar) * this->size_alloc_get()); } @@ -94,8 +102,10 @@ void GLVertBuf::bind() vbo_size_ = this->size_used_get(); /* Orphan the vbo to avoid sync then upload data. */ glBufferData(GL_ARRAY_BUFFER, vbo_size_, nullptr, to_gl(usage_)); - glBufferSubData(GL_ARRAY_BUFFER, 0, vbo_size_, data); - + /* Do not transfer data from host to device when buffer is device only. */ + if (usage_ != GPU_USAGE_DEVICE_ONLY) { + glBufferSubData(GL_ARRAY_BUFFER, 0, vbo_size_, data); + } memory_usage += vbo_size_; if (usage_ == GPU_USAGE_STATIC) { @@ -106,6 +116,37 @@ void GLVertBuf::bind() } } +void GLVertBuf::bind_as_ssbo(uint binding) +{ + bind(); + BLI_assert(vbo_id_ != 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, binding, vbo_id_); +} + +const void *GLVertBuf::read() const +{ + BLI_assert(is_active()); + void *result = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_ONLY); + return result; +} + +void *GLVertBuf::unmap(const void *mapped_data) const +{ + void *result = MEM_mallocN(vbo_size_, __func__); + memcpy(result, mapped_data, vbo_size_); + return result; +} + +bool GLVertBuf::is_active() const +{ + if (!vbo_id_) { + return false; + } + int active_vbo_id = 0; + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &active_vbo_id); + return vbo_id_ == active_vbo_id; +} + void GLVertBuf::update_sub(uint start, uint len, void *data) { glBufferSubData(GL_ARRAY_BUFFER, start, len, data); diff --git a/source/blender/gpu/opengl/gl_vertex_buffer.hh b/source/blender/gpu/opengl/gl_vertex_buffer.hh index e2bf6cd00e8..6c38a2225b3 100644 --- a/source/blender/gpu/opengl/gl_vertex_buffer.hh +++ b/source/blender/gpu/opengl/gl_vertex_buffer.hh @@ -47,12 +47,19 @@ class GLVertBuf : public VertBuf { void update_sub(uint start, uint len, void *data) override; + const void *read() const override; + void *unmap(const void *mapped_data) const override; + protected: void acquire_data(void) override; void resize_data(void) override; void release_data(void) override; void upload_data(void) override; void duplicate_data(VertBuf *dst) override; + void bind_as_ssbo(uint binding) override; + + private: + bool is_active() const; MEM_CXX_CLASS_ALLOC_FUNCS("GLVertBuf"); }; @@ -65,6 +72,7 @@ static inline GLenum to_gl(GPUUsageType type) case GPU_USAGE_DYNAMIC: return GL_DYNAMIC_DRAW; case GPU_USAGE_STATIC: + case GPU_USAGE_DEVICE_ONLY: return GL_STATIC_DRAW; default: BLI_assert(0); diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_tex_noise.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_tex_noise.glsl index d8d9ecdf287..5745f11ede4 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_tex_noise.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_tex_noise.glsl @@ -2,7 +2,7 @@ * coordinates to act as a seed since the noise functions don't have seed values. * A seed value is needed for generating distortion textures and color outputs. * The offset's components are in the range [100, 200], not too high to cause - * bad precision and not to small to be noticeable. We use float seed because + * bad precision and not too small to be noticeable. We use float seed because * OSL only support float hashes. */ diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_wavelength.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_wavelength.glsl new file mode 100644 index 00000000000..2c5d38eabbe --- /dev/null +++ b/source/blender/gpu/shaders/material/gpu_shader_material_wavelength.glsl @@ -0,0 +1,15 @@ +void node_wavelength(float wavelength, + sampler1DArray spectrummap, + float layer, + vec3 xyz_to_r, + vec3 xyz_to_g, + vec3 xyz_to_b, + out vec4 color) +{ + mat3 xyz_to_rgb = mat3(xyz_to_r, xyz_to_g, xyz_to_b); + float t = (wavelength - 380.0) / (780.0 - 380.0); + vec3 xyz = texture(spectrummap, vec2(t, layer)).rgb; + vec3 rgb = xyz * xyz_to_rgb; + rgb *= 1.0 / 2.52; /* Empirical scale from lg to make all comps <= 1. */ + color = vec4(clamp(rgb, 0.0, 1.0), 1.0); +} diff --git a/source/blender/gpu/tests/gpu_shader_test.cc b/source/blender/gpu/tests/gpu_shader_test.cc new file mode 100644 index 00000000000..e8645b89e41 --- /dev/null +++ b/source/blender/gpu/tests/gpu_shader_test.cc @@ -0,0 +1,301 @@ +/* Apache License, Version 2.0 */ + +#include "testing/testing.h" + +#include "GPU_capabilities.h" +#include "GPU_compute.h" +#include "GPU_index_buffer.h" +#include "GPU_shader.h" +#include "GPU_texture.h" +#include "GPU_vertex_buffer.h" +#include "GPU_vertex_format.h" + +#include "MEM_guardedalloc.h" + +#include "gpu_testing.hh" + +#include "GPU_glew.h" + +namespace blender::gpu::tests { + +TEST_F(GPUTest, gpu_shader_compute_2d) +{ + + if (!GPU_compute_shader_support()) { + /* We can't test as a the platform does not support compute shaders. */ + std::cout << "Skipping compute shader test: platform not supported"; + return; + } + + static constexpr uint SIZE = 512; + + /* Build compute shader. */ + const char *compute_glsl = R"( + +layout(local_size_x = 1, local_size_y = 1) in; +layout(rgba32f, binding = 0) uniform image2D img_output; + +void main() { + vec4 pixel = vec4(1.0, 0.5, 0.2, 1.0); + imageStore(img_output, ivec2(gl_GlobalInvocationID.xy), pixel); +} + +)"; + + GPUShader *shader = GPU_shader_create_compute( + compute_glsl, nullptr, nullptr, "gpu_shader_compute_2d"); + EXPECT_NE(shader, nullptr); + + /* Create texture to store result and attach to shader. */ + GPUTexture *texture = GPU_texture_create_2d( + "gpu_shader_compute_2d", SIZE, SIZE, 0, GPU_RGBA32F, nullptr); + EXPECT_NE(texture, nullptr); + + GPU_shader_bind(shader); + GPU_texture_image_bind(texture, GPU_shader_get_texture_binding(shader, "img_output")); + + /* Dispatch compute task. */ + GPU_compute_dispatch(shader, SIZE, SIZE, 1); + + /* Check if compute has been done. */ + GPU_memory_barrier(GPU_BARRIER_TEXTURE_FETCH); + float *data = static_cast<float *>(GPU_texture_read(texture, GPU_DATA_FLOAT, 0)); + EXPECT_NE(data, nullptr); + for (int index = 0; index < SIZE * SIZE; index++) { + EXPECT_FLOAT_EQ(data[index * 4 + 0], 1.0f); + EXPECT_FLOAT_EQ(data[index * 4 + 1], 0.5f); + EXPECT_FLOAT_EQ(data[index * 4 + 2], 0.2f); + EXPECT_FLOAT_EQ(data[index * 4 + 3], 1.0f); + } + MEM_freeN(data); + + /* Cleanup. */ + GPU_shader_unbind(); + GPU_texture_unbind(texture); + GPU_texture_free(texture); + GPU_shader_free(shader); +} + +TEST_F(GPUTest, gpu_shader_compute_1d) +{ + + if (!GPU_compute_shader_support()) { + /* We can't test as a the platform does not support compute shaders. */ + std::cout << "Skipping compute shader test: platform not supported"; + return; + } + + static constexpr uint SIZE = 10; + + /* Build compute shader. */ + const char *compute_glsl = R"( + +layout(local_size_x = 1) in; + +layout(rgba32f, binding = 1) uniform image1D outputVboData; + +void main() { + int index = int(gl_GlobalInvocationID.x); + vec4 pos = vec4(gl_GlobalInvocationID.x); + imageStore(outputVboData, index, pos); +} + +)"; + + GPUShader *shader = GPU_shader_create_compute( + compute_glsl, nullptr, nullptr, "gpu_shader_compute_1d"); + EXPECT_NE(shader, nullptr); + + /* Construct Texture. */ + GPUTexture *texture = GPU_texture_create_1d("gpu_shader_compute_1d", SIZE, 0, GPU_RGBA32F, NULL); + EXPECT_NE(texture, nullptr); + + GPU_shader_bind(shader); + GPU_texture_image_bind(texture, GPU_shader_get_texture_binding(shader, "outputVboData")); + + /* Dispatch compute task. */ + GPU_compute_dispatch(shader, SIZE, 1, 1); + + /* Check if compute has been done. */ + GPU_memory_barrier(GPU_BARRIER_TEXTURE_FETCH); + + /* Create texture to load back result. */ + float *data = static_cast<float *>(GPU_texture_read(texture, GPU_DATA_FLOAT, 0)); + EXPECT_NE(data, nullptr); + for (int index = 0; index < SIZE; index++) { + float expected_value = index; + EXPECT_FLOAT_EQ(data[index * 4 + 0], expected_value); + EXPECT_FLOAT_EQ(data[index * 4 + 1], expected_value); + EXPECT_FLOAT_EQ(data[index * 4 + 2], expected_value); + EXPECT_FLOAT_EQ(data[index * 4 + 3], expected_value); + } + MEM_freeN(data); + + /* Cleanup. */ + GPU_shader_unbind(); + GPU_texture_unbind(texture); + GPU_texture_free(texture); + GPU_shader_free(shader); +} + +TEST_F(GPUTest, gpu_shader_compute_vbo) +{ + + if (!GPU_compute_shader_support()) { + /* We can't test as a the platform does not support compute shaders. */ + std::cout << "Skipping compute shader test: platform not supported"; + return; + } + + static constexpr uint SIZE = 128; + + /* Build compute shader. */ + const char *compute_glsl = R"( + +layout(local_size_x = 1) in; + +layout(std430, binding = 0) writeonly buffer outputVboData +{ + vec4 out_positions[]; +}; + +void main() { + uint index = gl_GlobalInvocationID.x; + vec4 pos = vec4(gl_GlobalInvocationID.x); + out_positions[index] = pos; +} + +)"; + + GPUShader *shader = GPU_shader_create_compute( + compute_glsl, nullptr, nullptr, "gpu_shader_compute_vbo"); + EXPECT_NE(shader, nullptr); + GPU_shader_bind(shader); + + /* Construct VBO. */ + static GPUVertFormat format = {0}; + GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + GPUVertBuf *vbo = GPU_vertbuf_create_with_format_ex(&format, GPU_USAGE_DEVICE_ONLY); + GPU_vertbuf_data_alloc(vbo, SIZE); + GPU_vertbuf_bind_as_ssbo(vbo, GPU_shader_get_ssbo(shader, "outputVboData")); + + /* Dispatch compute task. */ + GPU_compute_dispatch(shader, SIZE, 1, 1); + + /* Check if compute has been done. */ + GPU_memory_barrier(GPU_BARRIER_SHADER_STORAGE); + + /* Download the vertex buffer. */ + const float *data = static_cast<const float *>(GPU_vertbuf_read(vbo)); + ASSERT_NE(data, nullptr); + for (int index = 0; index < SIZE; index++) { + float expected_value = index; + EXPECT_FLOAT_EQ(data[index * 4 + 0], expected_value); + EXPECT_FLOAT_EQ(data[index * 4 + 1], expected_value); + EXPECT_FLOAT_EQ(data[index * 4 + 2], expected_value); + EXPECT_FLOAT_EQ(data[index * 4 + 3], expected_value); + } + + /* Cleanup. */ + GPU_shader_unbind(); + GPU_vertbuf_discard(vbo); + GPU_shader_free(shader); +} + +TEST_F(GPUTest, gpu_shader_compute_ibo) +{ + + if (!GPU_compute_shader_support()) { + /* We can't test as a the platform does not support compute shaders. */ + std::cout << "Skipping compute shader test: platform not supported"; + return; + } + + static constexpr uint SIZE = 128; + + /* Build compute shader. */ + const char *compute_glsl = R"( + +layout(local_size_x = 1) in; + +layout(std430, binding = 1) writeonly buffer outputIboData +{ + uint out_indexes[]; +}; + +void main() { + uint store_index = int(gl_GlobalInvocationID.x); + out_indexes[store_index] = store_index; +} + +)"; + + GPUShader *shader = GPU_shader_create_compute( + compute_glsl, nullptr, nullptr, "gpu_shader_compute_vbo"); + EXPECT_NE(shader, nullptr); + GPU_shader_bind(shader); + + /* Construct IBO. */ + GPUIndexBuf *ibo = GPU_indexbuf_build_on_device(SIZE); + GPU_indexbuf_bind_as_ssbo(ibo, GPU_shader_get_ssbo(shader, "outputIboData")); + + /* Dispatch compute task. */ + GPU_compute_dispatch(shader, SIZE, 1, 1); + + /* Check if compute has been done. */ + GPU_memory_barrier(GPU_BARRIER_SHADER_STORAGE); + + /* Download the index buffer. */ + const uint32_t *data = GPU_indexbuf_read(ibo); + ASSERT_NE(data, nullptr); + for (int index = 0; index < SIZE; index++) { + uint32_t expected = index; + EXPECT_EQ(data[index], expected); + } + + /* Cleanup. */ + GPU_shader_unbind(); + GPU_indexbuf_discard(ibo); + GPU_shader_free(shader); +} + +TEST_F(GPUTest, gpu_shader_ssbo_binding) +{ + if (!GPU_compute_shader_support()) { + /* We can't test as a the platform does not support compute shaders. */ + std::cout << "Skipping compute shader test: platform not supported"; + return; + } + + /* Build compute shader. */ + const char *compute_glsl = R"( + +layout(local_size_x = 1) in; + +layout(std430, binding = 0) buffer ssboBinding0 +{ + int data0[]; +}; +layout(std430, binding = 1) buffer ssboBinding1 +{ + int data1[]; +}; + +void main() { +} + +)"; + + GPUShader *shader = GPU_shader_create_compute(compute_glsl, nullptr, nullptr, "gpu_shader_ssbo"); + EXPECT_NE(shader, nullptr); + GPU_shader_bind(shader); + + EXPECT_EQ(0, GPU_shader_get_ssbo(shader, "ssboBinding0")); + EXPECT_EQ(1, GPU_shader_get_ssbo(shader, "ssboBinding1")); + + /* Cleanup. */ + GPU_shader_unbind(); + GPU_shader_free(shader); +} + +} // namespace blender::gpu::tests diff --git a/source/blender/gpu/tests/gpu_testing.cc b/source/blender/gpu/tests/gpu_testing.cc index b9fc78dc084..ac42c5875c8 100644 --- a/source/blender/gpu/tests/gpu_testing.cc +++ b/source/blender/gpu/tests/gpu_testing.cc @@ -2,6 +2,8 @@ #include "testing/testing.h" +#include "CLG_log.h" + #include "GPU_context.h" #include "GPU_init_exit.h" #include "gpu_testing.hh" @@ -13,6 +15,7 @@ namespace blender::gpu { void GPUTest::SetUp() { GHOST_GLSettings glSettings = {0}; + CLG_init(); ghost_system = GHOST_CreateSystem(); ghost_context = GHOST_CreateOpenGLContext(ghost_system, glSettings); context = GPU_context_create(nullptr); @@ -26,6 +29,7 @@ void GPUTest::TearDown() GPU_context_discard(context); GHOST_DisposeOpenGLContext(ghost_system, ghost_context); GHOST_DisposeSystem(ghost_system); + CLG_exit(); } } // namespace blender::gpu diff --git a/source/blender/imbuf/CMakeLists.txt b/source/blender/imbuf/CMakeLists.txt index 7ce795280a3..be0e364c85f 100644 --- a/source/blender/imbuf/CMakeLists.txt +++ b/source/blender/imbuf/CMakeLists.txt @@ -166,12 +166,6 @@ if(WITH_CODEC_FFMPEG) ${OPENJPEG_LIBRARIES} ) add_definitions(-DWITH_FFMPEG) - - remove_strict_c_flags_file( - intern/anim_movie.c - intern/indexer.c - intern/util.c - ) endif() if(WITH_IMAGE_DDS) diff --git a/source/blender/imbuf/IMB_colormanagement.h b/source/blender/imbuf/IMB_colormanagement.h index f31839751b1..53b0e295385 100644 --- a/source/blender/imbuf/IMB_colormanagement.h +++ b/source/blender/imbuf/IMB_colormanagement.h @@ -69,9 +69,9 @@ bool IMB_colormanagement_space_name_is_data(const char *name); BLI_INLINE float IMB_colormanagement_get_luminance(const float rgb[3]); BLI_INLINE unsigned char IMB_colormanagement_get_luminance_byte(const unsigned char[3]); -BLI_INLINE void IMB_colormangement_xyz_to_rgb(float rgb[3], const float xyz[3]); -BLI_INLINE void IMB_colormangement_rgb_to_xyz(float xyz[3], const float rgb[3]); -const float *IMB_colormangement_get_xyz_to_rgb(void); +BLI_INLINE void IMB_colormanagement_xyz_to_rgb(float rgb[3], const float xyz[3]); +BLI_INLINE void IMB_colormanagement_rgb_to_xyz(float xyz[3], const float rgb[3]); +const float *IMB_colormanagement_get_xyz_to_rgb(void); /* ** Color space transformation functions ** */ void IMB_colormanagement_transform(float *buffer, diff --git a/source/blender/imbuf/intern/IMB_anim.h b/source/blender/imbuf/intern/IMB_anim.h index 1239d3881de..7d7864306a1 100644 --- a/source/blender/imbuf/intern/IMB_anim.h +++ b/source/blender/imbuf/intern/IMB_anim.h @@ -135,7 +135,7 @@ struct anim { struct ImBuf *last_frame; int64_t last_pts; int64_t next_pts; - AVPacket next_packet; + AVPacket *next_packet; #endif char index_dir[768]; diff --git a/source/blender/imbuf/intern/anim_movie.c b/source/blender/imbuf/intern/anim_movie.c index 96cd1fb61a4..5918a4bf16e 100644 --- a/source/blender/imbuf/intern/anim_movie.c +++ b/source/blender/imbuf/intern/anim_movie.c @@ -79,6 +79,7 @@ # include <libavcodec/avcodec.h> # include <libavformat/avformat.h> +# include <libavutil/imgutils.h> # include <libavutil/rational.h> # include <libswscale/swscale.h> @@ -519,12 +520,10 @@ static int startffmpeg(struct anim *anim) double frs_den; int streamcount; -# ifdef FFMPEG_SWSCALE_COLOR_SPACE_SUPPORT /* The following for color space determination */ int srcRange, dstRange, brightness, contrast, saturation; int *table; const int *inv_table; -# endif if (anim == NULL) { return (-1); @@ -547,7 +546,7 @@ static int startffmpeg(struct anim *anim) video_stream_index = -1; for (i = 0; i < pFormatCtx->nb_streams; i++) { - if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { if (streamcount > 0) { streamcount--; continue; @@ -563,16 +562,17 @@ static int startffmpeg(struct anim *anim) } video_stream = pFormatCtx->streams[video_stream_index]; - pCodecCtx = video_stream->codec; /* Find the decoder for the video stream */ - pCodec = avcodec_find_decoder(pCodecCtx->codec_id); + pCodec = avcodec_find_decoder(video_stream->codecpar->codec_id); if (pCodec == NULL) { avformat_close_input(&pFormatCtx); return -1; } - pCodecCtx->workaround_bugs = 1; + pCodecCtx = avcodec_alloc_context3(NULL); + avcodec_parameters_to_context(pCodecCtx, video_stream->codecpar); + pCodecCtx->workaround_bugs = FF_BUG_AUTODETECT; if (pCodec->capabilities & AV_CODEC_CAP_AUTO_THREADS) { pCodecCtx->thread_count = 0; @@ -593,7 +593,7 @@ static int startffmpeg(struct anim *anim) return -1; } if (pCodecCtx->pix_fmt == AV_PIX_FMT_NONE) { - avcodec_close(anim->pCodecCtx); + avcodec_free_context(&anim->pCodecCtx); avformat_close_input(&pFormatCtx); return -1; } @@ -639,7 +639,7 @@ static int startffmpeg(struct anim *anim) anim->params = 0; anim->x = pCodecCtx->width; - anim->y = av_get_cropped_height_from_codec(pCodecCtx); + anim->y = pCodecCtx->height; anim->pFormatCtx = pFormatCtx; anim->pCodecCtx = pCodecCtx; @@ -654,7 +654,8 @@ static int startffmpeg(struct anim *anim) anim->last_frame = 0; anim->last_pts = -1; anim->next_pts = -1; - anim->next_packet.stream_index = -1; + anim->next_packet = av_packet_alloc(); + anim->next_packet->stream_index = -1; anim->pFrame = av_frame_alloc(); anim->pFrameComplete = false; @@ -668,8 +669,9 @@ static int startffmpeg(struct anim *anim) if (av_frame_get_buffer(anim->pFrameRGB, 32) < 0) { fprintf(stderr, "Could not allocate frame data.\n"); - avcodec_close(anim->pCodecCtx); + avcodec_free_context(&anim->pCodecCtx); avformat_close_input(&anim->pFormatCtx); + av_packet_free(&anim->next_packet); av_frame_free(&anim->pFrameRGB); av_frame_free(&anim->pFrameDeinterlaced); av_frame_free(&anim->pFrame); @@ -678,10 +680,11 @@ static int startffmpeg(struct anim *anim) } } - if (avpicture_get_size(AV_PIX_FMT_RGBA, anim->x, anim->y) != anim->x * anim->y * 4) { + if (av_image_get_buffer_size(AV_PIX_FMT_RGBA, anim->x, anim->y, 1) != anim->x * anim->y * 4) { fprintf(stderr, "ffmpeg has changed alloc scheme ... ARGHHH!\n"); - avcodec_close(anim->pCodecCtx); + avcodec_free_context(&anim->pCodecCtx); avformat_close_input(&anim->pFormatCtx); + av_packet_free(&anim->next_packet); av_frame_free(&anim->pFrameRGB); av_frame_free(&anim->pFrameDeinterlaced); av_frame_free(&anim->pFrame); @@ -690,14 +693,17 @@ static int startffmpeg(struct anim *anim) } if (anim->ib_flags & IB_animdeinterlace) { - avpicture_fill((AVPicture *)anim->pFrameDeinterlaced, - MEM_callocN(avpicture_get_size(anim->pCodecCtx->pix_fmt, - anim->pCodecCtx->width, - anim->pCodecCtx->height), - "ffmpeg deinterlace"), - anim->pCodecCtx->pix_fmt, - anim->pCodecCtx->width, - anim->pCodecCtx->height); + av_image_fill_arrays(anim->pFrameDeinterlaced->data, + anim->pFrameDeinterlaced->linesize, + MEM_callocN(av_image_get_buffer_size(anim->pCodecCtx->pix_fmt, + anim->pCodecCtx->width, + anim->pCodecCtx->height, + 1), + "ffmpeg deinterlace"), + anim->pCodecCtx->pix_fmt, + anim->pCodecCtx->width, + anim->pCodecCtx->height, + 1); } if (pCodecCtx->has_b_frames) { @@ -720,8 +726,9 @@ static int startffmpeg(struct anim *anim) if (!anim->img_convert_ctx) { fprintf(stderr, "Can't transform color space??? Bailing out...\n"); - avcodec_close(anim->pCodecCtx); + avcodec_free_context(&anim->pCodecCtx); avformat_close_input(&anim->pFormatCtx); + av_packet_free(&anim->next_packet); av_frame_free(&anim->pFrameRGB); av_frame_free(&anim->pFrameDeinterlaced); av_frame_free(&anim->pFrame); @@ -729,7 +736,6 @@ static int startffmpeg(struct anim *anim) return -1; } -# ifdef FFMPEG_SWSCALE_COLOR_SPACE_SUPPORT /* Try do detect if input has 0-255 YCbCR range (JFIF Jpeg MotionJpeg) */ if (!sws_getColorspaceDetails(anim->img_convert_ctx, (int **)&inv_table, @@ -756,7 +762,6 @@ static int startffmpeg(struct anim *anim) else { fprintf(stderr, "Warning: Could not set libswscale colorspace details.\n"); } -# endif return 0; } @@ -795,11 +800,11 @@ static void ffmpeg_postprocess(struct anim *anim) input->data[3]); if (anim->ib_flags & IB_animdeinterlace) { - if (avpicture_deinterlace((AVPicture *)anim->pFrameDeinterlaced, - (const AVPicture *)anim->pFrame, - anim->pCodecCtx->pix_fmt, - anim->pCodecCtx->width, - anim->pCodecCtx->height) < 0) { + if (av_image_deinterlace(anim->pFrameDeinterlaced, + anim->pFrame, + anim->pCodecCtx->pix_fmt, + anim->pCodecCtx->width, + anim->pCodecCtx->height) < 0) { filter_y = true; } else { @@ -808,16 +813,18 @@ static void ffmpeg_postprocess(struct anim *anim) } if (!need_aligned_ffmpeg_buffer(anim)) { - avpicture_fill((AVPicture *)anim->pFrameRGB, - (unsigned char *)ibuf->rect, - AV_PIX_FMT_RGBA, - anim->x, - anim->y); + av_image_fill_arrays(anim->pFrameRGB->data, + anim->pFrameRGB->linesize, + (unsigned char *)ibuf->rect, + AV_PIX_FMT_RGBA, + anim->x, + anim->y, + 1); } # if defined(__x86_64__) || defined(_M_X64) /* Scale and flip image over Y axis in one go, using negative strides. - * This doesn't work with arm/powerpc though and may be misusing the API. + * This doesn't work with ARM/PowerPC though and may be misusing the API. * Limit it x86_64 where it appears to work. * http://trac.ffmpeg.org/ticket/9060 */ int *dstStride = anim->pFrameRGB->linesize; @@ -903,82 +910,70 @@ static int ffmpeg_decode_video_frame(struct anim *anim) av_log(anim->pFormatCtx, AV_LOG_DEBUG, " DECODE VIDEO FRAME\n"); - if (anim->next_packet.stream_index == anim->videoStream) { - av_free_packet(&anim->next_packet); - anim->next_packet.stream_index = -1; + if (anim->next_packet->stream_index == anim->videoStream) { + av_packet_unref(anim->next_packet); + anim->next_packet->stream_index = -1; } - while ((rval = av_read_frame(anim->pFormatCtx, &anim->next_packet)) >= 0) { + while ((rval = av_read_frame(anim->pFormatCtx, anim->next_packet)) >= 0) { av_log(anim->pFormatCtx, AV_LOG_DEBUG, "%sREAD: strID=%d (VID: %d) dts=%" PRId64 " pts=%" PRId64 " %s\n", - (anim->next_packet.stream_index == anim->videoStream) ? "->" : " ", - anim->next_packet.stream_index, + (anim->next_packet->stream_index == anim->videoStream) ? "->" : " ", + anim->next_packet->stream_index, anim->videoStream, - (anim->next_packet.dts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->next_packet.dts, - (anim->next_packet.pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->next_packet.pts, - (anim->next_packet.flags & AV_PKT_FLAG_KEY) ? " KEY" : ""); - if (anim->next_packet.stream_index == anim->videoStream) { + (anim->next_packet->dts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->next_packet->dts, + (anim->next_packet->pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->next_packet->pts, + (anim->next_packet->flags & AV_PKT_FLAG_KEY) ? " KEY" : ""); + if (anim->next_packet->stream_index == anim->videoStream) { anim->pFrameComplete = 0; - avcodec_decode_video2( - anim->pCodecCtx, anim->pFrame, &anim->pFrameComplete, &anim->next_packet); + avcodec_send_packet(anim->pCodecCtx, anim->next_packet); + anim->pFrameComplete = avcodec_receive_frame(anim->pCodecCtx, anim->pFrame) == 0; if (anim->pFrameComplete) { anim->next_pts = av_get_pts_from_frame(anim->pFormatCtx, anim->pFrame); av_log(anim->pFormatCtx, AV_LOG_DEBUG, - " FRAME DONE: next_pts=%" PRId64 " pkt_pts=%" PRId64 ", guessed_pts=%" PRId64 "\n", + " FRAME DONE: next_pts=%" PRId64 ", guessed_pts=%" PRId64 "\n", (anim->pFrame->pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->pFrame->pts, - (anim->pFrame->pkt_pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->pFrame->pkt_pts, (int64_t)anim->next_pts); break; } } - av_free_packet(&anim->next_packet); - anim->next_packet.stream_index = -1; + av_packet_unref(anim->next_packet); + anim->next_packet->stream_index = -1; } if (rval == AVERROR_EOF) { - /* this sets size and data fields to zero, - * which is necessary to decode the remaining data - * in the decoder engine after EOF. It also prevents a memory - * leak, since av_read_frame spills out a full size packet even - * on EOF... (and: it's safe to call on NULL packets) */ - - av_free_packet(&anim->next_packet); - - anim->next_packet.size = 0; - anim->next_packet.data = 0; - + /* Flush any remaining frames out of the decoder. */ anim->pFrameComplete = 0; - avcodec_decode_video2( - anim->pCodecCtx, anim->pFrame, &anim->pFrameComplete, &anim->next_packet); + avcodec_send_packet(anim->pCodecCtx, NULL); + anim->pFrameComplete = avcodec_receive_frame(anim->pCodecCtx, anim->pFrame) == 0; if (anim->pFrameComplete) { anim->next_pts = av_get_pts_from_frame(anim->pFormatCtx, anim->pFrame); av_log(anim->pFormatCtx, AV_LOG_DEBUG, - " FRAME DONE (after EOF): next_pts=%" PRId64 " pkt_pts=%" PRId64 - ", guessed_pts=%" PRId64 "\n", + " FRAME DONE (after EOF): next_pts=%" PRId64 ", guessed_pts=%" PRId64 "\n", (anim->pFrame->pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->pFrame->pts, - (anim->pFrame->pkt_pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->pFrame->pkt_pts, (int64_t)anim->next_pts); rval = 0; } } if (rval < 0) { - anim->next_packet.stream_index = -1; + av_packet_unref(anim->next_packet); + anim->next_packet->stream_index = -1; av_log(anim->pFormatCtx, AV_LOG_ERROR, " DECODE READ FAILED: av_read_frame() " - "returned error: %d\n", - rval); + "returned error: %s\n", + av_err2str(rval)); } return (rval >= 0); @@ -1128,7 +1123,7 @@ static void ffmpeg_decode_video_frame_scan(struct anim *anim, int64_t pts_to_sea } } -/* Wrapper over av_seek_frame(), for formats that doesn't have it's own read_seek() or read_seek2() +/* Wrapper over av_seek_frame(), for formats that doesn't have its own read_seek() or read_seek2() * functions defined. When seeking in these formats, rule to seek to last necessary I-frame is not * honored. It is not even guaranteed that I-frame, that must be decoded will be read. See * https://trac.ffmpeg.org/ticket/1607 and https://developer.blender.org/T86944. */ @@ -1154,7 +1149,7 @@ static int ffmpeg_generic_seek_workaround(struct anim *anim, int64_t requested_p /* Read first video stream packet. */ AVPacket read_packet = {0}; while (av_read_frame(anim->pFormatCtx, &read_packet) >= 0) { - if (anim->next_packet.stream_index == anim->videoStream) { + if (anim->next_packet->stream_index == anim->videoStream) { break; } } @@ -1246,9 +1241,9 @@ static void ffmpeg_seek_and_decode(struct anim *anim, int position, struct anim_ anim->next_pts = -1; - if (anim->next_packet.stream_index == anim->videoStream) { - av_free_packet(&anim->next_packet); - anim->next_packet.stream_index = -1; + if (anim->next_packet->stream_index == anim->videoStream) { + av_packet_unref(anim->next_packet); + anim->next_packet->stream_index = -1; } /* memset(anim->pFrame, ...) ?? */ @@ -1351,32 +1346,30 @@ static void free_anim_ffmpeg(struct anim *anim) } if (anim->pCodecCtx) { - avcodec_close(anim->pCodecCtx); + avcodec_free_context(&anim->pCodecCtx); avformat_close_input(&anim->pFormatCtx); + av_packet_free(&anim->next_packet); - /* Special case here: pFrame could share pointers with codec, - * so in order to avoid double-free we don't use av_frame_free() - * to free the frame. - * - * Could it be a bug in FFmpeg? - */ - av_free(anim->pFrame); + av_frame_free(&anim->pFrame); if (!need_aligned_ffmpeg_buffer(anim)) { /* If there's no need for own aligned buffer it means that FFmpeg's * frame shares the same buffer as temporary ImBuf. In this case we * should not free the buffer when freeing the FFmpeg buffer. */ - avpicture_fill((AVPicture *)anim->pFrameRGB, NULL, AV_PIX_FMT_RGBA, anim->x, anim->y); + av_image_fill_arrays(anim->pFrameRGB->data, + anim->pFrameRGB->linesize, + NULL, + AV_PIX_FMT_RGBA, + anim->x, + anim->y, + 1); } av_frame_free(&anim->pFrameRGB); av_frame_free(&anim->pFrameDeinterlaced); sws_freeContext(anim->img_convert_ctx); IMB_freeImBuf(anim->last_frame); - if (anim->next_packet.stream_index != -1) { - av_free_packet(&anim->next_packet); - } } anim->duration_in_frames = 0; } diff --git a/source/blender/imbuf/intern/colormanagement.c b/source/blender/imbuf/intern/colormanagement.c index fc0b99a82fa..68d0b516828 100644 --- a/source/blender/imbuf/intern/colormanagement.c +++ b/source/blender/imbuf/intern/colormanagement.c @@ -1409,7 +1409,7 @@ bool IMB_colormanagement_space_name_is_data(const char *name) return (colorspace && colorspace->is_data); } -const float *IMB_colormangement_get_xyz_to_rgb() +const float *IMB_colormanagement_get_xyz_to_rgb() { return &imbuf_xyz_to_rgb[0][0]; } diff --git a/source/blender/imbuf/intern/colormanagement_inline.c b/source/blender/imbuf/intern/colormanagement_inline.c index e93fdd51040..c304ad8d8e5 100644 --- a/source/blender/imbuf/intern/colormanagement_inline.c +++ b/source/blender/imbuf/intern/colormanagement_inline.c @@ -55,12 +55,12 @@ unsigned char IMB_colormanagement_get_luminance_byte(const unsigned char rgb[3]) return unit_float_to_uchar_clamp(val); } -void IMB_colormangement_xyz_to_rgb(float rgb[3], const float xyz[3]) +void IMB_colormanagement_xyz_to_rgb(float rgb[3], const float xyz[3]) { mul_v3_m3v3(rgb, imbuf_xyz_to_rgb, xyz); } -void IMB_colormangement_rgb_to_xyz(float xyz[3], const float rgb[3]) +void IMB_colormanagement_rgb_to_xyz(float xyz[3], const float rgb[3]) { mul_v3_m3v3(xyz, imbuf_rgb_to_xyz, rgb); } diff --git a/source/blender/imbuf/intern/indexer.c b/source/blender/imbuf/intern/indexer.c index ef9f6d861a3..11ce77e3091 100644 --- a/source/blender/imbuf/intern/indexer.c +++ b/source/blender/imbuf/intern/indexer.c @@ -48,6 +48,7 @@ #ifdef WITH_FFMPEG # include "ffmpeg_compat.h" +# include <libavutil/imgutils.h> #endif static const char magic[] = "BlenMIdx"; @@ -488,14 +489,14 @@ static struct proxy_output_ctx *alloc_proxy_output_ffmpeg( rv->of = avformat_alloc_context(); rv->of->oformat = av_guess_format("avi", NULL, NULL); - BLI_strncpy(rv->of->filename, fname, sizeof(rv->of->filename)); + rv->of->url = av_strdup(fname); - fprintf(stderr, "Starting work on proxy: %s\n", rv->of->filename); + fprintf(stderr, "Starting work on proxy: %s\n", rv->of->url); rv->st = avformat_new_stream(rv->of, NULL); rv->st->id = 0; - rv->c = rv->st->codec; + rv->c = avcodec_alloc_context3(NULL); rv->c->codec_type = AVMEDIA_TYPE_VIDEO; rv->c->codec_id = AV_CODEC_ID_H264; rv->c->width = width; @@ -513,7 +514,9 @@ static struct proxy_output_ctx *alloc_proxy_output_ffmpeg( fprintf(stderr, "No ffmpeg encoder available? " "Proxy not built!\n"); - av_free(rv->of); + avcodec_free_context(&rv->c); + avformat_free_context(rv->of); + MEM_freeN(rv); return NULL; } @@ -524,7 +527,7 @@ static struct proxy_output_ctx *alloc_proxy_output_ffmpeg( rv->c->pix_fmt = AV_PIX_FMT_YUVJ420P; } - rv->c->sample_aspect_ratio = rv->st->sample_aspect_ratio = st->codec->sample_aspect_ratio; + rv->c->sample_aspect_ratio = rv->st->sample_aspect_ratio = st->sample_aspect_ratio; rv->c->time_base.den = 25; rv->c->time_base.num = 1; @@ -557,34 +560,54 @@ static struct proxy_output_ctx *alloc_proxy_output_ffmpeg( } if (rv->of->flags & AVFMT_GLOBALHEADER) { - rv->c->flags |= CODEC_FLAG_GLOBAL_HEADER; + rv->c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } - if (avio_open(&rv->of->pb, fname, AVIO_FLAG_WRITE) < 0) { + avcodec_parameters_from_context(rv->st->codecpar, rv->c); + + int ret = avio_open(&rv->of->pb, fname, AVIO_FLAG_WRITE); + + if (ret < 0) { fprintf(stderr, - "Couldn't open outputfile! " - "Proxy not built!\n"); - av_free(rv->of); - return 0; + "Couldn't open IO: %s\n" + "Proxy not built!\n", + av_err2str(ret)); + avcodec_free_context(&rv->c); + avformat_free_context(rv->of); + MEM_freeN(rv); + return NULL; } - avcodec_open2(rv->c, rv->codec, &codec_opts); + ret = avcodec_open2(rv->c, rv->codec, &codec_opts); + if (ret < 0) { + fprintf(stderr, + "Couldn't open codec: %s\n" + "Proxy not built!\n", + av_err2str(ret)); + avcodec_free_context(&rv->c); + avformat_free_context(rv->of); + MEM_freeN(rv); + return NULL; + } - rv->orig_height = av_get_cropped_height_from_codec(st->codec); + rv->orig_height = st->codecpar->height; - if (st->codec->width != width || st->codec->height != height || - st->codec->pix_fmt != rv->c->pix_fmt) { + if (st->codecpar->width != width || st->codecpar->height != height || + st->codecpar->format != rv->c->pix_fmt) { rv->frame = av_frame_alloc(); - avpicture_fill((AVPicture *)rv->frame, - MEM_mallocN(avpicture_get_size(rv->c->pix_fmt, round_up(width, 16), height), - "alloc proxy output frame"), - rv->c->pix_fmt, - round_up(width, 16), - height); - - rv->sws_ctx = sws_getContext(st->codec->width, + av_image_fill_arrays( + rv->frame->data, + rv->frame->linesize, + MEM_mallocN(av_image_get_buffer_size(rv->c->pix_fmt, round_up(width, 16), height, 1), + "alloc proxy output frame"), + rv->c->pix_fmt, + round_up(width, 16), + height, + 1); + + rv->sws_ctx = sws_getContext(st->codecpar->width, rv->orig_height, - st->codec->pix_fmt, + st->codecpar->format, width, height, rv->c->pix_fmt, @@ -594,26 +617,30 @@ static struct proxy_output_ctx *alloc_proxy_output_ffmpeg( NULL); } - if (avformat_write_header(rv->of, NULL) < 0) { + ret = avformat_write_header(rv->of, NULL); + if (ret < 0) { fprintf(stderr, - "Couldn't set output parameters? " - "Proxy not built!\n"); - av_free(rv->of); - return 0; + "Couldn't write header: %s\n" + "Proxy not built!\n", + av_err2str(ret)); + + if (rv->frame) { + av_frame_free(&rv->frame); + } + + avcodec_free_context(&rv->c); + avformat_free_context(rv->of); + MEM_freeN(rv); + return NULL; } return rv; } -static int add_to_proxy_output_ffmpeg(struct proxy_output_ctx *ctx, AVFrame *frame) +static void add_to_proxy_output_ffmpeg(struct proxy_output_ctx *ctx, AVFrame *frame) { - AVPacket packet = {0}; - int ret, got_output; - - av_init_packet(&packet); - if (!ctx) { - return 0; + return; } if (ctx->sws_ctx && frame && @@ -633,35 +660,46 @@ static int add_to_proxy_output_ffmpeg(struct proxy_output_ctx *ctx, AVFrame *fra frame->pts = ctx->cfra++; } - ret = avcodec_encode_video2(ctx->c, &packet, frame, &got_output); + int ret = avcodec_send_frame(ctx->c, frame); if (ret < 0) { - fprintf(stderr, "Error encoding proxy frame %d for '%s'\n", ctx->cfra - 1, ctx->of->filename); - return 0; + /* Can't send frame to encoder. This shouldn't happen. */ + fprintf(stderr, "Can't send video frame: %s\n", av_err2str(ret)); + return; } + AVPacket *packet = av_packet_alloc(); + + while (ret >= 0) { + ret = avcodec_receive_packet(ctx->c, packet); - if (got_output) { - if (packet.pts != AV_NOPTS_VALUE) { - packet.pts = av_rescale_q(packet.pts, ctx->c->time_base, ctx->st->time_base); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + /* No more packets to flush. */ + break; } - if (packet.dts != AV_NOPTS_VALUE) { - packet.dts = av_rescale_q(packet.dts, ctx->c->time_base, ctx->st->time_base); + if (ret < 0) { + fprintf(stderr, + "Error encoding proxy frame %d for '%s': %s\n", + ctx->cfra - 1, + ctx->of->url, + av_err2str(ret)); + break; } - packet.stream_index = ctx->st->index; + packet->stream_index = ctx->st->index; + av_packet_rescale_ts(packet, ctx->c->time_base, ctx->st->time_base); - if (av_interleaved_write_frame(ctx->of, &packet) != 0) { + int write_ret = av_interleaved_write_frame(ctx->of, packet); + if (write_ret != 0) { fprintf(stderr, "Error writing proxy frame %d " - "into '%s'\n", + "into '%s': %s\n", ctx->cfra - 1, - ctx->of->filename); - return 0; + ctx->of->url, + av_err2str(write_ret)); + break; } - - return 1; } - return 0; + av_packet_free(&packet); } static void free_proxy_output_ffmpeg(struct proxy_output_ctx *ctx, int rollback) @@ -674,15 +712,15 @@ static void free_proxy_output_ffmpeg(struct proxy_output_ctx *ctx, int rollback) } if (!rollback) { - while (add_to_proxy_output_ffmpeg(ctx, NULL)) { - } + /* Flush the remaining packets. */ + add_to_proxy_output_ffmpeg(ctx, NULL); } avcodec_flush_buffers(ctx->c); av_write_trailer(ctx->of); - avcodec_close(ctx->c); + avcodec_free_context(&ctx->c); if (ctx->of->oformat) { if (!(ctx->of->oformat->flags & AVFMT_NOFILE)) { @@ -777,7 +815,7 @@ static IndexBuildContext *index_ffmpeg_create_context(struct anim *anim, /* Find the video stream */ context->videoStream = -1; for (i = 0; i < context->iFormatCtx->nb_streams; i++) { - if (context->iFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + if (context->iFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { if (streamcount > 0) { streamcount--; continue; @@ -794,9 +832,8 @@ static IndexBuildContext *index_ffmpeg_create_context(struct anim *anim, } context->iStream = context->iFormatCtx->streams[context->videoStream]; - context->iCodecCtx = context->iStream->codec; - context->iCodec = avcodec_find_decoder(context->iCodecCtx->codec_id); + context->iCodec = avcodec_find_decoder(context->iStream->codecpar->codec_id); if (context->iCodec == NULL) { avformat_close_input(&context->iFormatCtx); @@ -804,7 +841,9 @@ static IndexBuildContext *index_ffmpeg_create_context(struct anim *anim, return NULL; } - context->iCodecCtx->workaround_bugs = 1; + context->iCodecCtx = avcodec_alloc_context3(NULL); + avcodec_parameters_to_context(context->iCodecCtx, context->iStream->codecpar); + context->iCodecCtx->workaround_bugs = FF_BUG_AUTODETECT; if (context->iCodec->capabilities & AV_CODEC_CAP_AUTO_THREADS) { context->iCodecCtx->thread_count = 0; @@ -822,19 +861,19 @@ static IndexBuildContext *index_ffmpeg_create_context(struct anim *anim, if (avcodec_open2(context->iCodecCtx, context->iCodec, NULL) < 0) { avformat_close_input(&context->iFormatCtx); + avcodec_free_context(&context->iCodecCtx); MEM_freeN(context); return NULL; } for (i = 0; i < num_proxy_sizes; i++) { if (proxy_sizes_in_use & proxy_sizes[i]) { - context->proxy_ctx[i] = alloc_proxy_output_ffmpeg( - anim, - context->iStream, - proxy_sizes[i], - context->iCodecCtx->width * proxy_fac[i], - av_get_cropped_height_from_codec(context->iCodecCtx) * proxy_fac[i], - quality); + context->proxy_ctx[i] = alloc_proxy_output_ffmpeg(anim, + context->iStream, + proxy_sizes[i], + context->iCodecCtx->width * proxy_fac[i], + context->iCodecCtx->height * proxy_fac[i], + quality); if (!context->proxy_ctx[i]) { proxy_sizes_in_use &= ~proxy_sizes[i]; } @@ -873,7 +912,7 @@ static void index_rebuild_ffmpeg_finish(FFmpegIndexBuilderContext *context, int } } - avcodec_close(context->iCodecCtx); + avcodec_free_context(&context->iCodecCtx); avformat_close_input(&context->iFormatCtx); MEM_freeN(context); @@ -938,23 +977,18 @@ static int index_rebuild_ffmpeg(FFmpegIndexBuilderContext *context, short *do_update, float *progress) { - AVFrame *in_frame = 0; - AVPacket next_packet; + AVFrame *in_frame = av_frame_alloc(); + AVPacket *next_packet = av_packet_alloc(); uint64_t stream_size; - memset(&next_packet, 0, sizeof(AVPacket)); - - in_frame = av_frame_alloc(); - stream_size = avio_size(context->iFormatCtx->pb); context->frame_rate = av_q2d(av_guess_frame_rate(context->iFormatCtx, context->iStream, NULL)); context->pts_time_base = av_q2d(context->iStream->time_base); - while (av_read_frame(context->iFormatCtx, &next_packet) >= 0) { - int frame_finished = 0; + while (av_read_frame(context->iFormatCtx, next_packet) >= 0) { float next_progress = - (float)((int)floor(((double)next_packet.pos) * 100 / ((double)stream_size) + 0.5)) / 100; + (float)((int)floor(((double)next_packet->pos) * 100 / ((double)stream_size) + 0.5)) / 100; if (*progress != next_progress) { *progress = next_progress; @@ -962,50 +996,59 @@ static int index_rebuild_ffmpeg(FFmpegIndexBuilderContext *context, } if (*stop) { - av_free_packet(&next_packet); break; } - if (next_packet.stream_index == context->videoStream) { - if (next_packet.flags & AV_PKT_FLAG_KEY) { + if (next_packet->stream_index == context->videoStream) { + if (next_packet->flags & AV_PKT_FLAG_KEY) { context->last_seek_pos = context->seek_pos; context->last_seek_pos_dts = context->seek_pos_dts; - context->seek_pos = next_packet.pos; - context->seek_pos_dts = next_packet.dts; - context->seek_pos_pts = next_packet.pts; + context->seek_pos = next_packet->pos; + context->seek_pos_dts = next_packet->dts; + context->seek_pos_pts = next_packet->pts; } - avcodec_decode_video2(context->iCodecCtx, in_frame, &frame_finished, &next_packet); - } + int ret = avcodec_send_packet(context->iCodecCtx, next_packet); + while (ret >= 0) { + ret = avcodec_receive_frame(context->iCodecCtx, in_frame); - if (frame_finished) { - index_rebuild_ffmpeg_proc_decoded_frame(context, &next_packet, in_frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + /* No more frames to flush. */ + break; + } + if (ret < 0) { + fprintf(stderr, "Error decoding proxy frame: %s\n", av_err2str(ret)); + break; + } + index_rebuild_ffmpeg_proc_decoded_frame(context, next_packet, in_frame); + } } - av_free_packet(&next_packet); } /* process pictures still stuck in decoder engine after EOF - * according to ffmpeg docs using 0-size packets. + * according to ffmpeg docs using NULL packets. * * At least, if we haven't already stopped... */ - /* this creates the 0-size packet and prevents a memory leak. */ - av_free_packet(&next_packet); - if (!*stop) { - int frame_finished; - - do { - frame_finished = 0; + int ret = avcodec_send_packet(context->iCodecCtx, NULL); - avcodec_decode_video2(context->iCodecCtx, in_frame, &frame_finished, &next_packet); + while (ret >= 0) { + ret = avcodec_receive_frame(context->iCodecCtx, in_frame); - if (frame_finished) { - index_rebuild_ffmpeg_proc_decoded_frame(context, &next_packet, in_frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + /* No more frames to flush. */ + break; } - } while (frame_finished); + if (ret < 0) { + fprintf(stderr, "Error flushing proxy frame: %s\n", av_err2str(ret)); + break; + } + index_rebuild_ffmpeg_proc_decoded_frame(context, next_packet, in_frame); + } } + av_packet_free(&next_packet); av_free(in_frame); return 1; diff --git a/source/blender/imbuf/intern/jpeg.c b/source/blender/imbuf/intern/jpeg.c index 440375f60dc..48b5b0c34db 100644 --- a/source/blender/imbuf/intern/jpeg.c +++ b/source/blender/imbuf/intern/jpeg.c @@ -516,7 +516,8 @@ static void write_jpeg(struct jpeg_compress_struct *cinfo, struct ImBuf *ibuf) * The first "Blender" is a simple identify to help * in the read process. */ - text_len = BLI_snprintf(text, text_size, "Blender:%s:%s", prop->name, IDP_String(prop)); + text_len = BLI_snprintf_rlen( + text, text_size, "Blender:%s:%s", prop->name, IDP_String(prop)); jpeg_write_marker(cinfo, JPEG_COM, (JOCTET *)text, text_len + 1); /* TODO(sergey): Ideally we will try to re-use allocation as diff --git a/source/blender/imbuf/intern/tiff.c b/source/blender/imbuf/intern/tiff.c index 001cd4e1575..12f7553d5e4 100644 --- a/source/blender/imbuf/intern/tiff.c +++ b/source/blender/imbuf/intern/tiff.c @@ -374,7 +374,7 @@ static void scanline_separate_32bit(float *rectf, const float *fbuf, int scanlin static void imb_read_tiff_resolution(ImBuf *ibuf, TIFF *image) { - uint16 unit; + uint16_t unit; float xres; float yres; @@ -569,7 +569,7 @@ ImBuf *imb_loadtiff(const unsigned char *mem, TIFF *image = NULL; ImBuf *ibuf = NULL, *hbuf; ImbTIFFMemFile memFile; - uint32 width, height; + uint32_t width, height; char *format = NULL; int level; short spp; @@ -690,7 +690,7 @@ void imb_loadtiletiff( ImBuf *ibuf, const unsigned char *mem, size_t size, int tx, int ty, unsigned int *rect) { TIFF *image = NULL; - uint32 width, height; + uint32_t width, height; ImbTIFFMemFile memFile; image = imb_tiff_client_open(&memFile, mem, size); @@ -761,7 +761,7 @@ void imb_loadtiletiff( bool imb_savetiff(ImBuf *ibuf, const char *filepath, int flags) { TIFF *image = NULL; - uint16 samplesperpixel, bitspersample; + uint16_t samplesperpixel, bitspersample; size_t npixels; unsigned char *pixels = NULL; unsigned char *from = NULL, *to = NULL; @@ -774,7 +774,7 @@ bool imb_savetiff(ImBuf *ibuf, const char *filepath, int flags) /* check for a valid number of bytes per pixel. Like the PNG writer, * the TIFF writer supports 1, 3 or 4 bytes per pixel, corresponding * to gray, RGB, RGBA respectively. */ - samplesperpixel = (uint16)((ibuf->planes + 7) >> 3); + samplesperpixel = (uint16_t)((ibuf->planes + 7) >> 3); if ((samplesperpixel > 4) || (samplesperpixel == 2)) { fprintf(stderr, "imb_savetiff: unsupported number of bytes per " diff --git a/source/blender/imbuf/intern/util.c b/source/blender/imbuf/intern/util.c index 64dad5de902..1bb047f1317 100644 --- a/source/blender/imbuf/intern/util.c +++ b/source/blender/imbuf/intern/util.c @@ -182,7 +182,7 @@ bool IMB_ispic_type_matches(const char *filepath, int filetype) const ImFileType *type = IMB_file_type_from_ftype(filetype); if (type != NULL) { - /* Requesting to load a type that can't check it's own header doesn't make sense. + /* Requesting to load a type that can't check its own header doesn't make sense. * Keep the check for developers. */ BLI_assert(type->is_a != NULL); if (type->is_a != NULL) { @@ -245,7 +245,6 @@ static void ffmpeg_log_callback(void *ptr, int level, const char *format, va_lis void IMB_ffmpeg_init(void) { - av_register_all(); avdevice_register_all(); ffmpeg_last_error[0] = '\0'; @@ -269,7 +268,6 @@ static int isffmpeg(const char *filepath) unsigned int i; int videoStream; AVCodec *pCodec; - AVCodecContext *pCodecCtx; if (BLI_path_extension_check_n(filepath, ".swf", @@ -310,8 +308,8 @@ static int isffmpeg(const char *filepath) /* Find the first video stream */ videoStream = -1; for (i = 0; i < pFormatCtx->nb_streams; i++) { - if (pFormatCtx->streams[i] && pFormatCtx->streams[i]->codec && - (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)) { + if (pFormatCtx->streams[i] && pFormatCtx->streams[i]->codecpar && + (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)) { videoStream = i; break; } @@ -322,21 +320,15 @@ static int isffmpeg(const char *filepath) return 0; } - pCodecCtx = pFormatCtx->streams[videoStream]->codec; + AVCodecParameters *codec_par = pFormatCtx->streams[videoStream]->codecpar; /* Find the decoder for the video stream */ - pCodec = avcodec_find_decoder(pCodecCtx->codec_id); + pCodec = avcodec_find_decoder(codec_par->codec_id); if (pCodec == NULL) { avformat_close_input(&pFormatCtx); return 0; } - if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { - avformat_close_input(&pFormatCtx); - return 0; - } - - avcodec_close(pCodecCtx); avformat_close_input(&pFormatCtx); return 1; diff --git a/source/blender/io/alembic/ABC_alembic.h b/source/blender/io/alembic/ABC_alembic.h index 9785f6d68ab..5664a43233a 100644 --- a/source/blender/io/alembic/ABC_alembic.h +++ b/source/blender/io/alembic/ABC_alembic.h @@ -49,6 +49,7 @@ struct AlembicExportParams { bool uvs; bool normals; bool vcolors; + bool orcos; bool apply_subdiv; bool curves_as_mesh; bool flatten_hierarchy; diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.cc b/source/blender/io/alembic/exporter/abc_writer_mesh.cc index c00e57c8edc..fd7db005dd2 100644 --- a/source/blender/io/alembic/exporter/abc_writer_mesh.cc +++ b/source/blender/io/alembic/exporter/abc_writer_mesh.cc @@ -200,6 +200,7 @@ void ABCGenericMeshWriter::do_write(HierarchyContext &context) } m_custom_data_config.pack_uvs = args_.export_params->packuv; + m_custom_data_config.mesh = mesh; m_custom_data_config.mpoly = mesh->mpoly; m_custom_data_config.mloop = mesh->mloop; m_custom_data_config.totpoly = mesh->totpoly; @@ -250,7 +251,7 @@ void ABCGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh) UVSample uvs_and_indices; - if (!frame_has_been_written_ && args_.export_params->uvs) { + if (args_.export_params->uvs) { const char *name = get_uv_sample(uvs_and_indices, m_custom_data_config, &mesh->ldata); if (!uvs_and_indices.indices.empty() && !uvs_and_indices.uvs.empty()) { @@ -279,6 +280,10 @@ void ABCGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh) mesh_sample.setNormals(normals_sample); } + if (args_.export_params->orcos) { + write_generated_coordinates(abc_poly_mesh_schema_.getArbGeomParams(), m_custom_data_config); + } + if (liquid_sim_modifier_ != nullptr) { get_velocities(mesh, velocities); mesh_sample.setVelocities(V3fArraySample(velocities)); @@ -312,7 +317,7 @@ void ABCGenericMeshWriter::write_subd(HierarchyContext &context, struct Mesh *me V3fArraySample(points), Int32ArraySample(poly_verts), Int32ArraySample(loop_counts)); UVSample sample; - if (!frame_has_been_written_ && args_.export_params->uvs) { + if (args_.export_params->uvs) { const char *name = get_uv_sample(sample, m_custom_data_config, &mesh->ldata); if (!sample.indices.empty() && !sample.uvs.empty()) { @@ -329,6 +334,10 @@ void ABCGenericMeshWriter::write_subd(HierarchyContext &context, struct Mesh *me abc_subdiv_schema_.getArbGeomParams(), m_custom_data_config, &mesh->ldata, CD_MLOOPUV); } + if (args_.export_params->orcos) { + write_generated_coordinates(abc_poly_mesh_schema_.getArbGeomParams(), m_custom_data_config); + } + if (!crease_indices.empty()) { subdiv_sample.setCreaseIndices(Int32ArraySample(crease_indices)); subdiv_sample.setCreaseLengths(Int32ArraySample(crease_lengths)); diff --git a/source/blender/io/alembic/intern/abc_customdata.cc b/source/blender/io/alembic/intern/abc_customdata.cc index 66e05504303..ccf353595c9 100644 --- a/source/blender/io/alembic/intern/abc_customdata.cc +++ b/source/blender/io/alembic/intern/abc_customdata.cc @@ -22,12 +22,14 @@ */ #include "abc_customdata.h" +#include "abc_axis_conversion.h" #include <Alembic/AbcGeom/All.h> #include <algorithm> #include <unordered_map> #include "DNA_customdata_types.h" +#include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "BLI_math_base.h" @@ -50,8 +52,13 @@ using Alembic::Abc::V2fArraySample; using Alembic::AbcGeom::OC4fGeomParam; using Alembic::AbcGeom::OV2fGeomParam; +using Alembic::AbcGeom::OV3fGeomParam; namespace blender::io::alembic { +/* ORCO, Generated Coordinates, and Reference Points ("Pref") are all terms for the same thing. + * Other applications (Maya, Houdini) write these to a property called "Pref". */ +static const std::string propNameOriginalCoordinates("Pref"); + static void get_uvs(const CDStreamConfig &config, std::vector<Imath::V2f> &uvs, std::vector<uint32_t> &uvidx, @@ -222,6 +229,32 @@ static void write_mcol(const OCompoundProperty &prop, param.set(sample); } +void write_generated_coordinates(const OCompoundProperty &prop, CDStreamConfig &config) +{ + const void *customdata = CustomData_get_layer(&config.mesh->vdata, CD_ORCO); + if (customdata == nullptr) { + /* Data not available, so don't even bother creating an Alembic property for it. */ + return; + } + const float(*orcodata)[3] = static_cast<const float(*)[3]>(customdata); + + /* Convert 3D vertices from float[3] z=up to V3f y=up. */ + std::vector<Imath::V3f> coords(config.totvert); + float orco_yup[3]; + for (int vertex_idx = 0; vertex_idx < config.totvert; vertex_idx++) { + copy_yup_from_zup(orco_yup, orcodata[vertex_idx]); + coords[vertex_idx].setValue(orco_yup[0], orco_yup[1], orco_yup[2]); + } + + if (!config.abc_ocro.valid()) { + /* Create the Alembic property and keep a reference so future frames can reuse it. */ + config.abc_ocro = OV3fGeomParam(prop, propNameOriginalCoordinates, false, kVertexScope, 1); + } + + OV3fGeomParam::Sample sample(coords, kVertexScope); + config.abc_ocro.set(sample); +} + void write_custom_data(const OCompoundProperty &prop, CDStreamConfig &config, CustomData *data, @@ -263,6 +296,7 @@ using Alembic::Abc::PropertyHeader; using Alembic::AbcGeom::IC3fGeomParam; using Alembic::AbcGeom::IC4fGeomParam; using Alembic::AbcGeom::IV2fGeomParam; +using Alembic::AbcGeom::IV3fGeomParam; static void read_uvs(const CDStreamConfig &config, void *data, @@ -448,6 +482,44 @@ static void read_custom_data_uvs(const ICompoundProperty &prop, read_uvs(config, cd_data, sample.getVals(), sample.getIndices()); } +void read_generated_coordinates(const ICompoundProperty &prop, + const CDStreamConfig &config, + const Alembic::Abc::ISampleSelector &iss) +{ + if (prop.getPropertyHeader(propNameOriginalCoordinates) == nullptr) { + /* The ORCO property isn't there, so don't bother trying to process it. */ + return; + } + + IV3fGeomParam param(prop, propNameOriginalCoordinates); + if (!param.valid() || param.isIndexed()) { + /* Invalid or indexed coordinates aren't supported. */ + return; + } + if (param.getScope() != kVertexScope) { + /* These are original vertex coordinates, so must be vertex-scoped. */ + return; + } + + IV3fGeomParam::Sample sample = param.getExpandedValue(iss); + Alembic::AbcGeom::V3fArraySamplePtr abc_ocro = sample.getVals(); + const size_t totvert = abc_ocro.get()->size(); + + void *cd_data; + if (CustomData_has_layer(&config.mesh->vdata, CD_ORCO)) { + cd_data = CustomData_get_layer(&config.mesh->vdata, CD_ORCO); + } + else { + cd_data = CustomData_add_layer(&config.mesh->vdata, CD_ORCO, CD_CALLOC, nullptr, totvert); + } + + float(*orcodata)[3] = static_cast<float(*)[3]>(cd_data); + for (int vertex_idx = 0; vertex_idx < totvert; ++vertex_idx) { + const Imath::V3f &abc_coords = (*abc_ocro)[vertex_idx]; + copy_zup_from_yup(orcodata[vertex_idx], abc_coords.getValue()); + } +} + void read_custom_data(const std::string &iobject_full_name, const ICompoundProperty &prop, const CDStreamConfig &config, diff --git a/source/blender/io/alembic/intern/abc_customdata.h b/source/blender/io/alembic/intern/abc_customdata.h index 4eb515f132c..9ee964c0545 100644 --- a/source/blender/io/alembic/intern/abc_customdata.h +++ b/source/blender/io/alembic/intern/abc_customdata.h @@ -72,12 +72,16 @@ struct CDStreamConfig { const char **modifier_error_message; - /* Alembic needs Blender to keep references to C++ objects (the destructors - * finalize the writing to ABC). This map stores OV2fGeomParam objects for the - * 2nd and subsequent UV maps; the primary UV map is kept alive by the Alembic - * mesh sample itself. */ + /* Alembic needs Blender to keep references to C++ objects (the destructors finalize the writing + * to ABC). The following fields are all used to keep these references. */ + + /* Mapping from UV map name to its ABC property, for the 2nd and subsequent UV maps; the primary + * UV map is kept alive by the Alembic mesh sample itself. */ std::map<std::string, Alembic::AbcGeom::OV2fGeomParam> abc_uv_maps; + /* OCRO coordinates, aka Generated Coordinates. */ + Alembic::AbcGeom::OV3fGeomParam abc_ocro; + CDStreamConfig() : mloop(NULL), totloop(0), @@ -102,6 +106,12 @@ struct CDStreamConfig { * For now the active layer is used, maybe needs a better way to choose this. */ const char *get_uv_sample(UVSample &sample, const CDStreamConfig &config, CustomData *data); +void write_generated_coordinates(const OCompoundProperty &prop, CDStreamConfig &config); + +void read_generated_coordinates(const ICompoundProperty &prop, + const CDStreamConfig &config, + const Alembic::Abc::ISampleSelector &iss); + void write_custom_data(const OCompoundProperty &prop, CDStreamConfig &config, CustomData *data, diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.cc b/source/blender/io/alembic/intern/abc_reader_mesh.cc index 8133f615080..11b6c1c18ca 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.cc +++ b/source/blender/io/alembic/intern/abc_reader_mesh.cc @@ -439,6 +439,7 @@ static void read_mesh_sample(const std::string &iobject_full_name, if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0) { read_mverts(config, abc_mesh_data); + read_generated_coordinates(schema.getArbGeomParams(), config, selector); } if ((settings->read_flag & MOD_MESHSEQ_READ_POLY) != 0) { @@ -558,7 +559,7 @@ void AbcMeshReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelec /* XXX fixme after 2.80; mesh->flag isn't copied by BKE_mesh_nomain_to_mesh() */ /* read_mesh can be freed by BKE_mesh_nomain_to_mesh(), so get the flag before that happens. */ short autosmooth = (read_mesh->flag & ME_AUTOSMOOTH); - BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_MESH, true); + BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_EVERYTHING, true); mesh->flag |= autosmooth; } @@ -868,7 +869,7 @@ void AbcSubDReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelec Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, nullptr); if (read_mesh != mesh) { - BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_MESH, true); + BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_EVERYTHING, true); } ISubDSchema::Sample sample; diff --git a/source/blender/io/collada/Materials.cpp b/source/blender/io/collada/Materials.cpp index 194a9ea397e..ac4c65464c8 100644 --- a/source/blender/io/collada/Materials.cpp +++ b/source/blender/io/collada/Materials.cpp @@ -133,6 +133,19 @@ void MaterialNode::add_link(bNode *from_node, int from_index, bNode *to_node, in nodeAddLink(ntree, from_node, from_socket, to_node, to_socket); } +void MaterialNode::add_link(bNode *from_node, + const char *from_label, + bNode *to_node, + const char *to_label) +{ + bNodeSocket *from_socket = nodeFindSocket(from_node, SOCK_OUT, from_label); + bNodeSocket *to_socket = nodeFindSocket(to_node, SOCK_IN, to_label); + + if (from_socket && to_socket) { + nodeAddLink(ntree, from_node, from_socket, to_node, to_socket); + } +} + void MaterialNode::set_reflectivity(COLLADAFW::FloatOrParam &val) { float reflectivity = val.getFloatValue(); @@ -218,22 +231,32 @@ void MaterialNode::set_alpha(COLLADAFW::EffectCommon::OpaqueMode mode, void MaterialNode::set_diffuse(COLLADAFW::ColorOrTexture &cot) { int locy = -300 * (node_map.size() - 2); - if (cot.isColor()) { - COLLADAFW::Color col = cot.getColor(); - bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "Base Color"); - float *fcol = (float *)socket->default_value; - fcol[0] = material->r = col.getRed(); - fcol[1] = material->g = col.getGreen(); - fcol[2] = material->b = col.getBlue(); - fcol[3] = material->a = col.getAlpha(); - } - else if (cot.isTexture()) { + if (cot.isTexture()) { bNode *texture_node = add_texture_node(cot, -300, locy, "Base Color"); if (texture_node != nullptr) { add_link(texture_node, 0, shader_node, 0); } } + else { + bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "Base Color"); + float *fcol = (float *)socket->default_value; + + if (cot.isColor()) { + COLLADAFW::Color col = cot.getColor(); + fcol[0] = material->r = col.getRed(); + fcol[1] = material->g = col.getGreen(); + fcol[2] = material->b = col.getBlue(); + fcol[3] = material->a = col.getAlpha(); + } + else { + /* no diffuse term = same as black */ + fcol[0] = material->r = 0.0f; + fcol[1] = material->g = 0.0f; + fcol[2] = material->b = 0.0f; + fcol[3] = material->a = 1.0f; + } + } } Image *MaterialNode::get_diffuse_image() @@ -326,7 +349,7 @@ void MaterialNode::set_emission(COLLADAFW::ColorOrTexture &cot) else if (cot.isTexture()) { bNode *texture_node = add_texture_node(cot, -300, locy, "Emission"); if (texture_node != nullptr) { - add_link(texture_node, 0, shader_node, 0); + add_link(texture_node, "Color", shader_node, "Emission"); } } @@ -363,18 +386,38 @@ void MaterialNode::set_opacity(COLLADAFW::ColorOrTexture &cot) void MaterialNode::set_specular(COLLADAFW::ColorOrTexture &cot) { + bool has_specularity = true; int locy = -300 * (node_map.size() - 2); if (cot.isColor()) { COLLADAFW::Color col = cot.getColor(); - bNode *node = add_node(SH_NODE_RGB, -300, locy, "Specular"); - set_color(node, col); - /* TODO: Connect node */ + + if (col.getRed() == 0 && col.getGreen() == 0 && col.getBlue() == 0) { + has_specularity = false; + } + else { + bNode *node = add_node(SH_NODE_RGB, -300, locy, "Specular"); + set_color(node, col); + /* TODO: Connect node */ + } } - /* texture */ else if (cot.isTexture()) { add_texture_node(cot, -300, locy, "Specular"); /* TODO: Connect node */ } + else { + /* no specular term) */ + has_specularity = false; + } + + if (!has_specularity) { + /* If specularity is black or not defined reset the Specular value to 0 + TODO: This is a solution only for a corner case. We must find a better + way to handle specularity in general. Also note that currently we + do not export specularity values, see EffectExporter::operator() + */ + bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "Specular"); + ((bNodeSocketValueFloat *)socket->default_value)->value = 0.0f; + } } bNode *MaterialNode::add_texture_node(COLLADAFW::ColorOrTexture &cot, diff --git a/source/blender/io/collada/Materials.h b/source/blender/io/collada/Materials.h index 2d8c823a4c2..1886acb7a6a 100644 --- a/source/blender/io/collada/Materials.h +++ b/source/blender/io/collada/Materials.h @@ -48,6 +48,7 @@ class MaterialNode { bNodeTree *prepare_material_nodetree(); bNode *add_node(int node_type, int locx, int locy, std::string label); void add_link(bNode *from_node, int from_index, bNode *to_node, int to_index); + void add_link(bNode *from_node, const char *from_label, bNode *to_node, const char *to_label); bNode *add_texture_node(COLLADAFW::ColorOrTexture &cot, int locx, int locy, std::string label); void setShaderType(); diff --git a/source/blender/io/gpencil/intern/gpencil_io_base.cc b/source/blender/io/gpencil/intern/gpencil_io_base.cc index e79a2bc98ff..a2c1b8f5af6 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_base.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_base.cc @@ -41,6 +41,7 @@ #include "BKE_gpencil_geom.h" #include "BKE_main.h" #include "BKE_material.h" +#include "BKE_scene.h" #include "UI_view2d.h" @@ -69,18 +70,21 @@ GpencilIO::GpencilIO(const GpencilIOParams *iparams) cfra_ = iparams->frame_cur; /* Calculate camera matrix. */ - prepare_camera_params(iparams); + prepare_camera_params(scene_, iparams); } -void GpencilIO::prepare_camera_params(const GpencilIOParams *iparams) +void GpencilIO::prepare_camera_params(Scene *scene, const GpencilIOParams *iparams) { params_ = *iparams; const bool is_pdf = params_.mode == GP_EXPORT_TO_PDF; const bool any_camera = (params_.v3d->camera != nullptr); const bool force_camera_view = is_pdf && any_camera; + /* Ensure camera switch is applied. */ + BKE_scene_camera_switch_update(scene); + /* Calculate camera matrix. */ - Object *cam_ob = params_.v3d->camera; + Object *cam_ob = scene->camera; if (cam_ob != nullptr) { /* Set up parameters. */ CameraParams params; diff --git a/source/blender/io/gpencil/intern/gpencil_io_base.hh b/source/blender/io/gpencil/intern/gpencil_io_base.hh index 2e1e1707c78..c3c6f1156bb 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_base.hh +++ b/source/blender/io/gpencil/intern/gpencil_io_base.hh @@ -50,7 +50,7 @@ class GpencilIO { GpencilIO(const GpencilIOParams *iparams); void frame_number_set(const int value); - void prepare_camera_params(const GpencilIOParams *iparams); + void prepare_camera_params(Scene *scene, const GpencilIOParams *iparams); protected: GpencilIOParams params_; diff --git a/source/blender/io/gpencil/intern/gpencil_io_capi.cc b/source/blender/io/gpencil/intern/gpencil_io_capi.cc index 8093ec3c52d..544c51e0b4f 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_capi.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_capi.cc @@ -121,7 +121,7 @@ static bool gpencil_io_export_pdf(Depsgraph *depsgraph, CFRA = i; BKE_scene_graph_update_for_newframe(depsgraph); - exporter->prepare_camera_params(iparams); + exporter->prepare_camera_params(scene, iparams); exporter->frame_number_set(i); exporter->add_newpage(); exporter->add_body(); @@ -130,10 +130,11 @@ static bool gpencil_io_export_pdf(Depsgraph *depsgraph, /* Back to original frame. */ exporter->frame_number_set(iparams->frame_cur); CFRA = iparams->frame_cur; + BKE_scene_camera_switch_update(scene); BKE_scene_graph_update_for_newframe(depsgraph); } else { - exporter->prepare_camera_params(iparams); + exporter->prepare_camera_params(scene, iparams); exporter->add_newpage(); exporter->add_body(); result = exporter->write(); @@ -146,6 +147,7 @@ static bool gpencil_io_export_pdf(Depsgraph *depsgraph, /* Export current frame in SVG. */ #ifdef WITH_PUGIXML static bool gpencil_io_export_frame_svg(GpencilExporterSVG *exporter, + Scene *scene, const GpencilIOParams *iparams, const bool newpage, const bool body, @@ -153,7 +155,7 @@ static bool gpencil_io_export_frame_svg(GpencilExporterSVG *exporter, { bool result = false; exporter->frame_number_set(iparams->frame_cur); - exporter->prepare_camera_params(iparams); + exporter->prepare_camera_params(scene, iparams); if (newpage) { result |= exporter->add_newpage(); @@ -189,7 +191,7 @@ bool gpencil_io_export(const char *filename, GpencilIOParams *iparams) #ifdef WITH_PUGIXML case GP_EXPORT_TO_SVG: { GpencilExporterSVG exporter = GpencilExporterSVG(filename, iparams); - return gpencil_io_export_frame_svg(&exporter, iparams, true, true, true); + return gpencil_io_export_frame_svg(&exporter, scene_, iparams, true, true, true); break; } #endif diff --git a/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc b/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc index dc76b6ed661..73b3e46959b 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc @@ -102,7 +102,7 @@ bool GpencilImporterSVG::read() bGPDlayer *gpl = (bGPDlayer *)BLI_findstring( &gpd_->layers, layer_id, offsetof(bGPDlayer, info)); if (gpl == nullptr) { - gpl = BKE_gpencil_layer_addnew(gpd_, layer_id, true); + gpl = BKE_gpencil_layer_addnew(gpd_, layer_id, true, false); /* Disable lights. */ gpl->flag &= ~GP_LAYER_USE_LIGHTS; } diff --git a/source/blender/io/gpencil/nanosvg/nanosvg.h b/source/blender/io/gpencil/nanosvg/nanosvg.h index 6db32e9f40a..94dad37861a 100644 --- a/source/blender/io/gpencil/nanosvg/nanosvg.h +++ b/source/blender/io/gpencil/nanosvg/nanosvg.h @@ -1289,7 +1289,7 @@ static const char *nsvg__parseNumber(const char *s, char *it, const int size) return s; } -static const char *nsvg__getNextPathItem(const char *s, char *it) +static const char *nsvg__getNextPathItem(const char *s, char *it, char cmd, int nargs) { it[0] = '\0'; // Skip white spaces and commas @@ -1297,6 +1297,15 @@ static const char *nsvg__getNextPathItem(const char *s, char *it) s++; if (!*s) return s; + + /* Blender: Special case for arc command's 4th and 5th arguments. */ + if (ELEM(cmd, 'a', 'A') && ELEM(nargs, 3, 4)) { + it[0] = s[0]; + it[1] = '\0'; + s++; + return s; + } + if (*s == '-' || *s == '+' || *s == '.' || nsvg__isdigit(*s)) { s = nsvg__parseNumber(s, it, 64); } @@ -1576,8 +1585,8 @@ static int nsvg__isCoordinate(const char *s) // optional sign if (*s == '-' || *s == '+') s++; - // must have at least one digit - return nsvg__isdigit(*s); + // must have at least one digit, or start by a dot + return (nsvg__isdigit(*s) || *s == '.'); } static NSVGcoordinate nsvg__parseCoordinateRaw(const char *str) @@ -2353,7 +2362,12 @@ static void nsvg__pathArcTo(NSVGparser *p, float *cpx, float *cpy, float *args, // The loop assumes an iteration per end point (including start and end), this +1. ndivs = (int)(fabsf(da) / (NSVG_PI * 0.5f) + 1.0f); hda = (da / (float)ndivs) / 2.0f; - kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda)); + // Fix for ticket #179: division by 0: avoid cotangens around 0 (infinite) + if ((hda < 1e-3f) && (hda > -1e-3f)) + hda *= 0.5f; + else + hda = (1.0f - cosf(hda)) / sinf(hda); + kappa = fabsf(4.0f / 3.0f * hda); if (da < 0.0f) kappa = -kappa; @@ -2413,7 +2427,7 @@ static void nsvg__parsePath(NSVGparser *p, const char **attr) nargs = 0; while (*s) { - s = nsvg__getNextPathItem(s, item); + s = nsvg__getNextPathItem(s, item, cmd, nargs); if (!*item) break; if (cmd != '\0' && nsvg__isCoordinate(item)) { @@ -2740,7 +2754,7 @@ static void nsvg__parsePoly(NSVGparser *p, const char **attr, int closeFlag) s = attr[i + 1]; nargs = 0; while (*s) { - s = nsvg__getNextPathItem(s, item); + s = nsvg__getNextPathItem(s, item, '\0', nargs); args[nargs++] = (float)nsvg__atof(item); if (nargs >= 2) { if (npts == 0) diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index 8bf9afafa1b..dd262f78f6b 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -366,7 +366,7 @@ typedef struct Library { struct PackedFile *packedfile; - /* Temp data needed by read/write code. */ + /* Temp data needed by read/write code, and liboverride recursive resync. */ int temp_index; /** See BLENDER_FILE_VERSION, BLENDER_FILE_SUBVERSION, needed for do_versions. */ short versionfile, subversionfile; diff --git a/source/blender/makesdna/DNA_action_types.h b/source/blender/makesdna/DNA_action_types.h index 5cc525a6cff..583e56de9c2 100644 --- a/source/blender/makesdna/DNA_action_types.h +++ b/source/blender/makesdna/DNA_action_types.h @@ -265,9 +265,10 @@ typedef struct bPoseChannel { * since the alternative is highly complicated - campbell */ struct bPoseChannel *custom_tx; - float custom_scale; - - char _pad1[4]; + float custom_scale; /* Deprecated */ + float custom_scale_xyz[3]; + float custom_translation[3]; + float custom_rotation_euler[3]; /** Transforms - written in by actions or transform. */ float loc[3]; @@ -417,9 +418,9 @@ typedef enum ePchan_DrawFlag { PCHAN_DRAW_NO_CUSTOM_BONE_SIZE = (1 << 0), } ePchan_DrawFlag; -#define PCHAN_CUSTOM_DRAW_SIZE(pchan) \ - (pchan)->custom_scale *( \ - ((pchan)->drawflag & PCHAN_DRAW_NO_CUSTOM_BONE_SIZE) ? 1.0f : (pchan)->bone->length) +/* Note: It doesn't take custom_scale_xyz into account */ +#define PCHAN_CUSTOM_BONE_LENGTH(pchan) \ + (((pchan)->drawflag & PCHAN_DRAW_NO_CUSTOM_BONE_SIZE) ? 1.0f : (pchan)->bone->length) #ifdef DNA_DEPRECATED_ALLOW /* PoseChannel->bboneflag */ diff --git a/source/blender/makesdna/DNA_boid_types.h b/source/blender/makesdna/DNA_boid_types.h index 882d4eb1b3b..540446ccd9d 100644 --- a/source/blender/makesdna/DNA_boid_types.h +++ b/source/blender/makesdna/DNA_boid_types.h @@ -97,7 +97,8 @@ typedef struct BoidRuleFollowLeader { } BoidRuleFollowLeader; typedef struct BoidRuleAverageSpeed { BoidRule rule; - float wander, level, speed, rt; + float wander, level, speed; + char _pad0[4]; } BoidRuleAverageSpeed; typedef struct BoidRuleFight { BoidRule rule; @@ -178,7 +179,7 @@ typedef struct BoidState { //} BoidSignal; // typedef struct BoidSignalDefine { // struct BoidSignalDefine *next, *prev; -// int id, rt; +// int id, _pad[4]; // char name[32]; //} BoidSignalDefine; diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h index a11e7d77c67..986c009ac26 100644 --- a/source/blender/makesdna/DNA_brush_types.h +++ b/source/blender/makesdna/DNA_brush_types.h @@ -133,7 +133,8 @@ typedef struct BrushGpencilSettings { /** Factor to extend stroke extremes using fill tool. */ float fill_extend_fac; - char _pad3[4]; + /** Number of pixels to dilate fill area. */ + int dilate_pixels; struct CurveMapping *curve_sensitivity; struct CurveMapping *curve_strength; diff --git a/source/blender/makesdna/DNA_curve_types.h b/source/blender/makesdna/DNA_curve_types.h index 716c480bab8..f6242679808 100644 --- a/source/blender/makesdna/DNA_curve_types.h +++ b/source/blender/makesdna/DNA_curve_types.h @@ -43,6 +43,7 @@ struct Key; struct Material; struct Object; struct VFont; +struct CurveEval; /* These two Lines with # tell makesdna this struct can be excluded. */ # @@ -300,6 +301,12 @@ typedef struct Curve { char _pad2[6]; float fsize_realtime; + /** + * A pointer to curve data from geometry nodes, currently only set for evaluated + * objects by the dependency graph iterator, and owned by #geometry_set_eval. + */ + struct CurveEval *curve_eval; + void *batch_cache; } Curve; diff --git a/source/blender/makesdna/DNA_effect_types.h b/source/blender/makesdna/DNA_effect_types.h index 33f2e1b47c0..307a212a139 100644 --- a/source/blender/makesdna/DNA_effect_types.h +++ b/source/blender/makesdna/DNA_effect_types.h @@ -71,13 +71,15 @@ extern "C" { typedef struct Effect { struct Effect *next, *prev; - short type, flag, buttype, rt; + short type, flag, buttype; + char _pad0[2]; } Effect; typedef struct BuildEff { struct BuildEff *next, *prev; - short type, flag, buttype, rt; + short type, flag, buttype; + char _pad0[2]; float len, sfra; @@ -88,7 +90,8 @@ typedef struct BuildEff { typedef struct Particle { float co[3], no[3]; float time, lifetime; - short mat_nr, rt; + short mat_nr; + char _pad0[2]; } Particle; struct Collection; diff --git a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h index ea03789ddab..a4ab38f6022 100644 --- a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h +++ b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h @@ -184,6 +184,8 @@ .layer_pass = 0, \ .hardeness = 1.0f, \ .curve_intensity = NULL, \ + .fading_end = 10.0f, \ + .fading_end_factor = 0.2f, \ } #define _DNA_DEFAULT_SimplifyGpencilModifierData \ @@ -251,6 +253,8 @@ .thickness_fac = 1.0f, \ .thickness = 30, \ .layer_pass = 0, \ + .fading_end = 10.0f, \ + .fading_end_factor = 0.2f, \ } #define _DNA_DEFAULT_TimeGpencilModifierData \ @@ -295,4 +299,14 @@ .chaining_image_threshold = 0.001f, \ } +#define _DNA_DEFAULT_LengthGpencilModifierData \ + { \ + .start_fac = 0.1f,\ + .end_fac = 0.1f,\ + .overshoot_fac = 0.01f,\ + .pass_index = 0,\ + .material = NULL,\ + } + + /* clang-format off */ diff --git a/source/blender/makesdna/DNA_gpencil_modifier_types.h b/source/blender/makesdna/DNA_gpencil_modifier_types.h index 8d7b3896ef9..410212ce100 100644 --- a/source/blender/makesdna/DNA_gpencil_modifier_types.h +++ b/source/blender/makesdna/DNA_gpencil_modifier_types.h @@ -54,6 +54,7 @@ typedef enum GpencilModifierType { eGpencilModifierType_Multiply = 17, eGpencilModifierType_Texture = 18, eGpencilModifierType_Lineart = 19, + eGpencilModifierType_Length = 20, /* Keep last. */ NUM_GREASEPENCIL_MODIFIER_TYPES, } GpencilModifierType; @@ -187,7 +188,12 @@ typedef struct ThickGpencilModifierData { int thickness; /** Custom index for passes. */ int layer_pass; - char _pad[4]; + /** Start/end distances of the fading effect. */ + float fading_start; + float fading_end; + float fading_end_factor; + /** Fading reference object */ + struct Object *object; struct CurveMapping *curve_thickness; } ThickGpencilModifierData; @@ -199,6 +205,7 @@ typedef enum eThickGpencil_Flag { GP_THICK_NORMALIZE = (1 << 4), GP_THICK_INVERT_LAYERPASS = (1 << 5), GP_THICK_INVERT_MATERIAL = (1 << 6), + GP_THICK_FADING = (1 << 7), } eThickGpencil_Flag; typedef struct TimeGpencilModifierData { @@ -291,9 +298,16 @@ typedef struct OpacityGpencilModifierData { int flag; /** Main Opacity factor. */ float factor; + /** Fading controlling object */ + int _pad0; + struct Object *object; + /** Start/end distances of the fading effect. */ + float fading_start; + float fading_end; + float fading_end_factor; /** Modify stroke, fill or both. */ char modify_color; - char _pad[3]; + char _pad1[3]; /** Custom index for passes. */ int layer_pass; @@ -309,6 +323,7 @@ typedef enum eOpacityGpencil_Flag { GP_OPACITY_INVERT_MATERIAL = (1 << 5), GP_OPACITY_CUSTOM_CURVE = (1 << 6), GP_OPACITY_NORMALIZE = (1 << 7), + GP_OPACITY_FADING = (1 << 8), } eOpacityGpencil_Flag; typedef struct ArrayGpencilModifierData { @@ -470,6 +485,39 @@ typedef enum eLatticeGpencil_Flag { GP_LATTICE_INVERT_MATERIAL = (1 << 4), } eLatticeGpencil_Flag; +typedef struct LengthGpencilModifierData { + GpencilModifierData modifier; + /** Material for filtering. */ + struct Material *material; + /** Layer name. */ + char layername[64]; + /** Custom index for passes. */ + int pass_index; + /** Flags. */ + int flag; + /** Custom index for passes. */ + int layer_pass; + /** Length. */ + float start_fac, end_fac; + /** Overshoot trajectory factor. */ + float overshoot_fac; + /** Modifier mode. */ + int mode; + char _pad[4]; +} LengthGpencilModifierData; + +typedef enum eLengthGpencil_Flag { + GP_LENGTH_INVERT_LAYER = (1 << 0), + GP_LENGTH_INVERT_PASS = (1 << 1), + GP_LENGTH_INVERT_LAYERPASS = (1 << 2), + GP_LENGTH_INVERT_MATERIAL = (1 << 3), +} eLengthGpencil_Flag; + +typedef enum eLengthGpencil_Type { + GP_LENGTH_RELATIVE = 0, + GP_LENGTH_ABSOLUTE = 1, +} eLengthGpencil_Type; + typedef struct MirrorGpencilModifierData { GpencilModifierData modifier; struct Object *object; @@ -616,6 +664,14 @@ typedef struct OffsetGpencilModifierData { float loc[3]; float rot[3]; float scale[3]; + /** Random Offset. */ + float rnd_offset[3]; + /** Random Rotation. */ + float rnd_rot[3]; + /** Random Scales. */ + float rnd_scale[3]; + /** (first element is the index) random values. */ + int seed; /** Custom index for passes. */ int layer_pass; } OffsetGpencilModifierData; @@ -626,6 +682,7 @@ typedef enum eOffsetGpencil_Flag { GP_OFFSET_INVERT_VGROUP = (1 << 2), GP_OFFSET_INVERT_LAYERPASS = (1 << 3), GP_OFFSET_INVERT_MATERIAL = (1 << 4), + GP_OFFSET_UNIFORM_RANDOM_SCALE = (1 << 5), } eOffsetGpencil_Flag; typedef struct SmoothGpencilModifierData { diff --git a/source/blender/makesdna/DNA_gpencil_types.h b/source/blender/makesdna/DNA_gpencil_types.h index 8facdca2f9c..ea3c1ff7275 100644 --- a/source/blender/makesdna/DNA_gpencil_types.h +++ b/source/blender/makesdna/DNA_gpencil_types.h @@ -47,7 +47,7 @@ struct Curve; #define GP_DEFAULT_CURVE_EDIT_CORNER_ANGLE M_PI_2 #define GPENCIL_MIN_FILL_FAC 0.05f -#define GPENCIL_MAX_FILL_FAC 5.0f +#define GPENCIL_MAX_FILL_FAC 8.0f /* ***************************************** */ /* GP Stroke Points */ @@ -112,6 +112,8 @@ typedef enum eGPDspoint_Flag { GP_SPOINT_TAG = (1 << 1), /* stroke point is temp tagged (for some editing operation) */ GP_SPOINT_TEMP_TAG = (1 << 2), + /* stroke point is temp tagged (for some editing operation) */ + GP_SPOINT_TEMP_TAG2 = (1 << 3), } eGPSPoint_Flag; /* ***************************************** */ @@ -558,6 +560,8 @@ typedef enum eGPDlayer_Flag { GP_LAYER_USE_MASK = (1 << 13), /*TODO: DEPRECATED */ /* Ruler Layer */ GP_LAYER_IS_RULER = (1 << 14), + /* Disable masks in viewlayer render */ + GP_LAYER_DISABLE_MASKS_IN_VIEWLAYER = (1 << 15), } eGPDlayer_Flag; /** #bGPDlayer.onion_flag */ diff --git a/source/blender/makesdna/DNA_ipo_types.h b/source/blender/makesdna/DNA_ipo_types.h index 8bb94976414..c5e207c4e20 100644 --- a/source/blender/makesdna/DNA_ipo_types.h +++ b/source/blender/makesdna/DNA_ipo_types.h @@ -77,8 +77,9 @@ typedef struct IpoCurve { short totvert; /** Interpolation and extrapolation modes . */ short ipo, extrap; - /** Flag= settings; rt= ???. */ - short flag, rt; + /** Flag= settings. */ + short flag; + char _pad0[2]; /** Minimum/maximum y-extents for curve. */ float ymin, ymax; /** ???. */ diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 282d71f6a87..58c94b6f369 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -45,6 +45,8 @@ struct bNodePreview; struct bNodeTreeExec; struct bNodeType; struct uiBlock; +struct Tex; +struct Material; #define NODE_MAXSTR 64 @@ -165,6 +167,8 @@ typedef enum eNodeSocketDatatype { SOCK_IMAGE = 9, SOCK_GEOMETRY = 10, SOCK_COLLECTION = 11, + SOCK_TEXTURE = 12, + SOCK_MATERIAL = 13, } eNodeSocketDatatype; /* socket shape */ @@ -593,6 +597,14 @@ typedef struct bNodeSocketValueCollection { struct Collection *value; } bNodeSocketValueCollection; +typedef struct bNodeSocketValueTexture { + struct Tex *value; +} bNodeSocketValueTexture; + +typedef struct bNodeSocketValueMaterial { + struct Material *value; +} bNodeSocketValueMaterial; + /* data structs, for node->storage */ enum { CMP_NODE_MASKTYPE_ADD = 0, @@ -1184,10 +1196,31 @@ typedef struct NodeAttributeVectorMath { uint8_t input_type_c; } NodeAttributeVectorMath; +typedef struct NodeAttributeVectorRotate { + /* GeometryNodeAttributeVectorRotateMode */ + uint8_t mode; + + /* GeometryNodeAttributeInputMode */ + uint8_t input_type_vector; + uint8_t input_type_center; + uint8_t input_type_axis; + uint8_t input_type_angle; + uint8_t input_type_rotation; + char _pad[2]; +} NodeAttributeVectorRotate; + typedef struct NodeAttributeColorRamp { ColorBand color_ramp; } NodeAttributeColorRamp; +typedef struct NodeAttributeCurveMap { + /* CustomDataType. */ + uint8_t data_type; + char _pad[7]; + CurveMapping *curve_vec; + CurveMapping *curve_rgb; +} NodeAttributeCurveMap; + typedef struct NodeInputVector { float vector[3]; } NodeInputVector; @@ -1280,10 +1313,9 @@ typedef struct NodeAttributeSeparateXYZ { typedef struct NodeAttributeConvert { /* CustomDataType. */ - uint8_t data_type; - char _pad[1]; + int8_t data_type; /* AttributeDomain. */ - int16_t domain; + int8_t domain; } NodeAttributeConvert; typedef struct NodeGeometryMeshCircle { @@ -1308,6 +1340,23 @@ typedef struct NodeGeometryMeshLine { uint8_t count_mode; } NodeGeometryMeshLine; +typedef struct NodeSwitch { + /* NodeSwitch. */ + uint8_t input_type; +} NodeSwitch; + +typedef struct NodeGeometryCurveResample { + /* GeometryNodeCurveSampleMode. */ + uint8_t mode; +} NodeGeometryCurveResample; + +typedef struct NodeGeometryAttributeTransfer { + /* AttributeDomain. */ + int8_t domain; + /* GeometryNodeAttributeTransferMapMode. */ + uint8_t mapping; +} NodeGeometryAttributeTransfer; + /* script node mode */ #define NODE_SCRIPT_INTERNAL 0 #define NODE_SCRIPT_EXTERNAL 1 @@ -1751,6 +1800,14 @@ typedef enum GeometryNodeRotatePointsType { GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE = 1, } GeometryNodeRotatePointsType; +typedef enum GeometryNodeAttributeVectorRotateMode { + GEO_NODE_VECTOR_ROTATE_TYPE_AXIS = 0, + GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_X = 1, + GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_Y = 2, + GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_Z = 3, + GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ = 4, +} GeometryNodeAttributeVectorRotateMode; + typedef enum GeometryNodeAttributeRandomizeMode { GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE = 0, GEO_NODE_ATTRIBUTE_RANDOMIZE_ADD = 1, @@ -1802,6 +1859,16 @@ typedef enum GeometryNodeMeshLineCountMode { GEO_NODE_MESH_LINE_COUNT_RESOLUTION = 1, } GeometryNodeMeshLineCountMode; +typedef enum GeometryNodeCurveSampleMode { + GEO_NODE_CURVE_SAMPLE_COUNT = 0, + GEO_NODE_CURVE_SAMPLE_LENGTH = 1, +} GeometryNodeCurveSampleMode; + +typedef enum GeometryNodeAttributeTransferMapMode { + GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED = 0, + GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST = 1, +} GeometryNodeAttributeTransferMapMode; + #ifdef __cplusplus } #endif diff --git a/source/blender/makesdna/DNA_object_force_types.h b/source/blender/makesdna/DNA_object_force_types.h index 37013f5b4d6..3d8418fb734 100644 --- a/source/blender/makesdna/DNA_object_force_types.h +++ b/source/blender/makesdna/DNA_object_force_types.h @@ -182,8 +182,8 @@ typedef struct EffectorWeights { /** Effector type specific weights. */ float weight[14]; float global_gravity; - short flag, rt[3]; - char _pad[4]; + short flag; + char _pad[2]; } EffectorWeights; /* EffectorWeights->flag */ @@ -267,10 +267,9 @@ typedef struct SoftBody { char namedVG_Spring_K[64]; /* baking */ - int sfra, efra; - int interval; + char _pad1[6]; /** Local==1: use local coords for baking. */ - short local, solverflags; + char local, solverflags; /* -- these must be kept for backwards compatibility -- */ /** Array of size totpointkey. */ diff --git a/source/blender/makesdna/DNA_particle_types.h b/source/blender/makesdna/DNA_particle_types.h index cc40e26b92b..30b1fbe09d3 100644 --- a/source/blender/makesdna/DNA_particle_types.h +++ b/source/blender/makesdna/DNA_particle_types.h @@ -64,7 +64,7 @@ typedef struct BoidParticle { struct BoidData data; float gravity[3]; float wander[3]; - float rt; + char _pad0[4]; } BoidParticle; typedef struct ParticleSpring { @@ -82,7 +82,7 @@ typedef struct ChildParticle { float w[4]; /** Face vertex weights and offset. */ float fuv[4], foffset; - float rt; + char _pad0[4]; } ChildParticle; typedef struct ParticleTarget { @@ -99,7 +99,8 @@ typedef struct ParticleDupliWeight { short count; short flag; /** Only updated on file save and used on file load. */ - short index, rt; + short index; + char _pad0[2]; } ParticleDupliWeight; typedef struct ParticleData { @@ -191,7 +192,8 @@ typedef struct ParticleSettings { struct EffectorWeights *effector_weights; struct Collection *collision_group; - int flag, rt; + int flag; + char _pad1[4]; short type, from, distr, texact; /* physics modes */ short phystype, rotmode, avemode, reactevent; diff --git a/source/blender/makesdna/DNA_pointcache_types.h b/source/blender/makesdna/DNA_pointcache_types.h index de2fa3f10fe..ad5f386bf2b 100644 --- a/source/blender/makesdna/DNA_pointcache_types.h +++ b/source/blender/makesdna/DNA_pointcache_types.h @@ -107,7 +107,8 @@ typedef struct PointCache { int totpoint; /** Modifier stack index. */ int index; - short compression, rt; + short compression; + char _pad0[2]; char name[64]; char prev_name[64]; diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index c7f7e610a1a..0b07b8271a5 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -952,7 +952,8 @@ typedef struct ParticleEditSettings { /** Runtime. */ void *paintcursor; - float emitterdist, rt; + float emitterdist; + char _pad0[4]; int selectmode; int edittype; @@ -1550,7 +1551,8 @@ typedef struct UnitSettings { typedef struct PhysicsSettings { float gravity[3]; - int flag, quick_cache_step, rt; + int flag, quick_cache_step; + char _pad0[4]; } PhysicsSettings; /* ------------------------------------------- */ diff --git a/source/blender/makesdna/DNA_sequence_types.h b/source/blender/makesdna/DNA_sequence_types.h index fa11a7dfd13..4b95dd41b30 100644 --- a/source/blender/makesdna/DNA_sequence_types.h +++ b/source/blender/makesdna/DNA_sequence_types.h @@ -517,11 +517,11 @@ enum { SEQ_AUDIO_PAN_ANIMATED = (1 << 26), SEQ_AUDIO_DRAW_WAVEFORM = (1 << 27), - /* don't include Grease Pencil in OpenGL previews of Scene strips */ - SEQ_SCENE_NO_GPENCIL = (1 << 28), + /* don't include Annotations in OpenGL previews of Scene strips */ + SEQ_SCENE_NO_ANNOTATION = (1 << 28), SEQ_USE_VIEWS = (1 << 29), - /* access scene strips directly (like a metastrip) */ + /* Access scene strips directly (like a meta-strip). */ SEQ_SCENE_STRIPS = (1 << 30), SEQ_INVALID_EFFECT = (1u << 31), diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index ce3875dfbc7..9f3576a2cbe 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -1909,7 +1909,7 @@ typedef struct SpaceSpreadsheet { /** * List of #SpreadsheetContext. * This is a path to the data that is displayed in the spreadsheet. - * It can be set explicitely by an action of the user (e.g. clicking the preview icon in a + * It can be set explicitly by an action of the user (e.g. clicking the preview icon in a * geometry node) or it can be derived from context automatically based on some heuristic. */ ListBase context_path; diff --git a/source/blender/makesdna/DNA_texture_types.h b/source/blender/makesdna/DNA_texture_types.h index 5381c524e4d..60c255e8637 100644 --- a/source/blender/makesdna/DNA_texture_types.h +++ b/source/blender/makesdna/DNA_texture_types.h @@ -57,7 +57,8 @@ typedef struct MTex { short colormodel, pmapto, pmaptoneg; short normapspace, which_output; float r, g, b, k; - float def_var, rt; + float def_var; + char _pad1[4]; /* common */ float colfac, varfac; diff --git a/source/blender/makesdna/DNA_view3d_defaults.h b/source/blender/makesdna/DNA_view3d_defaults.h index 10d0bafec61..9dfc37e57b1 100644 --- a/source/blender/makesdna/DNA_view3d_defaults.h +++ b/source/blender/makesdna/DNA_view3d_defaults.h @@ -70,6 +70,7 @@ \ .gpencil_paper_opacity = 0.5f, \ .gpencil_grid_opacity = 0.9f, \ + .gpencil_vertex_paint_opacity = 1.0f, \ } #define _DNA_DEFAULT_View3DCursor \ diff --git a/source/blender/makesdna/DNA_xr_types.h b/source/blender/makesdna/DNA_xr_types.h index 2ce32a723a7..8e63760fef7 100644 --- a/source/blender/makesdna/DNA_xr_types.h +++ b/source/blender/makesdna/DNA_xr_types.h @@ -58,6 +58,21 @@ typedef enum eXRSessionBasePoseType { XR_BASE_POSE_CUSTOM = 2, } eXRSessionBasePoseType; +/** XR action type. Enum values match those in GHOST_XrActionType enum for consistency. */ +typedef enum eXrActionType { + XR_BOOLEAN_INPUT = 1, + XR_FLOAT_INPUT = 2, + XR_VECTOR2F_INPUT = 3, + XR_POSE_INPUT = 4, + XR_VIBRATION_OUTPUT = 100, +} eXrActionType; + +typedef enum eXrOpFlag { + XR_OP_PRESS = 0, + XR_OP_RELEASE = 1, + XR_OP_MODAL = 2, +} eXrOpFlag; + #ifdef __cplusplus } #endif diff --git a/source/blender/makesdna/intern/dna_defaults.c b/source/blender/makesdna/intern/dna_defaults.c index 95272fb7804..2d55ea05867 100644 --- a/source/blender/makesdna/intern/dna_defaults.c +++ b/source/blender/makesdna/intern/dna_defaults.c @@ -316,6 +316,7 @@ SDNA_DEFAULT_DECL_STRUCT(ThickGpencilModifierData); SDNA_DEFAULT_DECL_STRUCT(TimeGpencilModifierData); SDNA_DEFAULT_DECL_STRUCT(TintGpencilModifierData); SDNA_DEFAULT_DECL_STRUCT(LineartGpencilModifierData); +SDNA_DEFAULT_DECL_STRUCT(LengthGpencilModifierData); #undef SDNA_DEFAULT_DECL_STRUCT @@ -541,6 +542,7 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = { SDNA_DEFAULT_DECL(TimeGpencilModifierData), SDNA_DEFAULT_DECL(TintGpencilModifierData), SDNA_DEFAULT_DECL(LineartGpencilModifierData), + SDNA_DEFAULT_DECL(LengthGpencilModifierData), }; #undef SDNA_DEFAULT_DECL #undef SDNA_DEFAULT_DECL_EX diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index 54e077c624c..379e3e77b6e 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -336,6 +336,7 @@ extern StructRNA RNA_LatticeModifier; extern StructRNA RNA_LatticePoint; extern StructRNA RNA_LayerCollection; extern StructRNA RNA_LayerObjects; +extern StructRNA RNA_LengthGpencilModifier; extern StructRNA RNA_Library; extern StructRNA RNA_Light; extern StructRNA RNA_LightProbe; @@ -845,6 +846,7 @@ const char *RNA_property_description(PropertyRNA *prop); PropertyType RNA_property_type(PropertyRNA *prop); PropertySubType RNA_property_subtype(PropertyRNA *prop); PropertyUnit RNA_property_unit(PropertyRNA *prop); +PropertyScaleType RNA_property_ui_scale(PropertyRNA *prop); int RNA_property_flag(PropertyRNA *prop); int RNA_property_override_flag(PropertyRNA *prop); int RNA_property_tags(PropertyRNA *prop); diff --git a/source/blender/makesrna/RNA_define.h b/source/blender/makesrna/RNA_define.h index 17309d847bd..a31182b2f5a 100644 --- a/source/blender/makesrna/RNA_define.h +++ b/source/blender/makesrna/RNA_define.h @@ -388,6 +388,7 @@ void RNA_def_property_string_default(PropertyRNA *prop, const char *value); void RNA_def_property_ui_text(PropertyRNA *prop, const char *name, const char *description); void RNA_def_property_ui_range( PropertyRNA *prop, double min, double max, double step, int precision); +void RNA_def_property_ui_scale_type(PropertyRNA *prop, PropertyScaleType scale_type); void RNA_def_property_ui_icon(PropertyRNA *prop, int icon, int consecutive); void RNA_def_property_update(PropertyRNA *prop, int noteflag, const char *updatefunc); diff --git a/source/blender/makesrna/RNA_enum_types.h b/source/blender/makesrna/RNA_enum_types.h index b0895609e9a..71af69f77a1 100644 --- a/source/blender/makesrna/RNA_enum_types.h +++ b/source/blender/makesrna/RNA_enum_types.h @@ -237,6 +237,7 @@ extern const EnumPropertyItem rna_enum_curveprofile_preset_items[]; extern const EnumPropertyItem rna_enum_preference_section_items[]; extern const EnumPropertyItem rna_enum_attribute_type_items[]; +extern const EnumPropertyItem rna_enum_attribute_type_with_auto_items[]; extern const EnumPropertyItem rna_enum_attribute_domain_items[]; extern const EnumPropertyItem rna_enum_attribute_domain_with_auto_items[]; extern const EnumPropertyItem *rna_enum_attribute_domain_itemf(struct ID *id, bool *r_free); diff --git a/source/blender/makesrna/RNA_types.h b/source/blender/makesrna/RNA_types.h index 6cd3678017e..4a6d6dddec7 100644 --- a/source/blender/makesrna/RNA_types.h +++ b/source/blender/makesrna/RNA_types.h @@ -95,6 +95,32 @@ typedef enum PropertyUnit { PROP_UNIT_TEMPERATURE = (11 << 16), /* C */ } PropertyUnit; +/** + * Use values besides #PROP_SCALE_LINEAR + * so the movement of the mouse doesn't map linearly to the value of the slider. + * + * For some settings it's useful to space motion in a non-linear way, see T77868. + * + * NOTE: The scale types are available for all float sliders. + * For integer sliders they are only available if they use the visible value bar. + * Sliders with logarithmic scale and value bar must have a range > 0 + * while logarithmic sliders without the value bar can have a range of >= 0. + */ +typedef enum PropertyScaleType { + /** Linear scale (default). */ + PROP_SCALE_LINEAR = 0, + /** + * Logarithmic scale + * - Maximum range: `0 <= x < inf` + */ + PROP_SCALE_LOG = 1, + /** + * Cubic scale. + * - Maximum range: `-inf < x < inf` + */ + PROP_SCALE_CUBIC = 2, +} PropertyScaleType; + #define RNA_SUBTYPE_UNIT(subtype) ((subtype)&0x00FF0000) #define RNA_SUBTYPE_VALUE(subtype) ((subtype) & ~0x00FF0000) #define RNA_SUBTYPE_UNIT_VALUE(subtype) ((subtype) >> 16) diff --git a/source/blender/makesrna/intern/CMakeLists.txt b/source/blender/makesrna/intern/CMakeLists.txt index 4fafa356879..95b7b7e5406 100644 --- a/source/blender/makesrna/intern/CMakeLists.txt +++ b/source/blender/makesrna/intern/CMakeLists.txt @@ -99,7 +99,7 @@ set(DEFSRC ) if(WITH_EXPERIMENTAL_FEATURES) - add_definitions(-DWITH_GEOMETRY_NODES) + add_definitions(-DWITH_SIMULATION_DATABLOCK) add_definitions(-DWITH_POINT_CLOUD) add_definitions(-DWITH_HAIR_NODES) list(APPEND DEFSRC diff --git a/source/blender/makesrna/intern/makesrna.c b/source/blender/makesrna/intern/makesrna.c index b20d9218316..efe12114d55 100644 --- a/source/blender/makesrna/intern/makesrna.c +++ b/source/blender/makesrna/intern/makesrna.c @@ -684,6 +684,29 @@ static char *rna_def_property_get_func( } } } + + /* Check log scale sliders for negative range. */ + if (prop->type == PROP_FLOAT) { + FloatPropertyRNA *fprop = (FloatPropertyRNA *)prop; + /* NOTE: UI_BTYPE_NUM_SLIDER can't have a softmin of zero. */ + if ((fprop->ui_scale_type == PROP_SCALE_LOG) && (fprop->hardmin < 0 || fprop->softmin < 0)) { + CLOG_ERROR( + &LOG, "\"%s.%s\", range for log scale < 0.", srna->identifier, prop->identifier); + DefRNA.error = true; + return NULL; + } + } + if (prop->type == PROP_INT) { + IntPropertyRNA *iprop = (IntPropertyRNA *)prop; + /* Only UI_BTYPE_NUM_SLIDER is implemented and that one can't have a softmin of zero. */ + if ((iprop->ui_scale_type == PROP_SCALE_LOG) && + (iprop->hardmin <= 0 || iprop->softmin <= 0)) { + CLOG_ERROR( + &LOG, "\"%s.%s\", range for log scale <= 0.", srna->identifier, prop->identifier); + DefRNA.error = true; + return NULL; + } + } } func = rna_alloc_function_name(srna->identifier, rna_safe_id(prop->identifier), "get"); @@ -3935,6 +3958,8 @@ static void rna_generate_property(FILE *f, StructRNA *srna, const char *nest, Pr rna_function_string(iprop->getarray_ex), rna_function_string(iprop->setarray_ex), rna_function_string(iprop->range_ex)); + rna_int_print(f, iprop->ui_scale_type); + fprintf(f, ", "); rna_int_print(f, iprop->softmin); fprintf(f, ", "); rna_int_print(f, iprop->softmax); @@ -3969,6 +3994,8 @@ static void rna_generate_property(FILE *f, StructRNA *srna, const char *nest, Pr rna_function_string(fprop->getarray_ex), rna_function_string(fprop->setarray_ex), rna_function_string(fprop->range_ex)); + rna_float_print(f, fprop->ui_scale_type); + fprintf(f, ", "); rna_float_print(f, fprop->softmin); fprintf(f, ", "); rna_float_print(f, fprop->softmax); @@ -4341,7 +4368,7 @@ static RNAProcessItem PROCESS_ITEMS[] = { {"rna_screen.c", NULL, RNA_def_screen}, {"rna_sculpt_paint.c", NULL, RNA_def_sculpt_paint}, {"rna_sequencer.c", "rna_sequencer_api.c", RNA_def_sequencer}, -#ifdef WITH_GEOMETRY_NODES +#ifdef WITH_SIMULATION_DATABLOCK {"rna_simulation.c", NULL, RNA_def_simulation}, #endif {"rna_space.c", "rna_space_api.c", RNA_def_space}, diff --git a/source/blender/makesrna/intern/rna_ID.c b/source/blender/makesrna/intern/rna_ID.c index 657af98f5fe..2818f251085 100644 --- a/source/blender/makesrna/intern/rna_ID.c +++ b/source/blender/makesrna/intern/rna_ID.c @@ -148,6 +148,8 @@ static const EnumPropertyItem rna_enum_override_library_property_operation_items # include "DEG_depsgraph_build.h" # include "DEG_depsgraph_query.h" +# include "ED_asset.h" + # include "WM_api.h" void rna_ID_override_library_property_operation_refname_get(PointerRNA *ptr, char *value) @@ -344,7 +346,7 @@ short RNA_type_to_ID_code(const StructRNA *type) if (base_type == &RNA_Screen) { return ID_SCR; } -# ifdef WITH_GEOMETRY_NODES +# ifdef WITH_SIMULATION_DATABLOCK if (base_type == &RNA_Simulation) { return ID_SIM; } @@ -452,7 +454,7 @@ StructRNA *ID_code_to_RNA_type(short idcode) case ID_SCR: return &RNA_Screen; case ID_SIM: -# ifdef WITH_GEOMETRY_NODES +# ifdef WITH_SIMULATION_DATABLOCK return &RNA_Simulation; # else return &RNA_ID; @@ -575,6 +577,22 @@ static ID *rna_ID_copy(ID *id, Main *bmain) return newid; } +static void rna_ID_asset_mark(ID *id, bContext *C) +{ + if (ED_asset_mark_id(C, id)) { + WM_main_add_notifier(NC_ID | NA_EDITED, NULL); + WM_main_add_notifier(NC_ASSET | NA_ADDED, NULL); + } +} + +static void rna_ID_asset_clear(ID *id) +{ + if (ED_asset_clear_id(id)) { + WM_main_add_notifier(NC_ID | NA_EDITED, NULL); + WM_main_add_notifier(NC_ASSET | NA_REMOVED, NULL); + } +} + static ID *rna_ID_override_create(ID *id, Main *bmain, bool remap_local_usages) { if (!ID_IS_OVERRIDABLE_LIBRARY(id)) { @@ -1119,7 +1137,7 @@ static void rna_ImagePreview_icon_reload(PreviewImage *prv) static PointerRNA rna_IDPreview_get(PointerRNA *ptr) { ID *id = (ID *)ptr->data; - PreviewImage *prv_img = BKE_previewimg_id_ensure(id); + PreviewImage *prv_img = BKE_previewimg_id_get(id); return rna_pointer_inherit_refine(ptr, &RNA_ImagePreview, prv_img); } @@ -1689,12 +1707,12 @@ static void rna_def_ID(BlenderRNA *brna) srna, "override_library", "IDOverrideLibrary", "Library Override", "Library override data"); RNA_def_property_clear_flag(prop, PROP_EDITABLE); - prop = RNA_def_pointer( - srna, - "preview", - "ImagePreview", - "Preview", - "Preview image and icon of this data-block (None if not supported for this type of data)"); + prop = RNA_def_pointer(srna, + "preview", + "ImagePreview", + "Preview", + "Preview image and icon of this data-block (always None if not supported " + "for this type of data)"); RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_property_override_flag(prop, PROPOVERRIDE_NO_COMPARISON); RNA_def_property_pointer_funcs(prop, "rna_IDPreview_get", NULL, NULL, NULL); @@ -1716,6 +1734,18 @@ static void rna_def_ID(BlenderRNA *brna) parm = RNA_def_pointer(func, "id", "ID", "", "New copy of the ID"); RNA_def_function_return(func, parm); + func = RNA_def_function(srna, "asset_mark", "rna_ID_asset_mark"); + RNA_def_function_ui_description( + func, + "Enable easier reuse of the data-block through the Asset Browser, with the help of " + "customizable metadata (like previews, descriptions and tags)"); + RNA_def_function_flag(func, FUNC_USE_CONTEXT); + + func = RNA_def_function(srna, "asset_clear", "rna_ID_asset_clear"); + RNA_def_function_ui_description( + func, + "Delete all asset metadata and turn the asset data-block back into a normal data-block"); + func = RNA_def_function(srna, "override_create", "rna_ID_override_create"); RNA_def_function_ui_description(func, "Create an overridden local copy of this linked data-block (not " @@ -1796,6 +1826,13 @@ static void rna_def_ID(BlenderRNA *brna) "e.g. when calling :class:`bpy.types.Scene.update`"); RNA_def_enum_flag(func, "refresh", update_flag_items, 0, "", "Type of updates to perform"); + func = RNA_def_function(srna, "preview_ensure", "BKE_previewimg_id_ensure"); + RNA_def_function_ui_description(func, + "Ensure that this ID has preview data (if ID type supports it)"); + parm = RNA_def_pointer( + func, "preview_image", "ImagePreview", "", "The existing or created preview"); + RNA_def_function_return(func, parm); + # ifdef WITH_PYTHON RNA_def_struct_register_funcs(srna, NULL, NULL, "rna_ID_instance"); # endif diff --git a/source/blender/makesrna/intern/rna_access.c b/source/blender/makesrna/intern/rna_access.c index 9b57096ec19..150a455f1c7 100644 --- a/source/blender/makesrna/intern/rna_access.c +++ b/source/blender/makesrna/intern/rna_access.c @@ -1189,6 +1189,24 @@ PropertyUnit RNA_property_unit(PropertyRNA *prop) return RNA_SUBTYPE_UNIT(RNA_property_subtype(prop)); } +PropertyScaleType RNA_property_ui_scale(PropertyRNA *prop) +{ + PropertyRNA *rna_prop = rna_ensure_property(prop); + + switch (rna_prop->type) { + case PROP_INT: { + IntPropertyRNA *iprop = (IntPropertyRNA *)rna_prop; + return iprop->ui_scale_type; + } + case PROP_FLOAT: { + FloatPropertyRNA *fprop = (FloatPropertyRNA *)rna_prop; + return fprop->ui_scale_type; + } + default: + return PROP_SCALE_LINEAR; + } +} + int RNA_property_flag(PropertyRNA *prop) { return rna_ensure_property(prop)->flag; diff --git a/source/blender/makesrna/intern/rna_armature.c b/source/blender/makesrna/intern/rna_armature.c index c54621372ba..c8fccfc27f8 100644 --- a/source/blender/makesrna/intern/rna_armature.c +++ b/source/blender/makesrna/intern/rna_armature.c @@ -674,7 +674,7 @@ static bool rna_Armature_is_editmode_get(PointerRNA *ptr) return (arm->edbo != NULL); } -static void rna_Armature_transform(bArmature *arm, float *mat) +static void rna_Armature_transform(bArmature *arm, float mat[16]) { ED_armature_transform(arm, (const float(*)[4])mat, true); } diff --git a/source/blender/makesrna/intern/rna_armature_api.c b/source/blender/makesrna/intern/rna_armature_api.c index 36aa0401c7d..a02f55667e3 100644 --- a/source/blender/makesrna/intern/rna_armature_api.c +++ b/source/blender/makesrna/intern/rna_armature_api.c @@ -44,7 +44,7 @@ static void rna_EditBone_align_roll(EditBone *ebo, float no[3]) ebo->roll = ED_armature_ebone_roll_to_vector(ebo, no, false); } -static float rna_Bone_do_envelope(Bone *bone, float *vec) +static float rna_Bone_do_envelope(Bone *bone, float vec[3]) { float scale = (bone->flag & BONE_MULT_VG_ENV) == BONE_MULT_VG_ENV ? bone->weight : 1.0f; return distfactor_to_bone(vec, @@ -56,11 +56,11 @@ static float rna_Bone_do_envelope(Bone *bone, float *vec) } static void rna_Bone_convert_local_to_pose(Bone *bone, - float *r_matrix, - float *matrix, - float *matrix_local, - float *parent_matrix, - float *parent_matrix_local, + float r_matrix[16], + float matrix[16], + float matrix_local[16], + float parent_matrix[16], + float parent_matrix_local[16], bool invert) { BoneParentTransform bpt; @@ -89,14 +89,14 @@ static void rna_Bone_convert_local_to_pose(Bone *bone, BKE_bone_parent_transform_apply(&bpt, (float(*)[4])matrix, (float(*)[4])r_matrix); } -static void rna_Bone_MatrixFromAxisRoll(float *axis, float roll, float *r_matrix) +static void rna_Bone_MatrixFromAxisRoll(float axis[3], float roll, float r_matrix[9]) { vec_roll_to_mat3(axis, roll, (float(*)[3])r_matrix); } -static void rna_Bone_AxisRollFromMatrix(float *matrix, - float *axis_override, - float *r_axis, +static void rna_Bone_AxisRollFromMatrix(float matrix[9], + float axis_override[3], + float r_axis[3], float *r_roll) { float mat[3][3]; diff --git a/source/blender/makesrna/intern/rna_attribute.c b/source/blender/makesrna/intern/rna_attribute.c index 7976df3e4e4..a256002ffc1 100644 --- a/source/blender/makesrna/intern/rna_attribute.c +++ b/source/blender/makesrna/intern/rna_attribute.c @@ -49,6 +49,19 @@ const EnumPropertyItem rna_enum_attribute_type_items[] = { {0, NULL, 0, NULL, NULL}, }; +const EnumPropertyItem rna_enum_attribute_type_with_auto_items[] = { + {CD_AUTO_FROM_NAME, "AUTO", 0, "Auto", ""}, + {CD_PROP_FLOAT, "FLOAT", 0, "Float", "Floating-point value"}, + {CD_PROP_INT32, "INT", 0, "Integer", "32-bit integer"}, + {CD_PROP_FLOAT3, "FLOAT_VECTOR", 0, "Vector", "3D vector with floating-point values"}, + {CD_PROP_COLOR, "FLOAT_COLOR", 0, "Color", "RGBA color with floating-point precisions"}, + {CD_MLOOPCOL, "BYTE_COLOR", 0, "Byte Color", "RGBA color with 8-bit precision"}, + {CD_PROP_STRING, "STRING", 0, "String", "Text string"}, + {CD_PROP_BOOL, "BOOLEAN", 0, "Boolean", "True or false"}, + {CD_PROP_FLOAT2, "FLOAT2", 0, "2D Vector", "2D vector with floating-point values"}, + {0, NULL, 0, NULL, NULL}, +}; + const EnumPropertyItem rna_enum_attribute_domain_items[] = { /* Not implement yet */ // {ATTR_DOMAIN_GEOMETRY, "GEOMETRY", 0, "Geometry", "Attribute on (whole) geometry"}, @@ -58,7 +71,7 @@ const EnumPropertyItem rna_enum_attribute_domain_items[] = { {ATTR_DOMAIN_CORNER, "CORNER", 0, "Face Corner", "Attribute on mesh face corner"}, /* Not implement yet */ // {ATTR_DOMAIN_GRIDS, "GRIDS", 0, "Grids", "Attribute on mesh multires grids"}, - {ATTR_DOMAIN_CURVE, "CURVE", 0, "Curve", "Attribute on hair curve"}, + {ATTR_DOMAIN_CURVE, "CURVE", 0, "Spline", "Attribute on spline"}, {0, NULL, 0, NULL, NULL}, }; @@ -68,6 +81,7 @@ const EnumPropertyItem rna_enum_attribute_domain_with_auto_items[] = { {ATTR_DOMAIN_EDGE, "EDGE", 0, "Edge", "Attribute on mesh edge"}, {ATTR_DOMAIN_FACE, "FACE", 0, "Face", "Attribute on mesh faces"}, {ATTR_DOMAIN_CORNER, "CORNER", 0, "Face Corner", "Attribute on mesh face corner"}, + {ATTR_DOMAIN_CURVE, "CURVE", 0, "Spline", "Attribute on spline"}, {0, NULL, 0, NULL, NULL}, }; diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index e7daa55af6c..7e1d513502c 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -1617,6 +1617,15 @@ static void rna_def_gpencil_options(BlenderRNA *brna) prop, "Stroke Extension", "Strokes end extension for closing gaps, use zero to disable"); RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); + /* Number of pixels to dilate fill area. */ + prop = RNA_def_property(srna, "dilate", PROP_INT, PROP_PIXEL); + RNA_def_property_int_sdna(prop, NULL, "dilate_pixels"); + RNA_def_property_range(prop, 0, 20); + RNA_def_property_int_default(prop, 1); + RNA_def_property_ui_text(prop, "Dilate", "Number of pixels to dilate fill area"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, NULL); + /* Flags */ prop = RNA_def_property(srna, "use_pressure", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_BRUSH_USE_PRESSURE); diff --git a/source/blender/makesrna/intern/rna_curve_api.c b/source/blender/makesrna/intern/rna_curve_api.c index 94fdb130026..beea607e8c0 100644 --- a/source/blender/makesrna/intern/rna_curve_api.c +++ b/source/blender/makesrna/intern/rna_curve_api.c @@ -35,7 +35,7 @@ #include "rna_internal.h" /* own include */ #ifdef RNA_RUNTIME -static void rna_Curve_transform(Curve *cu, float *mat, bool shape_keys) +static void rna_Curve_transform(Curve *cu, float mat[16], bool shape_keys) { BKE_curve_transform(cu, (const float(*)[4])mat, shape_keys, true); diff --git a/source/blender/makesrna/intern/rna_curveprofile.c b/source/blender/makesrna/intern/rna_curveprofile.c index bb54d55f8bd..b3ab8cc15a2 100644 --- a/source/blender/makesrna/intern/rna_curveprofile.c +++ b/source/blender/makesrna/intern/rna_curveprofile.c @@ -137,7 +137,7 @@ static void rna_CurveProfile_remove_point(CurveProfile *profile, static void rna_CurveProfile_evaluate(struct CurveProfile *profile, ReportList *reports, float length_portion, - float *location) + float location[2]) { if (!profile->table) { BKE_report(reports, RPT_ERROR, "CurveProfile table not initialized, call initialize()"); diff --git a/source/blender/makesrna/intern/rna_define.c b/source/blender/makesrna/intern/rna_define.c index 2fdf7e5eaa7..9b9d561603b 100644 --- a/source/blender/makesrna/intern/rna_define.c +++ b/source/blender/makesrna/intern/rna_define.c @@ -1754,6 +1754,28 @@ void RNA_def_property_ui_range( } } +void RNA_def_property_ui_scale_type(PropertyRNA *prop, PropertyScaleType ui_scale_type) +{ + StructRNA *srna = DefRNA.laststruct; + + switch (prop->type) { + case PROP_INT: { + IntPropertyRNA *iprop = (IntPropertyRNA *)prop; + iprop->ui_scale_type = ui_scale_type; + break; + } + case PROP_FLOAT: { + FloatPropertyRNA *fprop = (FloatPropertyRNA *)prop; + fprop->ui_scale_type = ui_scale_type; + break; + } + default: + CLOG_ERROR(&LOG, "\"%s.%s\", invalid type for scale.", srna->identifier, prop->identifier); + DefRNA.error = true; + break; + } +} + void RNA_def_property_range(PropertyRNA *prop, double min, double max) { StructRNA *srna = DefRNA.laststruct; diff --git a/source/blender/makesrna/intern/rna_fcurve.c b/source/blender/makesrna/intern/rna_fcurve.c index d4697721bc2..1b89d866aba 100644 --- a/source/blender/makesrna/intern/rna_fcurve.c +++ b/source/blender/makesrna/intern/rna_fcurve.c @@ -1160,6 +1160,7 @@ static void rna_def_fmodifier_generator(BlenderRNA *brna) /* define common props */ prop = RNA_def_property(srna, "use_additive", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FCM_GENERATOR_ADDITIVE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Additive", "Values generated by this modifier are applied on top of " @@ -1168,12 +1169,14 @@ static void rna_def_fmodifier_generator(BlenderRNA *brna) prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, generator_mode_items); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Mode", "Type of generator to use"); RNA_def_property_update( prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_verify_data_update"); /* order of the polynomial */ prop = RNA_def_property(srna, "poly_order", PROP_INT, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Polynomial Order", @@ -1184,6 +1187,7 @@ static void rna_def_fmodifier_generator(BlenderRNA *brna) /* coefficients array */ prop = RNA_def_property(srna, "coefficients", PROP_FLOAT, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_array(prop, 32); RNA_def_property_flag(prop, PROP_DYNAMIC); RNA_def_property_dynamic_array_funcs(prop, "rna_FModifierGenerator_coefficients_get_length"); @@ -1221,25 +1225,30 @@ static void rna_def_fmodifier_function_generator(BlenderRNA *brna) /* coefficients */ prop = RNA_def_property(srna, "amplitude", PROP_FLOAT, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Amplitude", "Scale factor determining the maximum/minimum values"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "phase_multiplier", PROP_FLOAT, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Phase Multiple", "Scale factor determining the 'speed' of the function"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "phase_offset", PROP_FLOAT, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Phase Offset", "Constant factor to offset time by for function"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "value_offset", PROP_FLOAT, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Value Offset", "Constant factor to offset values by"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); /* flags */ prop = RNA_def_property(srna, "use_additive", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_boolean_sdna(prop, NULL, "flag", FCM_GENERATOR_ADDITIVE); RNA_def_property_ui_text(prop, "Additive", @@ -1248,6 +1257,7 @@ static void rna_def_fmodifier_function_generator(BlenderRNA *brna) RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "function_type", PROP_ENUM, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_enum_sdna(prop, NULL, "type"); RNA_def_property_enum_items(prop, prop_type_items); RNA_def_property_ui_text(prop, "Type", "Type of built-in function to use"); @@ -1271,17 +1281,20 @@ static void rna_def_fmodifier_envelope_ctrl(BlenderRNA *brna) */ prop = RNA_def_property(srna, "min", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "min"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Minimum Value", "Lower bound of envelope at this control-point"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "max", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "max"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Maximum Value", "Upper bound of envelope at this control-point"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); /* Frame */ prop = RNA_def_property(srna, "frame", PROP_FLOAT, PROP_TIME); RNA_def_property_float_sdna(prop, NULL, "time"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Frame", "Frame this control-point occurs on"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); @@ -1340,6 +1353,7 @@ static void rna_def_fmodifier_envelope(BlenderRNA *brna) /* Collections */ prop = RNA_def_property(srna, "control_points", PROP_COLLECTION, PROP_NONE); RNA_def_property_collection_sdna(prop, NULL, "data", "totvert"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_struct_type(prop, "FModifierEnvelopeControlPoint"); RNA_def_property_ui_text( prop, "Control Points", "Control points defining the shape of the envelope"); @@ -1348,18 +1362,21 @@ static void rna_def_fmodifier_envelope(BlenderRNA *brna) /* Range Settings */ prop = RNA_def_property(srna, "reference_value", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "midval"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Reference Value", "Value that envelope's influence is centered around / based on"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "default_min", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "min"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Default Minimum", "Lower distance from Reference Value for 1:1 default influence"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "default_max", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "max"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Default Maximum", "Upper distance from Reference Value for 1:1 default influence"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); @@ -1395,12 +1412,14 @@ static void rna_def_fmodifier_cycles(BlenderRNA *brna) /* before */ prop = RNA_def_property(srna, "mode_before", PROP_ENUM, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_enum_sdna(prop, NULL, "before_mode"); RNA_def_property_enum_items(prop, prop_type_items); RNA_def_property_ui_text(prop, "Before Mode", "Cycling mode to use before first keyframe"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "cycles_before", PROP_INT, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_int_sdna(prop, NULL, "before_cycles"); RNA_def_property_ui_text( prop, @@ -1410,12 +1429,14 @@ static void rna_def_fmodifier_cycles(BlenderRNA *brna) /* after */ prop = RNA_def_property(srna, "mode_after", PROP_ENUM, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_enum_sdna(prop, NULL, "after_mode"); RNA_def_property_enum_items(prop, prop_type_items); RNA_def_property_ui_text(prop, "After Mode", "Cycling mode to use after last keyframe"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "cycles_after", PROP_INT, PROP_NONE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_int_sdna(prop, NULL, "after_cycles"); RNA_def_property_ui_text(prop, "After Cycles", @@ -1450,26 +1471,31 @@ static void rna_def_fmodifier_limits(BlenderRNA *brna) prop = RNA_def_property(srna, "use_min_x", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FCM_LIMIT_XMIN); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Minimum X", "Use the minimum X value"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "use_min_y", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FCM_LIMIT_YMIN); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Minimum Y", "Use the minimum Y value"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "use_max_x", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FCM_LIMIT_XMAX); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Maximum X", "Use the maximum X value"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "use_max_y", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FCM_LIMIT_YMAX); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Maximum Y", "Use the maximum Y value"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "min_x", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "rect.xmin"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs( prop, NULL, "rna_FModifierLimits_minx_set", "rna_FModifierLimits_minx_range"); RNA_def_property_ui_text(prop, "Minimum X", "Lowest X value to allow"); @@ -1477,6 +1503,7 @@ static void rna_def_fmodifier_limits(BlenderRNA *brna) prop = RNA_def_property(srna, "min_y", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "rect.ymin"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs( prop, NULL, "rna_FModifierLimits_miny_set", "rna_FModifierLimits_miny_range"); RNA_def_property_ui_text(prop, "Minimum Y", "Lowest Y value to allow"); @@ -1484,6 +1511,7 @@ static void rna_def_fmodifier_limits(BlenderRNA *brna) prop = RNA_def_property(srna, "max_x", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "rect.xmax"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs( prop, NULL, "rna_FModifierLimits_maxx_set", "rna_FModifierLimits_maxx_range"); RNA_def_property_ui_text(prop, "Maximum X", "Highest X value to allow"); @@ -1491,6 +1519,7 @@ static void rna_def_fmodifier_limits(BlenderRNA *brna) prop = RNA_def_property(srna, "max_y", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "rect.ymax"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs( prop, NULL, "rna_FModifierLimits_maxy_set", "rna_FModifierLimits_maxy_range"); RNA_def_property_ui_text(prop, "Maximum Y", "Highest Y value to allow"); @@ -1519,16 +1548,19 @@ static void rna_def_fmodifier_noise(BlenderRNA *brna) prop = RNA_def_property(srna, "blend_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "modification"); RNA_def_property_enum_items(prop, prop_modification_items); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Blend Type", "Method of modifying the existing F-Curve"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "scale", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "size"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Scale", "Scaling (in time) of the noise"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "strength", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "strength"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Strength", @@ -1537,16 +1569,19 @@ static void rna_def_fmodifier_noise(BlenderRNA *brna) prop = RNA_def_property(srna, "phase", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "phase"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Phase", "A random seed for the noise effect"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "offset", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "offset"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Offset", "Time offset for the noise effect"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "depth", PROP_INT, PROP_UNSIGNED); RNA_def_property_int_sdna(prop, NULL, "depth"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Depth", "Amount of fine level detail present in the noise"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); } @@ -1569,11 +1604,13 @@ static void rna_def_fmodifier_stepped(BlenderRNA *brna) /* properties */ prop = RNA_def_property(srna, "frame_step", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "step_size"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Step Size", "Number of frames to hold each value"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "frame_offset", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "offset"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Offset", "Reference number of frames before frames get held " @@ -1582,18 +1619,21 @@ static void rna_def_fmodifier_stepped(BlenderRNA *brna) prop = RNA_def_property(srna, "use_frame_start", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FCM_STEPPED_NO_BEFORE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Use Start Frame", "Restrict modifier to only act after its 'start' frame"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "use_frame_end", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FCM_STEPPED_NO_AFTER); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Use End Frame", "Restrict modifier to only act before its 'end' frame"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, "rna_FModifier_update"); prop = RNA_def_property(srna, "frame_start", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "start_frame"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs(prop, NULL, "rna_FModifierStepped_frame_start_set", @@ -1604,6 +1644,7 @@ static void rna_def_fmodifier_stepped(BlenderRNA *brna) prop = RNA_def_property(srna, "frame_end", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "end_frame"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs( prop, NULL, "rna_FModifierStepped_frame_end_set", "rna_FModifierStepped_end_frame_range"); RNA_def_property_ui_text( @@ -1641,12 +1682,14 @@ static void rna_def_fmodifier(BlenderRNA *brna) prop = RNA_def_property(srna, "show_expanded", PROP_BOOLEAN, PROP_NONE); RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE); RNA_def_property_boolean_sdna(prop, NULL, "ui_expand_flag", 0); + // RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_boolean_funcs(prop, NULL, "rna_FModifier_show_expanded_set"); RNA_def_property_ui_text(prop, "Expanded", "F-Curve Modifier's panel is expanded in UI"); RNA_def_property_ui_icon(prop, ICON_DISCLOSURE_TRI_RIGHT, 1); prop = RNA_def_property(srna, "mute", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FMODIFIER_FLAG_MUTED); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Enabled", "Enable F-Curve modifier evaluation"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME_PROP, "rna_FModifier_update"); RNA_def_property_ui_icon(prop, ICON_CHECKBOX_HLT, -1); @@ -1654,6 +1697,7 @@ static void rna_def_fmodifier(BlenderRNA *brna) prop = RNA_def_property(srna, "is_valid", PROP_BOOLEAN, PROP_NONE); RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", FMODIFIER_FLAG_DISABLED); + // RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Disabled", "F-Curve Modifier has invalid settings and will not be evaluated"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME_PROP, "rna_FModifier_update"); @@ -1661,6 +1705,7 @@ static void rna_def_fmodifier(BlenderRNA *brna) /* TODO: setting this to true must ensure that all others in stack are turned off too... */ prop = RNA_def_property(srna, "active", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FMODIFIER_FLAG_ACTIVE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text(prop, "Active", "F-Curve modifier will show settings in the editor"); RNA_def_property_boolean_funcs(prop, NULL, "rna_FModifier_active_set"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME_PROP, "rna_FModifier_active_update"); @@ -1669,6 +1714,7 @@ static void rna_def_fmodifier(BlenderRNA *brna) /* restricted range */ prop = RNA_def_property(srna, "use_restricted_range", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FMODIFIER_FLAG_RANGERESTRICT); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Restrict Frame Range", @@ -1678,6 +1724,7 @@ static void rna_def_fmodifier(BlenderRNA *brna) prop = RNA_def_property(srna, "frame_start", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "sfra"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs( prop, NULL, "rna_FModifier_start_frame_set", "rna_FModifier_start_frame_range"); RNA_def_property_ui_text( @@ -1688,6 +1735,7 @@ static void rna_def_fmodifier(BlenderRNA *brna) prop = RNA_def_property(srna, "frame_end", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "efra"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs( prop, NULL, "rna_FModifier_end_frame_set", "rna_FModifier_end_frame_range"); RNA_def_property_ui_text( @@ -1698,6 +1746,7 @@ static void rna_def_fmodifier(BlenderRNA *brna) prop = RNA_def_property(srna, "blend_in", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "blendin"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs(prop, NULL, NULL, "rna_FModifier_blending_range"); RNA_def_property_ui_text( prop, "Blend In", "Number of frames from start frame for influence to take effect"); @@ -1705,6 +1754,7 @@ static void rna_def_fmodifier(BlenderRNA *brna) prop = RNA_def_property(srna, "blend_out", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, NULL, "blendout"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_float_funcs(prop, NULL, NULL, "rna_FModifier_blending_range"); RNA_def_property_ui_text( prop, "Blend Out", "Number of frames from end frame for influence to fade out"); @@ -1713,12 +1763,14 @@ static void rna_def_fmodifier(BlenderRNA *brna) /* influence */ prop = RNA_def_property(srna, "use_influence", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", FMODIFIER_FLAG_USEINFLUENCE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_ui_text( prop, "Use Influence", "F-Curve Modifier's effects will be tempered by a default factor"); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME_PROP, "rna_FModifier_update"); prop = RNA_def_property(srna, "influence", PROP_FLOAT, PROP_FACTOR); RNA_def_property_float_sdna(prop, NULL, "influence"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_range(prop, 0.0f, 1.0f); RNA_def_property_float_default(prop, 1.0f); RNA_def_property_ui_text( @@ -2162,6 +2214,7 @@ static void rna_def_fcurve_modifiers(BlenderRNA *brna, PropertyRNA *cprop) /* Collection active property */ prop = RNA_def_property(srna, "active", PROP_POINTER, PROP_NONE); RNA_def_property_struct_type(prop, "FModifier"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_pointer_funcs( prop, "rna_FCurve_active_modifier_get", "rna_FCurve_active_modifier_set", NULL, NULL); RNA_def_property_flag(prop, PROP_EDITABLE); diff --git a/source/blender/makesrna/intern/rna_gpencil.c b/source/blender/makesrna/intern/rna_gpencil.c index 1fbbffa99d5..5f865f9d4cd 100644 --- a/source/blender/makesrna/intern/rna_gpencil.c +++ b/source/blender/makesrna/intern/rna_gpencil.c @@ -1037,7 +1037,7 @@ static bGPDframe *rna_GPencil_frame_copy(bGPDlayer *layer, bGPDframe *src) static bGPDlayer *rna_GPencil_layer_new(bGPdata *gpd, const char *name, bool setactive) { - bGPDlayer *gpl = BKE_gpencil_layer_addnew(gpd, name, setactive != 0); + bGPDlayer *gpl = BKE_gpencil_layer_addnew(gpd, name, setactive != 0, false); WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, NULL); @@ -2114,6 +2114,12 @@ static void rna_def_gpencil_layer(BlenderRNA *brna) "ViewLayer", "Only include Layer in this View Layer render output (leave blank to include always)"); + prop = RNA_def_property(srna, "use_viewlayer_masks", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", GP_LAYER_DISABLE_MASKS_IN_VIEWLAYER); + RNA_def_property_ui_text( + prop, "Use Masks in Render", "Include the mask layers when rendering the viewlayer"); + RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_update"); + /* blend mode */ prop = RNA_def_property(srna, "blend_mode", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "blend_mode"); @@ -2571,7 +2577,7 @@ static void rna_def_gpencil_data(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Auto-Lock Layers", - "Lock automatically all layers except active one to avoid accidental changes"); + "Automatically lock all layers except the active one to avoid accidental changes"); RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_GPencil_autolock"); prop = RNA_def_property(srna, "edit_line_color", PROP_FLOAT, PROP_COLOR_GAMMA); diff --git a/source/blender/makesrna/intern/rna_gpencil_modifier.c b/source/blender/makesrna/intern/rna_gpencil_modifier.c index 03a55c5a7da..4aad0741151 100644 --- a/source/blender/makesrna/intern/rna_gpencil_modifier.c +++ b/source/blender/makesrna/intern/rna_gpencil_modifier.c @@ -109,6 +109,11 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = { ICON_MOD_LATTICE, "Lattice", "Deform strokes using lattice"}, + {eGpencilModifierType_Length, + "GP_LENGTH", + ICON_MOD_EDGESPLIT, + "Length", + "Extend or shrink strokes"}, {eGpencilModifierType_Noise, "GP_NOISE", ICON_MOD_NOISE, "Noise", "Add noise to strokes"}, {eGpencilModifierType_Offset, "GP_OFFSET", @@ -188,6 +193,11 @@ static const EnumPropertyItem gpencil_tint_type_items[] = { {GP_TINT_GRADIENT, "GRADIENT", 0, "Gradient", ""}, {0, NULL, 0, NULL, NULL}, }; +static const EnumPropertyItem gpencil_length_mode_items[] = { + {GP_LENGTH_RELATIVE, "RELATIVE", 0, "Relative", "Length in ratio to the stroke's length"}, + {GP_LENGTH_ABSOLUTE, "ABSOLUTE", 0, "Absolute", "Length in geometry space"}, + {0, NULL, 0, NULL, NULL}, +}; #endif #ifdef RNA_RUNTIME @@ -233,6 +243,8 @@ static StructRNA *rna_GpencilModifier_refine(struct PointerRNA *ptr) return &RNA_OpacityGpencilModifier; case eGpencilModifierType_Lattice: return &RNA_LatticeGpencilModifier; + case eGpencilModifierType_Length: + return &RNA_LengthGpencilModifier; case eGpencilModifierType_Mirror: return &RNA_MirrorGpencilModifier; case eGpencilModifierType_Smooth: @@ -351,6 +363,8 @@ static void greasepencil_modifier_object_set(Object *self, RNA_GP_MOD_OBJECT_SET(Armature, object, OB_ARMATURE); RNA_GP_MOD_OBJECT_SET(Lattice, object, OB_LATTICE); RNA_GP_MOD_OBJECT_SET(Mirror, object, OB_EMPTY); +RNA_GP_MOD_OBJECT_SET(Opacity, object, OB_EMPTY); +RNA_GP_MOD_OBJECT_SET(Thick, object, OB_EMPTY); # undef RNA_GP_MOD_OBJECT_SET @@ -1116,6 +1130,38 @@ static void rna_def_modifier_gpencilthick(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Thickness Factor", "Factor to multiply the thickness with"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + prop = RNA_def_property(srna, "use_fading", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_THICK_FADING); + RNA_def_property_ui_text(prop, "Fading", "Fading effect"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + /* Distance reference object */ + prop = RNA_def_property(srna, "object", PROP_POINTER, PROP_NONE); + RNA_def_property_ui_text(prop, "Object", "Object used as distance reference"); + RNA_def_property_pointer_funcs(prop, NULL, "rna_ThickGpencilModifier_object_set", NULL, NULL); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_dependency_update"); + + prop = RNA_def_property(srna, "fading_start", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "fading_start"); + RNA_def_property_ui_range(prop, 0, 1000.0, 1.0, 2); + RNA_def_property_ui_text(prop, "Fading Start", "Start distance of fading effect"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "fading_end", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "fading_end"); + RNA_def_property_ui_range(prop, 0, 1000.0, 1.0, 2); + RNA_def_property_ui_text(prop, "Fading End", "End distance of fading effect"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "fading_end_factor", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "fading_end_factor"); + RNA_def_property_range(prop, 0.0, FLT_MAX); + RNA_def_property_ui_range(prop, 0.0, 10.0, 0.1, 3); + RNA_def_property_ui_text(prop, "End Factor", "Fading end thickness factor"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_NONE); RNA_def_property_int_sdna(prop, NULL, "pass_index"); RNA_def_property_range(prop, 0, 100); @@ -1256,6 +1302,34 @@ static void rna_def_modifier_gpenciloffset(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Scale", "Values for changes in scale"); RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 1, RNA_TRANSLATION_PREC_DEFAULT); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "random_offset", PROP_FLOAT, PROP_XYZ); + RNA_def_property_float_sdna(prop, NULL, "rnd_offset"); + RNA_def_property_ui_text(prop, "Random Offset", "Value for changes in location"); + RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 1, RNA_TRANSLATION_PREC_DEFAULT); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "random_rotation", PROP_FLOAT, PROP_EULER); + RNA_def_property_float_sdna(prop, NULL, "rnd_rot"); + RNA_def_property_ui_text(prop, "Random Rotation", "Value for changes in rotation"); + RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 1, RNA_TRANSLATION_PREC_DEFAULT); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "random_scale", PROP_FLOAT, PROP_XYZ); + RNA_def_property_float_sdna(prop, NULL, "rnd_scale"); + RNA_def_property_ui_text(prop, "Scale", "Value for changes in scale"); + RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 1, RNA_TRANSLATION_PREC_DEFAULT); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "seed", PROP_INT, PROP_UNSIGNED); + RNA_def_property_ui_text(prop, "Seed", "Random seed"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "use_uniform_random_scale", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_OFFSET_UNIFORM_RANDOM_SCALE); + RNA_def_property_ui_text( + prop, "Uniform Scale", "Use the same random seed for each scale axis for a uniform scale"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); } static void rna_def_modifier_gpenciltint(BlenderRNA *brna) @@ -1625,6 +1699,38 @@ static void rna_def_modifier_gpencilopacity(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Hardness", "Factor of stroke hardness"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + prop = RNA_def_property(srna, "use_fading", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_OPACITY_FADING); + RNA_def_property_ui_text(prop, "Fading", "Fading effect"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + /* Distance reference object */ + prop = RNA_def_property(srna, "object", PROP_POINTER, PROP_NONE); + RNA_def_property_ui_text(prop, "Object", "Object used as distance reference"); + RNA_def_property_pointer_funcs(prop, NULL, "rna_OpacityGpencilModifier_object_set", NULL, NULL); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_dependency_update"); + + prop = RNA_def_property(srna, "fading_start", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "fading_start"); + RNA_def_property_ui_range(prop, 0, 1000.0, 1.0, 2); + RNA_def_property_ui_text(prop, "Fading Start", "Start distance of fading effect"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "fading_end", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "fading_end"); + RNA_def_property_ui_range(prop, 0, 1000.0, 1.0, 2); + RNA_def_property_ui_text(prop, "Fading End", "End distance of fading effect"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "fading_end_factor", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "fading_end_factor"); + RNA_def_property_range(prop, 0.0, FLT_MAX); + RNA_def_property_ui_range(prop, 0.0, 10.0, 0.1, 3); + RNA_def_property_ui_text(prop, "End Factor", "Fading end thickness factor"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_NONE); RNA_def_property_int_sdna(prop, NULL, "pass_index"); RNA_def_property_range(prop, 0, 100); @@ -2807,6 +2913,87 @@ static void rna_def_modifier_gpencillineart(BlenderRNA *brna) RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); } +static void rna_def_modifier_gpencillength(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "LengthGpencilModifier", "GpencilModifier"); + RNA_def_struct_ui_text(srna, "Length Modifier", "Stretch or shrink strokes"); + RNA_def_struct_sdna(srna, "LengthGpencilModifierData"); + RNA_def_struct_ui_icon(srna, ICON_MOD_EDGESPLIT); + + prop = RNA_def_property(srna, "start_factor", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "start_fac"); + RNA_def_property_ui_range(prop, -10.0f, 10.0f, 0.1, 1); + RNA_def_property_ui_text(prop, "Start Factor", "Length difference for each segment"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "end_factor", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "end_fac"); + RNA_def_property_ui_range(prop, -10.0f, 10.0f, 0.1, 1); + RNA_def_property_ui_text(prop, "End Factor", "Length difference for each segment"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "overshoot_factor", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, NULL, "overshoot_fac"); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_ui_text( + prop, + "Overshoot Factor", + "Defines how precise must follow the stroke trajectory for the overshoot extremes"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "mode"); + RNA_def_property_enum_items(prop, gpencil_length_mode_items); + RNA_def_property_ui_text(prop, "Mode", "Mode to define length"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "layer", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "layername"); + RNA_def_property_ui_text(prop, "Layer", "Layer name"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "material", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); + RNA_def_property_ui_text(prop, "Material", "Material used for filtering effect"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "pass_index"); + RNA_def_property_range(prop, 0, 100); + RNA_def_property_ui_text(prop, "Pass", "Pass index"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "invert_layers", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_LAYER); + RNA_def_property_ui_text(prop, "Inverse Layers", "Inverse filter"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "invert_materials", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_MATERIAL); + RNA_def_property_ui_text(prop, "Inverse Materials", "Inverse filter"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "invert_material_pass", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_PASS); + RNA_def_property_ui_text(prop, "Inverse Pass", "Inverse filter"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "layer_pass", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "layer_pass"); + RNA_def_property_range(prop, 0, 100); + RNA_def_property_ui_text(prop, "Pass", "Layer pass index"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "invert_layer_pass", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_LAYERPASS); + RNA_def_property_ui_text(prop, "Inverse Pass", "Inverse filter"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); +} + void RNA_def_greasepencil_modifier(BlenderRNA *brna) { StructRNA *srna; @@ -2882,6 +3069,7 @@ void RNA_def_greasepencil_modifier(BlenderRNA *brna) rna_def_modifier_gpencilmultiply(brna); rna_def_modifier_gpenciltexture(brna); rna_def_modifier_gpencillineart(brna); + rna_def_modifier_gpencillength(brna); } #endif diff --git a/source/blender/makesrna/intern/rna_internal.h b/source/blender/makesrna/intern/rna_internal.h index 95972dd444f..bfe9d4bb77c 100644 --- a/source/blender/makesrna/intern/rna_internal.h +++ b/source/blender/makesrna/intern/rna_internal.h @@ -476,7 +476,7 @@ void RNA_def_main_hairs(BlenderRNA *brna, PropertyRNA *cprop); void RNA_def_main_pointclouds(BlenderRNA *brna, PropertyRNA *cprop); #endif void RNA_def_main_volumes(BlenderRNA *brna, PropertyRNA *cprop); -#ifdef WITH_GEOMETRY_NODES +#ifdef WITH_SIMULATION_DATABLOCK void RNA_def_main_simulations(BlenderRNA *brna, PropertyRNA *cprop); #endif diff --git a/source/blender/makesrna/intern/rna_internal_types.h b/source/blender/makesrna/intern/rna_internal_types.h index 0c0260c889c..245730919b0 100644 --- a/source/blender/makesrna/intern/rna_internal_types.h +++ b/source/blender/makesrna/intern/rna_internal_types.h @@ -400,6 +400,7 @@ typedef struct IntPropertyRNA { PropIntArraySetFuncEx setarray_ex; PropIntRangeFuncEx range_ex; + PropertyScaleType ui_scale_type; int softmin, softmax; int hardmin, hardmax; int step; @@ -423,6 +424,7 @@ typedef struct FloatPropertyRNA { PropFloatArraySetFuncEx setarray_ex; PropFloatRangeFuncEx range_ex; + PropertyScaleType ui_scale_type; float softmin, softmax; float hardmin, hardmax; float step; diff --git a/source/blender/makesrna/intern/rna_lattice_api.c b/source/blender/makesrna/intern/rna_lattice_api.c index 0b61603dd09..5b69a743d47 100644 --- a/source/blender/makesrna/intern/rna_lattice_api.c +++ b/source/blender/makesrna/intern/rna_lattice_api.c @@ -33,7 +33,7 @@ #include "rna_internal.h" /* own include */ #ifdef RNA_RUNTIME -static void rna_Lattice_transform(Lattice *lt, float *mat, bool shape_keys) +static void rna_Lattice_transform(Lattice *lt, float mat[16], bool shape_keys) { BKE_lattice_transform(lt, (float(*)[4])mat, shape_keys); diff --git a/source/blender/makesrna/intern/rna_main.c b/source/blender/makesrna/intern/rna_main.c index c80f856dd6b..464abc6b543 100644 --- a/source/blender/makesrna/intern/rna_main.c +++ b/source/blender/makesrna/intern/rna_main.c @@ -134,7 +134,7 @@ RNA_MAIN_LISTBASE_FUNCS_DEF(pointclouds) RNA_MAIN_LISTBASE_FUNCS_DEF(scenes) RNA_MAIN_LISTBASE_FUNCS_DEF(screens) RNA_MAIN_LISTBASE_FUNCS_DEF(shapekeys) -# ifdef WITH_GEOMETRY_NODES +# ifdef WITH_SIMULATION_DATABLOCK RNA_MAIN_LISTBASE_FUNCS_DEF(simulations) # endif RNA_MAIN_LISTBASE_FUNCS_DEF(sounds) @@ -407,7 +407,7 @@ void RNA_def_main(BlenderRNA *brna) "Volumes", "Volume data-blocks", RNA_def_main_volumes}, -# ifdef WITH_GEOMETRY_NODES +# ifdef WITH_SIMULATION_DATABLOCK {"simulations", "Simulation", "rna_Main_simulations_begin", diff --git a/source/blender/makesrna/intern/rna_main_api.c b/source/blender/makesrna/intern/rna_main_api.c index 6c2fb649986..8e6ff961721 100644 --- a/source/blender/makesrna/intern/rna_main_api.c +++ b/source/blender/makesrna/intern/rna_main_api.c @@ -806,7 +806,7 @@ static Volume *rna_Main_volumes_new(Main *bmain, const char *name) return volume; } -# ifdef WITH_GEOMETRY_NODES +# ifdef WITH_SIMULATION_DATABLOCK static Simulation *rna_Main_simulations_new(Main *bmain, const char *name) { char safe_name[MAX_ID_NAME - 2]; @@ -870,7 +870,7 @@ RNA_MAIN_ID_TAG_FUNCS_DEF(hairs, hairs, ID_HA) RNA_MAIN_ID_TAG_FUNCS_DEF(pointclouds, pointclouds, ID_PT) # endif RNA_MAIN_ID_TAG_FUNCS_DEF(volumes, volumes, ID_VO) -# ifdef WITH_GEOMETRY_NODES +# ifdef WITH_SIMULATION_DATABLOCK RNA_MAIN_ID_TAG_FUNCS_DEF(simulations, simulations, ID_SIM) # endif @@ -2412,7 +2412,7 @@ void RNA_def_main_volumes(BlenderRNA *brna, PropertyRNA *cprop) RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); } -# ifdef WITH_GEOMETRY_NODES +# ifdef WITH_SIMULATION_DATABLOCK void RNA_def_main_simulations(BlenderRNA *brna, PropertyRNA *cprop) { StructRNA *srna; diff --git a/source/blender/makesrna/intern/rna_mesh_api.c b/source/blender/makesrna/intern/rna_mesh_api.c index f92a2932f06..2b0582cae9a 100644 --- a/source/blender/makesrna/intern/rna_mesh_api.c +++ b/source/blender/makesrna/intern/rna_mesh_api.c @@ -172,7 +172,7 @@ static void rna_Mesh_normals_split_custom_set_from_vertices(Mesh *mesh, DEG_id_tag_update(&mesh->id, 0); } -static void rna_Mesh_transform(Mesh *mesh, float *mat, bool shape_keys) +static void rna_Mesh_transform(Mesh *mesh, float mat[16], bool shape_keys) { BKE_mesh_transform(mesh, (float(*)[4])mat, shape_keys); diff --git a/source/blender/makesrna/intern/rna_meta_api.c b/source/blender/makesrna/intern/rna_meta_api.c index 19d0b35959b..93bd9fe3b9c 100644 --- a/source/blender/makesrna/intern/rna_meta_api.c +++ b/source/blender/makesrna/intern/rna_meta_api.c @@ -35,7 +35,7 @@ #include "rna_internal.h" /* own include */ #ifdef RNA_RUNTIME -static void rna_Meta_transform(struct MetaBall *mb, float *mat) +static void rna_Meta_transform(struct MetaBall *mb, float mat[16]) { BKE_mball_transform(mb, (float(*)[4])mat, true); diff --git a/source/blender/makesrna/intern/rna_modifier.c b/source/blender/makesrna/intern/rna_modifier.c index e3fb443951f..674e5845ccb 100644 --- a/source/blender/makesrna/intern/rna_modifier.c +++ b/source/blender/makesrna/intern/rna_modifier.c @@ -2781,7 +2781,8 @@ static void rna_def_modifier_boolean(BlenderRNA *brna) prop = RNA_def_property(srna, "double_threshold", PROP_FLOAT, PROP_DISTANCE); RNA_def_property_float_sdna(prop, NULL, "double_threshold"); RNA_def_property_range(prop, 0, 1.0f); - RNA_def_property_ui_range(prop, 0, 1, 0.0001, 6); + RNA_def_property_ui_range(prop, 0, 1, 1.0, 6); + RNA_def_property_ui_scale_type(prop, PROP_SCALE_LOG); RNA_def_property_ui_text( prop, "Overlap Threshold", "Threshold for checking overlapping geometry"); RNA_def_property_update(prop, 0, "rna_Modifier_update"); @@ -5530,6 +5531,7 @@ static void rna_def_modifier_remesh(BlenderRNA *brna) RNA_def_property_float_sdna(prop, NULL, "voxel_size"); RNA_def_property_range(prop, 0.0001f, FLT_MAX); RNA_def_property_ui_range(prop, 0.0001, 2, 0.1, 3); + RNA_def_property_ui_scale_type(prop, PROP_SCALE_LOG); RNA_def_property_ui_text(prop, "Voxel Size", "Size of the voxel in object space used for volume evaluation. Lower " diff --git a/source/blender/makesrna/intern/rna_movieclip.c b/source/blender/makesrna/intern/rna_movieclip.c index c9520c939f4..f8fa2aab5e7 100644 --- a/source/blender/makesrna/intern/rna_movieclip.c +++ b/source/blender/makesrna/intern/rna_movieclip.c @@ -402,7 +402,7 @@ static void rna_def_movieclip(BlenderRNA *brna) "Start Frame", "Global scene frame number at which this movie starts playing " "(affects all data associated with a clip)"); - RNA_def_property_update(prop, NC_MOVIECLIP | ND_DISPLAY, "rna_MovieClip_reload_update"); + RNA_def_property_update(prop, NC_MOVIECLIP | ND_DISPLAY, NULL); /* frame_offset */ prop = RNA_def_property(srna, "frame_offset", PROP_INT, PROP_NONE); @@ -412,7 +412,7 @@ static void rna_def_movieclip(BlenderRNA *brna) "Frame Offset", "Offset of footage first frame relative to its file name " "(affects only how footage is loading, does not change data associated with a clip)"); - RNA_def_property_update(prop, NC_MOVIECLIP | ND_DISPLAY, "rna_MovieClip_reload_update"); + RNA_def_property_update(prop, NC_MOVIECLIP | ND_DISPLAY, NULL); /* length */ prop = RNA_def_property(srna, "frame_duration", PROP_INT, PROP_NONE); diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index b1b2e9738c1..11f5ff0441a 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -65,6 +65,22 @@ const EnumPropertyItem rna_enum_node_socket_in_out_items[] = { {SOCK_IN, "IN", 0, "Input", ""}, {SOCK_OUT, "OUT", 0, "Output", ""}, {0, NULL, 0, NULL, NULL}}; +static const EnumPropertyItem node_socket_data_type_items[] = { + {SOCK_FLOAT, "FLOAT", 0, "Float", ""}, + {SOCK_INT, "INT", 0, "Integer", ""}, + {SOCK_BOOLEAN, "BOOLEAN", 0, "Boolean", ""}, + {SOCK_VECTOR, "VECTOR", 0, "Vector", ""}, + {SOCK_STRING, "STRING", 0, "String", ""}, + {SOCK_RGBA, "RGBA", 0, "Color", ""}, + {SOCK_OBJECT, "OBJECT", 0, "Object", ""}, + {SOCK_IMAGE, "IMAGE", 0, "Image", ""}, + {SOCK_GEOMETRY, "GEOMETRY", 0, "Geometry", ""}, + {SOCK_COLLECTION, "COLLECTION", 0, "Collection", ""}, + {SOCK_TEXTURE, "TEXTURE", 0, "Texture", ""}, + {SOCK_MATERIAL, "MATERIAL", 0, "Material", ""}, + {0, NULL, 0, NULL, NULL}, +}; + #ifndef RNA_RUNTIME static const EnumPropertyItem rna_enum_node_socket_display_shape_items[] = { {SOCK_DISPLAY_SHAPE_CIRCLE, "CIRCLE", 0, "Circle", ""}, @@ -78,7 +94,7 @@ static const EnumPropertyItem rna_enum_node_socket_display_shape_items[] = { static const EnumPropertyItem node_socket_type_items[] = { {SOCK_CUSTOM, "CUSTOM", 0, "Custom", ""}, {SOCK_FLOAT, "VALUE", 0, "Value", ""}, - {SOCK_INT, "INT", 0, "Int", ""}, + {SOCK_INT, "INT", 0, "Integer", ""}, {SOCK_BOOLEAN, "BOOLEAN", 0, "Boolean", ""}, {SOCK_VECTOR, "VECTOR", 0, "Vector", ""}, {SOCK_STRING, "STRING", 0, "String", ""}, @@ -88,6 +104,8 @@ static const EnumPropertyItem node_socket_type_items[] = { {SOCK_IMAGE, "IMAGE", 0, "Image", ""}, {SOCK_GEOMETRY, "GEOMETRY", 0, "Geometry", ""}, {SOCK_COLLECTION, "COLLECTION", 0, "Collection", ""}, + {SOCK_TEXTURE, "TEXTURE", 0, "Texture", ""}, + {SOCK_MATERIAL, "MATERIAL", 0, "Material", ""}, {0, NULL, 0, NULL, NULL}, }; @@ -957,6 +975,32 @@ static void rna_NodeTree_get_from_context( RNA_parameter_list_free(&list); } +static bool rna_NodeTree_valid_socket_type(eNodeSocketDatatype socket_type, + bNodeTreeType *ntreetype) +{ + extern FunctionRNA rna_NodeTree_valid_socket_type_func; + + PointerRNA ptr; + ParameterList list; + FunctionRNA *func; + void *ret; + bool valid; + + RNA_pointer_create(NULL, ntreetype->rna_ext.srna, NULL, &ptr); /* dummy */ + func = &rna_NodeTree_valid_socket_type_func; + + RNA_parameter_list_create(&list, &ptr, func); + RNA_parameter_set_lookup(&list, "type", &socket_type); + ntreetype->rna_ext.call(NULL, &ptr, func, &list); + + RNA_parameter_get_lookup(&list, "valid", &ret); + valid = *(bool *)ret; + + RNA_parameter_list_free(&list); + + return valid; +} + static void rna_NodeTree_unregister(Main *UNUSED(bmain), StructRNA *type) { bNodeTreeType *nt = RNA_struct_blender_type_get(type); @@ -985,7 +1029,7 @@ static StructRNA *rna_NodeTree_register(Main *bmain, bNodeTreeType *nt, dummynt; bNodeTree dummyntree; PointerRNA dummyptr; - int have_function[3]; + int have_function[4]; /* setup dummy tree & tree type to store static properties in */ memset(&dummynt, 0, sizeof(bNodeTreeType)); @@ -1031,6 +1075,7 @@ static StructRNA *rna_NodeTree_register(Main *bmain, nt->poll = (have_function[0]) ? rna_NodeTree_poll : NULL; nt->update = (have_function[1]) ? rna_NodeTree_update_reg : NULL; nt->get_from_context = (have_function[2]) ? rna_NodeTree_get_from_context : NULL; + nt->valid_socket_type = (have_function[3]) ? rna_NodeTree_valid_socket_type : NULL; ntreeTypeAdd(nt); @@ -1914,6 +1959,29 @@ static const EnumPropertyItem *itemf_function_check( return item_array; } +static bool switch_type_supported(const EnumPropertyItem *item) +{ + return ELEM(item->value, + SOCK_FLOAT, + SOCK_INT, + SOCK_BOOLEAN, + SOCK_VECTOR, + SOCK_STRING, + SOCK_RGBA, + SOCK_GEOMETRY, + SOCK_OBJECT, + SOCK_COLLECTION); +} + +static const EnumPropertyItem *rna_GeometryNodeSwitch_type_itemf(bContext *UNUSED(C), + PointerRNA *UNUSED(ptr), + PropertyRNA *UNUSED(prop), + bool *r_free) +{ + *r_free = true; + return itemf_function_check(node_socket_data_type_items, switch_type_supported); +} + static bool attribute_clamp_type_supported(const EnumPropertyItem *item) { return ELEM(item->value, CD_PROP_FLOAT, CD_PROP_FLOAT3, CD_PROP_INT32, CD_PROP_COLOR); @@ -1984,6 +2052,7 @@ static void rna_GeometryNodeAttributeRandomize_data_type_update(Main *bmain, static bool attribute_convert_type_supported(const EnumPropertyItem *item) { return ELEM(item->value, + CD_AUTO_FROM_NAME, CD_PROP_FLOAT, CD_PROP_FLOAT2, CD_PROP_FLOAT3, @@ -1995,7 +2064,8 @@ static const EnumPropertyItem *rna_GeometryNodeAttributeConvert_type_itemf( bContext *UNUSED(C), PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) { *r_free = true; - return itemf_function_check(rna_enum_attribute_type_items, attribute_convert_type_supported); + return itemf_function_check(rna_enum_attribute_type_with_auto_items, + attribute_convert_type_supported); } static bool attribute_fill_type_supported(const EnumPropertyItem *item) @@ -2117,6 +2187,17 @@ static const EnumPropertyItem *rna_GeometryNodeAttributeMapRange_type_itemf( return itemf_function_check(rna_enum_attribute_type_items, attribute_map_range_type_supported); } +static bool attribute_curve_map_type_supported(const EnumPropertyItem *item) +{ + return ELEM(item->value, CD_PROP_FLOAT, CD_PROP_FLOAT3, CD_PROP_COLOR); +} +static const EnumPropertyItem *rna_GeometryNodeAttributeCurveMap_type_itemf( + bContext *UNUSED(C), PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free) +{ + *r_free = true; + return itemf_function_check(rna_enum_attribute_type_items, attribute_curve_map_type_supported); +} + static StructRNA *rna_ShaderNode_register(Main *bmain, ReportList *reports, void *data, @@ -3053,7 +3134,7 @@ static void rna_NodeSocketStandard_draw(ID *id, } static void rna_NodeSocketStandard_draw_color( - ID *id, bNodeSocket *sock, struct bContext *C, PointerRNA *nodeptr, float *r_color) + ID *id, bNodeSocket *sock, struct bContext *C, PointerRNA *nodeptr, float r_color[4]) { PointerRNA ptr; RNA_pointer_create(id, &RNA_NodeSocket, sock, &ptr); @@ -3073,7 +3154,7 @@ static void rna_NodeSocketInterfaceStandard_draw(ID *id, static void rna_NodeSocketInterfaceStandard_draw_color(ID *id, bNodeSocket *sock, struct bContext *C, - float *r_color) + float r_color[4]) { PointerRNA ptr; RNA_pointer_create(id, &RNA_NodeSocket, sock, &ptr); @@ -4375,6 +4456,13 @@ void rna_ShaderNodePointDensity_density_minmax(bNode *self, RE_point_density_minmax(depsgraph, pd, r_min, r_max); } +bool rna_NodeSocketMaterial_default_value_poll(PointerRNA *UNUSED(ptr), PointerRNA value) +{ + /* Do not show grease pencil materials for now. */ + Material *ma = (Material *)value.data; + return ma->gp_style == NULL; +} + #else static const EnumPropertyItem prop_image_layer_items[] = { @@ -8954,9 +9042,9 @@ static void def_geo_attribute_convert(StructRNA *srna) RNA_def_struct_sdna_from(srna, "NodeAttributeConvert", "storage"); prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE); - RNA_def_property_enum_items(prop, rna_enum_attribute_type_items); + RNA_def_property_enum_items(prop, rna_enum_attribute_type_with_auto_items); RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_GeometryNodeAttributeConvert_type_itemf"); - RNA_def_property_enum_default(prop, CD_PROP_FLOAT); + RNA_def_property_enum_default(prop, CD_AUTO_FROM_NAME); RNA_def_property_ui_text(prop, "Data Type", "The data type to save the result attribute with"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_GeometryNode_socket_update"); @@ -9197,6 +9285,85 @@ static void def_geo_attribute_color_ramp(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_geo_attribute_curve_map(StructRNA *srna) +{ + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeAttributeCurveMap", "storage"); + + prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "data_type"); + RNA_def_property_enum_items(prop, rna_enum_attribute_type_items); + RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_GeometryNodeAttributeCurveMap_type_itemf"); + RNA_def_property_ui_text(prop, "Data Type", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + + prop = RNA_def_property(srna, "curve_vec", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "CurveMapping"); + RNA_def_property_ui_text(prop, "Mapping", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + prop = RNA_def_property(srna, "curve_rgb", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "CurveMapping"); + RNA_def_property_ui_text(prop, "Mapping", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + +static void def_geo_attribute_vector_rotate(StructRNA *srna) +{ + static const EnumPropertyItem rotate_mode_items[] = { + {GEO_NODE_VECTOR_ROTATE_TYPE_AXIS, + "AXIS_ANGLE", + 0, + "Axis Angle", + "Rotate a point using axis angle"}, + {GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_X, "X_AXIS", 0, "X Axis", "Rotate a point using X axis"}, + {GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_Y, "Y_AXIS", 0, "Y Axis", "Rotate a point using Y axis"}, + {GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_Z, "Z_AXIS", 0, "Z Axis", "Rotate a point using Z axis"}, + {GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ, + "EULER_XYZ", + 0, + "Euler", + "Rotate a point using XYZ order"}, + {0, NULL, 0, NULL, NULL}, + }; + + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeAttributeVectorRotate", "storage"); + + prop = RNA_def_property(srna, "rotation_mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "mode"); + RNA_def_property_enum_items(prop, rotate_mode_items); + RNA_def_property_ui_text(prop, "Mode", "Type of rotation"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_ShaderNode_socket_update"); + + prop = RNA_def_property(srna, "input_type_vector", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items_vector); + RNA_def_property_ui_text(prop, "Input Type Vector", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + + prop = RNA_def_property(srna, "input_type_center", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items_vector); + RNA_def_property_ui_text(prop, "Input Type Center", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + + prop = RNA_def_property(srna, "input_type_axis", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items_vector); + RNA_def_property_ui_text(prop, "Input Type Axis", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + + prop = RNA_def_property(srna, "input_type_angle", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items_float); + RNA_def_property_ui_text(prop, "Input Type Angle", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + + prop = RNA_def_property(srna, "input_type_rotation", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_node_geometry_attribute_input_type_items_vector); + RNA_def_property_ui_text(prop, "Input Type Rotation", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); +} + static void def_geo_point_rotate(StructRNA *srna) { static const EnumPropertyItem type_items[] = { @@ -9351,19 +9518,6 @@ static void def_geo_point_translate(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } -static void def_geo_attribute_sample_texture(StructRNA *srna) -{ - PropertyRNA *prop; - - prop = RNA_def_property(srna, "texture", PROP_POINTER, PROP_NONE); - RNA_def_property_pointer_sdna(prop, NULL, "id"); - RNA_def_property_struct_type(prop, "Texture"); - RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT); - RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); - RNA_def_property_ui_text(prop, "Texture", "Texture to sample values from"); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update_relations"); -} - static void def_geo_object_info(StructRNA *srna) { PropertyRNA *prop; @@ -9631,6 +9785,93 @@ static void def_geo_mesh_line(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } +static void def_geo_switch(StructRNA *srna) +{ + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeSwitch", "storage"); + prop = RNA_def_property(srna, "input_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "input_type"); + RNA_def_property_enum_items(prop, node_socket_data_type_items); + RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_GeometryNodeSwitch_type_itemf"); + RNA_def_property_ui_text(prop, "Input Type", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); +} + +static void def_geo_curve_resample(StructRNA *srna) +{ + PropertyRNA *prop; + + static EnumPropertyItem mode_items[] = { + {GEO_NODE_CURVE_SAMPLE_COUNT, + "COUNT", + 0, + "Count", + "Sample the specified number of points along each spline"}, + {GEO_NODE_CURVE_SAMPLE_LENGTH, + "LENGTH", + 0, + "Length", + "Calculate the number of samples by splitting each spline into segments with the specified " + "length"}, + {0, NULL, 0, NULL, NULL}, + }; + + RNA_def_struct_sdna_from(srna, "NodeGeometryCurveResample", "storage"); + + prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, mode_items); + RNA_def_property_ui_text(prop, "Mode", "How to specify the amount of samples"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); +} + +static void def_geo_attribute_transfer(StructRNA *srna) +{ + static EnumPropertyItem mapping_items[] = { + {GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED, + "NEAREST_FACE_INTERPOLATED", + 0, + "Nearest Face Interpolated", + "Transfer the attribute from the nearest face on a surface (loose points and edges are " + "ignored)"}, + {GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST, + "NEAREST", + 0, + "Nearest", + "Transfer the element from the nearest element (using face and edge centers for the " + "distance computation)"}, + {0, NULL, 0, NULL, NULL}, + }; + + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeGeometryAttributeTransfer", "storage"); + + prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_attribute_domain_with_auto_items); + RNA_def_property_enum_default(prop, ATTR_DOMAIN_AUTO); + RNA_def_property_ui_text(prop, "Domain", "The geometry domain to save the result attribute in"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + prop = RNA_def_property(srna, "mapping", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, mapping_items); + RNA_def_property_ui_text(prop, "Mapping", "Mapping between geometries"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + +static void def_geo_input_material(StructRNA *srna) +{ + PropertyRNA *prop; + + prop = RNA_def_property(srna, "material", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "id"); + RNA_def_property_struct_type(prop, "Material"); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); + RNA_def_property_ui_text(prop, "Material", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + /* -------------------------------------------------------------------------- */ static void rna_def_shader_node(BlenderRNA *brna) @@ -10403,6 +10644,80 @@ static void rna_def_node_socket_collection(BlenderRNA *brna, RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeSocketInterface_update"); } +static void rna_def_node_socket_texture(BlenderRNA *brna, + const char *identifier, + const char *interface_idname) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, identifier, "NodeSocketStandard"); + RNA_def_struct_ui_text(srna, "Texture Node Socket", "Texture socket of a node"); + RNA_def_struct_sdna(srna, "bNodeSocket"); + + RNA_def_struct_sdna_from(srna, "bNodeSocketValueTexture", "default_value"); + + prop = RNA_def_property(srna, "default_value", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "value"); + RNA_def_property_struct_type(prop, "Texture"); + RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket"); + RNA_def_property_update( + prop, NC_NODE | NA_EDITED, "rna_NodeSocketStandard_value_and_relation_update"); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT | PROP_CONTEXT_UPDATE); + + /* socket interface */ + srna = RNA_def_struct(brna, interface_idname, "NodeSocketInterfaceStandard"); + RNA_def_struct_ui_text(srna, "Texture Node Socket Interface", "Texture socket of a node"); + RNA_def_struct_sdna(srna, "bNodeSocket"); + + RNA_def_struct_sdna_from(srna, "bNodeSocketValueTexture", "default_value"); + + prop = RNA_def_property(srna, "default_value", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "value"); + RNA_def_property_struct_type(prop, "Texture"); + RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeSocketInterface_update"); +} + +static void rna_def_node_socket_material(BlenderRNA *brna, + const char *identifier, + const char *interface_idname) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, identifier, "NodeSocketStandard"); + RNA_def_struct_ui_text(srna, "Material Node Socket", "Material socket of a node"); + RNA_def_struct_sdna(srna, "bNodeSocket"); + + RNA_def_struct_sdna_from(srna, "bNodeSocketValueMaterial", "default_value"); + + prop = RNA_def_property(srna, "default_value", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "value"); + RNA_def_property_struct_type(prop, "Material"); + RNA_def_property_pointer_funcs( + prop, NULL, NULL, NULL, "rna_NodeSocketMaterial_default_value_poll"); + RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket"); + RNA_def_property_update( + prop, NC_NODE | NA_EDITED, "rna_NodeSocketStandard_value_and_relation_update"); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT | PROP_CONTEXT_UPDATE); + + /* socket interface */ + srna = RNA_def_struct(brna, interface_idname, "NodeSocketInterfaceStandard"); + RNA_def_struct_ui_text(srna, "Material Node Socket Interface", "Material socket of a node"); + RNA_def_struct_sdna(srna, "bNodeSocket"); + + RNA_def_struct_sdna_from(srna, "bNodeSocketValueMaterial", "default_value"); + + prop = RNA_def_property(srna, "default_value", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "value"); + RNA_def_property_struct_type(prop, "Material"); + RNA_def_property_pointer_funcs( + prop, NULL, NULL, NULL, "rna_NodeSocketMaterial_default_value_poll"); + RNA_def_property_ui_text(prop, "Default Value", "Input value used for unconnected socket"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeSocketInterface_update"); +} + static void rna_def_node_socket_standard_types(BlenderRNA *brna) { /* XXX Workaround: Registered functions are not exposed in python by bpy, @@ -10547,6 +10862,10 @@ static void rna_def_node_socket_standard_types(BlenderRNA *brna) rna_def_node_socket_geometry(brna, "NodeSocketGeometry", "NodeSocketInterfaceGeometry"); rna_def_node_socket_collection(brna, "NodeSocketCollection", "NodeSocketInterfaceCollection"); + + rna_def_node_socket_texture(brna, "NodeSocketTexture", "NodeSocketInterfaceTexture"); + + rna_def_node_socket_material(brna, "NodeSocketMaterial", "NodeSocketInterfaceMaterial"); } static void rna_def_internal_node(BlenderRNA *brna) @@ -11331,6 +11650,14 @@ static void rna_def_nodetree(BlenderRNA *brna) parm = RNA_def_pointer( func, "result_3", "ID", "From ID", "Original ID data-block selected from the context"); RNA_def_function_output(func, parm); + + /* Check for support of a socket type. */ + func = RNA_def_function(srna, "valid_socket_type", NULL); + RNA_def_function_ui_description(func, "Check if the socket type is valid for the node tree"); + RNA_def_function_flag(func, FUNC_NO_SELF | FUNC_REGISTER_OPTIONAL); + parm = RNA_def_enum(func, "type", node_socket_type_items, 0, "", ""); + RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); + RNA_def_function_return(func, RNA_def_boolean(func, "valid", false, "", "")); } static void rna_def_composite_nodetree(BlenderRNA *brna) diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c index 6b93a1c223c..b339682222c 100644 --- a/source/blender/makesrna/intern/rna_object.c +++ b/source/blender/makesrna/intern/rna_object.c @@ -62,6 +62,8 @@ #include "WM_api.h" #include "WM_types.h" +#include "DEG_depsgraph_query.h" + const EnumPropertyItem rna_enum_object_mode_items[] = { {OB_MODE_OBJECT, "OBJECT", ICON_OBJECT_DATAMODE, "Object Mode", ""}, {OB_MODE_EDIT, "EDIT", ICON_EDITMODE_HLT, "Edit Mode", ""}, @@ -84,7 +86,7 @@ const EnumPropertyItem rna_enum_object_mode_items[] = { {OB_MODE_PAINT_GPENCIL, "PAINT_GPENCIL", ICON_GREASEPENCIL, - "Draw", + "Draw Mode", "Paint Grease Pencil Strokes"}, {OB_MODE_WEIGHT_GPENCIL, "WEIGHT_GPENCIL", @@ -1253,10 +1255,16 @@ static int rna_Object_rotation_4d_editable(PointerRNA *ptr, int index) return PROP_EDITABLE; } +static int rna_MaterialSlot_index(PointerRNA *ptr) +{ + /* There is an offset of one, so that `ptr->data` is not null. */ + return POINTER_AS_INT(ptr->data) - 1; +} + static int rna_MaterialSlot_material_editable(PointerRNA *ptr, const char **UNUSED(r_info)) { Object *ob = (Object *)ptr->owner_id; - const int index = (Material **)ptr->data - ob->mat; + const int index = rna_MaterialSlot_index(ptr); bool is_editable; if ((ob->matbits == NULL) || ob->matbits[index]) { @@ -1273,9 +1281,14 @@ static PointerRNA rna_MaterialSlot_material_get(PointerRNA *ptr) { Object *ob = (Object *)ptr->owner_id; Material *ma; - const int index = (Material **)ptr->data - ob->mat; + const int index = rna_MaterialSlot_index(ptr); - ma = BKE_object_material_get(ob, index + 1); + if (DEG_is_evaluated_object(ob)) { + ma = BKE_object_material_get_eval(ob, index + 1); + } + else { + ma = BKE_object_material_get(ob, index + 1); + } return rna_pointer_inherit_refine(ptr, &RNA_Material, ma); } @@ -1284,7 +1297,7 @@ static void rna_MaterialSlot_material_set(PointerRNA *ptr, struct ReportList *UNUSED(reports)) { Object *ob = (Object *)ptr->owner_id; - int index = (Material **)ptr->data - ob->mat; + int index = rna_MaterialSlot_index(ptr); BLI_assert(BKE_id_is_in_global_main(&ob->id)); BLI_assert(BKE_id_is_in_global_main(value.data)); @@ -1309,15 +1322,17 @@ static bool rna_MaterialSlot_material_poll(PointerRNA *ptr, PointerRNA value) static int rna_MaterialSlot_link_get(PointerRNA *ptr) { Object *ob = (Object *)ptr->owner_id; - int index = (Material **)ptr->data - ob->mat; - - return ob->matbits[index] != 0; + int index = rna_MaterialSlot_index(ptr); + if (index < ob->totcol) { + return ob->matbits[index] != 0; + } + return false; } static void rna_MaterialSlot_link_set(PointerRNA *ptr, int value) { Object *ob = (Object *)ptr->owner_id; - int index = (Material **)ptr->data - ob->mat; + int index = rna_MaterialSlot_index(ptr); if (value) { ob->matbits[index] = 1; @@ -1335,7 +1350,7 @@ static int rna_MaterialSlot_name_length(PointerRNA *ptr) { Object *ob = (Object *)ptr->owner_id; Material *ma; - int index = (Material **)ptr->data - ob->mat; + int index = rna_MaterialSlot_index(ptr); ma = BKE_object_material_get(ob, index + 1); @@ -1350,7 +1365,7 @@ static void rna_MaterialSlot_name_get(PointerRNA *ptr, char *str) { Object *ob = (Object *)ptr->owner_id; Material *ma; - int index = (Material **)ptr->data - ob->mat; + int index = rna_MaterialSlot_index(ptr); ma = BKE_object_material_get(ob, index + 1); @@ -1373,10 +1388,49 @@ static void rna_MaterialSlot_update(Main *bmain, Scene *scene, PointerRNA *ptr) static char *rna_MaterialSlot_path(PointerRNA *ptr) { + int index = rna_MaterialSlot_index(ptr); + return BLI_sprintfN("material_slots[%d]", index); +} + +static int rna_Object_material_slots_length(PointerRNA *ptr) +{ Object *ob = (Object *)ptr->owner_id; - int index = (Material **)ptr->data - ob->mat; + if (DEG_is_evaluated_object(ob)) { + return BKE_object_material_count_eval(ob); + } + else { + return ob->totcol; + } +} - return BLI_sprintfN("material_slots[%d]", index); +static void rna_Object_material_slots_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) +{ + const int length = rna_Object_material_slots_length(ptr); + iter->internal.count.item = 0; + iter->internal.count.ptr = ptr->owner_id; + iter->valid = length > 0; +} + +static void rna_Object_material_slots_next(CollectionPropertyIterator *iter) +{ + const int length = rna_Object_material_slots_length(&iter->ptr); + iter->internal.count.item++; + iter->valid = iter->internal.count.item < length; +} + +static PointerRNA rna_Object_material_slots_get(CollectionPropertyIterator *iter) +{ + PointerRNA ptr; + RNA_pointer_create((ID *)iter->internal.count.ptr, + &RNA_MaterialSlot, + /* Add one, so that `ptr->data` is not null. */ + POINTER_FROM_INT(iter->internal.count.item + 1), + &ptr); + return ptr; +} + +static void rna_Object_material_slots_end(CollectionPropertyIterator *UNUSED(iter)) +{ } static PointerRNA rna_Object_display_get(PointerRNA *ptr) @@ -1448,11 +1502,6 @@ static PointerRNA rna_Object_field_get(PointerRNA *ptr) { Object *ob = (Object *)ptr->owner_id; - /* weak */ - if (!ob->pd) { - ob->pd = BKE_partdeflect_new(0); - } - return rna_pointer_inherit_refine(ptr, &RNA_FieldSettings, ob->pd); } @@ -1464,11 +1513,6 @@ static PointerRNA rna_Object_collision_get(PointerRNA *ptr) return PointerRNA_NULL; } - /* weak */ - if (!ob->pd) { - ob->pd = BKE_partdeflect_new(0); - } - return rna_pointer_inherit_refine(ptr, &RNA_CollisionSettings, ob->pd); } @@ -2958,12 +3002,18 @@ static void rna_def_object(BlenderRNA *brna) /* materials */ prop = RNA_def_property(srna, "material_slots", PROP_COLLECTION, PROP_NONE); - RNA_def_property_collection_sdna(prop, NULL, "mat", "totcol"); RNA_def_property_struct_type(prop, "MaterialSlot"); RNA_def_property_override_flag(prop, PROPOVERRIDE_NO_PROP_NAME); - /* don't dereference pointer! */ - RNA_def_property_collection_funcs( - prop, NULL, NULL, NULL, "rna_iterator_array_get", NULL, NULL, NULL, NULL); + /* Don't dereference the material slot pointer, it is the slot index encoded in a pointer. */ + RNA_def_property_collection_funcs(prop, + "rna_Object_material_slots_begin", + "rna_Object_material_slots_next", + "rna_Object_material_slots_end", + "rna_Object_material_slots_get", + "rna_Object_material_slots_length", + NULL, + NULL, + NULL); RNA_def_property_ui_text(prop, "Material Slots", "Material slots in the object"); prop = RNA_def_property(srna, "active_material", PROP_POINTER, PROP_NONE); diff --git a/source/blender/makesrna/intern/rna_object_api.c b/source/blender/makesrna/intern/rna_object_api.c index df628caa000..e463323c6dc 100644 --- a/source/blender/makesrna/intern/rna_object_api.c +++ b/source/blender/makesrna/intern/rna_object_api.c @@ -296,8 +296,8 @@ static bool rna_Object_visible_in_viewport_get(Object *ob, View3D *v3d) static void rna_Object_mat_convert_space(Object *ob, ReportList *reports, bPoseChannel *pchan, - float *mat, - float *mat_ret, + float mat[16], + float mat_ret[16], int from, int to) { diff --git a/source/blender/makesrna/intern/rna_particle.c b/source/blender/makesrna/intern/rna_particle.c index d94e68a6808..7ff2a82a465 100644 --- a/source/blender/makesrna/intern/rna_particle.c +++ b/source/blender/makesrna/intern/rna_particle.c @@ -1413,24 +1413,12 @@ static const EnumPropertyItem *rna_Particle_ren_as_itemf(bContext *UNUSED(C), static PointerRNA rna_Particle_field1_get(PointerRNA *ptr) { ParticleSettings *part = (ParticleSettings *)ptr->owner_id; - - /* weak */ - if (!part->pd) { - part->pd = BKE_partdeflect_new(0); - } - return rna_pointer_inherit_refine(ptr, &RNA_FieldSettings, part->pd); } static PointerRNA rna_Particle_field2_get(PointerRNA *ptr) { ParticleSettings *part = (ParticleSettings *)ptr->owner_id; - - /* weak */ - if (!part->pd2) { - part->pd2 = BKE_partdeflect_new(0); - } - return rna_pointer_inherit_refine(ptr, &RNA_FieldSettings, part->pd2); } diff --git a/source/blender/makesrna/intern/rna_pose.c b/source/blender/makesrna/intern/rna_pose.c index ba65e42895c..b8bb4f58dcd 100644 --- a/source/blender/makesrna/intern/rna_pose.c +++ b/source/blender/makesrna/intern/rna_pose.c @@ -1359,12 +1359,27 @@ static void rna_def_pose_channel(BlenderRNA *brna) RNA_def_property_editable_func(prop, "rna_PoseChannel_proxy_editable"); RNA_def_property_update(prop, NC_OBJECT | ND_POSE, "rna_Pose_dependency_update"); - prop = RNA_def_property(srna, "custom_shape_scale", PROP_FLOAT, PROP_NONE); - RNA_def_property_float_sdna(prop, NULL, "custom_scale"); - RNA_def_property_range(prop, 0.0f, 1000.0f); + prop = RNA_def_property(srna, "custom_shape_scale_xyz", PROP_FLOAT, PROP_XYZ); + RNA_def_property_float_sdna(prop, NULL, "custom_scale_xyz"); + RNA_def_property_flag(prop, PROP_PROPORTIONAL); + RNA_def_property_float_array_default(prop, rna_default_scale_3d); RNA_def_property_ui_text(prop, "Custom Shape Scale", "Adjust the size of the custom shape"); RNA_def_property_update(prop, NC_OBJECT | ND_POSE, "rna_Pose_update"); + prop = RNA_def_property(srna, "custom_shape_translation", PROP_FLOAT, PROP_XYZ); + RNA_def_property_float_sdna(prop, NULL, "custom_translation"); + RNA_def_property_flag(prop, PROP_PROPORTIONAL); + RNA_def_property_ui_text( + prop, "Custom Shape Translation", "Adjust the location of the custom shape"); + RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 1, RNA_TRANSLATION_PREC_DEFAULT); + RNA_def_property_update(prop, NC_OBJECT | ND_POSE, "rna_Pose_update"); + + prop = RNA_def_property(srna, "custom_shape_rotation_euler", PROP_FLOAT, PROP_EULER); + RNA_def_property_float_sdna(prop, NULL, "custom_rotation_euler"); + RNA_def_property_ui_text( + prop, "Custom Shape Rotation", "Adjust the rotation of the custom shape"); + RNA_def_property_update(prop, NC_OBJECT | ND_POSE, "rna_Pose_update"); + prop = RNA_def_property(srna, "use_custom_shape_bone_size", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_negative_sdna(prop, NULL, "drawflag", PCHAN_DRAW_NO_CUSTOM_BONE_SIZE); RNA_def_property_ui_text( diff --git a/source/blender/makesrna/intern/rna_pose_api.c b/source/blender/makesrna/intern/rna_pose_api.c index 29516830058..0d35365c2d8 100644 --- a/source/blender/makesrna/intern/rna_pose_api.c +++ b/source/blender/makesrna/intern/rna_pose_api.c @@ -47,7 +47,7 @@ # include "BLI_ghash.h" -static float rna_PoseBone_do_envelope(bPoseChannel *chan, float *vec) +static float rna_PoseBone_do_envelope(bPoseChannel *chan, float vec[3]) { Bone *bone = chan->bone; diff --git a/source/blender/makesrna/intern/rna_scene_api.c b/source/blender/makesrna/intern/rna_scene_api.c index c2089004da2..c49b41867a8 100644 --- a/source/blender/makesrna/intern/rna_scene_api.c +++ b/source/blender/makesrna/intern/rna_scene_api.c @@ -98,7 +98,7 @@ static void rna_Scene_frame_set(Scene *scene, Main *bmain, int frame, float subf } } -static void rna_Scene_uvedit_aspect(Scene *UNUSED(scene), Object *ob, float *aspect) +static void rna_Scene_uvedit_aspect(Scene *UNUSED(scene), Object *ob, float aspect[2]) { if ((ob->type == OB_MESH) && (ob->mode == OB_MODE_EDIT)) { BMEditMesh *em; diff --git a/source/blender/makesrna/intern/rna_sculpt_paint.c b/source/blender/makesrna/intern/rna_sculpt_paint.c index 4481555b931..81acedb76f2 100644 --- a/source/blender/makesrna/intern/rna_sculpt_paint.c +++ b/source/blender/makesrna/intern/rna_sculpt_paint.c @@ -786,7 +786,8 @@ static void rna_def_sculpt(BlenderRNA *brna) RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_Sculpt_ShowMask_update"); prop = RNA_def_property(srna, "detail_size", PROP_FLOAT, PROP_PIXEL); - RNA_def_property_ui_range(prop, 0.5, 40.0, 10, 2); + RNA_def_property_ui_range(prop, 0.5, 40.0, 0.1, 2); + RNA_def_property_ui_scale_type(prop, PROP_SCALE_CUBIC); RNA_def_property_ui_text( prop, "Detail Size", "Maximum edge length for dynamic topology sculpting (in pixels)"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); diff --git a/source/blender/makesrna/intern/rna_sequencer.c b/source/blender/makesrna/intern/rna_sequencer.c index 24d051fecc8..9ba92431723 100644 --- a/source/blender/makesrna/intern/rna_sequencer.c +++ b/source/blender/makesrna/intern/rna_sequencer.c @@ -293,7 +293,7 @@ static void do_sequence_frame_change_update(Scene *scene, Sequence *seq) if (SEQ_transform_test_overlap(seqbase, seq)) { SEQ_transform_seqbase_shuffle(seqbase, seq, scene); /* XXX - BROKEN!, uses context seqbasep */ } - SEQ_sort(scene); + SEQ_sort(seqbase); } /* A simple wrapper around above func, directly usable as prop update func. @@ -476,7 +476,7 @@ static void rna_Sequence_channel_set(PointerRNA *ptr, int value) /* XXX - BROKEN!, uses context seqbasep */ SEQ_transform_seqbase_shuffle_ex(seqbase, seq, scene, channel_delta); } - SEQ_sort(scene); + SEQ_sort(seqbase); SEQ_relations_invalidate_cache_composite(scene, seq); } @@ -505,7 +505,7 @@ static Sequence *sequence_get_by_transform(Editing *ed, StripTransform *transfor data.data = transform; /* irritating we need to search for our sequence! */ - SEQ_iterator_seqbase_recursive_apply(&ed->seqbase, transform_seq_cmp_fn, &data); + SEQ_seqbase_recursive_apply(&ed->seqbase, transform_seq_cmp_fn, &data); return data.seq; } @@ -557,7 +557,7 @@ static Sequence *sequence_get_by_crop(Editing *ed, StripCrop *crop) data.data = crop; /* irritating we need to search for our sequence! */ - SEQ_iterator_seqbase_recursive_apply(&ed->seqbase, crop_seq_cmp_fn, &data); + SEQ_seqbase_recursive_apply(&ed->seqbase, crop_seq_cmp_fn, &data); return data.seq; } @@ -951,7 +951,7 @@ static Sequence *sequence_get_by_proxy(Editing *ed, StripProxy *proxy) data.seq = NULL; data.data = proxy; - SEQ_iterator_seqbase_recursive_apply(&ed->seqbase, seqproxy_seq_cmp_fn, &data); + SEQ_seqbase_recursive_apply(&ed->seqbase, seqproxy_seq_cmp_fn, &data); return data.seq; } @@ -990,18 +990,14 @@ static int colbalance_seq_cmp_fn(Sequence *seq, void *arg_pt) { SequenceSearchData *data = arg_pt; - if (seq->modifiers.first) { - SequenceModifierData *smd = seq->modifiers.first; + for (SequenceModifierData *smd = seq->modifiers.first; smd; smd = smd->next) { + if (smd->type == seqModifierType_ColorBalance) { + ColorBalanceModifierData *cbmd = (ColorBalanceModifierData *)smd; - for (smd = seq->modifiers.first; smd; smd = smd->next) { - if (smd->type == seqModifierType_ColorBalance) { - ColorBalanceModifierData *cbmd = (ColorBalanceModifierData *)smd; - - if (&cbmd->color_balance == data->data) { - data->seq = seq; - data->smd = smd; - return -1; /* done so bail out */ - } + if (&cbmd->color_balance == data->data) { + data->seq = seq; + data->smd = smd; + return -1; /* done so bail out */ } } } @@ -1020,7 +1016,7 @@ static Sequence *sequence_get_by_colorbalance(Editing *ed, data.data = cb; /* irritating we need to search for our sequence! */ - SEQ_iterator_seqbase_recursive_apply(&ed->seqbase, colbalance_seq_cmp_fn, &data); + SEQ_seqbase_recursive_apply(&ed->seqbase, colbalance_seq_cmp_fn, &data); *r_smd = data.smd; @@ -1144,7 +1140,7 @@ static Sequence *sequence_get_by_modifier(Editing *ed, SequenceModifierData *smd data.data = smd; /* irritating we need to search for our sequence! */ - SEQ_iterator_seqbase_recursive_apply(&ed->seqbase, modifier_seq_cmp_fn, &data); + SEQ_seqbase_recursive_apply(&ed->seqbase, modifier_seq_cmp_fn, &data); return data.seq; } @@ -2394,11 +2390,10 @@ static void rna_def_scene(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Input", "Input type to use for the Scene strip"); RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_use_sequence"); - prop = RNA_def_property(srna, "use_grease_pencil", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", SEQ_SCENE_NO_GPENCIL); - RNA_def_property_ui_text( - prop, "Use Grease Pencil", "Show Grease Pencil strokes in OpenGL previews"); - RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, NULL); + prop = RNA_def_property(srna, "use_annotations", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", SEQ_SCENE_NO_ANNOTATION); + RNA_def_property_ui_text(prop, "Use Annotations", "Show Annotations in OpenGL previews"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_invalidate_raw_update"); rna_def_filter_video(srna); rna_def_proxy(srna); diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index 2a513691762..d744f67c6f6 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -3046,6 +3046,10 @@ static void rna_SpaceSpreadsheet_geometry_component_type_update(Main *UNUSED(bma if (sspreadsheet->geometry_component_type == GEO_COMPONENT_TYPE_POINT_CLOUD) { sspreadsheet->attribute_domain = ATTR_DOMAIN_POINT; } + if (sspreadsheet->geometry_component_type == GEO_COMPONENT_TYPE_CURVE && + !ELEM(sspreadsheet->attribute_domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE)) { + sspreadsheet->attribute_domain = ATTR_DOMAIN_POINT; + } } const EnumPropertyItem *rna_SpaceSpreadsheet_attribute_domain_itemf(bContext *UNUSED(C), @@ -3091,6 +3095,11 @@ const EnumPropertyItem *rna_SpaceSpreadsheet_attribute_domain_itemf(bContext *UN continue; } } + if (component_type == GEO_COMPONENT_TYPE_CURVE) { + if (!ELEM(item->value, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE)) { + continue; + } + } if (item->value == ATTR_DOMAIN_POINT && component_type == GEO_COMPONENT_TYPE_MESH) { RNA_enum_item_add(&item_array, &items_len, &mesh_vertex_domain_item); } @@ -4483,7 +4492,6 @@ static void rna_def_space_view3d_overlay(BlenderRNA *brna) prop = RNA_def_property(srna, "gpencil_vertex_paint_opacity", PROP_FLOAT, PROP_FACTOR); RNA_def_property_float_sdna(prop, NULL, "overlay.gpencil_vertex_paint_opacity"); RNA_def_property_range(prop, 0.0f, 1.0f); - RNA_def_property_float_default(prop, 1.0f); RNA_def_property_ui_text(prop, "Opacity", "Vertex Paint mix factor"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, "rna_GPencil_update"); } @@ -7485,6 +7493,11 @@ static void rna_def_space_spreadsheet(BlenderRNA *brna) ICON_POINTCLOUD_DATA, "Point Cloud", "Point cloud component containing only point data"}, + {GEO_COMPONENT_TYPE_CURVE, + "CURVE", + ICON_CURVE_DATA, + "Curve", + "Curve component containing spline and control point data"}, {GEO_COMPONENT_TYPE_INSTANCES, "INSTANCES", ICON_EMPTY_AXIS, diff --git a/source/blender/makesrna/intern/rna_tracking.c b/source/blender/makesrna/intern/rna_tracking.c index c136605c727..336359a9dc0 100644 --- a/source/blender/makesrna/intern/rna_tracking.c +++ b/source/blender/makesrna/intern/rna_tracking.c @@ -697,7 +697,7 @@ static MovieTrackingMarker *rna_trackingMarkers_find_frame(MovieTrackingTrack *t static MovieTrackingMarker *rna_trackingMarkers_insert_frame(MovieTrackingTrack *track, int framenr, - float *co) + float co[2]) { MovieTrackingMarker marker, *new_marker; diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 26887b51f81..bacd3943141 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -6160,7 +6160,7 @@ static void rna_def_userdef_filepaths(BlenderRNA *brna) prop, "Python Scripts Directory", "Alternate script path, matching the default layout with subdirectories: " - "startup, add-ons and modules (requires restart)"); + "startup, addons, modules, and presets (requires restart)"); /* TODO, editing should reset sys.path! */ prop = RNA_def_property(srna, "i18n_branches_directory", PROP_STRING, PROP_DIRPATH); diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt index 54caaed9231..91327b97fe4 100644 --- a/source/blender/modifiers/CMakeLists.txt +++ b/source/blender/modifiers/CMakeLists.txt @@ -79,6 +79,7 @@ set(SRC intern/MOD_mirror.c intern/MOD_multires.c intern/MOD_nodes.cc + intern/MOD_nodes_evaluator.cc intern/MOD_none.c intern/MOD_normal_edit.c intern/MOD_ocean.c @@ -118,6 +119,7 @@ set(SRC MOD_modifiertypes.h MOD_nodes.h intern/MOD_meshcache_util.h + intern/MOD_nodes_evaluator.hh intern/MOD_solidify_util.h intern/MOD_ui_common.h intern/MOD_util.h @@ -191,6 +193,18 @@ if(WITH_GMP) ) endif() +if(WITH_TBB) + add_definitions(-DWITH_TBB) + + list(APPEND INC_SYS + ${TBB_INCLUDE_DIRS} + ) + + list(APPEND LIB + ${TBB_LIBRARIES} + ) +endif() + if(WITH_OPENVDB) list(APPEND INC ../../../intern/openvdb @@ -206,7 +220,7 @@ if(WITH_OPENVDB) endif() if(WITH_EXPERIMENTAL_FEATURES) - add_definitions(-DWITH_GEOMETRY_NODES) + add_definitions(-DWITH_SIMULATION_DATABLOCK) add_definitions(-DWITH_POINT_CLOUD) add_definitions(-DWITH_HAIR_NODES) endif() diff --git a/source/blender/modifiers/intern/MOD_armature.c b/source/blender/modifiers/intern/MOD_armature.c index 6769f14f88f..649d36e3d57 100644 --- a/source/blender/modifiers/intern/MOD_armature.c +++ b/source/blender/modifiers/intern/MOD_armature.c @@ -296,7 +296,6 @@ ModifierTypeInfo modifierType_Armature = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_array.c b/source/blender/modifiers/intern/MOD_array.c index 0282f0d2934..93a9e76ffe4 100644 --- a/source/blender/modifiers/intern/MOD_array.c +++ b/source/blender/modifiers/intern/MOD_array.c @@ -1021,7 +1021,6 @@ ModifierTypeInfo modifierType_Array = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_bevel.c b/source/blender/modifiers/intern/MOD_bevel.c index a94411d897e..8fdd222402e 100644 --- a/source/blender/modifiers/intern/MOD_bevel.c +++ b/source/blender/modifiers/intern/MOD_bevel.c @@ -448,7 +448,6 @@ ModifierTypeInfo modifierType_Bevel = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, /* freeData */ freeData, diff --git a/source/blender/modifiers/intern/MOD_boolean.cc b/source/blender/modifiers/intern/MOD_boolean.cc index 40d5386c2fa..9b8782737c3 100644 --- a/source/blender/modifiers/intern/MOD_boolean.cc +++ b/source/blender/modifiers/intern/MOD_boolean.cc @@ -635,7 +635,6 @@ ModifierTypeInfo modifierType_Boolean = { /* modifyMesh */ modifyMesh, /* modifyHair */ nullptr, /* modifyGeometrySet */ nullptr, - /* modifyVolume */ nullptr, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_build.c b/source/blender/modifiers/intern/MOD_build.c index 0b1c661baed..c38e5126f6b 100644 --- a/source/blender/modifiers/intern/MOD_build.c +++ b/source/blender/modifiers/intern/MOD_build.c @@ -347,7 +347,6 @@ ModifierTypeInfo modifierType_Build = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_cast.c b/source/blender/modifiers/intern/MOD_cast.c index f905a38ae12..715bc26e5d3 100644 --- a/source/blender/modifiers/intern/MOD_cast.c +++ b/source/blender/modifiers/intern/MOD_cast.c @@ -591,7 +591,6 @@ ModifierTypeInfo modifierType_Cast = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_cloth.c b/source/blender/modifiers/intern/MOD_cloth.c index a25d65347c5..40d027f3044 100644 --- a/source/blender/modifiers/intern/MOD_cloth.c +++ b/source/blender/modifiers/intern/MOD_cloth.c @@ -311,7 +311,6 @@ ModifierTypeInfo modifierType_Cloth = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_collision.c b/source/blender/modifiers/intern/MOD_collision.c index e72e0279263..5dd57469914 100644 --- a/source/blender/modifiers/intern/MOD_collision.c +++ b/source/blender/modifiers/intern/MOD_collision.c @@ -317,7 +317,6 @@ ModifierTypeInfo modifierType_Collision = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_correctivesmooth.c b/source/blender/modifiers/intern/MOD_correctivesmooth.c index 001c7d8d098..fef235b456b 100644 --- a/source/blender/modifiers/intern/MOD_correctivesmooth.c +++ b/source/blender/modifiers/intern/MOD_correctivesmooth.c @@ -852,7 +852,6 @@ ModifierTypeInfo modifierType_CorrectiveSmooth = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_curve.c b/source/blender/modifiers/intern/MOD_curve.c index d5d53edfd54..20dbb299767 100644 --- a/source/blender/modifiers/intern/MOD_curve.c +++ b/source/blender/modifiers/intern/MOD_curve.c @@ -236,7 +236,6 @@ ModifierTypeInfo modifierType_Curve = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_datatransfer.c b/source/blender/modifiers/intern/MOD_datatransfer.c index 8b299a82f95..dbdc76f0edc 100644 --- a/source/blender/modifiers/intern/MOD_datatransfer.c +++ b/source/blender/modifiers/intern/MOD_datatransfer.c @@ -495,7 +495,6 @@ ModifierTypeInfo modifierType_DataTransfer = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_decimate.c b/source/blender/modifiers/intern/MOD_decimate.c index 03db09a5aec..faad1175f3a 100644 --- a/source/blender/modifiers/intern/MOD_decimate.c +++ b/source/blender/modifiers/intern/MOD_decimate.c @@ -300,7 +300,6 @@ ModifierTypeInfo modifierType_Decimate = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_displace.c b/source/blender/modifiers/intern/MOD_displace.c index abe78943508..a7ac9f618af 100644 --- a/source/blender/modifiers/intern/MOD_displace.c +++ b/source/blender/modifiers/intern/MOD_displace.c @@ -507,7 +507,6 @@ ModifierTypeInfo modifierType_Displace = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_dynamicpaint.c b/source/blender/modifiers/intern/MOD_dynamicpaint.c index 3e607e88cdd..8b1d541d45d 100644 --- a/source/blender/modifiers/intern/MOD_dynamicpaint.c +++ b/source/blender/modifiers/intern/MOD_dynamicpaint.c @@ -221,7 +221,6 @@ ModifierTypeInfo modifierType_DynamicPaint = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_edgesplit.c b/source/blender/modifiers/intern/MOD_edgesplit.c index e02befd7efa..2874bebe13a 100644 --- a/source/blender/modifiers/intern/MOD_edgesplit.c +++ b/source/blender/modifiers/intern/MOD_edgesplit.c @@ -187,7 +187,6 @@ ModifierTypeInfo modifierType_EdgeSplit = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_explode.c b/source/blender/modifiers/intern/MOD_explode.c index c12019a325e..e1197439c7c 100644 --- a/source/blender/modifiers/intern/MOD_explode.c +++ b/source/blender/modifiers/intern/MOD_explode.c @@ -1255,7 +1255,6 @@ ModifierTypeInfo modifierType_Explode = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_fluid.c b/source/blender/modifiers/intern/MOD_fluid.c index 8a8d6f2305f..36d2ab2a11a 100644 --- a/source/blender/modifiers/intern/MOD_fluid.c +++ b/source/blender/modifiers/intern/MOD_fluid.c @@ -239,7 +239,6 @@ ModifierTypeInfo modifierType_Fluid = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_hook.c b/source/blender/modifiers/intern/MOD_hook.c index 2fa05a319d5..ff581e92cdd 100644 --- a/source/blender/modifiers/intern/MOD_hook.c +++ b/source/blender/modifiers/intern/MOD_hook.c @@ -574,7 +574,6 @@ ModifierTypeInfo modifierType_Hook = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_laplaciandeform.c b/source/blender/modifiers/intern/MOD_laplaciandeform.c index bda0f9ba5a4..6efeec1970f 100644 --- a/source/blender/modifiers/intern/MOD_laplaciandeform.c +++ b/source/blender/modifiers/intern/MOD_laplaciandeform.c @@ -889,7 +889,6 @@ ModifierTypeInfo modifierType_LaplacianDeform = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_laplaciansmooth.c b/source/blender/modifiers/intern/MOD_laplaciansmooth.c index fc527304e76..78e0bf3fa8f 100644 --- a/source/blender/modifiers/intern/MOD_laplaciansmooth.c +++ b/source/blender/modifiers/intern/MOD_laplaciansmooth.c @@ -635,7 +635,6 @@ ModifierTypeInfo modifierType_LaplacianSmooth = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ init_data, /* requiredDataMask */ required_data_mask, diff --git a/source/blender/modifiers/intern/MOD_lattice.c b/source/blender/modifiers/intern/MOD_lattice.c index e3c42e39dda..29d1ecf6050 100644 --- a/source/blender/modifiers/intern/MOD_lattice.c +++ b/source/blender/modifiers/intern/MOD_lattice.c @@ -193,7 +193,6 @@ ModifierTypeInfo modifierType_Lattice = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_mask.cc b/source/blender/modifiers/intern/MOD_mask.cc index 191d39d9fce..812bfe3b375 100644 --- a/source/blender/modifiers/intern/MOD_mask.cc +++ b/source/blender/modifiers/intern/MOD_mask.cc @@ -463,7 +463,6 @@ ModifierTypeInfo modifierType_Mask = { /* modifyMesh */ modifyMesh, /* modifyHair */ nullptr, /* modifyGeometrySet */ nullptr, - /* modifyVolume */ nullptr, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_mesh_to_volume.cc b/source/blender/modifiers/intern/MOD_mesh_to_volume.cc index cc007651733..778b5746471 100644 --- a/source/blender/modifiers/intern/MOD_mesh_to_volume.cc +++ b/source/blender/modifiers/intern/MOD_mesh_to_volume.cc @@ -20,6 +20,7 @@ #include <vector> +#include "BKE_geometry_set.hh" #include "BKE_lib_query.h" #include "BKE_mesh_runtime.h" #include "BKE_mesh_wrapper.h" @@ -204,7 +205,9 @@ static float compute_voxel_size(const ModifierEvalContext *ctx, } #endif -static Volume *modifyVolume(ModifierData *md, const ModifierEvalContext *ctx, Volume *input_volume) +static Volume *mesh_to_volume(ModifierData *md, + const ModifierEvalContext *ctx, + Volume *input_volume) { #ifdef WITH_OPENVDB using namespace blender; @@ -278,6 +281,17 @@ static Volume *modifyVolume(ModifierData *md, const ModifierEvalContext *ctx, Vo #endif } +static void modifyGeometrySet(ModifierData *md, + const ModifierEvalContext *ctx, + GeometrySet *geometry_set) +{ + Volume *input_volume = geometry_set->get_volume_for_write(); + Volume *result_volume = mesh_to_volume(md, ctx, input_volume); + if (result_volume != input_volume) { + geometry_set->replace_volume(result_volume); + } +} + ModifierTypeInfo modifierType_MeshToVolume = { /* name */ "Mesh to Volume", /* structName */ "MeshToVolumeModifierData", @@ -295,8 +309,7 @@ ModifierTypeInfo modifierType_MeshToVolume = { /* deformMatricesEM */ nullptr, /* modifyMesh */ nullptr, /* modifyHair */ nullptr, - /* modifyGeometrySet */ nullptr, - /* modifyVolume */ modifyVolume, + /* modifyGeometrySet */ modifyGeometrySet, /* initData */ initData, /* requiredDataMask */ nullptr, diff --git a/source/blender/modifiers/intern/MOD_meshcache.c b/source/blender/modifiers/intern/MOD_meshcache.c index 6d2e0f242d7..361454120ca 100644 --- a/source/blender/modifiers/intern/MOD_meshcache.c +++ b/source/blender/modifiers/intern/MOD_meshcache.c @@ -389,7 +389,6 @@ ModifierTypeInfo modifierType_MeshCache = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_meshdeform.c b/source/blender/modifiers/intern/MOD_meshdeform.c index a94dd6da477..8e1e03570b5 100644 --- a/source/blender/modifiers/intern/MOD_meshdeform.c +++ b/source/blender/modifiers/intern/MOD_meshdeform.c @@ -644,7 +644,6 @@ ModifierTypeInfo modifierType_MeshDeform = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_meshsequencecache.c b/source/blender/modifiers/intern/MOD_meshsequencecache.c index 2c01857adb1..c2f9cd8c867 100644 --- a/source/blender/modifiers/intern/MOD_meshsequencecache.c +++ b/source/blender/modifiers/intern/MOD_meshsequencecache.c @@ -271,7 +271,6 @@ ModifierTypeInfo modifierType_MeshSequenceCache = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_mirror.c b/source/blender/modifiers/intern/MOD_mirror.c index b800ce7f803..6116cf8146a 100644 --- a/source/blender/modifiers/intern/MOD_mirror.c +++ b/source/blender/modifiers/intern/MOD_mirror.c @@ -239,7 +239,6 @@ ModifierTypeInfo modifierType_Mirror = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_multires.c b/source/blender/modifiers/intern/MOD_multires.c index 1182c8db093..c3b34f3cd23 100644 --- a/source/blender/modifiers/intern/MOD_multires.c +++ b/source/blender/modifiers/intern/MOD_multires.c @@ -520,7 +520,6 @@ ModifierTypeInfo modifierType_Multires = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index fd3634ad278..8fa80025790 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -36,6 +36,7 @@ #include "DNA_collection_types.h" #include "DNA_defaults.h" +#include "DNA_material_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_modifier_types.h" @@ -75,16 +76,14 @@ #include "MOD_modifiertypes.h" #include "MOD_nodes.h" +#include "MOD_nodes_evaluator.hh" #include "MOD_ui_common.h" #include "ED_spreadsheet.h" #include "NOD_derived_node_tree.hh" #include "NOD_geometry.h" -#include "NOD_geometry_exec.hh" #include "NOD_node_tree_multi_function.hh" -#include "NOD_type_callbacks.hh" -#include "NOD_type_conversions.hh" using blender::float3; using blender::FunctionRef; @@ -95,12 +94,8 @@ using blender::Span; using blender::StringRef; using blender::StringRefNull; using blender::Vector; -using blender::bke::PersistentCollectionHandle; -using blender::bke::PersistentDataHandleMap; -using blender::bke::PersistentObjectHandle; using blender::fn::GMutablePointer; using blender::fn::GPointer; -using blender::fn::GValueMap; using blender::nodes::GeoNodeExecParams; using namespace blender::fn::multi_function_types; using namespace blender::nodes::derived_node_tree_types; @@ -129,6 +124,18 @@ static void addIdsUsedBySocket(const ListBase *sockets, Set<ID *> &ids) ids.add(&collection->id); } } + else if (socket->type == SOCK_MATERIAL) { + Material *material = ((bNodeSocketValueMaterial *)socket->default_value)->value; + if (material != nullptr) { + ids.add(&material->id); + } + } + else if (socket->type == SOCK_TEXTURE) { + Tex *texture = ((bNodeSocketValueTexture *)socket->default_value)->value; + if (texture != nullptr) { + ids.add(&texture->id); + } + } } } @@ -203,18 +210,28 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte find_used_ids_from_settings(nmd->settings, used_ids); find_used_ids_from_nodes(*nmd->node_group, used_ids); for (ID *id : used_ids) { - if (GS(id->name) == ID_OB) { - Object *object = reinterpret_cast<Object *>(id); - add_object_relation(ctx, *object); - } - if (GS(id->name) == ID_GR) { - Collection *collection = reinterpret_cast<Collection *>(id); - add_collection_relation(ctx, *collection); + switch ((ID_Type)GS(id->name)) { + case ID_OB: { + Object *object = reinterpret_cast<Object *>(id); + add_object_relation(ctx, *object); + break; + } + case ID_GR: { + Collection *collection = reinterpret_cast<Collection *>(id); + add_collection_relation(ctx, *collection); + break; + } + case ID_TE: { + DEG_add_generic_id_relation(ctx->node, id, "Nodes Modifier"); + } + default: { + /* Purposefully don't add relations for materials. While there are material sockets, + * the pointers are only passed around as handles rather than dereferenced. */ + break; + } } } } - - /* TODO: Add dependency for adding and removing objects in collections. */ } static void foreachIDLink(ModifierData *md, Object *ob, IDWalkFunc walk, void *userData) @@ -268,368 +285,6 @@ static bool logging_enabled(const ModifierEvalContext *ctx) return true; } -class GeometryNodesEvaluator { - public: - using LogSocketValueFn = std::function<void(DSocket, Span<GPointer>)>; - - private: - blender::LinearAllocator<> allocator_; - Map<std::pair<DInputSocket, DOutputSocket>, GMutablePointer> value_by_input_; - Vector<DInputSocket> group_outputs_; - blender::nodes::MultiFunctionByNode &mf_by_node_; - const blender::nodes::DataTypeConversions &conversions_; - const PersistentDataHandleMap &handle_map_; - const Object *self_object_; - const ModifierData *modifier_; - Depsgraph *depsgraph_; - LogSocketValueFn log_socket_value_fn_; - - public: - GeometryNodesEvaluator(const Map<DOutputSocket, GMutablePointer> &group_input_data, - Vector<DInputSocket> group_outputs, - blender::nodes::MultiFunctionByNode &mf_by_node, - const PersistentDataHandleMap &handle_map, - const Object *self_object, - const ModifierData *modifier, - Depsgraph *depsgraph, - LogSocketValueFn log_socket_value_fn) - : group_outputs_(std::move(group_outputs)), - mf_by_node_(mf_by_node), - conversions_(blender::nodes::get_implicit_type_conversions()), - handle_map_(handle_map), - self_object_(self_object), - modifier_(modifier), - depsgraph_(depsgraph), - log_socket_value_fn_(std::move(log_socket_value_fn)) - { - for (auto item : group_input_data.items()) { - this->log_socket_value(item.key, item.value); - this->forward_to_inputs(item.key, item.value); - } - } - - Vector<GMutablePointer> execute() - { - Vector<GMutablePointer> results; - for (const DInputSocket &group_output : group_outputs_) { - Vector<GMutablePointer> result = this->get_input_values(group_output); - this->log_socket_value(group_output, result); - results.append(result[0]); - } - for (GMutablePointer value : value_by_input_.values()) { - value.destruct(); - } - return results; - } - - private: - Vector<GMutablePointer> get_input_values(const DInputSocket socket_to_compute) - { - Vector<DSocket> from_sockets; - socket_to_compute.foreach_origin_socket([&](DSocket socket) { from_sockets.append(socket); }); - - if (from_sockets.is_empty()) { - /* The input is not connected, use the value from the socket itself. */ - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo()); - return {get_unlinked_input_value(socket_to_compute, type)}; - } - - /* Multi-input sockets contain a vector of inputs. */ - if (socket_to_compute->is_multi_input_socket()) { - return this->get_inputs_from_incoming_links(socket_to_compute, from_sockets); - } - - const DSocket from_socket = from_sockets[0]; - GMutablePointer value = this->get_input_from_incoming_link(socket_to_compute, from_socket); - return {value}; - } - - Vector<GMutablePointer> get_inputs_from_incoming_links(const DInputSocket socket_to_compute, - const Span<DSocket> from_sockets) - { - Vector<GMutablePointer> values; - for (const int i : from_sockets.index_range()) { - const DSocket from_socket = from_sockets[i]; - const int first_occurence = from_sockets.take_front(i).first_index_try(from_socket); - if (first_occurence == -1) { - values.append(this->get_input_from_incoming_link(socket_to_compute, from_socket)); - } - else { - /* If the same from-socket occurs more than once, we make a copy of the first value. This - * can happen when a node linked to a multi-input-socket is muted. */ - GMutablePointer value = values[first_occurence]; - const CPPType *type = value.type(); - void *copy_buffer = allocator_.allocate(type->size(), type->alignment()); - type->copy_to_uninitialized(value.get(), copy_buffer); - values.append({type, copy_buffer}); - } - } - return values; - } - - GMutablePointer get_input_from_incoming_link(const DInputSocket socket_to_compute, - const DSocket from_socket) - { - if (from_socket->is_output()) { - const DOutputSocket from_output_socket{from_socket}; - const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(socket_to_compute, - from_output_socket); - std::optional<GMutablePointer> value = value_by_input_.pop_try(key); - if (value.has_value()) { - /* This input has been computed before, return it directly. */ - return {*value}; - } - - /* Compute the socket now. */ - this->compute_output_and_forward(from_output_socket); - return {value_by_input_.pop(key)}; - } - - /* Get value from an unlinked input socket. */ - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo()); - const DInputSocket from_input_socket{from_socket}; - return {get_unlinked_input_value(from_input_socket, type)}; - } - - void compute_output_and_forward(const DOutputSocket socket_to_compute) - { - const DNode node{socket_to_compute.context(), &socket_to_compute->node()}; - - if (!socket_to_compute->is_available()) { - /* If the output is not available, use a default value. */ - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo()); - void *buffer = allocator_.allocate(type.size(), type.alignment()); - type.copy_to_uninitialized(type.default_value(), buffer); - this->forward_to_inputs(socket_to_compute, {type, buffer}); - return; - } - - /* Prepare inputs required to execute the node. */ - GValueMap<StringRef> node_inputs_map{allocator_}; - for (const InputSocketRef *input_socket : node->inputs()) { - if (input_socket->is_available()) { - DInputSocket dsocket{node.context(), input_socket}; - Vector<GMutablePointer> values = this->get_input_values(dsocket); - this->log_socket_value(dsocket, values); - for (int i = 0; i < values.size(); ++i) { - /* Values from Multi Input Sockets are stored in input map with the format - * <identifier>[<index>]. */ - blender::StringRefNull key = allocator_.copy_string( - input_socket->identifier() + (i > 0 ? ("[" + std::to_string(i)) + "]" : "")); - node_inputs_map.add_new_direct(key, std::move(values[i])); - } - } - } - - /* Execute the node. */ - GValueMap<StringRef> node_outputs_map{allocator_}; - GeoNodeExecParams params{ - node, node_inputs_map, node_outputs_map, handle_map_, self_object_, modifier_, depsgraph_}; - this->execute_node(node, params); - - /* Forward computed outputs to linked input sockets. */ - for (const OutputSocketRef *output_socket : node->outputs()) { - if (output_socket->is_available()) { - const DOutputSocket dsocket{node.context(), output_socket}; - GMutablePointer value = node_outputs_map.extract(output_socket->identifier()); - this->log_socket_value(dsocket, value); - this->forward_to_inputs(dsocket, value); - } - } - } - - void log_socket_value(const DSocket socket, Span<GPointer> values) - { - if (log_socket_value_fn_) { - log_socket_value_fn_(socket, values); - } - } - - void log_socket_value(const DSocket socket, Span<GMutablePointer> values) - { - this->log_socket_value(socket, values.cast<GPointer>()); - } - - void log_socket_value(const DSocket socket, GPointer value) - { - this->log_socket_value(socket, Span<GPointer>(&value, 1)); - } - - void execute_node(const DNode node, GeoNodeExecParams params) - { - const bNode &bnode = params.node(); - - /* Use the geometry-node-execute callback if it exists. */ - if (bnode.typeinfo->geometry_node_execute != nullptr) { - bnode.typeinfo->geometry_node_execute(params); - return; - } - - /* Use the multi-function implementation if it exists. */ - const MultiFunction *multi_function = mf_by_node_.lookup_default(node, nullptr); - if (multi_function != nullptr) { - this->execute_multi_function_node(node, params, *multi_function); - return; - } - - /* Just output default values if no implementation exists. */ - this->execute_unknown_node(node, params); - } - - void execute_multi_function_node(const DNode node, - GeoNodeExecParams params, - const MultiFunction &fn) - { - MFContextBuilder fn_context; - MFParamsBuilder fn_params{fn, 1}; - Vector<GMutablePointer> input_data; - for (const InputSocketRef *socket_ref : node->inputs()) { - if (socket_ref->is_available()) { - GMutablePointer data = params.extract_input(socket_ref->identifier()); - fn_params.add_readonly_single_input(GSpan(*data.type(), data.get(), 1)); - input_data.append(data); - } - } - Vector<GMutablePointer> output_data; - for (const OutputSocketRef *socket_ref : node->outputs()) { - if (socket_ref->is_available()) { - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_ref->typeinfo()); - void *buffer = allocator_.allocate(type.size(), type.alignment()); - fn_params.add_uninitialized_single_output(GMutableSpan(type, buffer, 1)); - output_data.append(GMutablePointer(type, buffer)); - } - } - fn.call(IndexRange(1), fn_params, fn_context); - for (GMutablePointer value : input_data) { - value.destruct(); - } - int output_index = 0; - for (const int i : node->outputs().index_range()) { - if (node->output(i).is_available()) { - GMutablePointer value = output_data[output_index]; - params.set_output_by_move(node->output(i).identifier(), value); - value.destruct(); - output_index++; - } - } - } - - void execute_unknown_node(const DNode node, GeoNodeExecParams params) - { - for (const OutputSocketRef *socket : node->outputs()) { - if (socket->is_available()) { - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket->typeinfo()); - params.set_output_by_copy(socket->identifier(), {type, type.default_value()}); - } - } - } - - void forward_to_inputs(const DOutputSocket from_socket, GMutablePointer value_to_forward) - { - /* For all sockets that are linked with the from_socket push the value to their node. */ - Vector<DInputSocket> to_sockets_all; - - auto handle_target_socket_fn = [&](DInputSocket to_socket) { - to_sockets_all.append_non_duplicates(to_socket); - }; - auto handle_skipped_socket_fn = [&, this](DSocket socket) { - this->log_socket_value(socket, value_to_forward); - }; - - from_socket.foreach_target_socket(handle_target_socket_fn, handle_skipped_socket_fn); - - const CPPType &from_type = *value_to_forward.type(); - Vector<DInputSocket> to_sockets_same_type; - for (const DInputSocket &to_socket : to_sockets_all) { - const CPPType &to_type = *blender::nodes::socket_cpp_type_get(*to_socket->typeinfo()); - const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(to_socket, from_socket); - if (from_type == to_type) { - to_sockets_same_type.append(to_socket); - } - else { - void *buffer = allocator_.allocate(to_type.size(), to_type.alignment()); - if (conversions_.is_convertible(from_type, to_type)) { - conversions_.convert_to_uninitialized( - from_type, to_type, value_to_forward.get(), buffer); - } - else { - to_type.copy_to_uninitialized(to_type.default_value(), buffer); - } - add_value_to_input_socket(key, GMutablePointer{to_type, buffer}); - } - } - - if (to_sockets_same_type.size() == 0) { - /* This value is not further used, so destruct it. */ - value_to_forward.destruct(); - } - else if (to_sockets_same_type.size() == 1) { - /* This value is only used on one input socket, no need to copy it. */ - const DInputSocket to_socket = to_sockets_same_type[0]; - const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(to_socket, from_socket); - - add_value_to_input_socket(key, value_to_forward); - } - else { - /* Multiple inputs use the value, make a copy for every input except for one. */ - const DInputSocket first_to_socket = to_sockets_same_type[0]; - Span<DInputSocket> other_to_sockets = to_sockets_same_type.as_span().drop_front(1); - const CPPType &type = *value_to_forward.type(); - const std::pair<DInputSocket, DOutputSocket> first_key = std::make_pair(first_to_socket, - from_socket); - add_value_to_input_socket(first_key, value_to_forward); - for (const DInputSocket &to_socket : other_to_sockets) { - const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(to_socket, from_socket); - void *buffer = allocator_.allocate(type.size(), type.alignment()); - type.copy_to_uninitialized(value_to_forward.get(), buffer); - add_value_to_input_socket(key, GMutablePointer{type, buffer}); - } - } - } - - void add_value_to_input_socket(const std::pair<DInputSocket, DOutputSocket> key, - GMutablePointer value) - { - value_by_input_.add_new(key, value); - } - - GMutablePointer get_unlinked_input_value(const DInputSocket &socket, - const CPPType &required_type) - { - bNodeSocket *bsocket = socket->bsocket(); - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket->typeinfo()); - void *buffer = allocator_.allocate(type.size(), type.alignment()); - - if (bsocket->type == SOCK_OBJECT) { - Object *object = socket->default_value<bNodeSocketValueObject>()->value; - PersistentObjectHandle object_handle = handle_map_.lookup(object); - new (buffer) PersistentObjectHandle(object_handle); - } - else if (bsocket->type == SOCK_COLLECTION) { - Collection *collection = socket->default_value<bNodeSocketValueCollection>()->value; - PersistentCollectionHandle collection_handle = handle_map_.lookup(collection); - new (buffer) PersistentCollectionHandle(collection_handle); - } - else { - blender::nodes::socket_cpp_value_get(*bsocket, buffer); - } - - if (type == required_type) { - return {type, buffer}; - } - if (conversions_.is_convertible(type, required_type)) { - void *converted_buffer = allocator_.allocate(required_type.size(), - required_type.alignment()); - conversions_.convert_to_uninitialized(type, required_type, buffer, converted_buffer); - type.destruct(buffer); - return {required_type, converted_buffer}; - } - void *default_buffer = allocator_.allocate(required_type.size(), required_type.alignment()); - required_type.copy_to_uninitialized(required_type.default_value(), default_buffer); - return {required_type, default_buffer}; - } -}; - /** * This code is responsible for creating the new property and also creating the group of * properties in the prop_ui_container group for the UI info, the mapping for which is @@ -649,9 +304,7 @@ struct SocketPropertyType { IDProperty *(*create_default_ui_prop)(const bNodeSocket &socket, const char *name); PropertyType (*rna_subtype_get)(const bNodeSocket &socket); bool (*is_correct_type)(const IDProperty &property); - void (*init_cpp_value)(const IDProperty &property, - const PersistentDataHandleMap &handles, - void *r_value); + void (*init_cpp_value)(const IDProperty &property, void *r_value); }; static IDProperty *socket_add_property(IDProperty *settings_prop_group, @@ -744,9 +397,7 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso return (PropertyType)((bNodeSocketValueFloat *)socket.default_value)->subtype; }, [](const IDProperty &property) { return ELEM(property.type, IDP_FLOAT, IDP_DOUBLE); }, - [](const IDProperty &property, - const PersistentDataHandleMap &UNUSED(handles), - void *r_value) { + [](const IDProperty &property, void *r_value) { if (property.type == IDP_FLOAT) { *(float *)r_value = IDP_Float(&property); } @@ -787,9 +438,7 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso return (PropertyType)((bNodeSocketValueInt *)socket.default_value)->subtype; }, [](const IDProperty &property) { return property.type == IDP_INT; }, - [](const IDProperty &property, - const PersistentDataHandleMap &UNUSED(handles), - void *r_value) { *(int *)r_value = IDP_Int(&property); }, + [](const IDProperty &property, void *r_value) { *(int *)r_value = IDP_Int(&property); }, }; return &int_type; } @@ -832,9 +481,9 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT && property.len == 3; }, - [](const IDProperty &property, - const PersistentDataHandleMap &UNUSED(handles), - void *r_value) { copy_v3_v3((float *)r_value, (const float *)IDP_Array(&property)); }, + [](const IDProperty &property, void *r_value) { + copy_v3_v3((float *)r_value, (const float *)IDP_Array(&property)); + }, }; return &vector_type; } @@ -864,9 +513,9 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso }, nullptr, [](const IDProperty &property) { return property.type == IDP_INT; }, - [](const IDProperty &property, - const PersistentDataHandleMap &UNUSED(handles), - void *r_value) { *(bool *)r_value = IDP_Int(&property) != 0; }, + [](const IDProperty &property, void *r_value) { + *(bool *)r_value = IDP_Int(&property) != 0; + }, }; return &boolean_type; } @@ -886,9 +535,9 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso }, nullptr, [](const IDProperty &property) { return property.type == IDP_STRING; }, - [](const IDProperty &property, - const PersistentDataHandleMap &UNUSED(handles), - void *r_value) { new (r_value) std::string(IDP_String(&property)); }, + [](const IDProperty &property, void *r_value) { + new (r_value) std::string(IDP_String(&property)); + }, }; return &string_type; } @@ -905,10 +554,10 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso nullptr, nullptr, [](const IDProperty &property) { return property.type == IDP_ID; }, - [](const IDProperty &property, const PersistentDataHandleMap &handles, void *r_value) { + [](const IDProperty &property, void *r_value) { ID *id = IDP_Id(&property); Object *object = (id && GS(id->name) == ID_OB) ? (Object *)id : nullptr; - new (r_value) PersistentObjectHandle(handles.lookup(object)); + *(Object **)r_value = object; }, }; return &object_type; @@ -926,10 +575,52 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso nullptr, nullptr, [](const IDProperty &property) { return property.type == IDP_ID; }, - [](const IDProperty &property, const PersistentDataHandleMap &handles, void *r_value) { + [](const IDProperty &property, void *r_value) { ID *id = IDP_Id(&property); Collection *collection = (id && GS(id->name) == ID_GR) ? (Collection *)id : nullptr; - new (r_value) PersistentCollectionHandle(handles.lookup(collection)); + *(Collection **)r_value = collection; + }, + }; + return &collection_type; + } + case SOCK_TEXTURE: { + static const SocketPropertyType collection_type = { + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueTexture *value = (bNodeSocketValueTexture *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.id = (ID *)value->value; + return IDP_New(IDP_ID, &idprop, name); + }, + nullptr, + nullptr, + nullptr, + nullptr, + [](const IDProperty &property) { return property.type == IDP_ID; }, + [](const IDProperty &property, void *r_value) { + ID *id = IDP_Id(&property); + Tex *texture = (id && GS(id->name) == ID_TE) ? (Tex *)id : nullptr; + *(Tex **)r_value = texture; + }, + }; + return &collection_type; + } + case SOCK_MATERIAL: { + static const SocketPropertyType collection_type = { + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueMaterial *value = (bNodeSocketValueMaterial *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.id = (ID *)value->value; + return IDP_New(IDP_ID, &idprop, name); + }, + nullptr, + nullptr, + nullptr, + nullptr, + [](const IDProperty &property) { return property.type == IDP_ID; }, + [](const IDProperty &property, void *r_value) { + ID *id = IDP_Id(&property); + Material *material = (id && GS(id->name) == ID_MA) ? (Material *)id : nullptr; + *(Material **)r_value = material; }, }; return &collection_type; @@ -1017,7 +708,6 @@ void MOD_nodes_init(Main *bmain, NodesModifierData *nmd) } static void initialize_group_input(NodesModifierData &nmd, - const PersistentDataHandleMap &handle_map, const bNodeSocket &socket, const CPPType &cpp_type, void *r_value) @@ -1041,22 +731,7 @@ static void initialize_group_input(NodesModifierData &nmd, blender::nodes::socket_cpp_value_get(socket, r_value); return; } - property_type->init_cpp_value(*property, handle_map, r_value); -} - -static void fill_data_handle_map(const NodesModifierSettings &settings, - const DerivedNodeTree &tree, - PersistentDataHandleMap &handle_map) -{ - Set<ID *> used_ids; - find_used_ids_from_settings(settings, used_ids); - find_used_ids_from_nodes(*tree.root_context().tree().btree(), used_ids); - - int current_handle = 0; - for (ID *id : used_ids) { - handle_map.add(current_handle, *id); - current_handle++; - } + property_type->init_cpp_value(*property, r_value); } static void reset_tree_ui_storage(Span<const blender::nodes::NodeTreeRef *> trees, @@ -1247,9 +922,6 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree, blender::LinearAllocator<> &allocator = scope.linear_allocator(); blender::nodes::MultiFunctionByNode mf_by_node = get_multi_function_per_node(tree, scope); - PersistentDataHandleMap handle_map; - fill_data_handle_map(nmd->settings, tree, handle_map); - Map<DOutputSocket, GMutablePointer> group_inputs; const DTreeContext *root_context = &tree.root_context(); @@ -1275,7 +947,7 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree, for (const OutputSocketRef *socket : remaining_input_sockets) { const CPPType &cpp_type = *blender::nodes::socket_cpp_type_get(*socket->typeinfo()); void *value_in = allocator.allocate(cpp_type.size(), cpp_type.alignment()); - initialize_group_input(*nmd, handle_map, *socket->bsocket(), cpp_type, value_in); + initialize_group_input(*nmd, *socket->bsocket(), cpp_type, value_in); group_inputs.add_new({root_context, socket}, {cpp_type, value_in}); } } @@ -1300,21 +972,19 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree, log_ui_hints(socket, values, ctx->object, nmd); }; - GeometryNodesEvaluator evaluator{group_inputs, - group_outputs, - mf_by_node, - handle_map, - ctx->object, - (ModifierData *)nmd, - ctx->depsgraph, - log_socket_value}; - - Vector<GMutablePointer> results = evaluator.execute(); - BLI_assert(results.size() == 1); - GMutablePointer result = results[0]; - - GeometrySet output_geometry = std::move(*(GeometrySet *)result.get()); - return output_geometry; + blender::modifiers::geometry_nodes::GeometryNodesEvaluationParams eval_params; + eval_params.input_values = group_inputs; + eval_params.output_sockets = group_outputs; + eval_params.mf_by_node = &mf_by_node; + eval_params.modifier_ = nmd; + eval_params.depsgraph = ctx->depsgraph; + eval_params.self_object = ctx->object; + eval_params.log_socket_value_fn = log_socket_value; + blender::modifiers::geometry_nodes::evaluate_geometry_nodes(eval_params); + + BLI_assert(eval_params.r_output_values.size() == 1); + GMutablePointer result = eval_params.r_output_values[0]; + return result.relocate_out<GeometrySet>(); } /** @@ -1453,36 +1123,45 @@ static void draw_property_for_socket(uiLayout *layout, /* IDProperties can be removed with python, so there could be a situation where * there isn't a property for a socket or it doesn't have the correct type. */ - if (property != nullptr && property_type->is_correct_type(*property)) { + if (property == nullptr || !property_type->is_correct_type(*property)) { + return; + } - char socket_id_esc[sizeof(socket.identifier) * 2]; - BLI_str_escape(socket_id_esc, socket.identifier, sizeof(socket_id_esc)); + char socket_id_esc[sizeof(socket.identifier) * 2]; + BLI_str_escape(socket_id_esc, socket.identifier, sizeof(socket_id_esc)); - char rna_path[sizeof(socket_id_esc) + 4]; - BLI_snprintf(rna_path, ARRAY_SIZE(rna_path), "[\"%s\"]", socket_id_esc); + char rna_path[sizeof(socket_id_esc) + 4]; + BLI_snprintf(rna_path, ARRAY_SIZE(rna_path), "[\"%s\"]", socket_id_esc); - /* Use #uiItemPointerR to draw pointer properties because #uiItemR would not have enough - * information about what type of ID to select for editing the values. This is because - * pointer IDProperties contain no information about their type. */ - switch (socket.type) { - case SOCK_OBJECT: { - uiItemPointerR( - layout, md_ptr, rna_path, bmain_ptr, "objects", socket.name, ICON_OBJECT_DATA); - break; - } - case SOCK_COLLECTION: { - uiItemPointerR(layout, - md_ptr, - rna_path, - bmain_ptr, - "collections", - socket.name, - ICON_OUTLINER_COLLECTION); - break; - } - default: - uiItemR(layout, md_ptr, rna_path, 0, socket.name, ICON_NONE); + /* Use #uiItemPointerR to draw pointer properties because #uiItemR would not have enough + * information about what type of ID to select for editing the values. This is because + * pointer IDProperties contain no information about their type. */ + switch (socket.type) { + case SOCK_OBJECT: { + uiItemPointerR( + layout, md_ptr, rna_path, bmain_ptr, "objects", socket.name, ICON_OBJECT_DATA); + break; } + case SOCK_COLLECTION: { + uiItemPointerR(layout, + md_ptr, + rna_path, + bmain_ptr, + "collections", + socket.name, + ICON_OUTLINER_COLLECTION); + break; + } + case SOCK_MATERIAL: { + uiItemPointerR(layout, md_ptr, rna_path, bmain_ptr, "materials", socket.name, ICON_MATERIAL); + break; + } + case SOCK_TEXTURE: { + uiItemPointerR(layout, md_ptr, rna_path, bmain_ptr, "textures", socket.name, ICON_TEXTURE); + break; + } + default: + uiItemR(layout, md_ptr, rna_path, 0, socket.name, ICON_NONE); } } @@ -1594,7 +1273,6 @@ ModifierTypeInfo modifierType_Nodes = { /* modifyMesh */ modifyMesh, /* modifyHair */ nullptr, /* modifyGeometrySet */ modifyGeometrySet, - /* modifyVolume */ nullptr, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc new file mode 100644 index 00000000000..10ef2f4d8eb --- /dev/null +++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc @@ -0,0 +1,1568 @@ +/* + * 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 "MOD_nodes_evaluator.hh" + +#include "NOD_geometry_exec.hh" +#include "NOD_type_conversions.hh" + +#include "DEG_depsgraph_query.h" + +#include "FN_generic_value_map.hh" +#include "FN_multi_function.hh" + +#include "BLI_enumerable_thread_specific.hh" +#include "BLI_stack.hh" +#include "BLI_task.h" +#include "BLI_task.hh" +#include "BLI_vector_set.hh" + +namespace blender::modifiers::geometry_nodes { + +using fn::CPPType; +using fn::GValueMap; +using nodes::GeoNodeExecParams; +using namespace fn::multi_function_types; + +enum class ValueUsage : uint8_t { + /* The value is definitely used. */ + Required, + /* The value may be used. */ + Maybe, + /* The value will definitely not be used. */ + Unused, +}; + +struct SingleInputValue { + /** + * Points either to null or to a value of the type of input. + */ + void *value = nullptr; +}; + +struct MultiInputValueItem { + /** + * The socket where this value is coming from. This is required to sort the inputs correctly + * based on the link order later on. + */ + DSocket origin; + /** + * Should only be null directly after construction. After that it should always point to a value + * of the correct type. + */ + void *value = nullptr; +}; + +struct MultiInputValue { + /** + * Collection of all the inputs that have been provided already. Note, the same origin can occur + * multiple times. However, it is guaranteed that if two items have the same origin, they will + * also have the same value (the pointer is different, but they point to values that would + * compare equal). + */ + Vector<MultiInputValueItem> items; + /** + * Number of items that need to be added until all inputs have been provided. + */ + int expected_size = 0; +}; + +struct InputState { + + /** + * Type of the socket. If this is null, the socket should just be ignored. + */ + const CPPType *type = nullptr; + + /** + * Value of this input socket. By default, the value is empty. When other nodes are done + * computing their outputs, the computed values will be forwarded to linked input sockets. + * The value will then live here until it is consumed by the node or it was found that the value + * is not needed anymore. + * Whether the `single` or `multi` value is used depends on the socket. + */ + union { + SingleInputValue *single; + MultiInputValue *multi; + } value; + + /** + * How the node intends to use this input. By default all inputs may be used. Based on which + * outputs are used, a node can tell the evaluator that an input will definitely be used or is + * never used. This allows the evaluator to free values early, avoid copies and other unnecessary + * computations. + */ + ValueUsage usage = ValueUsage::Maybe; + + /** + * True when this input is/was used for an execution. While a node is running, only the inputs + * that have this set to true are allowed to be used. This makes sure that inputs created while + * the node is running correctly trigger the node to run again. Furthermore, it gives the node a + * consistent view of which inputs are available that does not change unexpectedly. + * + * While the node is running, this can be checked without a lock, because no one is writing to + * it. If this is true, the value can be read without a lock as well, because the value is not + * changed by others anymore. + */ + bool was_ready_for_execution = false; +}; + +struct OutputState { + /** + * If this output has been computed and forwarded already. If this is true, the value is not + * computed/forwarded again. + */ + bool has_been_computed = false; + + /** + * Keeps track of how the output value is used. If a connected input becomes required, this + * output has to become required as well. The output becomes ignored when it has zero potential + * users that are counted below. + */ + ValueUsage output_usage = ValueUsage::Maybe; + + /** + * This is a copy of `output_usage` that is done right before node execution starts. This is + * done so that the node gets a consistent view of what outputs are used, even when this changes + * while the node is running (the node might be reevaluated in that case). + * + * While the node is running, this can be checked without a lock, because no one is writing to + * it. + */ + ValueUsage output_usage_for_execution = ValueUsage::Maybe; + + /** + * Counts how many times the value from this output might be used. If this number reaches zero, + * the output is not needed anymore. + */ + int potential_users = 0; +}; + +enum class NodeScheduleState { + /** + * Default state of every node. + */ + NotScheduled, + /** + * The node has been added to the task group and will be executed by it in the future. + */ + Scheduled, + /** + * The node is currently running. + */ + Running, + /** + * The node is running and has been rescheduled while running. In this case the node will run + * again. However, we don't add it to the task group immediately, because then the node might run + * twice at the same time, which is not allowed. Instead, once the node is done running, it will + * reschedule itself. + */ + RunningAndRescheduled, +}; + +struct NodeState { + /** + * Needs to be locked when any data in this state is accessed that is not explicitly marked as + * otherwise. + */ + std::mutex mutex; + + /** + * States of the individual input and output sockets. One can index into these arrays without + * locking. However, to access the data inside a lock is generally necessary. + * + * These spans have to be indexed with the socket index. Unavailable sockets have a state as + * well. Maybe we can handle unavailable sockets differently in Blender in general, so I did not + * want to add complexity around it here. + */ + MutableSpan<InputState> inputs; + MutableSpan<OutputState> outputs; + + /** + * Nodes that don't support laziness have some special handling the first time they are executed. + */ + bool non_lazy_node_is_initialized = false; + + /** + * Used to check that nodes that don't support laziness do not run more than once. + */ + bool has_been_executed = false; + + /** + * Becomes true when the node will never be executed again and its inputs are destructed. + * Generally, a node has finished once all of its outputs with (potential) users have been + * computed. + */ + bool node_has_finished = false; + + /** + * Counts the number of values that still have to be forwarded to this node until it should run + * again. It counts values from a multi input socket separately. + * This is used as an optimization so that nodes are not scheduled unnecessarily in many cases. + */ + int missing_required_inputs = 0; + + /** + * A node is always in one specific schedule state. This helps to ensure that the same node does + * not run twice at the same time accidentally. + */ + NodeScheduleState schedule_state = NodeScheduleState::NotScheduled; +}; + +/** + * Container for a node and its state. Packing them into a single struct allows the use of + * `VectorSet` instead of a `Map` for `node_states_` which simplifies parallel loops over all + * states. + * + * Equality operators and a hash function for `DNode` are provided so that one can lookup this type + * in `node_states_` just with a `DNode`. + */ +struct NodeWithState { + DNode node; + /* Store a pointer instead of `NodeState` directly to keep it small and movable. */ + NodeState *state = nullptr; + + friend bool operator==(const NodeWithState &a, const NodeWithState &b) + { + return a.node == b.node; + } + + friend bool operator==(const NodeWithState &a, const DNode &b) + { + return a.node == b; + } + + friend bool operator==(const DNode &a, const NodeWithState &b) + { + return a == b.node; + } + + uint64_t hash() const + { + return node.hash(); + } + + static uint64_t hash_as(const DNode &node) + { + return node.hash(); + } +}; + +class GeometryNodesEvaluator; + +/** + * Utility class that locks the state of a node. Having this is a separate class is useful because + * it allows methods to communicate that they expect the node to be locked. + */ +class LockedNode : NonCopyable, NonMovable { + private: + GeometryNodesEvaluator &evaluator_; + + public: + /** + * This is the node that is currently locked. + */ + const DNode node; + NodeState &node_state; + + /** + * Used to delay notifying (and therefore locking) other nodes until the current node is not + * locked anymore. This might not be strictly necessary to avoid deadlocks in the current code, + * but it is a good measure to avoid accidentally adding a deadlock later on. By not locking + * more than one node per thread at a time, deadlocks are avoided. + * + * The notifications will be send right after the node is not locked anymore. + */ + Vector<DOutputSocket> delayed_required_outputs; + Vector<DOutputSocket> delayed_unused_outputs; + Vector<DNode> delayed_scheduled_nodes; + + LockedNode(GeometryNodesEvaluator &evaluator, const DNode node, NodeState &node_state) + : evaluator_(evaluator), node(node), node_state(node_state) + { + node_state.mutex.lock(); + } + + ~LockedNode(); +}; + +static const CPPType *get_socket_cpp_type(const DSocket socket) +{ + return nodes::socket_cpp_type_get(*socket->typeinfo()); +} + +static const CPPType *get_socket_cpp_type(const SocketRef &socket) +{ + return nodes::socket_cpp_type_get(*socket.typeinfo()); +} + +static bool node_supports_laziness(const DNode node) +{ + return node->typeinfo()->geometry_node_execute_supports_laziness; +} + +/** Implements the callbacks that might be called when a node is executed. */ +class NodeParamsProvider : public nodes::GeoNodeExecParamsProvider { + private: + GeometryNodesEvaluator &evaluator_; + NodeState &node_state_; + + public: + NodeParamsProvider(GeometryNodesEvaluator &evaluator, DNode dnode, NodeState &node_state); + + bool can_get_input(StringRef identifier) const override; + bool can_set_output(StringRef identifier) const override; + GMutablePointer extract_input(StringRef identifier) override; + Vector<GMutablePointer> extract_multi_input(StringRef identifier) override; + GPointer get_input(StringRef identifier) const override; + GMutablePointer alloc_output_value(const CPPType &type) override; + void set_output(StringRef identifier, GMutablePointer value) override; + void set_input_unused(StringRef identifier) override; + bool output_is_required(StringRef identifier) const override; + + bool lazy_require_input(StringRef identifier) override; + bool lazy_output_is_required(StringRef identifier) const override; +}; + +class GeometryNodesEvaluator { + private: + /** + * This allocator lives on after the evaluator has been destructed. Therefore outputs of the + * entire evaluator should be allocated here. + */ + LinearAllocator<> &outer_allocator_; + /** + * A local linear allocator for each thread. Only use this for values that do not need to live + * longer than the lifetime of the evaluator itself. Considerations for the future: + * - We could use an allocator that can free here, some temporary values don't live long. + * - If we ever run into false sharing bottlenecks, we could use local allocators that allocate + * on cache line boundaries. Note, just because a value is allocated in one specific thread, + * does not mean that it will only be used by that thread. + */ + EnumerableThreadSpecific<LinearAllocator<>> local_allocators_; + + /** + * Every node that is reachable from the output gets its own state. Once all states have been + * constructed, this map can be used for lookups from multiple threads. + */ + VectorSet<NodeWithState> node_states_; + + /** + * Contains all the tasks for the nodes that are currently scheduled. + */ + TaskPool *task_pool_ = nullptr; + + GeometryNodesEvaluationParams ¶ms_; + const blender::nodes::DataTypeConversions &conversions_; + + friend NodeParamsProvider; + + public: + GeometryNodesEvaluator(GeometryNodesEvaluationParams ¶ms) + : outer_allocator_(params.allocator), + params_(params), + conversions_(blender::nodes::get_implicit_type_conversions()) + { + } + + void execute() + { + /* Disable threading until T88598 is resolved. */ + task_pool_ = BLI_task_pool_create_no_threads(this); + + this->create_states_for_reachable_nodes(); + this->forward_group_inputs(); + this->schedule_initial_nodes(); + + /* This runs until all initially requested inputs have been computed. */ + BLI_task_pool_work_and_wait(task_pool_); + BLI_task_pool_free(task_pool_); + + this->extract_group_outputs(); + this->destruct_node_states(); + } + + void create_states_for_reachable_nodes() + { + /* This does a depth first search for all the nodes that are reachable from the group + * outputs. This finds all nodes that are relevant. */ + Stack<DNode> nodes_to_check; + /* Start at the output sockets. */ + for (const DInputSocket &socket : params_.output_sockets) { + nodes_to_check.push(socket.node()); + } + /* Use the local allocator because the states do not need to outlive the evaluator. */ + LinearAllocator<> &allocator = local_allocators_.local(); + while (!nodes_to_check.is_empty()) { + const DNode node = nodes_to_check.pop(); + if (node_states_.contains_as(node)) { + /* This node has been handled already. */ + continue; + } + /* Create a new state for the node. */ + NodeState &node_state = *allocator.construct<NodeState>().release(); + node_states_.add_new({node, &node_state}); + + /* Push all linked origins on the stack. */ + for (const InputSocketRef *input_ref : node->inputs()) { + const DInputSocket input{node.context(), input_ref}; + input.foreach_origin_socket( + [&](const DSocket origin) { nodes_to_check.push(origin.node()); }); + } + } + + /* Initialize the more complex parts of the node states in parallel. At this point no new + * node states are added anymore, so it is safe to lookup states from `node_states_` from + * multiple threads. */ + parallel_for(IndexRange(node_states_.size()), 50, [&, this](const IndexRange range) { + LinearAllocator<> &allocator = this->local_allocators_.local(); + for (const NodeWithState &item : node_states_.as_span().slice(range)) { + this->initialize_node_state(item.node, *item.state, allocator); + } + }); + } + + void initialize_node_state(const DNode node, NodeState &node_state, LinearAllocator<> &allocator) + { + /* Construct arrays of the correct size. */ + node_state.inputs = allocator.construct_array<InputState>(node->inputs().size()); + node_state.outputs = allocator.construct_array<OutputState>(node->outputs().size()); + + /* Initialize input states. */ + for (const int i : node->inputs().index_range()) { + InputState &input_state = node_state.inputs[i]; + const DInputSocket socket = node.input(i); + if (!socket->is_available()) { + /* Unavailable sockets should never be used. */ + input_state.type = nullptr; + input_state.usage = ValueUsage::Unused; + continue; + } + const CPPType *type = get_socket_cpp_type(socket); + input_state.type = type; + if (type == nullptr) { + /* This is not a known data socket, it shouldn't be used. */ + input_state.usage = ValueUsage::Unused; + continue; + } + /* Construct the correct struct that can hold the input(s). */ + if (socket->is_multi_input_socket()) { + input_state.value.multi = allocator.construct<MultiInputValue>().release(); + /* Count how many values should be added until the socket is complete. */ + socket.foreach_origin_socket( + [&](DSocket UNUSED(origin)) { input_state.value.multi->expected_size++; }); + /* If no links are connected, we do read the value from socket itself. */ + if (input_state.value.multi->expected_size == 0) { + input_state.value.multi->expected_size = 1; + } + } + else { + input_state.value.single = allocator.construct<SingleInputValue>().release(); + } + } + /* Initialize output states. */ + for (const int i : node->outputs().index_range()) { + OutputState &output_state = node_state.outputs[i]; + const DOutputSocket socket = node.output(i); + if (!socket->is_available()) { + /* Unavailable outputs should never be used. */ + output_state.output_usage = ValueUsage::Unused; + continue; + } + const CPPType *type = get_socket_cpp_type(socket); + if (type == nullptr) { + /* Non data sockets should never be used. */ + output_state.output_usage = ValueUsage::Unused; + continue; + } + /* Count the number of potential users for this socket. */ + socket.foreach_target_socket( + [&, this](const DInputSocket target_socket) { + const DNode target_node = target_socket.node(); + if (!this->node_states_.contains_as(target_node)) { + /* The target node is not computed because it is not computed to the output. */ + return; + } + output_state.potential_users += 1; + }, + {}); + if (output_state.potential_users == 0) { + /* If it does not have any potential users, it is unused. */ + output_state.output_usage = ValueUsage::Unused; + } + } + } + + void destruct_node_states() + { + parallel_for(IndexRange(node_states_.size()), 50, [&, this](const IndexRange range) { + for (const NodeWithState &item : node_states_.as_span().slice(range)) { + this->destruct_node_state(item.node, *item.state); + } + }); + } + + void destruct_node_state(const DNode node, NodeState &node_state) + { + /* Need to destruct stuff manually, because it's allocated by a custom allocator. */ + for (const int i : node->inputs().index_range()) { + InputState &input_state = node_state.inputs[i]; + if (input_state.type == nullptr) { + continue; + } + const InputSocketRef &socket_ref = node->input(i); + if (socket_ref.is_multi_input_socket()) { + MultiInputValue &multi_value = *input_state.value.multi; + for (MultiInputValueItem &item : multi_value.items) { + input_state.type->destruct(item.value); + } + multi_value.~MultiInputValue(); + } + else { + SingleInputValue &single_value = *input_state.value.single; + void *value = single_value.value; + if (value != nullptr) { + input_state.type->destruct(value); + } + single_value.~SingleInputValue(); + } + } + + destruct_n(node_state.inputs.data(), node_state.inputs.size()); + destruct_n(node_state.outputs.data(), node_state.outputs.size()); + + node_state.~NodeState(); + } + + void forward_group_inputs() + { + for (auto &&item : params_.input_values.items()) { + const DOutputSocket socket = item.key; + GMutablePointer value = item.value; + this->log_socket_value(socket, value); + + const DNode node = socket.node(); + if (!node_states_.contains_as(node)) { + /* The socket is not connected to any output. */ + value.destruct(); + continue; + } + this->forward_output(socket, value); + } + } + + void schedule_initial_nodes() + { + for (const DInputSocket &socket : params_.output_sockets) { + const DNode node = socket.node(); + NodeState &node_state = this->get_node_state(node); + LockedNode locked_node{*this, node, node_state}; + /* Setting an input as required will schedule any linked node. */ + this->set_input_required(locked_node, socket); + } + } + + void schedule_node(LockedNode &locked_node) + { + switch (locked_node.node_state.schedule_state) { + case NodeScheduleState::NotScheduled: { + /* The node will be scheduled once it is not locked anymore. We could schedule the node + * right here, but that would result in a deadlock if the task pool decides to run the task + * immediately (this only happens when Blender is started with a single thread). */ + locked_node.node_state.schedule_state = NodeScheduleState::Scheduled; + locked_node.delayed_scheduled_nodes.append(locked_node.node); + break; + } + case NodeScheduleState::Scheduled: { + /* Scheduled already, nothing to do. */ + break; + } + case NodeScheduleState::Running: { + /* Reschedule node while it is running. + * The node will reschedule itself when it is done. */ + locked_node.node_state.schedule_state = NodeScheduleState::RunningAndRescheduled; + break; + } + case NodeScheduleState::RunningAndRescheduled: { + /* Scheduled already, nothing to do. */ + break; + } + } + } + + static void run_node_from_task_pool(TaskPool *task_pool, void *task_data) + { + void *user_data = BLI_task_pool_user_data(task_pool); + GeometryNodesEvaluator &evaluator = *(GeometryNodesEvaluator *)user_data; + const NodeWithState *node_with_state = (const NodeWithState *)task_data; + + evaluator.node_task_run(node_with_state->node, *node_with_state->state); + } + + void node_task_run(const DNode node, NodeState &node_state) + { + /* These nodes are sometimes scheduled. We could also check for them in other places, but + * it's the easiest to do it here. */ + if (node->is_group_input_node() || node->is_group_output_node()) { + return; + } + + const bool do_execute_node = this->node_task_preprocessing(node, node_state); + + /* Only execute the node if all prerequisites are met. There has to be an output that is + * required and all required inputs have to be provided already. */ + if (do_execute_node) { + this->execute_node(node, node_state); + } + + this->node_task_postprocessing(node, node_state); + } + + bool node_task_preprocessing(const DNode node, NodeState &node_state) + { + LockedNode locked_node{*this, node, node_state}; + BLI_assert(node_state.schedule_state == NodeScheduleState::Scheduled); + node_state.schedule_state = NodeScheduleState::Running; + + /* Early return if the node has finished already. */ + if (locked_node.node_state.node_has_finished) { + return false; + } + /* Prepare outputs and check if actually any new outputs have to be computed. */ + if (!this->prepare_node_outputs_for_execution(locked_node)) { + return false; + } + /* Initialize nodes that don't support laziness. This is done after at least one output is + * required and before we check that all required inputs are provided. This reduces the + * number of "round-trips" through the task pool by one for most nodes. */ + if (!node_state.non_lazy_node_is_initialized && !node_supports_laziness(node)) { + this->initialize_non_lazy_node(locked_node); + node_state.non_lazy_node_is_initialized = true; + } + /* Prepare inputs and check if all required inputs are provided. */ + if (!this->prepare_node_inputs_for_execution(locked_node)) { + return false; + } + return true; + } + + /* A node is finished when it has computed all outputs that may be used. */ + bool finish_node_if_possible(LockedNode &locked_node) + { + if (locked_node.node_state.node_has_finished) { + /* Early return in case this node is known to have finished already. */ + return true; + } + + /* Check if there is any output that might be used but has not been computed yet. */ + bool has_remaining_output = false; + for (OutputState &output_state : locked_node.node_state.outputs) { + if (output_state.has_been_computed) { + continue; + } + if (output_state.output_usage != ValueUsage::Unused) { + has_remaining_output = true; + break; + } + } + if (!has_remaining_output) { + /* If there are no remaining outputs, all the inputs can be destructed and/or can become + * unused. This can also trigger a chain reaction where nodes to the left become finished + * too. */ + for (const int i : locked_node.node->inputs().index_range()) { + const DInputSocket socket = locked_node.node.input(i); + InputState &input_state = locked_node.node_state.inputs[i]; + if (input_state.usage == ValueUsage::Maybe) { + this->set_input_unused(locked_node, socket); + } + else if (input_state.usage == ValueUsage::Required) { + /* The value was required, so it cannot become unused. However, we can destruct the + * value. */ + this->destruct_input_value_if_exists(locked_node, socket); + } + } + locked_node.node_state.node_has_finished = true; + } + return locked_node.node_state.node_has_finished; + } + + bool prepare_node_outputs_for_execution(LockedNode &locked_node) + { + bool execution_is_necessary = false; + for (OutputState &output_state : locked_node.node_state.outputs) { + /* Update the output usage for execution to the latest value. */ + output_state.output_usage_for_execution = output_state.output_usage; + if (!output_state.has_been_computed) { + if (output_state.output_usage == ValueUsage::Required) { + /* Only evaluate when there is an output that is required but has not been computed. */ + execution_is_necessary = true; + } + } + } + return execution_is_necessary; + } + + void initialize_non_lazy_node(LockedNode &locked_node) + { + for (const int i : locked_node.node->inputs().index_range()) { + InputState &input_state = locked_node.node_state.inputs[i]; + if (input_state.type == nullptr) { + /* Ignore unavailable/non-data sockets. */ + continue; + } + /* Nodes that don't support laziness require all inputs. */ + const DInputSocket input_socket = locked_node.node.input(i); + this->set_input_required(locked_node, input_socket); + } + } + + /** + * Checks if requested inputs are available and "marks" all the inputs that are available + * during the node execution. Inputs that are provided after this function ends but before the + * node is executed, cannot be read by the node in the execution (note that this only affects + * nodes that support lazy inputs). + */ + bool prepare_node_inputs_for_execution(LockedNode &locked_node) + { + for (const int i : locked_node.node_state.inputs.index_range()) { + InputState &input_state = locked_node.node_state.inputs[i]; + if (input_state.type == nullptr) { + /* Ignore unavailable and non-data sockets. */ + continue; + } + const DInputSocket socket = locked_node.node.input(i); + const bool is_required = input_state.usage == ValueUsage::Required; + + /* No need to check this socket again. */ + if (input_state.was_ready_for_execution) { + continue; + } + + if (socket->is_multi_input_socket()) { + MultiInputValue &multi_value = *input_state.value.multi; + /* Checks if all the linked sockets have been provided already. */ + if (multi_value.items.size() == multi_value.expected_size) { + input_state.was_ready_for_execution = true; + this->log_socket_value(socket, input_state, multi_value.items); + } + else if (is_required) { + /* The input is required but is not fully provided yet. Therefore the node cannot be + * executed yet. */ + return false; + } + } + else { + SingleInputValue &single_value = *input_state.value.single; + if (single_value.value != nullptr) { + input_state.was_ready_for_execution = true; + this->log_socket_value(socket, GPointer{input_state.type, single_value.value}); + } + else if (is_required) { + /* The input is required but has not been provided yet. Therefore the node cannot be + * executed yet. */ + return false; + } + } + } + /* All required inputs have been provided. */ + return true; + } + + /** + * Actually execute the node. All the required inputs are available and at least one output is + * required. + */ + void execute_node(const DNode node, NodeState &node_state) + { + const bNode &bnode = *node->bnode(); + + if (node_state.has_been_executed) { + if (!node_supports_laziness(node)) { + /* Nodes that don't support laziness must not be executed more than once. */ + BLI_assert_unreachable(); + } + } + node_state.has_been_executed = true; + + /* Use the geometry node execute callback if it exists. */ + if (bnode.typeinfo->geometry_node_execute != nullptr) { + this->execute_geometry_node(node, node_state); + return; + } + + /* Use the multi-function implementation if it exists. */ + const MultiFunction *multi_function = params_.mf_by_node->lookup_default(node, nullptr); + if (multi_function != nullptr) { + this->execute_multi_function_node(node, *multi_function, node_state); + return; + } + + this->execute_unknown_node(node, node_state); + } + + void execute_geometry_node(const DNode node, NodeState &node_state) + { + const bNode &bnode = *node->bnode(); + + NodeParamsProvider params_provider{*this, node, node_state}; + GeoNodeExecParams params{params_provider}; + bnode.typeinfo->geometry_node_execute(params); + } + + void execute_multi_function_node(const DNode node, + const MultiFunction &fn, + NodeState &node_state) + { + MFContextBuilder fn_context; + MFParamsBuilder fn_params{fn, 1}; + LinearAllocator<> &allocator = local_allocators_.local(); + + /* Prepare the inputs for the multi function. */ + for (const int i : node->inputs().index_range()) { + const InputSocketRef &socket_ref = node->input(i); + if (!socket_ref.is_available()) { + continue; + } + BLI_assert(!socket_ref.is_multi_input_socket()); + InputState &input_state = node_state.inputs[i]; + BLI_assert(input_state.was_ready_for_execution); + SingleInputValue &single_value = *input_state.value.single; + BLI_assert(single_value.value != nullptr); + fn_params.add_readonly_single_input(GPointer{*input_state.type, single_value.value}); + } + /* Prepare the outputs for the multi function. */ + Vector<GMutablePointer> outputs; + for (const int i : node->outputs().index_range()) { + const OutputSocketRef &socket_ref = node->output(i); + if (!socket_ref.is_available()) { + continue; + } + const CPPType &type = *get_socket_cpp_type(socket_ref); + void *buffer = allocator.allocate(type.size(), type.alignment()); + fn_params.add_uninitialized_single_output(GMutableSpan{type, buffer, 1}); + outputs.append({type, buffer}); + } + + fn.call(IndexRange(1), fn_params, fn_context); + + /* Forward the computed outputs. */ + int output_index = 0; + for (const int i : node->outputs().index_range()) { + const OutputSocketRef &socket_ref = node->output(i); + if (!socket_ref.is_available()) { + continue; + } + OutputState &output_state = node_state.outputs[i]; + const DOutputSocket socket{node.context(), &socket_ref}; + GMutablePointer value = outputs[output_index]; + this->forward_output(socket, value); + output_state.has_been_computed = true; + output_index++; + } + } + + void execute_unknown_node(const DNode node, NodeState &node_state) + { + LinearAllocator<> &allocator = local_allocators_.local(); + for (const OutputSocketRef *socket : node->outputs()) { + if (!socket->is_available()) { + continue; + } + const CPPType *type = get_socket_cpp_type(*socket); + if (type == nullptr) { + continue; + } + /* Just forward the default value of the type as a fallback. That's typically better than + * crashing or doing nothing. */ + OutputState &output_state = node_state.outputs[socket->index()]; + output_state.has_been_computed = true; + void *buffer = allocator.allocate(type->size(), type->alignment()); + type->copy_to_uninitialized(type->default_value(), buffer); + this->forward_output({node.context(), socket}, {*type, buffer}); + } + } + + void node_task_postprocessing(const DNode node, NodeState &node_state) + { + LockedNode locked_node{*this, node, node_state}; + + const bool node_has_finished = this->finish_node_if_possible(locked_node); + const bool reschedule_requested = node_state.schedule_state == + NodeScheduleState::RunningAndRescheduled; + node_state.schedule_state = NodeScheduleState::NotScheduled; + if (reschedule_requested && !node_has_finished) { + /* Either the node rescheduled itself or another node tried to schedule it while it ran. */ + this->schedule_node(locked_node); + } + + this->assert_expected_outputs_have_been_computed(locked_node); + } + + void assert_expected_outputs_have_been_computed(LockedNode &locked_node) + { +#ifdef DEBUG + /* Outputs can only be computed when all required inputs have been provided. */ + if (locked_node.node_state.missing_required_inputs > 0) { + return; + } + /* If the node is still scheduled, it is not necessary that all its expected outputs are + * computed yet. */ + if (locked_node.node_state.schedule_state == NodeScheduleState::Scheduled) { + return; + } + + const bool supports_laziness = node_supports_laziness(locked_node.node); + /* Iterating over sockets instead of the states directly, because that makes it easier to + * figure out which socket is missing when one of the asserts is hit. */ + for (const OutputSocketRef *socket_ref : locked_node.node->outputs()) { + OutputState &output_state = locked_node.node_state.outputs[socket_ref->index()]; + if (supports_laziness) { + /* Expected that at least all required sockets have been computed. If more outputs become + * required later, the node will be executed again. */ + if (output_state.output_usage_for_execution == ValueUsage::Required) { + BLI_assert(output_state.has_been_computed); + } + } + else { + /* Expect that all outputs that may be used have been computed, because the node cannot + * be executed again. */ + if (output_state.output_usage_for_execution != ValueUsage::Unused) { + BLI_assert(output_state.has_been_computed); + } + } + } +#else + UNUSED_VARS(locked_node); +#endif + } + + void extract_group_outputs() + { + for (const DInputSocket &socket : params_.output_sockets) { + BLI_assert(socket->is_available()); + BLI_assert(!socket->is_multi_input_socket()); + + const DNode node = socket.node(); + NodeState &node_state = this->get_node_state(node); + InputState &input_state = node_state.inputs[socket->index()]; + + SingleInputValue &single_value = *input_state.value.single; + void *value = single_value.value; + + /* The value should have been computed by now. If this assert is hit, it means that there + * was some scheduling issue before. */ + BLI_assert(value != nullptr); + + /* Move value into memory owned by the outer allocator. */ + const CPPType &type = *input_state.type; + void *buffer = outer_allocator_.allocate(type.size(), type.alignment()); + type.move_to_uninitialized(value, buffer); + + params_.r_output_values.append({type, buffer}); + } + } + + /** + * Load the required input from the socket or trigger nodes to the left to compute the value. + * When this function is called, the node will always be executed again eventually (either + * immediately, or when all required inputs have been computed by other nodes). + */ + void set_input_required(LockedNode &locked_node, const DInputSocket input_socket) + { + BLI_assert(locked_node.node == input_socket.node()); + InputState &input_state = locked_node.node_state.inputs[input_socket->index()]; + + /* Value set as unused cannot become used again. */ + BLI_assert(input_state.usage != ValueUsage::Unused); + + if (input_state.usage == ValueUsage::Required) { + /* The value is already required, but the node might expect to be evaluated again. */ + this->schedule_node(locked_node); + /* Returning here also ensure that the code below is executed at most once per input. */ + return; + } + input_state.usage = ValueUsage::Required; + + if (input_state.was_ready_for_execution) { + /* The value was already ready, but the node might expect to be evaluated again. */ + this->schedule_node(locked_node); + return; + } + + /* Count how many values still have to be added to this input until it is "complete". */ + int missing_values = 0; + if (input_socket->is_multi_input_socket()) { + MultiInputValue &multi_value = *input_state.value.multi; + missing_values = multi_value.expected_size - multi_value.items.size(); + } + else { + SingleInputValue &single_value = *input_state.value.single; + if (single_value.value == nullptr) { + missing_values = 1; + } + } + if (missing_values == 0) { + /* The input is fully available already, but the node might expect to be evaluated again. */ + this->schedule_node(locked_node); + return; + } + /* Increase the total number of missing required inputs. This ensures that the node will be + * scheduled correctly when all inputs have been provided. */ + locked_node.node_state.missing_required_inputs += missing_values; + + /* Get all origin sockets, because we have to tag those as required as well. */ + Vector<DSocket> origin_sockets; + input_socket.foreach_origin_socket( + [&](const DSocket origin_socket) { origin_sockets.append(origin_socket); }); + + if (origin_sockets.is_empty()) { + /* If there are no origin sockets, just load the value from the socket directly. */ + this->load_unlinked_input_value(locked_node, input_socket, input_state, input_socket); + locked_node.node_state.missing_required_inputs -= 1; + this->schedule_node(locked_node); + return; + } + bool will_be_triggered_by_other_node = false; + for (const DSocket origin_socket : origin_sockets) { + if (origin_socket->is_input()) { + /* Load the value directly from the origin socket. In most cases this is an unlinked + * group input. */ + this->load_unlinked_input_value(locked_node, input_socket, input_state, origin_socket); + locked_node.node_state.missing_required_inputs -= 1; + this->schedule_node(locked_node); + return; + } + /* The value has not been computed yet, so when it will be forwarded by another node, this + * node will be triggered. */ + will_be_triggered_by_other_node = true; + + locked_node.delayed_required_outputs.append(DOutputSocket(origin_socket)); + } + /* If this node will be triggered by another node, we don't have to schedule it now. */ + if (!will_be_triggered_by_other_node) { + this->schedule_node(locked_node); + } + } + + void set_input_unused(LockedNode &locked_node, const DInputSocket socket) + { + InputState &input_state = locked_node.node_state.inputs[socket->index()]; + + /* A required socket cannot become unused. */ + BLI_assert(input_state.usage != ValueUsage::Required); + + if (input_state.usage == ValueUsage::Unused) { + /* Nothing to do in this case. */ + return; + } + input_state.usage = ValueUsage::Unused; + + /* If the input is unused, it's value can be destructed now. */ + this->destruct_input_value_if_exists(locked_node, socket); + + if (input_state.was_ready_for_execution) { + /* If the value was already computed, we don't need to notify origin nodes. */ + return; + } + + /* Notify origin nodes that might want to set its inputs as unused as well. */ + socket.foreach_origin_socket([&](const DSocket origin_socket) { + if (origin_socket->is_input()) { + /* Values from these sockets are loaded directly from the sockets, so there is no node to + * notify. */ + return; + } + /* Delay notification of the other node until this node is not locked anymore. */ + locked_node.delayed_unused_outputs.append(DOutputSocket(origin_socket)); + }); + } + + void send_output_required_notification(const DOutputSocket socket) + { + const DNode node = socket.node(); + NodeState &node_state = this->get_node_state(node); + OutputState &output_state = node_state.outputs[socket->index()]; + + LockedNode locked_node{*this, node, node_state}; + if (output_state.output_usage == ValueUsage::Required) { + /* Output is marked as required already. So the node is scheduled already. */ + return; + } + /* The origin node needs to be scheduled so that it provides the requested input + * eventually. */ + output_state.output_usage = ValueUsage::Required; + this->schedule_node(locked_node); + } + + void send_output_unused_notification(const DOutputSocket socket) + { + const DNode node = socket.node(); + NodeState &node_state = this->get_node_state(node); + OutputState &output_state = node_state.outputs[socket->index()]; + + LockedNode locked_node{*this, node, node_state}; + output_state.potential_users -= 1; + if (output_state.potential_users == 0) { + /* The output socket has no users anymore. */ + output_state.output_usage = ValueUsage::Unused; + /* Schedule the origin node in case it wants to set its inputs as unused as well. */ + this->schedule_node(locked_node); + } + } + + void add_node_to_task_pool(const DNode node) + { + /* Push the task to the pool while it is not locked to avoid a deadlock in case when the task + * is executed immediately. */ + const NodeWithState *node_with_state = node_states_.lookup_key_ptr_as(node); + BLI_task_pool_push( + task_pool_, run_node_from_task_pool, (void *)node_with_state, false, nullptr); + } + + /** + * Moves a newly computed value from an output socket to all the inputs that might need it. + */ + void forward_output(const DOutputSocket from_socket, GMutablePointer value_to_forward) + { + BLI_assert(value_to_forward.get() != nullptr); + + Vector<DInputSocket> to_sockets; + auto handle_target_socket_fn = [&, this](const DInputSocket to_socket) { + if (this->should_forward_to_socket(to_socket)) { + to_sockets.append(to_socket); + } + }; + auto handle_skipped_socket_fn = [&, this](const DSocket socket) { + /* Log socket value on intermediate sockets to support e.g. attribute search or spreadsheet + * breadcrumbs on group nodes. */ + this->log_socket_value(socket, value_to_forward); + }; + from_socket.foreach_target_socket(handle_target_socket_fn, handle_skipped_socket_fn); + + LinearAllocator<> &allocator = local_allocators_.local(); + + const CPPType &from_type = *value_to_forward.type(); + Vector<DInputSocket> to_sockets_same_type; + for (const DInputSocket &to_socket : to_sockets) { + const CPPType &to_type = *get_socket_cpp_type(to_socket); + if (from_type == to_type) { + /* All target sockets that do not need a conversion will be handled afterwards. */ + to_sockets_same_type.append(to_socket); + continue; + } + this->forward_to_socket_with_different_type( + allocator, value_to_forward, from_socket, to_socket, to_type); + } + this->forward_to_sockets_with_same_type( + allocator, to_sockets_same_type, value_to_forward, from_socket); + } + + bool should_forward_to_socket(const DInputSocket socket) + { + const DNode to_node = socket.node(); + const NodeWithState *target_node_with_state = node_states_.lookup_key_ptr_as(to_node); + if (target_node_with_state == nullptr) { + /* If the socket belongs to a node that has no state, the entire node is not used. */ + return false; + } + NodeState &target_node_state = *target_node_with_state->state; + InputState &target_input_state = target_node_state.inputs[socket->index()]; + + std::lock_guard lock{target_node_state.mutex}; + /* Do not forward to an input socket whose value won't be used. */ + return target_input_state.usage != ValueUsage::Unused; + } + + void forward_to_socket_with_different_type(LinearAllocator<> &allocator, + const GPointer value_to_forward, + const DOutputSocket from_socket, + const DInputSocket to_socket, + const CPPType &to_type) + { + const CPPType &from_type = *value_to_forward.type(); + + /* Allocate a buffer for the converted value. */ + void *buffer = allocator.allocate(to_type.size(), to_type.alignment()); + + if (conversions_.is_convertible(from_type, to_type)) { + /* Do the conversion if possible. */ + conversions_.convert_to_uninitialized(from_type, to_type, value_to_forward.get(), buffer); + } + else { + /* Cannot convert, use default value instead. */ + to_type.copy_to_uninitialized(to_type.default_value(), buffer); + } + this->add_value_to_input_socket(to_socket, from_socket, {to_type, buffer}); + } + + void forward_to_sockets_with_same_type(LinearAllocator<> &allocator, + Span<DInputSocket> to_sockets, + GMutablePointer value_to_forward, + const DOutputSocket from_socket) + { + if (to_sockets.is_empty()) { + /* Value is not used anymore, so it can be destructed. */ + value_to_forward.destruct(); + } + else if (to_sockets.size() == 1) { + /* Value is only used by one input socket, no need to copy it. */ + const DInputSocket to_socket = to_sockets[0]; + this->add_value_to_input_socket(to_socket, from_socket, value_to_forward); + } + else { + /* Multiple inputs use the value, make a copy for every input except for one. */ + /* First make the copies, so that the next node does not start modifying the value while we + * are still making copies. */ + const CPPType &type = *value_to_forward.type(); + for (const DInputSocket &to_socket : to_sockets.drop_front(1)) { + void *buffer = allocator.allocate(type.size(), type.alignment()); + type.copy_to_uninitialized(value_to_forward.get(), buffer); + this->add_value_to_input_socket(to_socket, from_socket, {type, buffer}); + } + /* Forward the original value to one of the targets. */ + const DInputSocket to_socket = to_sockets[0]; + this->add_value_to_input_socket(to_socket, from_socket, value_to_forward); + } + } + + void add_value_to_input_socket(const DInputSocket socket, + const DOutputSocket origin, + GMutablePointer value) + { + BLI_assert(socket->is_available()); + + const DNode node = socket.node(); + NodeState &node_state = this->get_node_state(node); + InputState &input_state = node_state.inputs[socket->index()]; + + /* Lock the node because we want to change its state. */ + LockedNode locked_node{*this, node, node_state}; + + if (socket->is_multi_input_socket()) { + /* Add a new value to the multi-input. */ + MultiInputValue &multi_value = *input_state.value.multi; + multi_value.items.append({origin, value.get()}); + } + else { + /* Assign the value to the input. */ + SingleInputValue &single_value = *input_state.value.single; + BLI_assert(single_value.value == nullptr); + single_value.value = value.get(); + } + + if (input_state.usage == ValueUsage::Required) { + node_state.missing_required_inputs--; + if (node_state.missing_required_inputs == 0) { + /* Schedule node if all the required inputs have been provided. */ + this->schedule_node(locked_node); + } + } + } + + void load_unlinked_input_value(LockedNode &locked_node, + const DInputSocket input_socket, + InputState &input_state, + const DSocket origin_socket) + { + /* Only takes locked node as parameter, because the node needs to be locked. */ + UNUSED_VARS(locked_node); + + GMutablePointer value = this->get_value_from_socket(origin_socket, *input_state.type); + if (input_socket->is_multi_input_socket()) { + MultiInputValue &multi_value = *input_state.value.multi; + multi_value.items.append({input_socket, value.get()}); + } + else { + SingleInputValue &single_value = *input_state.value.single; + single_value.value = value.get(); + } + } + + void destruct_input_value_if_exists(LockedNode &locked_node, const DInputSocket socket) + { + InputState &input_state = locked_node.node_state.inputs[socket->index()]; + if (socket->is_multi_input_socket()) { + MultiInputValue &multi_value = *input_state.value.multi; + for (MultiInputValueItem &item : multi_value.items) { + input_state.type->destruct(item.value); + } + multi_value.items.clear(); + } + else { + SingleInputValue &single_value = *input_state.value.single; + if (single_value.value != nullptr) { + input_state.type->destruct(single_value.value); + single_value.value = nullptr; + } + } + } + + GMutablePointer get_value_from_socket(const DSocket socket, const CPPType &required_type) + { + LinearAllocator<> &allocator = local_allocators_.local(); + + bNodeSocket *bsocket = socket->bsocket(); + const CPPType &type = *get_socket_cpp_type(socket); + void *buffer = allocator.allocate(type.size(), type.alignment()); + blender::nodes::socket_cpp_value_get(*bsocket, buffer); + + if (type == required_type) { + return {type, buffer}; + } + if (conversions_.is_convertible(type, required_type)) { + /* Convert the loaded value to the required type if possible. */ + void *converted_buffer = allocator.allocate(required_type.size(), required_type.alignment()); + conversions_.convert_to_uninitialized(type, required_type, buffer, converted_buffer); + type.destruct(buffer); + return {required_type, converted_buffer}; + } + /* Use a default fallback value when the loaded type is not compatible. */ + void *default_buffer = allocator.allocate(required_type.size(), required_type.alignment()); + required_type.copy_to_uninitialized(required_type.default_value(), default_buffer); + return {required_type, default_buffer}; + } + + NodeState &get_node_state(const DNode node) + { + return *node_states_.lookup_key_as(node).state; + } + + void log_socket_value(const DSocket socket, Span<GPointer> values) + { + if (params_.log_socket_value_fn) { + params_.log_socket_value_fn(socket, values); + } + } + + void log_socket_value(const DSocket socket, + InputState &input_state, + Span<MultiInputValueItem> values) + { + Vector<GPointer, 16> value_pointers; + value_pointers.reserve(values.size()); + const CPPType &type = *input_state.type; + for (const MultiInputValueItem &item : values) { + value_pointers.append({type, item.value}); + } + this->log_socket_value(socket, value_pointers); + } + + void log_socket_value(const DSocket socket, GPointer value) + { + this->log_socket_value(socket, Span<GPointer>(&value, 1)); + } +}; + +LockedNode::~LockedNode() +{ + /* First unlock the current node. */ + node_state.mutex.unlock(); + /* Then send notifications to the other nodes. */ + for (const DOutputSocket &socket : delayed_required_outputs) { + evaluator_.send_output_required_notification(socket); + } + for (const DOutputSocket &socket : delayed_unused_outputs) { + evaluator_.send_output_unused_notification(socket); + } + for (const DNode &node : delayed_scheduled_nodes) { + evaluator_.add_node_to_task_pool(node); + } +} + +/* TODO: Use a map data structure or so to make this faster. */ +static DInputSocket get_input_by_identifier(const DNode node, const StringRef identifier) +{ + for (const InputSocketRef *socket : node->inputs()) { + if (socket->identifier() == identifier) { + return {node.context(), socket}; + } + } + return {}; +} + +static DOutputSocket get_output_by_identifier(const DNode node, const StringRef identifier) +{ + for (const OutputSocketRef *socket : node->outputs()) { + if (socket->identifier() == identifier) { + return {node.context(), socket}; + } + } + return {}; +} + +NodeParamsProvider::NodeParamsProvider(GeometryNodesEvaluator &evaluator, + DNode dnode, + NodeState &node_state) + : evaluator_(evaluator), node_state_(node_state) +{ + this->dnode = dnode; + this->self_object = evaluator.params_.self_object; + this->modifier = &evaluator.params_.modifier_->modifier; + this->depsgraph = evaluator.params_.depsgraph; +} + +bool NodeParamsProvider::can_get_input(StringRef identifier) const +{ + const DInputSocket socket = get_input_by_identifier(this->dnode, identifier); + BLI_assert(socket); + + InputState &input_state = node_state_.inputs[socket->index()]; + if (!input_state.was_ready_for_execution) { + return false; + } + + if (socket->is_multi_input_socket()) { + MultiInputValue &multi_value = *input_state.value.multi; + return multi_value.items.size() == multi_value.expected_size; + } + SingleInputValue &single_value = *input_state.value.single; + return single_value.value != nullptr; +} + +bool NodeParamsProvider::can_set_output(StringRef identifier) const +{ + const DOutputSocket socket = get_output_by_identifier(this->dnode, identifier); + BLI_assert(socket); + + OutputState &output_state = node_state_.outputs[socket->index()]; + return !output_state.has_been_computed; +} + +GMutablePointer NodeParamsProvider::extract_input(StringRef identifier) +{ + const DInputSocket socket = get_input_by_identifier(this->dnode, identifier); + BLI_assert(socket); + BLI_assert(!socket->is_multi_input_socket()); + BLI_assert(this->can_get_input(identifier)); + + InputState &input_state = node_state_.inputs[socket->index()]; + SingleInputValue &single_value = *input_state.value.single; + void *value = single_value.value; + single_value.value = nullptr; + return {*input_state.type, value}; +} + +Vector<GMutablePointer> NodeParamsProvider::extract_multi_input(StringRef identifier) +{ + const DInputSocket socket = get_input_by_identifier(this->dnode, identifier); + BLI_assert(socket); + BLI_assert(socket->is_multi_input_socket()); + BLI_assert(this->can_get_input(identifier)); + + InputState &input_state = node_state_.inputs[socket->index()]; + MultiInputValue &multi_value = *input_state.value.multi; + + Vector<GMutablePointer> ret_values; + socket.foreach_origin_socket([&](DSocket origin) { + for (const MultiInputValueItem &item : multi_value.items) { + if (item.origin == origin) { + ret_values.append({*input_state.type, item.value}); + return; + } + } + BLI_assert_unreachable(); + }); + multi_value.items.clear(); + return ret_values; +} + +GPointer NodeParamsProvider::get_input(StringRef identifier) const +{ + const DInputSocket socket = get_input_by_identifier(this->dnode, identifier); + BLI_assert(socket); + BLI_assert(!socket->is_multi_input_socket()); + BLI_assert(this->can_get_input(identifier)); + + InputState &input_state = node_state_.inputs[socket->index()]; + SingleInputValue &single_value = *input_state.value.single; + return {*input_state.type, single_value.value}; +} + +GMutablePointer NodeParamsProvider::alloc_output_value(const CPPType &type) +{ + LinearAllocator<> &allocator = evaluator_.local_allocators_.local(); + return {type, allocator.allocate(type.size(), type.alignment())}; +} + +void NodeParamsProvider::set_output(StringRef identifier, GMutablePointer value) +{ + const DOutputSocket socket = get_output_by_identifier(this->dnode, identifier); + BLI_assert(socket); + + evaluator_.log_socket_value(socket, value); + + OutputState &output_state = node_state_.outputs[socket->index()]; + BLI_assert(!output_state.has_been_computed); + evaluator_.forward_output(socket, value); + output_state.has_been_computed = true; +} + +bool NodeParamsProvider::lazy_require_input(StringRef identifier) +{ + BLI_assert(node_supports_laziness(this->dnode)); + const DInputSocket socket = get_input_by_identifier(this->dnode, identifier); + BLI_assert(socket); + + InputState &input_state = node_state_.inputs[socket->index()]; + if (input_state.was_ready_for_execution) { + return false; + } + LockedNode locked_node{evaluator_, this->dnode, node_state_}; + evaluator_.set_input_required(locked_node, socket); + return true; +} + +void NodeParamsProvider::set_input_unused(StringRef identifier) +{ + const DInputSocket socket = get_input_by_identifier(this->dnode, identifier); + BLI_assert(socket); + + LockedNode locked_node{evaluator_, this->dnode, node_state_}; + evaluator_.set_input_unused(locked_node, socket); +} + +bool NodeParamsProvider::output_is_required(StringRef identifier) const +{ + const DOutputSocket socket = get_output_by_identifier(this->dnode, identifier); + BLI_assert(socket); + + OutputState &output_state = node_state_.outputs[socket->index()]; + if (output_state.has_been_computed) { + return false; + } + return output_state.output_usage_for_execution != ValueUsage::Unused; +} + +bool NodeParamsProvider::lazy_output_is_required(StringRef identifier) const +{ + BLI_assert(node_supports_laziness(this->dnode)); + const DOutputSocket socket = get_output_by_identifier(this->dnode, identifier); + BLI_assert(socket); + + OutputState &output_state = node_state_.outputs[socket->index()]; + if (output_state.has_been_computed) { + return false; + } + return output_state.output_usage_for_execution == ValueUsage::Required; +} + +void evaluate_geometry_nodes(GeometryNodesEvaluationParams ¶ms) +{ + GeometryNodesEvaluator evaluator{params}; + evaluator.execute(); +} + +} // namespace blender::modifiers::geometry_nodes diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.hh b/source/blender/modifiers/intern/MOD_nodes_evaluator.hh new file mode 100644 index 00000000000..84249e4244e --- /dev/null +++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.hh @@ -0,0 +1,52 @@ +/* + * 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 + +#include "BLI_map.hh" + +#include "NOD_derived_node_tree.hh" +#include "NOD_node_tree_multi_function.hh" + +#include "FN_generic_pointer.hh" + +#include "DNA_modifier_types.h" + +namespace blender::modifiers::geometry_nodes { + +using namespace nodes::derived_node_tree_types; +using fn::GMutablePointer; +using fn::GPointer; + +using LogSocketValueFn = std::function<void(DSocket, Span<GPointer>)>; + +struct GeometryNodesEvaluationParams { + blender::LinearAllocator<> allocator; + + Map<DOutputSocket, GMutablePointer> input_values; + Vector<DInputSocket> output_sockets; + nodes::MultiFunctionByNode *mf_by_node; + const NodesModifierData *modifier_; + Depsgraph *depsgraph; + Object *self_object; + LogSocketValueFn log_socket_value_fn; + + Vector<GMutablePointer> r_output_values; +}; + +void evaluate_geometry_nodes(GeometryNodesEvaluationParams ¶ms); + +} // namespace blender::modifiers::geometry_nodes diff --git a/source/blender/modifiers/intern/MOD_none.c b/source/blender/modifiers/intern/MOD_none.c index 4daa527577b..a01f63be791 100644 --- a/source/blender/modifiers/intern/MOD_none.c +++ b/source/blender/modifiers/intern/MOD_none.c @@ -60,7 +60,6 @@ ModifierTypeInfo modifierType_None = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ NULL, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_normal_edit.c b/source/blender/modifiers/intern/MOD_normal_edit.c index ec10b18665e..4bfd6aba4b2 100644 --- a/source/blender/modifiers/intern/MOD_normal_edit.c +++ b/source/blender/modifiers/intern/MOD_normal_edit.c @@ -805,7 +805,6 @@ ModifierTypeInfo modifierType_NormalEdit = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_ocean.c b/source/blender/modifiers/intern/MOD_ocean.c index 9c940745920..f7ac59f9e4b 100644 --- a/source/blender/modifiers/intern/MOD_ocean.c +++ b/source/blender/modifiers/intern/MOD_ocean.c @@ -737,7 +737,6 @@ ModifierTypeInfo modifierType_Ocean = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_particleinstance.c b/source/blender/modifiers/intern/MOD_particleinstance.c index e7f1fa9943e..60c5667472e 100644 --- a/source/blender/modifiers/intern/MOD_particleinstance.c +++ b/source/blender/modifiers/intern/MOD_particleinstance.c @@ -679,7 +679,6 @@ ModifierTypeInfo modifierType_ParticleInstance = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_particlesystem.c b/source/blender/modifiers/intern/MOD_particlesystem.c index 4c1179af431..38cce5e6a50 100644 --- a/source/blender/modifiers/intern/MOD_particlesystem.c +++ b/source/blender/modifiers/intern/MOD_particlesystem.c @@ -337,7 +337,6 @@ ModifierTypeInfo modifierType_ParticleSystem = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_remesh.c b/source/blender/modifiers/intern/MOD_remesh.c index 175435fcd44..88851f91337 100644 --- a/source/blender/modifiers/intern/MOD_remesh.c +++ b/source/blender/modifiers/intern/MOD_remesh.c @@ -301,7 +301,6 @@ ModifierTypeInfo modifierType_Remesh = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_screw.c b/source/blender/modifiers/intern/MOD_screw.c index 84360caa345..b236e0896b7 100644 --- a/source/blender/modifiers/intern/MOD_screw.c +++ b/source/blender/modifiers/intern/MOD_screw.c @@ -1258,7 +1258,6 @@ ModifierTypeInfo modifierType_Screw = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_shapekey.c b/source/blender/modifiers/intern/MOD_shapekey.c index 81a0ee72496..b517bc102f8 100644 --- a/source/blender/modifiers/intern/MOD_shapekey.c +++ b/source/blender/modifiers/intern/MOD_shapekey.c @@ -141,7 +141,6 @@ ModifierTypeInfo modifierType_ShapeKey = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ NULL, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_shrinkwrap.c b/source/blender/modifiers/intern/MOD_shrinkwrap.c index 93626309727..a12724ec23c 100644 --- a/source/blender/modifiers/intern/MOD_shrinkwrap.c +++ b/source/blender/modifiers/intern/MOD_shrinkwrap.c @@ -293,7 +293,6 @@ ModifierTypeInfo modifierType_Shrinkwrap = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_simpledeform.c b/source/blender/modifiers/intern/MOD_simpledeform.c index 951eacdecda..db01dec4d19 100644 --- a/source/blender/modifiers/intern/MOD_simpledeform.c +++ b/source/blender/modifiers/intern/MOD_simpledeform.c @@ -21,10 +21,9 @@ * \ingroup modifiers */ -#include "BLI_utildefines.h" - #include "BLI_math.h" - +#include "BLI_task.h" +#include "BLI_utildefines.h" #include "BLT_translation.h" #include "DNA_defaults.h" @@ -57,6 +56,21 @@ #define BEND_EPS 0.000001f +ALIGN_STRUCT struct DeformUserData { + bool invert_vgroup; + char mode; + char deform_axis; + int lock_axis; + int vgroup; + int limit_axis; + float weight; + float smd_factor; + float smd_limit[2]; + float (*vertexCos)[3]; + const SpaceTransform *transf; + const MDeformVert *dvert; +}; + /* Re-maps the indices for X Y Z by shifting them up and wrapping, such that * X = Y, Y = Z, Z = X (for X axis), and X = Z, Y = X, Z = Y (for Y axis). This * exists because the deformations (excluding bend) are based on the Z axis. @@ -205,6 +219,88 @@ static void simpleDeform_bend(const float factor, } } +static void simple_helper(void *__restrict userdata, + const int iter, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + const struct DeformUserData *curr_deform_data = userdata; + float weight = BKE_defvert_array_find_weight_safe( + curr_deform_data->dvert, iter, curr_deform_data->vgroup); + const uint *axis_map = axis_map_table[(curr_deform_data->mode != MOD_SIMPLEDEFORM_MODE_BEND) ? + curr_deform_data->deform_axis : + 2]; + const float base_limit[2] = {0.0f, 0.0f}; + + if (curr_deform_data->invert_vgroup) { + weight = 1.0f - weight; + } + + if (weight != 0.0f) { + float co[3], dcut[3] = {0.0f, 0.0f, 0.0f}; + + if (curr_deform_data->transf) { + BLI_space_transform_apply(curr_deform_data->transf, curr_deform_data->vertexCos[iter]); + } + + copy_v3_v3(co, curr_deform_data->vertexCos[iter]); + + /* Apply axis limits, and axis mappings */ + if (curr_deform_data->lock_axis & MOD_SIMPLEDEFORM_LOCK_AXIS_X) { + axis_limit(0, base_limit, co, dcut); + } + if (curr_deform_data->lock_axis & MOD_SIMPLEDEFORM_LOCK_AXIS_Y) { + axis_limit(1, base_limit, co, dcut); + } + if (curr_deform_data->lock_axis & MOD_SIMPLEDEFORM_LOCK_AXIS_Z) { + axis_limit(2, base_limit, co, dcut); + } + axis_limit(curr_deform_data->limit_axis, curr_deform_data->smd_limit, co, dcut); + + /* apply the deform to a mapped copy of the vertex, and then re-map it back. */ + float co_remap[3]; + float dcut_remap[3]; + copy_v3_v3_map(co_remap, co, axis_map); + copy_v3_v3_map(dcut_remap, dcut, axis_map); + switch (curr_deform_data->mode) { + case MOD_SIMPLEDEFORM_MODE_TWIST: + simpleDeform_twist(curr_deform_data->smd_factor, + curr_deform_data->deform_axis, + dcut_remap, + co_remap); /* apply deform */ + break; + case MOD_SIMPLEDEFORM_MODE_BEND: + simpleDeform_bend(curr_deform_data->smd_factor, + curr_deform_data->deform_axis, + dcut_remap, + co_remap); /* apply deform */ + break; + case MOD_SIMPLEDEFORM_MODE_TAPER: + simpleDeform_taper(curr_deform_data->smd_factor, + curr_deform_data->deform_axis, + dcut_remap, + co_remap); /* apply deform */ + break; + case MOD_SIMPLEDEFORM_MODE_STRETCH: + simpleDeform_stretch(curr_deform_data->smd_factor, + curr_deform_data->deform_axis, + dcut_remap, + co_remap); /* apply deform */ + break; + default: + return; /* No simple-deform mode? */ + } + copy_v3_v3_unmap(co, co_remap, axis_map); + + /* Use vertex weight has coef of linear interpolation */ + interp_v3_v3v3( + curr_deform_data->vertexCos[iter], curr_deform_data->vertexCos[iter], co, weight); + + if (curr_deform_data->transf) { + BLI_space_transform_invert(curr_deform_data->transf, curr_deform_data->vertexCos[iter]); + } + } +} + /* simple deform modifier */ static void SimpleDeformModifier_do(SimpleDeformModifierData *smd, const ModifierEvalContext *UNUSED(ctx), @@ -213,14 +309,9 @@ static void SimpleDeformModifier_do(SimpleDeformModifierData *smd, float (*vertexCos)[3], int numVerts) { - const float base_limit[2] = {0.0f, 0.0f}; int i; float smd_limit[2], smd_factor; SpaceTransform *transf = NULL, tmp_transf; - void (*simpleDeform_callback)(const float factor, - const int axis, - const float dcut[3], - float co[3]) = NULL; /* Mode callback */ int vgroup; MDeformVert *dvert; @@ -302,23 +393,6 @@ static void SimpleDeformModifier_do(SimpleDeformModifierData *smd, smd_factor = smd->factor / max_ff(FLT_EPSILON, smd_limit[1] - smd_limit[0]); } - switch (smd->mode) { - case MOD_SIMPLEDEFORM_MODE_TWIST: - simpleDeform_callback = simpleDeform_twist; - break; - case MOD_SIMPLEDEFORM_MODE_BEND: - simpleDeform_callback = simpleDeform_bend; - break; - case MOD_SIMPLEDEFORM_MODE_TAPER: - simpleDeform_callback = simpleDeform_taper; - break; - case MOD_SIMPLEDEFORM_MODE_STRETCH: - simpleDeform_callback = simpleDeform_stretch; - break; - default: - return; /* No simple-deform mode? */ - } - if (smd->mode == MOD_SIMPLEDEFORM_MODE_BEND) { if (fabsf(smd_factor) < BEND_EPS) { return; @@ -327,53 +401,26 @@ static void SimpleDeformModifier_do(SimpleDeformModifierData *smd, MOD_get_vgroup(ob, mesh, smd->vgroup_name, &dvert, &vgroup); const bool invert_vgroup = (smd->flag & MOD_SIMPLEDEFORM_FLAG_INVERT_VGROUP) != 0; - const uint *axis_map = - axis_map_table[(smd->mode != MOD_SIMPLEDEFORM_MODE_BEND) ? deform_axis : 2]; - - for (i = 0; i < numVerts; i++) { - float weight = BKE_defvert_array_find_weight_safe(dvert, i, vgroup); - - if (invert_vgroup) { - weight = 1.0f - weight; - } - - if (weight != 0.0f) { - float co[3], dcut[3] = {0.0f, 0.0f, 0.0f}; - if (transf) { - BLI_space_transform_apply(transf, vertexCos[i]); - } - - copy_v3_v3(co, vertexCos[i]); - - /* Apply axis limits, and axis mappings */ - if (lock_axis & MOD_SIMPLEDEFORM_LOCK_AXIS_X) { - axis_limit(0, base_limit, co, dcut); - } - if (lock_axis & MOD_SIMPLEDEFORM_LOCK_AXIS_Y) { - axis_limit(1, base_limit, co, dcut); - } - if (lock_axis & MOD_SIMPLEDEFORM_LOCK_AXIS_Z) { - axis_limit(2, base_limit, co, dcut); - } - axis_limit(limit_axis, smd_limit, co, dcut); - - /* apply the deform to a mapped copy of the vertex, and then re-map it back. */ - float co_remap[3]; - float dcut_remap[3]; - copy_v3_v3_map(co_remap, co, axis_map); - copy_v3_v3_map(dcut_remap, dcut, axis_map); - simpleDeform_callback(smd_factor, deform_axis, dcut_remap, co_remap); /* apply deform */ - copy_v3_v3_unmap(co, co_remap, axis_map); - - /* Use vertex weight has coef of linear interpolation */ - interp_v3_v3v3(vertexCos[i], vertexCos[i], co, weight); - - if (transf) { - BLI_space_transform_invert(transf, vertexCos[i]); - } - } - } + /* Build our data. */ + const struct DeformUserData deform_pool_data = { + .mode = smd->mode, + .smd_factor = smd_factor, + .deform_axis = deform_axis, + .transf = transf, + .vertexCos = vertexCos, + .invert_vgroup = invert_vgroup, + .lock_axis = lock_axis, + .vgroup = vgroup, + .smd_limit[0] = smd_limit[0], + .smd_limit[1] = smd_limit[1], + .dvert = dvert, + .limit_axis = limit_axis, + }; + /* Do deformation. */ + TaskParallelSettings settings; + BLI_parallel_range_settings_defaults(&settings); + BLI_task_parallel_range(0, numVerts, (void *)&deform_pool_data, simple_helper, &settings); } /* SimpleDeform */ @@ -555,7 +602,6 @@ ModifierTypeInfo modifierType_SimpleDeform = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_skin.c b/source/blender/modifiers/intern/MOD_skin.c index 5e412185cea..58d70ef3a4a 100644 --- a/source/blender/modifiers/intern/MOD_skin.c +++ b/source/blender/modifiers/intern/MOD_skin.c @@ -90,6 +90,46 @@ #include "bmesh.h" +/* -------------------------------------------------------------------- */ +/** \name Generic BMesh Utilities + * \{ */ + +static void vert_face_normal_mark_set(BMVert *v) +{ + BMIter iter; + BMFace *f; + BM_ITER_ELEM (f, &iter, v, BM_FACES_OF_VERT) { + f->no[0] = FLT_MAX; + } +} + +static void vert_face_normal_mark_update(BMVert *v) +{ + BMIter iter; + BMFace *f; + BM_ITER_ELEM (f, &iter, v, BM_FACES_OF_VERT) { + if (f->no[0] == FLT_MAX) { + BM_face_normal_update(f); + } + } +} + +/** + * Recalculate the normals of all faces connected to `verts`. + */ +static void vert_array_face_normal_update(BMVert **verts, int verts_len) +{ + for (int i = 0; i < verts_len; i++) { + vert_face_normal_mark_set(verts[i]); + } + + for (int i = 0; i < verts_len; i++) { + vert_face_normal_mark_update(verts[i]); + } +} + +/** \} */ + typedef struct { float mat[3][3]; /* Vert that edge is pointing away from, no relation to @@ -1352,13 +1392,25 @@ static void skin_fix_hole_no_good_verts(BMesh *bm, Frame *frame, BMFace *split_f split_face = collapse_face_corners(bm, split_face, 4, vert_buf); } - /* Done with dynamic array, split_face must now be a quad */ - BLI_array_free(vert_buf); + /* `split_face` should now be a quad. */ BLI_assert(split_face->len == 4); + + /* Account for the highly unlikely case that it's not a quad. */ if (split_face->len != 4) { + /* Reuse `vert_buf` for updating normals. */ + BLI_array_clear(vert_buf); + BLI_array_grow_items(vert_buf, split_face->len); + + BM_iter_as_array(bm, BM_FACES_OF_VERT, split_face, (void **)vert_buf, split_face->len); + + vert_array_face_normal_update(vert_buf, split_face->len); + BLI_array_free(vert_buf); return; } + /* Done with dynamic array. */ + BLI_array_free(vert_buf); + /* Get split face's verts */ // BM_iter_as_array(bm, BM_VERTS_OF_FACE, split_face, (void **)verts, 4); BM_face_as_array_vert_quad(split_face, verts); @@ -1373,6 +1425,8 @@ static void skin_fix_hole_no_good_verts(BMesh *bm, Frame *frame, BMFace *split_f } BMO_op_exec(bm, &op); BMO_op_finish(bm, &op); + + vert_array_face_normal_update(frame->verts, 4); } /* If the frame has some vertices that are inside the hull (detached) @@ -1731,6 +1785,11 @@ static void skin_smooth_hulls(BMesh *bm, /* Done with original coordinates */ BM_data_layer_free_n(bm, &bm->vdata, CD_SHAPEKEY, skey); + + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + BM_face_normal_update(f); + } } /* Returns true if all hulls are successfully built, false otherwise */ @@ -2044,7 +2103,6 @@ ModifierTypeInfo modifierType_Skin = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_smooth.c b/source/blender/modifiers/intern/MOD_smooth.c index c5011ed15c1..97027e2ecff 100644 --- a/source/blender/modifiers/intern/MOD_smooth.c +++ b/source/blender/modifiers/intern/MOD_smooth.c @@ -286,7 +286,6 @@ ModifierTypeInfo modifierType_Smooth = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_softbody.c b/source/blender/modifiers/intern/MOD_softbody.c index 9a657c44fca..d7d2f948955 100644 --- a/source/blender/modifiers/intern/MOD_softbody.c +++ b/source/blender/modifiers/intern/MOD_softbody.c @@ -120,7 +120,6 @@ ModifierTypeInfo modifierType_Softbody = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ NULL, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_solidify.c b/source/blender/modifiers/intern/MOD_solidify.c index 8e519a72df1..736dd08a713 100644 --- a/source/blender/modifiers/intern/MOD_solidify.c +++ b/source/blender/modifiers/intern/MOD_solidify.c @@ -276,7 +276,6 @@ ModifierTypeInfo modifierType_Solidify = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_subsurf.c b/source/blender/modifiers/intern/MOD_subsurf.c index c3611488f5f..4dc45ad0324 100644 --- a/source/blender/modifiers/intern/MOD_subsurf.c +++ b/source/blender/modifiers/intern/MOD_subsurf.c @@ -509,7 +509,6 @@ ModifierTypeInfo modifierType_Subsurf = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_surface.c b/source/blender/modifiers/intern/MOD_surface.c index 7416a4baf38..d2c011a21d3 100644 --- a/source/blender/modifiers/intern/MOD_surface.c +++ b/source/blender/modifiers/intern/MOD_surface.c @@ -241,7 +241,6 @@ ModifierTypeInfo modifierType_Surface = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_surfacedeform.c b/source/blender/modifiers/intern/MOD_surfacedeform.c index 99011c5e351..0cc68c2c4a3 100644 --- a/source/blender/modifiers/intern/MOD_surfacedeform.c +++ b/source/blender/modifiers/intern/MOD_surfacedeform.c @@ -1646,7 +1646,6 @@ ModifierTypeInfo modifierType_SurfaceDeform = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_triangulate.c b/source/blender/modifiers/intern/MOD_triangulate.c index 04d24ac0883..ef633494c7b 100644 --- a/source/blender/modifiers/intern/MOD_triangulate.c +++ b/source/blender/modifiers/intern/MOD_triangulate.c @@ -178,7 +178,6 @@ ModifierTypeInfo modifierType_Triangulate = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, // requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_uvproject.c b/source/blender/modifiers/intern/MOD_uvproject.c index 3162a33edc2..724d1370a47 100644 --- a/source/blender/modifiers/intern/MOD_uvproject.c +++ b/source/blender/modifiers/intern/MOD_uvproject.c @@ -385,7 +385,6 @@ ModifierTypeInfo modifierType_UVProject = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_uvwarp.c b/source/blender/modifiers/intern/MOD_uvwarp.c index 77b79167c2f..5742144b6dd 100644 --- a/source/blender/modifiers/intern/MOD_uvwarp.c +++ b/source/blender/modifiers/intern/MOD_uvwarp.c @@ -342,7 +342,6 @@ ModifierTypeInfo modifierType_UVWarp = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_volume_displace.cc b/source/blender/modifiers/intern/MOD_volume_displace.cc index e7c4004853e..af4b31d6bfc 100644 --- a/source/blender/modifiers/intern/MOD_volume_displace.cc +++ b/source/blender/modifiers/intern/MOD_volume_displace.cc @@ -18,6 +18,7 @@ * \ingroup modifiers */ +#include "BKE_geometry_set.hh" #include "BKE_lib_query.h" #include "BKE_mesh_runtime.h" #include "BKE_modifier.h" @@ -284,7 +285,7 @@ struct DisplaceGridOp { #endif -static Volume *modifyVolume(ModifierData *md, const ModifierEvalContext *ctx, Volume *volume) +static void displace_volume(ModifierData *md, const ModifierEvalContext *ctx, Volume *volume) { #ifdef WITH_OPENVDB VolumeDisplaceModifierData *vdmd = reinterpret_cast<VolumeDisplaceModifierData *>(md); @@ -303,14 +304,22 @@ static Volume *modifyVolume(ModifierData *md, const ModifierEvalContext *ctx, Vo BKE_volume_grid_type_operation(grid_type, displace_grid_op); } - return volume; #else - UNUSED_VARS(md, ctx); + UNUSED_VARS(md, volume, ctx); BKE_modifier_set_error(ctx->object, md, "Compiled without OpenVDB"); - return volume; #endif } +static void modifyGeometrySet(ModifierData *md, + const ModifierEvalContext *ctx, + GeometrySet *geometry_set) +{ + Volume *input_volume = geometry_set->get_volume_for_write(); + if (input_volume != nullptr) { + displace_volume(md, ctx, input_volume); + } +} + ModifierTypeInfo modifierType_VolumeDisplace = { /* name */ "Volume Displace", /* structName */ "VolumeDisplaceModifierData", @@ -328,8 +337,7 @@ ModifierTypeInfo modifierType_VolumeDisplace = { /* deformMatricesEM */ nullptr, /* modifyMesh */ nullptr, /* modifyHair */ nullptr, - /* modifyGeometrySet */ nullptr, - /* modifyVolume */ modifyVolume, + /* modifyGeometrySet */ modifyGeometrySet, /* initData */ initData, /* requiredDataMask */ nullptr, diff --git a/source/blender/modifiers/intern/MOD_volume_to_mesh.cc b/source/blender/modifiers/intern/MOD_volume_to_mesh.cc index 3bf5cf6ab5b..c0bf07b8eec 100644 --- a/source/blender/modifiers/intern/MOD_volume_to_mesh.cc +++ b/source/blender/modifiers/intern/MOD_volume_to_mesh.cc @@ -223,7 +223,6 @@ ModifierTypeInfo modifierType_VolumeToMesh = { /* modifyMesh */ modifyMesh, /* modifyHair */ nullptr, /* modifyGeometrySet */ nullptr, - /* modifyVolume */ nullptr, /* initData */ initData, /* requiredDataMask */ nullptr, diff --git a/source/blender/modifiers/intern/MOD_warp.c b/source/blender/modifiers/intern/MOD_warp.c index 9d3d5b0658c..3bebc52c503 100644 --- a/source/blender/modifiers/intern/MOD_warp.c +++ b/source/blender/modifiers/intern/MOD_warp.c @@ -539,7 +539,6 @@ ModifierTypeInfo modifierType_Warp = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_wave.c b/source/blender/modifiers/intern/MOD_wave.c index 863656b85a5..c6bab89247e 100644 --- a/source/blender/modifiers/intern/MOD_wave.c +++ b/source/blender/modifiers/intern/MOD_wave.c @@ -492,7 +492,6 @@ ModifierTypeInfo modifierType_Wave = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_weighted_normal.c b/source/blender/modifiers/intern/MOD_weighted_normal.c index 40265e37db9..2f2da7d6554 100644 --- a/source/blender/modifiers/intern/MOD_weighted_normal.c +++ b/source/blender/modifiers/intern/MOD_weighted_normal.c @@ -764,7 +764,6 @@ ModifierTypeInfo modifierType_WeightedNormal = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_weightvgedit.c b/source/blender/modifiers/intern/MOD_weightvgedit.c index 915adccc745..b5f72c88800 100644 --- a/source/blender/modifiers/intern/MOD_weightvgedit.c +++ b/source/blender/modifiers/intern/MOD_weightvgedit.c @@ -428,7 +428,6 @@ ModifierTypeInfo modifierType_WeightVGEdit = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_weightvgmix.c b/source/blender/modifiers/intern/MOD_weightvgmix.c index 52cee7ce7e5..a71a2f3b480 100644 --- a/source/blender/modifiers/intern/MOD_weightvgmix.c +++ b/source/blender/modifiers/intern/MOD_weightvgmix.c @@ -514,7 +514,6 @@ ModifierTypeInfo modifierType_WeightVGMix = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_weightvgproximity.c b/source/blender/modifiers/intern/MOD_weightvgproximity.c index aac29cabf0f..cd03175f16c 100644 --- a/source/blender/modifiers/intern/MOD_weightvgproximity.c +++ b/source/blender/modifiers/intern/MOD_weightvgproximity.c @@ -768,7 +768,6 @@ ModifierTypeInfo modifierType_WeightVGProximity = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_weld.c b/source/blender/modifiers/intern/MOD_weld.c index fd1254fc948..1590f342666 100644 --- a/source/blender/modifiers/intern/MOD_weld.c +++ b/source/blender/modifiers/intern/MOD_weld.c @@ -2053,7 +2053,6 @@ ModifierTypeInfo modifierType_Weld = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_wireframe.c b/source/blender/modifiers/intern/MOD_wireframe.c index 3d8e74d2cf5..16bf1f7d763 100644 --- a/source/blender/modifiers/intern/MOD_wireframe.c +++ b/source/blender/modifiers/intern/MOD_wireframe.c @@ -196,7 +196,6 @@ ModifierTypeInfo modifierType_Wireframe = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index 8168f0dba73..9d21ff19f46 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -145,6 +145,7 @@ set(SRC geometry/nodes/node_geo_attribute_combine_xyz.cc geometry/nodes/node_geo_attribute_compare.cc geometry/nodes/node_geo_attribute_convert.cc + geometry/nodes/node_geo_attribute_curve_map.cc geometry/nodes/node_geo_attribute_fill.cc geometry/nodes/node_geo_attribute_map_range.cc geometry/nodes/node_geo_attribute_math.cc @@ -154,14 +155,21 @@ set(SRC geometry/nodes/node_geo_attribute_remove.cc geometry/nodes/node_geo_attribute_sample_texture.cc geometry/nodes/node_geo_attribute_separate_xyz.cc + geometry/nodes/node_geo_attribute_transfer.cc geometry/nodes/node_geo_attribute_vector_math.cc + geometry/nodes/node_geo_attribute_vector_rotate.cc geometry/nodes/node_geo_boolean.cc geometry/nodes/node_geo_bounding_box.cc geometry/nodes/node_geo_collection_info.cc geometry/nodes/node_geo_common.cc + geometry/nodes/node_geo_curve_to_mesh.cc + geometry/nodes/node_geo_curve_resample.cc geometry/nodes/node_geo_edge_split.cc + geometry/nodes/node_geo_input_material.cc geometry/nodes/node_geo_is_viewport.cc geometry/nodes/node_geo_join_geometry.cc + geometry/nodes/node_geo_material_assign.cc + geometry/nodes/node_geo_material_replace.cc geometry/nodes/node_geo_mesh_primitive_circle.cc geometry/nodes/node_geo_mesh_primitive_cone.cc geometry/nodes/node_geo_mesh_primitive_cube.cc @@ -180,6 +188,7 @@ set(SRC geometry/nodes/node_geo_points_to_volume.cc geometry/nodes/node_geo_subdivide.cc geometry/nodes/node_geo_subdivision_surface.cc + geometry/nodes/node_geo_switch.cc geometry/nodes/node_geo_transform.cc geometry/nodes/node_geo_triangulate.cc geometry/nodes/node_geo_volume_to_mesh.cc @@ -210,7 +219,7 @@ set(SRC shader/nodes/node_shader_camera.c shader/nodes/node_shader_clamp.cc shader/nodes/node_shader_common.c - shader/nodes/node_shader_curves.c + shader/nodes/node_shader_curves.cc shader/nodes/node_shader_displacement.c shader/nodes/node_shader_eevee_specular.c shader/nodes/node_shader_emission.c diff --git a/source/blender/nodes/NOD_derived_node_tree.hh b/source/blender/nodes/NOD_derived_node_tree.hh index e294bef2ea8..7ff05449c0b 100644 --- a/source/blender/nodes/NOD_derived_node_tree.hh +++ b/source/blender/nodes/NOD_derived_node_tree.hh @@ -92,6 +92,9 @@ class DNode { operator bool() const; uint64_t hash() const; + + DInputSocket input(int index) const; + DOutputSocket output(int index) const; }; /* A (nullable) reference to a socket and the context it is in. It is unique within an entire @@ -274,6 +277,16 @@ inline uint64_t DNode::hash() const return get_default_hash_2(context_, node_ref_); } +inline DInputSocket DNode::input(int index) const +{ + return {context_, &node_ref_->input(index)}; +} + +inline DOutputSocket DNode::output(int index) const +{ + return {context_, &node_ref_->output(index)}; +} + /* -------------------------------------------------------------------- * DSocket inline methods. */ diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index b84c80e916c..d2a702c30a6 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -35,6 +35,7 @@ void register_node_type_geo_attribute_color_ramp(void); void register_node_type_geo_attribute_combine_xyz(void); void register_node_type_geo_attribute_compare(void); void register_node_type_geo_attribute_convert(void); +void register_node_type_geo_attribute_curve_map(void); void register_node_type_geo_attribute_fill(void); void register_node_type_geo_attribute_map_range(void); void register_node_type_geo_attribute_math(void); @@ -42,14 +43,21 @@ void register_node_type_geo_attribute_mix(void); void register_node_type_geo_attribute_proximity(void); void register_node_type_geo_attribute_randomize(void); void register_node_type_geo_attribute_separate_xyz(void); +void register_node_type_geo_attribute_transfer(void); void register_node_type_geo_attribute_vector_math(void); +void register_node_type_geo_attribute_vector_rotate(void); void register_node_type_geo_attribute_remove(void); void register_node_type_geo_boolean(void); void register_node_type_geo_bounding_box(void); void register_node_type_geo_collection_info(void); +void register_node_type_geo_curve_to_mesh(void); +void register_node_type_geo_curve_resample(void); void register_node_type_geo_edge_split(void); +void register_node_type_geo_input_material(void); void register_node_type_geo_is_viewport(void); void register_node_type_geo_join_geometry(void); +void register_node_type_geo_material_assign(void); +void register_node_type_geo_material_replace(void); void register_node_type_geo_mesh_primitive_circle(void); void register_node_type_geo_mesh_primitive_cone(void); void register_node_type_geo_mesh_primitive_cube(void); @@ -69,6 +77,7 @@ void register_node_type_geo_points_to_volume(void); void register_node_type_geo_sample_texture(void); void register_node_type_geo_subdivide(void); void register_node_type_geo_subdivision_surface(void); +void register_node_type_geo_switch(void); void register_node_type_geo_transform(void); void register_node_type_geo_triangulate(void); void register_node_type_geo_volume_to_mesh(void); diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh index 5d1a217db9b..7b176b2f395 100644 --- a/source/blender/nodes/NOD_geometry_exec.hh +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -22,7 +22,6 @@ #include "BKE_geometry_set.hh" #include "BKE_geometry_set_instances.hh" #include "BKE_node_ui_storage.hh" -#include "BKE_persistent_data_handle.hh" #include "DNA_node_types.h" @@ -33,55 +32,90 @@ struct ModifierData; namespace blender::nodes { -using bke::BooleanReadAttribute; -using bke::BooleanWriteAttribute; -using bke::Color4fReadAttribute; -using bke::Color4fWriteAttribute; -using bke::Float2ReadAttribute; -using bke::Float2WriteAttribute; -using bke::Float3ReadAttribute; -using bke::Float3WriteAttribute; -using bke::FloatReadAttribute; -using bke::FloatWriteAttribute; using bke::geometry_set_realize_instances; -using bke::Int32ReadAttribute; -using bke::Int32WriteAttribute; -using bke::PersistentDataHandleMap; -using bke::PersistentObjectHandle; -using bke::ReadAttribute; -using bke::ReadAttributePtr; -using bke::WriteAttribute; -using bke::WriteAttributePtr; +using bke::OutputAttribute; +using bke::OutputAttribute_Typed; +using bke::ReadAttributeLookup; +using bke::WriteAttributeLookup; using fn::CPPType; using fn::GMutablePointer; +using fn::GMutableSpan; using fn::GPointer; +using fn::GSpan; using fn::GValueMap; +using fn::GVArray; +using fn::GVArray_GSpan; +using fn::GVArray_Span; +using fn::GVArray_Typed; +using fn::GVArrayPtr; +using fn::GVMutableArray; +using fn::GVMutableArray_GSpan; +using fn::GVMutableArray_Typed; +using fn::GVMutableArrayPtr; + +/** + * This class exists to separate the memory management details of the geometry nodes evaluator from + * the node execution functions and related utilities. + */ +class GeoNodeExecParamsProvider { + public: + DNode dnode; + const Object *self_object = nullptr; + const ModifierData *modifier = nullptr; + Depsgraph *depsgraph = nullptr; + + /** + * Returns true when the node is allowed to get/extract the input value. The identifier is + * expected to be valid. This may return false if the input value has been consumed already. + */ + virtual bool can_get_input(StringRef identifier) const = 0; + + /** + * Returns true when the node is allowed to set the output value. The identifier is expected to + * be valid. This may return false if the output value has been set already. + */ + virtual bool can_set_output(StringRef identifier) const = 0; + + /** + * Take ownership of an input value. The caller is responsible for destructing the value. It does + * not have to be freed, because the memory is managed by the geometry nodes evaluator. + */ + virtual GMutablePointer extract_input(StringRef identifier) = 0; + + /** + * Similar to #extract_input, but has to be used for multi-input sockets. + */ + virtual Vector<GMutablePointer> extract_multi_input(StringRef identifier) = 0; + + /** + * Get the input value for the identifier without taking ownership of it. + */ + virtual GPointer get_input(StringRef identifier) const = 0; + + /** + * Prepare a memory buffer for an output value of the node. The returned memory has to be + * initialized by the caller. The identifier and type are expected to be correct. + */ + virtual GMutablePointer alloc_output_value(const CPPType &type) = 0; + + /** + * The value has been allocated with #alloc_output_value. + */ + virtual void set_output(StringRef identifier, GMutablePointer value) = 0; + + /* A description for these methods is provided in GeoNodeExecParams. */ + virtual void set_input_unused(StringRef identifier) = 0; + virtual bool output_is_required(StringRef identifier) const = 0; + virtual bool lazy_require_input(StringRef identifier) = 0; + virtual bool lazy_output_is_required(StringRef identifier) const = 0; +}; class GeoNodeExecParams { private: - const DNode node_; - GValueMap<StringRef> &input_values_; - GValueMap<StringRef> &output_values_; - const PersistentDataHandleMap &handle_map_; - const Object *self_object_; - const ModifierData *modifier_; - Depsgraph *depsgraph_; + GeoNodeExecParamsProvider *provider_; public: - GeoNodeExecParams(const DNode node, - GValueMap<StringRef> &input_values, - GValueMap<StringRef> &output_values, - const PersistentDataHandleMap &handle_map, - const Object *self_object, - const ModifierData *modifier, - Depsgraph *depsgraph) - : node_(node), - input_values_(input_values), - output_values_(output_values), - handle_map_(handle_map), - self_object_(self_object), - modifier_(modifier), - depsgraph_(depsgraph) + GeoNodeExecParams(GeoNodeExecParamsProvider &provider) : provider_(&provider) { } @@ -94,9 +128,9 @@ class GeoNodeExecParams { GMutablePointer extract_input(StringRef identifier) { #ifdef DEBUG - this->check_extract_input(identifier); + this->check_input_access(identifier); #endif - return input_values_.extract(identifier); + return provider_->extract_input(identifier); } /** @@ -107,9 +141,10 @@ class GeoNodeExecParams { template<typename T> T extract_input(StringRef identifier) { #ifdef DEBUG - this->check_extract_input(identifier, &CPPType::get<T>()); + this->check_input_access(identifier, &CPPType::get<T>()); #endif - return input_values_.extract<T>(identifier); + GMutablePointer gvalue = this->extract_input(identifier); + return gvalue.relocate_out<T>(); } /** @@ -119,18 +154,10 @@ class GeoNodeExecParams { */ template<typename T> Vector<T> extract_multi_input(StringRef identifier) { + Vector<GMutablePointer> gvalues = provider_->extract_multi_input(identifier); Vector<T> values; - int index = 0; - while (true) { - std::string sub_identifier = identifier; - if (index > 0) { - sub_identifier += "[" + std::to_string(index) + "]"; - } - if (!input_values_.contains(sub_identifier)) { - break; - } - values.append(input_values_.extract<T, StringRef>(sub_identifier)); - index++; + for (GMutablePointer gvalue : gvalues) { + values.append(gvalue.relocate_out<T>()); } return values; } @@ -141,67 +168,83 @@ class GeoNodeExecParams { template<typename T> const T &get_input(StringRef identifier) const { #ifdef DEBUG - this->check_extract_input(identifier, &CPPType::get<T>()); + this->check_input_access(identifier, &CPPType::get<T>()); #endif - return input_values_.lookup<T>(identifier); + GPointer gvalue = provider_->get_input(identifier); + BLI_assert(gvalue.is_type<T>()); + return *(const T *)gvalue.get(); } /** - * Move-construct a new value based on the given value and store it for the given socket - * identifier. + * Store the output value for the given socket identifier. */ - void set_output_by_move(StringRef identifier, GMutablePointer value) + template<typename T> void set_output(StringRef identifier, T &&value) { + using StoredT = std::decay_t<T>; + const CPPType &type = CPPType::get<std::decay_t<T>>(); #ifdef DEBUG - BLI_assert(value.type() != nullptr); - BLI_assert(value.get() != nullptr); - this->check_set_output(identifier, *value.type()); + this->check_output_access(identifier, type); #endif - output_values_.add_new_by_move(identifier, value); + GMutablePointer gvalue = provider_->alloc_output_value(type); + new (gvalue.get()) StoredT(std::forward<T>(value)); + provider_->set_output(identifier, gvalue); } - void set_output_by_copy(StringRef identifier, GPointer value) + /** + * Tell the evaluator that a specific input won't be used anymore. + */ + void set_input_unused(StringRef identifier) { -#ifdef DEBUG - BLI_assert(value.type() != nullptr); - BLI_assert(value.get() != nullptr); - this->check_set_output(identifier, *value.type()); -#endif - output_values_.add_new_by_copy(identifier, value); + provider_->set_input_unused(identifier); } /** - * Store the output value for the given socket identifier. + * Returns true when the output has to be computed. + * Nodes that support laziness could use the #lazy_output_is_required variant to possibly avoid + * some computations. */ - template<typename T> void set_output(StringRef identifier, T &&value) + bool output_is_required(StringRef identifier) const { -#ifdef DEBUG - this->check_set_output(identifier, CPPType::get<std::decay_t<T>>()); -#endif - output_values_.add_new(identifier, std::forward<T>(value)); + return provider_->output_is_required(identifier); } /** - * Get the node that is currently being executed. + * Tell the evaluator that a specific input is required. + * This returns true when the input will only be available in the next execution. + * False is returned if the input is available already. + * This can only be used when the node supports laziness. */ - const bNode &node() const + bool lazy_require_input(StringRef identifier) { - return *node_->bnode(); + return provider_->lazy_require_input(identifier); } - const PersistentDataHandleMap &handle_map() const + /** + * Asks the evaluator if a specific output is required right now. If this returns false, the + * value might still need to be computed later. + * This can only be used when the node supports laziness. + */ + bool lazy_output_is_required(StringRef identifier) + { + return provider_->lazy_output_is_required(identifier); + } + + /** + * Get the node that is currently being executed. + */ + const bNode &node() const { - return handle_map_; + return *provider_->dnode->bnode(); } const Object *self_object() const { - return self_object_; + return provider_->self_object; } Depsgraph *depsgraph() const { - return depsgraph_; + return provider_->depsgraph; } /** @@ -217,20 +260,21 @@ class GeoNodeExecParams { * \note This will add an error message if the string socket is active and * the input attribute does not exist. */ - ReadAttributePtr get_input_attribute(const StringRef name, - const GeometryComponent &component, - const AttributeDomain domain, - const CustomDataType type, - const void *default_value) const; + GVArrayPtr get_input_attribute(const StringRef name, + const GeometryComponent &component, + const AttributeDomain domain, + const CustomDataType type, + const void *default_value) const; template<typename T> - bke::TypedReadAttribute<T> get_input_attribute(const StringRef name, - const GeometryComponent &component, - const AttributeDomain domain, - const T &default_value) const + GVArray_Typed<T> get_input_attribute(const StringRef name, + const GeometryComponent &component, + const AttributeDomain domain, + const T &default_value) const { const CustomDataType type = bke::cpp_type_to_custom_data_type(CPPType::get<T>()); - return this->get_input_attribute(name, component, domain, type, &default_value); + GVArrayPtr varray = this->get_input_attribute(name, component, domain, type, &default_value); + return GVArray_Typed<T>(std::move(varray)); } /** @@ -247,8 +291,8 @@ class GeoNodeExecParams { private: /* Utilities for detecting common errors at when using this class. */ - void check_extract_input(StringRef identifier, const CPPType *requested_type = nullptr) const; - void check_set_output(StringRef identifier, const CPPType &value_type) const; + void check_input_access(StringRef identifier, const CPPType *requested_type = nullptr) const; + void check_output_access(StringRef identifier, const CPPType &value_type) const; /* Find the active socket socket with the input name (not the identifier). */ const bNodeSocket *find_available_socket(const StringRef name) const; diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index bf6bf34325a..ce1813fdac3 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -263,53 +263,62 @@ DefNode(TextureNode, TEX_NODE_PROC+TEX_DISTNOISE, 0, "TEX_DI DefNode(FunctionNode, FN_NODE_BOOLEAN_MATH, def_boolean_math, "BOOLEAN_MATH", BooleanMath, "Boolean Math", "") DefNode(FunctionNode, FN_NODE_FLOAT_COMPARE, def_float_compare, "FLOAT_COMPARE", FloatCompare, "Float Compare", "") -DefNode(FunctionNode, FN_NODE_RANDOM_FLOAT, 0, "RANDOM_FLOAT", RandomFloat, "Random Float", "") -DefNode(FunctionNode, FN_NODE_INPUT_VECTOR, def_fn_input_vector, "INPUT_VECTOR", InputVector, "Vector", "") DefNode(FunctionNode, FN_NODE_INPUT_STRING, def_fn_input_string, "INPUT_STRING", InputString, "String", "") +DefNode(FunctionNode, FN_NODE_INPUT_VECTOR, def_fn_input_vector, "INPUT_VECTOR", InputVector, "Vector", "") +DefNode(FunctionNode, FN_NODE_RANDOM_FLOAT, 0, "RANDOM_FLOAT", RandomFloat, "Random Float", "") -DefNode(GeometryNode, GEO_NODE_TRIANGULATE, def_geo_triangulate, "TRIANGULATE", Triangulate, "Triangulate", "") -DefNode(GeometryNode, GEO_NODE_EDGE_SPLIT, 0, "EDGE_SPLIT", EdgeSplit, "Edge Split", "") -DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "") -DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, 0, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "") -DefNode(GeometryNode, GEO_NODE_BOOLEAN, def_geo_boolean, "BOOLEAN", Boolean, "Boolean", "") -DefNode(GeometryNode, GEO_NODE_POINT_DISTRIBUTE, def_geo_point_distribute, "POINT_DISTRIBUTE", PointDistribute, "Point Distribute", "") -DefNode(GeometryNode, GEO_NODE_POINT_INSTANCE, def_geo_point_instance, "POINT_INSTANCE", PointInstance, "Point Instance", "") -DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", ObjectInfo, "Object Info", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_RANDOMIZE, def_geo_attribute_randomize, "ATTRIBUTE_RANDOMIZE", AttributeRandomize, "Attribute Randomize", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MATH, def_geo_attribute_math, "ATTRIBUTE_MATH", AttributeMath, "Attribute Math", "") -DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_FILL, def_geo_attribute_fill, "ATTRIBUTE_FILL", AttributeFill, "Attribute Fill", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MIX, def_geo_attribute_mix, "ATTRIBUTE_MIX", AttributeMix, "Attribute Mix", "") +DefNode(GeometryNode, GEO_NODE_ALIGN_ROTATION_TO_VECTOR, def_geo_align_rotation_to_vector, "ALIGN_ROTATION_TO_VECTOR", AlignRotationToVector, "Align Rotation to Vector", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "ATTRIBUTE_CLAMP", AttributeClamp, "Attribute Clamp", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COLOR_RAMP, def_geo_attribute_color_ramp, "ATTRIBUTE_COLOR_RAMP", AttributeColorRamp, "Attribute Color Ramp", "") -DefNode(GeometryNode, GEO_NODE_POINT_SEPARATE, 0, "POINT_SEPARATE", PointSeparate, "Point Separate", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMBINE_XYZ, def_geo_attribute_combine_xyz, "ATTRIBUTE_COMBINE_XYZ", AttributeCombineXYZ, "Attribute Combine XYZ", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMPARE, def_geo_attribute_attribute_compare, "ATTRIBUTE_COMPARE", AttributeCompare, "Attribute Compare", "") -DefNode(GeometryNode, GEO_NODE_POINT_ROTATE, def_geo_point_rotate, "POINT_ROTATE", RotatePoints, "Point Rotate", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CONVERT, def_geo_attribute_convert, "ATTRIBUTE_CONVERT", AttributeConvert, "Attribute Convert", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CURVE_MAP, def_geo_attribute_curve_map, "ATTRIBUTE_CURVE_MAP", AttributeCurveMap, "Attribute Curve Map", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_FILL, def_geo_attribute_fill, "ATTRIBUTE_FILL", AttributeFill, "Attribute Fill", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MAP_RANGE, def_geo_attribute_map_range, "ATTRIBUTE_MAP_RANGE", AttributeMapRange, "Attribute Map Range", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MATH, def_geo_attribute_math, "ATTRIBUTE_MATH", AttributeMath, "Attribute Math", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MIX, def_geo_attribute_mix, "ATTRIBUTE_MIX", AttributeMix, "Attribute Mix", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_PROXIMITY, def_geo_attribute_proximity, "ATTRIBUTE_PROXIMITY", AttributeProximity, "Attribute Proximity", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_RANDOMIZE, def_geo_attribute_randomize, "ATTRIBUTE_RANDOMIZE", AttributeRandomize, "Attribute Randomize", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_REMOVE, 0, "ATTRIBUTE_REMOVE", AttributeRemove, "Attribute Remove", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE, 0, "ATTRIBUTE_SAMPLE_TEXTURE", AttributeSampleTexture, "Attribute Sample Texture", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_SEPARATE_XYZ, def_geo_attribute_separate_xyz, "ATTRIBUTE_SEPARATE_XYZ", AttributeSeparateXYZ, "Attribute Separate XYZ", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_TRANSFER, def_geo_attribute_transfer, "ATTRIBUTE_TRANSFER", AttributeTransfer, "Attribute Transfer", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_MATH, def_geo_attribute_vector_math, "ATTRIBUTE_VECTOR_MATH", AttributeVectorMath, "Attribute Vector Math", "") -DefNode(GeometryNode, GEO_NODE_ALIGN_ROTATION_TO_VECTOR, def_geo_align_rotation_to_vector, "ALIGN_ROTATION_TO_VECTOR", AlignRotationToVector, "Align Rotation to Vector", "") -DefNode(GeometryNode, GEO_NODE_POINT_SCALE, def_geo_point_scale, "POINT_SCALE", PointScale, "Point Scale", "") -DefNode(GeometryNode, GEO_NODE_POINT_TRANSLATE, def_geo_point_translate, "POINT_TRANSLATE", PointTranslate, "Point Translate", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_SAMPLE_TEXTURE, def_geo_attribute_sample_texture, "ATTRIBUTE_SAMPLE_TEXTURE", AttributeSampleTexture, "Attribute Sample Texture", "") -DefNode(GeometryNode, GEO_NODE_POINTS_TO_VOLUME, def_geo_points_to_volume, "POINTS_TO_VOLUME", PointsToVolume, "Points to Volume", "") +DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_ROTATE, def_geo_attribute_vector_rotate, "ATTRIBUTE_VECTOR_ROTATE", AttributeVectorRotate, "Attribute Vector Rotate", "") +DefNode(GeometryNode, GEO_NODE_BOOLEAN, def_geo_boolean, "BOOLEAN", Boolean, "Boolean", "") +DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "") DefNode(GeometryNode, GEO_NODE_COLLECTION_INFO, def_geo_collection_info, "COLLECTION_INFO", CollectionInfo, "Collection Info", "") +DefNode(GeometryNode, GEO_NODE_CURVE_RESAMPLE, def_geo_curve_resample, "CURVE_RESAMPLE", CurveResample, "Resample Curve", "") +DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "") +DefNode(GeometryNode, GEO_NODE_EDGE_SPLIT, 0, "EDGE_SPLIT", EdgeSplit, "Edge Split", "") +DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "") DefNode(GeometryNode, GEO_NODE_IS_VIEWPORT, 0, "IS_VIEWPORT", IsViewport, "Is Viewport", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_PROXIMITY, def_geo_attribute_proximity, "ATTRIBUTE_PROXIMITY", AttributeProximity, "Attribute Proximity", "") -DefNode(GeometryNode, GEO_NODE_VOLUME_TO_MESH, def_geo_volume_to_mesh, "VOLUME_TO_MESH", VolumeToMesh, "Volume to Mesh", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMBINE_XYZ, def_geo_attribute_combine_xyz, "ATTRIBUTE_COMBINE_XYZ", AttributeCombineXYZ, "Attribute Combine XYZ", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_SEPARATE_XYZ, def_geo_attribute_separate_xyz, "ATTRIBUTE_SEPARATE_XYZ", AttributeSeparateXYZ, "Attribute Separate XYZ", "") -DefNode(GeometryNode, GEO_NODE_SUBDIVIDE, 0, "SUBDIVIDE", Subdivide, "Subdivide", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_REMOVE, 0, "ATTRIBUTE_REMOVE", AttributeRemove, "Attribute Remove", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CONVERT, def_geo_attribute_convert, "ATTRIBUTE_CONVERT", AttributeConvert, "Attribute Convert", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CUBE, 0, "MESH_PRIMITIVE_CUBE", MeshCube, "Cube", "") +DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "") +DefNode(GeometryNode, GEO_NODE_MATERIAL_ASSIGN, 0, "MATERIAL_ASSIGN", MaterialAssign, "Material Assign", "") +DefNode(GeometryNode, GEO_NODE_MATERIAL_REPLACE, 0, "MATERIAL_REPLACE", MaterialReplace, "Material Replace", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CIRCLE, def_geo_mesh_circle, "MESH_PRIMITIVE_CIRCLE", MeshCircle, "Circle", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_UV_SPHERE, 0, "MESH_PRIMITIVE_UV_SPHERE", MeshUVSphere, "UV Sphere", "") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CONE, def_geo_mesh_cone, "MESH_PRIMITIVE_CONE", MeshCone, "Cone", "") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CUBE, 0, "MESH_PRIMITIVE_CUBE", MeshCube, "Cube", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CYLINDER, def_geo_mesh_cylinder, "MESH_PRIMITIVE_CYLINDER", MeshCylinder, "Cylinder", "") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_GRID, 0, "MESH_PRIMITIVE_GRID", MeshGrid, "Grid", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_ICO_SPHERE, 0, "MESH_PRIMITIVE_ICO_SPHERE", MeshIcoSphere, "Ico Sphere", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_CONE, def_geo_mesh_cone, "MESH_PRIMITIVE_CONE", MeshCone, "Cone", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_LINE, def_geo_mesh_line, "MESH_PRIMITIVE_LINE", MeshLine, "Line", "") -DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_GRID, 0, "MESH_PRIMITIVE_GRID", MeshGrid, "Grid", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MAP_RANGE, def_geo_attribute_map_range, "ATTRIBUTE_MAP_RANGE", AttributeMapRange, "Attribute Map Range", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "ATTRIBUTE_CLAMP", AttributeClamp, "Attribute Clamp", "") -DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "") +DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_UV_SPHERE, 0, "MESH_PRIMITIVE_UV_SPHERE", MeshUVSphere, "UV Sphere", "") +DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", ObjectInfo, "Object Info", "") +DefNode(GeometryNode, GEO_NODE_POINT_DISTRIBUTE, def_geo_point_distribute, "POINT_DISTRIBUTE", PointDistribute, "Point Distribute", "") +DefNode(GeometryNode, GEO_NODE_POINT_INSTANCE, def_geo_point_instance, "POINT_INSTANCE", PointInstance, "Point Instance", "") +DefNode(GeometryNode, GEO_NODE_POINT_ROTATE, def_geo_point_rotate, "POINT_ROTATE", RotatePoints, "Point Rotate", "") +DefNode(GeometryNode, GEO_NODE_POINT_SCALE, def_geo_point_scale, "POINT_SCALE", PointScale, "Point Scale", "") +DefNode(GeometryNode, GEO_NODE_POINT_SEPARATE, 0, "POINT_SEPARATE", PointSeparate, "Point Separate", "") +DefNode(GeometryNode, GEO_NODE_POINT_TRANSLATE, def_geo_point_translate, "POINT_TRANSLATE", PointTranslate, "Point Translate", "") +DefNode(GeometryNode, GEO_NODE_POINTS_TO_VOLUME, def_geo_points_to_volume, "POINTS_TO_VOLUME", PointsToVolume, "Points to Volume", "") +DefNode(GeometryNode, GEO_NODE_SUBDIVIDE, 0, "SUBDIVIDE", Subdivide, "Subdivide", "") +DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, 0, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "") +DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "") +DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "") +DefNode(GeometryNode, GEO_NODE_TRIANGULATE, def_geo_triangulate, "TRIANGULATE", Triangulate, "Triangulate", "") +DefNode(GeometryNode, GEO_NODE_VOLUME_TO_MESH, def_geo_volume_to_mesh, "VOLUME_TO_MESH", VolumeToMesh, "Volume to Mesh", "") /* undefine macros */ #undef DefNode diff --git a/source/blender/nodes/NOD_type_conversions.hh b/source/blender/nodes/NOD_type_conversions.hh index 34225208fe6..ec4859f0657 100644 --- a/source/blender/nodes/NOD_type_conversions.hh +++ b/source/blender/nodes/NOD_type_conversions.hh @@ -72,6 +72,10 @@ class DataTypeConversions { const CPPType &to_type, const void *from_value, void *to_value) const; + + fn::GVArrayPtr try_convert(fn::GVArrayPtr varray, const CPPType &to_type) const; + + fn::GVMutableArrayPtr try_convert(fn::GVMutableArrayPtr varray, const CPPType &to_type) const; }; const DataTypeConversions &get_implicit_type_conversions(); diff --git a/source/blender/nodes/composite/node_composite_tree.c b/source/blender/nodes/composite/node_composite_tree.c index 085b5c463b9..19815d01278 100644 --- a/source/blender/nodes/composite/node_composite_tree.c +++ b/source/blender/nodes/composite/node_composite_tree.c @@ -205,6 +205,12 @@ static void composite_node_add_init(bNodeTree *UNUSED(bnodetree), bNode *bnode) } } +static bool composite_node_tree_socket_type_valid(eNodeSocketDatatype socket_type, + bNodeTreeType *UNUSED(ntreetype)) +{ + return ELEM(socket_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA); +} + bNodeTreeType *ntreeType_Composite; void register_node_tree_type_cmp(void) @@ -227,6 +233,7 @@ void register_node_tree_type_cmp(void) tt->update = update; tt->get_from_context = composite_get_from_context; tt->node_add_init = composite_node_add_init; + tt->valid_socket_type = composite_node_tree_socket_type_valid; tt->rna_ext.srna = &RNA_CompositorNodeTree; diff --git a/source/blender/nodes/geometry/node_geometry_tree.cc b/source/blender/nodes/geometry/node_geometry_tree.cc index 6d3b1d55005..f4cd00b88ed 100644 --- a/source/blender/nodes/geometry/node_geometry_tree.cc +++ b/source/blender/nodes/geometry/node_geometry_tree.cc @@ -84,6 +84,34 @@ static void foreach_nodeclass(Scene *UNUSED(scene), void *calldata, bNodeClassCa func(calldata, NODE_CLASS_LAYOUT, N_("Layout")); } +static bool geometry_node_tree_validate_link(bNodeTree *UNUSED(ntree), bNodeLink *link) +{ + /* Geometry, string, object, material, texture and collection sockets can only be connected to + * themselves. The other types can be converted between each other. */ + if (ELEM(link->fromsock->type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT) && + ELEM(link->tosock->type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT)) { + return true; + } + return (link->tosock->type == link->fromsock->type); +} + +static bool geometry_node_tree_socket_type_valid(eNodeSocketDatatype socket_type, + bNodeTreeType *UNUSED(ntreetype)) +{ + return ELEM(socket_type, + SOCK_FLOAT, + SOCK_VECTOR, + SOCK_RGBA, + SOCK_BOOLEAN, + SOCK_INT, + SOCK_STRING, + SOCK_OBJECT, + SOCK_GEOMETRY, + SOCK_COLLECTION, + SOCK_TEXTURE, + SOCK_MATERIAL); +} + void register_node_tree_type_geo(void) { bNodeTreeType *tt = ntreeType_Geometry = static_cast<bNodeTreeType *>( @@ -97,6 +125,8 @@ void register_node_tree_type_geo(void) tt->update = geometry_node_tree_update; tt->get_from_context = geometry_node_tree_get_from_context; tt->foreach_nodeclass = foreach_nodeclass; + tt->valid_socket_type = geometry_node_tree_socket_type_valid; + tt->validate_link = geometry_node_tree_validate_link; ntreeTypeAdd(tt); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc b/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc index f10b81a33b4..d1b71d6f2ba 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc @@ -15,6 +15,7 @@ */ #include "BLI_math_rotation.h" +#include "BLI_task.hh" #include "UI_interface.h" #include "UI_resources.h" @@ -50,91 +51,117 @@ static void geo_node_align_rotation_to_vector_layout(uiLayout *layout, namespace blender::nodes { -static void align_rotations_auto_pivot(const Float3ReadAttribute &vectors, - const FloatReadAttribute &factors, +static void geo_node_align_rotation_to_vector_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryAlignRotationToVector *node_storage = (NodeGeometryAlignRotationToVector *) + MEM_callocN(sizeof(NodeGeometryAlignRotationToVector), __func__); + + node_storage->axis = GEO_NODE_ALIGN_ROTATION_TO_VECTOR_AXIS_X; + node_storage->input_type_factor = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; + node_storage->input_type_vector = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + + node->storage = node_storage; +} + +static void geo_node_align_rotation_to_vector_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryAlignRotationToVector *node_storage = (NodeGeometryAlignRotationToVector *) + node->storage; + update_attribute_input_socket_availabilities( + *node, "Factor", (GeometryNodeAttributeInputMode)node_storage->input_type_factor); + update_attribute_input_socket_availabilities( + *node, "Vector", (GeometryNodeAttributeInputMode)node_storage->input_type_vector); +} + +static void align_rotations_auto_pivot(const VArray<float3> &vectors, + const VArray<float> &factors, const float3 local_main_axis, - MutableSpan<float3> rotations) + const MutableSpan<float3> rotations) { - for (const int i : IndexRange(vectors.size())) { - const float3 vector = vectors[i]; - if (is_zero_v3(vector)) { - continue; - } + parallel_for(IndexRange(vectors.size()), 128, [&](IndexRange range) { + for (const int i : range) { + const float3 vector = vectors[i]; + if (is_zero_v3(vector)) { + continue; + } - float old_rotation[3][3]; - eul_to_mat3(old_rotation, rotations[i]); - float3 old_axis; - mul_v3_m3v3(old_axis, old_rotation, local_main_axis); + float old_rotation[3][3]; + eul_to_mat3(old_rotation, rotations[i]); + float3 old_axis; + mul_v3_m3v3(old_axis, old_rotation, local_main_axis); - const float3 new_axis = vector.normalized(); - float3 rotation_axis = float3::cross_high_precision(old_axis, new_axis); - if (is_zero_v3(rotation_axis)) { - /* The vectors are linearly dependent, so we fall back to another axis. */ - rotation_axis = float3::cross_high_precision(old_axis, float3(1, 0, 0)); + const float3 new_axis = vector.normalized(); + float3 rotation_axis = float3::cross_high_precision(old_axis, new_axis); if (is_zero_v3(rotation_axis)) { - /* This is now guaranteed to not be zero. */ - rotation_axis = float3::cross_high_precision(old_axis, float3(0, 1, 0)); + /* The vectors are linearly dependent, so we fall back to another axis. */ + rotation_axis = float3::cross_high_precision(old_axis, float3(1, 0, 0)); + if (is_zero_v3(rotation_axis)) { + /* This is now guaranteed to not be zero. */ + rotation_axis = float3::cross_high_precision(old_axis, float3(0, 1, 0)); + } } - } - const float full_angle = angle_normalized_v3v3(old_axis, new_axis); - const float angle = factors[i] * full_angle; + const float full_angle = angle_normalized_v3v3(old_axis, new_axis); + const float angle = factors[i] * full_angle; - float rotation[3][3]; - axis_angle_to_mat3(rotation, rotation_axis, angle); + float rotation[3][3]; + axis_angle_to_mat3(rotation, rotation_axis, angle); - float new_rotation_matrix[3][3]; - mul_m3_m3m3(new_rotation_matrix, rotation, old_rotation); + float new_rotation_matrix[3][3]; + mul_m3_m3m3(new_rotation_matrix, rotation, old_rotation); - float3 new_rotation; - mat3_to_eul(new_rotation, new_rotation_matrix); + float3 new_rotation; + mat3_to_eul(new_rotation, new_rotation_matrix); - rotations[i] = new_rotation; - } + rotations[i] = new_rotation; + } + }); } -static void align_rotations_fixed_pivot(const Float3ReadAttribute &vectors, - const FloatReadAttribute &factors, +static void align_rotations_fixed_pivot(const VArray<float3> &vectors, + const VArray<float> &factors, const float3 local_main_axis, const float3 local_pivot_axis, - MutableSpan<float3> rotations) + const MutableSpan<float3> rotations) { if (local_main_axis == local_pivot_axis) { /* Can't compute any meaningful rotation angle in this case. */ return; } - for (const int i : IndexRange(vectors.size())) { - const float3 vector = vectors[i]; - if (is_zero_v3(vector)) { - continue; - } + parallel_for(IndexRange(vectors.size()), 128, [&](IndexRange range) { + for (const int i : range) { + const float3 vector = vectors[i]; + if (is_zero_v3(vector)) { + continue; + } - float old_rotation[3][3]; - eul_to_mat3(old_rotation, rotations[i]); - float3 old_axis; - mul_v3_m3v3(old_axis, old_rotation, local_main_axis); - float3 pivot_axis; - mul_v3_m3v3(pivot_axis, old_rotation, local_pivot_axis); - - float full_angle = angle_signed_on_axis_v3v3_v3(vector, old_axis, pivot_axis); - if (full_angle > M_PI) { - /* Make sure the point is rotated as little as possible. */ - full_angle -= 2.0f * M_PI; - } - const float angle = factors[i] * full_angle; + float old_rotation[3][3]; + eul_to_mat3(old_rotation, rotations[i]); + float3 old_axis; + mul_v3_m3v3(old_axis, old_rotation, local_main_axis); + float3 pivot_axis; + mul_v3_m3v3(pivot_axis, old_rotation, local_pivot_axis); + + float full_angle = angle_signed_on_axis_v3v3_v3(vector, old_axis, pivot_axis); + if (full_angle > M_PI) { + /* Make sure the point is rotated as little as possible. */ + full_angle -= 2.0f * M_PI; + } + const float angle = factors[i] * full_angle; - float rotation[3][3]; - axis_angle_to_mat3(rotation, pivot_axis, angle); + float rotation[3][3]; + axis_angle_to_mat3(rotation, pivot_axis, angle); - float new_rotation_matrix[3][3]; - mul_m3_m3m3(new_rotation_matrix, rotation, old_rotation); + float new_rotation_matrix[3][3]; + mul_m3_m3m3(new_rotation_matrix, rotation, old_rotation); - float3 new_rotation; - mat3_to_eul(new_rotation, new_rotation_matrix); + float3 new_rotation; + mat3_to_eul(new_rotation, new_rotation_matrix); - rotations[i] = new_rotation; - } + rotations[i] = new_rotation; + } + }); } static void align_rotations_on_component(GeometryComponent &component, @@ -144,30 +171,30 @@ static void align_rotations_on_component(GeometryComponent &component, const NodeGeometryAlignRotationToVector &storage = *(const NodeGeometryAlignRotationToVector *) node.storage; - OutputAttributePtr rotation_attribute = component.attribute_try_get_for_output( - "rotation", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3); - if (!rotation_attribute) { + OutputAttribute_Typed<float3> rotations = component.attribute_try_get_for_output<float3>( + "rotation", ATTR_DOMAIN_POINT, {0, 0, 0}); + if (!rotations) { return; } - MutableSpan<float3> rotations = rotation_attribute->get_span<float3>(); - FloatReadAttribute factors = params.get_input_attribute<float>( + GVArray_Typed<float> factors = params.get_input_attribute<float>( "Factor", component, ATTR_DOMAIN_POINT, 1.0f); - Float3ReadAttribute vectors = params.get_input_attribute<float3>( + GVArray_Typed<float3> vectors = params.get_input_attribute<float3>( "Vector", component, ATTR_DOMAIN_POINT, {0, 0, 1}); float3 local_main_axis{0, 0, 0}; local_main_axis[storage.axis] = 1; if (storage.pivot_axis == GEO_NODE_ALIGN_ROTATION_TO_VECTOR_PIVOT_AXIS_AUTO) { - align_rotations_auto_pivot(vectors, factors, local_main_axis, rotations); + align_rotations_auto_pivot(vectors, factors, local_main_axis, rotations.as_span()); } else { float3 local_pivot_axis{0, 0, 0}; local_pivot_axis[storage.pivot_axis - 1] = 1; - align_rotations_fixed_pivot(vectors, factors, local_main_axis, local_pivot_axis, rotations); + align_rotations_fixed_pivot( + vectors, factors, local_main_axis, local_pivot_axis, rotations.as_span()); } - rotation_attribute.apply_span_and_save(); + rotations.save(); } static void geo_node_align_rotation_to_vector_exec(GeoNodeExecParams params) @@ -183,32 +210,13 @@ static void geo_node_align_rotation_to_vector_exec(GeoNodeExecParams params) align_rotations_on_component(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + align_rotations_on_component(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } -static void geo_node_align_rotation_to_vector_init(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryAlignRotationToVector *node_storage = (NodeGeometryAlignRotationToVector *) - MEM_callocN(sizeof(NodeGeometryAlignRotationToVector), __func__); - - node_storage->axis = GEO_NODE_ALIGN_ROTATION_TO_VECTOR_AXIS_X; - node_storage->input_type_factor = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; - node_storage->input_type_vector = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; - - node->storage = node_storage; -} - -static void geo_node_align_rotation_to_vector_update(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryAlignRotationToVector *node_storage = (NodeGeometryAlignRotationToVector *) - node->storage; - update_attribute_input_socket_availabilities( - *node, "Factor", (GeometryNodeAttributeInputMode)node_storage->input_type_factor); - update_attribute_input_socket_availabilities( - *node, "Vector", (GeometryNodeAttributeInputMode)node_storage->input_type_vector); -} - } // namespace blender::nodes void register_node_type_geo_align_rotation_to_vector() diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc index 95fa24c8bac..71643df1cb6 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc @@ -101,9 +101,12 @@ template<> inline float3 clamp_value(const float3 val, const float3 min, const f return tmp; } -template<> inline Color4f clamp_value(const Color4f val, const Color4f min, const Color4f max) +template<> +inline ColorGeometry4f clamp_value(const ColorGeometry4f val, + const ColorGeometry4f min, + const ColorGeometry4f max) { - Color4f tmp; + ColorGeometry4f tmp; tmp.r = std::min(std::max(val.r, min.r), max.r); tmp.g = std::min(std::max(val.g, min.g), max.g); tmp.b = std::min(std::max(val.b, min.b), max.b); @@ -112,10 +115,13 @@ template<> inline Color4f clamp_value(const Color4f val, const Color4f min, cons } template<typename T> -static void clamp_attribute(Span<T> read_span, MutableSpan<T> span, const T min, const T max) +static void clamp_attribute(const VArray<T> &inputs, + const MutableSpan<T> outputs, + const T min, + const T max) { - for (const int i : span.index_range()) { - span[i] = clamp_value<T>(read_span[i], min, max); + for (const int i : IndexRange(outputs.size())) { + outputs[i] = clamp_value<T>(inputs[i], min, max); } } @@ -123,13 +129,13 @@ static AttributeDomain get_result_domain(const GeometryComponent &component, StringRef source_name, StringRef result_name) { - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); - if (result_attribute) { - return result_attribute->domain(); + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(result_name); + if (result_info) { + return result_info->domain; } - ReadAttributePtr source_attribute = component.attribute_try_get_for_read(source_name); - if (source_attribute) { - return source_attribute->domain(); + std::optional<AttributeMetaData> source_info = component.attribute_get_meta_data(source_name); + if (source_info) { + return source_info->domain; } return ATTR_DOMAIN_POINT; } @@ -154,10 +160,10 @@ static void clamp_attribute(GeometryComponent &component, const GeoNodeExecParam const AttributeDomain domain = get_result_domain(component, attribute_name, result_name); const int operation = static_cast<int>(storage.operation); - ReadAttributePtr attribute_input = component.attribute_try_get_for_read( + GVArrayPtr attribute_input = component.attribute_try_get_for_read( attribute_name, domain, data_type); - OutputAttributePtr attribute_result = component.attribute_try_get_for_output( + OutputAttribute attribute_result = component.attribute_try_get_for_output_only( result_name, domain, data_type); if (!attribute_result) { @@ -169,8 +175,6 @@ static void clamp_attribute(GeometryComponent &component, const GeoNodeExecParam switch (data_type) { case CD_PROP_FLOAT3: { - Span<float3> read_span = attribute_input->get_span<float3>(); - MutableSpan<float3> span = attribute_result->get_span_for_write_only<float3>(); float3 min = params.get_input<float3>("Min"); float3 max = params.get_input<float3>("Max"); if (operation == NODE_CLAMP_RANGE) { @@ -184,40 +188,37 @@ static void clamp_attribute(GeometryComponent &component, const GeoNodeExecParam std::swap(min.z, max.z); } } - clamp_attribute<float3>(read_span, span, min, max); + MutableSpan<float3> results = attribute_result.as_span<float3>(); + clamp_attribute<float3>(attribute_input->typed<float3>(), results, min, max); break; } case CD_PROP_FLOAT: { - Span<float> read_span = attribute_input->get_span<float>(); - MutableSpan<float> span = attribute_result->get_span_for_write_only<float>(); const float min = params.get_input<float>("Min_001"); const float max = params.get_input<float>("Max_001"); + MutableSpan<float> results = attribute_result.as_span<float>(); if (operation == NODE_CLAMP_RANGE && min > max) { - clamp_attribute<float>(read_span, span, max, min); + clamp_attribute<float>(attribute_input->typed<float>(), results, max, min); } else { - clamp_attribute<float>(read_span, span, min, max); + clamp_attribute<float>(attribute_input->typed<float>(), results, min, max); } break; } case CD_PROP_INT32: { - Span<int> read_span = attribute_input->get_span<int>(); - MutableSpan<int> span = attribute_result->get_span_for_write_only<int>(); const int min = params.get_input<int>("Min_002"); const int max = params.get_input<int>("Max_002"); + MutableSpan<int> results = attribute_result.as_span<int>(); if (operation == NODE_CLAMP_RANGE && min > max) { - clamp_attribute<int>(read_span, span, max, min); + clamp_attribute<int>(attribute_input->typed<int>(), results, max, min); } else { - clamp_attribute<int>(read_span, span, min, max); + clamp_attribute<int>(attribute_input->typed<int>(), results, min, max); } break; } case CD_PROP_COLOR: { - Span<Color4f> read_span = attribute_input->get_span<Color4f>(); - MutableSpan<Color4f> span = attribute_result->get_span_for_write_only<Color4f>(); - Color4f min = params.get_input<Color4f>("Min_003"); - Color4f max = params.get_input<Color4f>("Max_003"); + ColorGeometry4f min = params.get_input<ColorGeometry4f>("Min_003"); + ColorGeometry4f max = params.get_input<ColorGeometry4f>("Max_003"); if (operation == NODE_CLAMP_RANGE) { if (min.r > max.r) { std::swap(min.r, max.r); @@ -232,7 +233,9 @@ static void clamp_attribute(GeometryComponent &component, const GeoNodeExecParam std::swap(min.a, max.a); } } - clamp_attribute<Color4f>(read_span, span, min, max); + MutableSpan<ColorGeometry4f> results = attribute_result.as_span<ColorGeometry4f>(); + clamp_attribute<ColorGeometry4f>( + attribute_input->typed<ColorGeometry4f>(), results, min, max); break; } default: { @@ -241,7 +244,7 @@ static void clamp_attribute(GeometryComponent &component, const GeoNodeExecParam } } - attribute_result.apply_span_and_save(); + attribute_result.save(); } static void geo_node_attribute_clamp_exec(GeoNodeExecParams params) @@ -256,6 +259,9 @@ static void geo_node_attribute_clamp_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { clamp_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + clamp_attribute(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc index 2b913beb670..5293dd8c876 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc @@ -14,6 +14,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BLI_task.hh" + #include "BKE_colorband.h" #include "UI_interface.h" @@ -42,20 +44,28 @@ static void geo_node_attribute_color_ramp_layout(uiLayout *layout, namespace blender::nodes { +static void geo_node_attribute_color_ramp_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeColorRamp *node_storage = (NodeAttributeColorRamp *)MEM_callocN( + sizeof(NodeAttributeColorRamp), __func__); + BKE_colorband_init(&node_storage->color_ramp, true); + node->storage = node_storage; +} + static AttributeDomain get_result_domain(const GeometryComponent &component, StringRef input_name, StringRef result_name) { /* Use the domain of the result attribute if it already exists. */ - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); - if (result_attribute) { - return result_attribute->domain(); + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(result_name); + if (result_info) { + return result_info->domain; } /* Otherwise use the input attribute's domain if it exists. */ - ReadAttributePtr input_attribute = component.attribute_try_get_for_read(input_name); - if (input_attribute) { - return input_attribute->domain(); + std::optional<AttributeMetaData> source_info = component.attribute_get_meta_data(input_name); + if (source_info) { + return source_info->domain; } return ATTR_DOMAIN_POINT; @@ -71,27 +81,27 @@ static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryCompon /* Always output a color attribute for now. We might want to allow users to customize. * Using the type of an existing attribute could work, but does not have a real benefit * currently. */ - const CustomDataType result_type = CD_PROP_COLOR; const AttributeDomain result_domain = get_result_domain(component, input_name, result_name); - OutputAttributePtr attribute_result = component.attribute_try_get_for_output( - result_name, result_domain, result_type); + OutputAttribute_Typed<ColorGeometry4f> attribute_result = + component.attribute_try_get_for_output_only<ColorGeometry4f>(result_name, result_domain); if (!attribute_result) { return; } - FloatReadAttribute attribute_in = component.attribute_get_for_read<float>( + GVArray_Typed<float> attribute_in = component.attribute_get_for_read<float>( input_name, result_domain, 0.0f); - Span<float> data_in = attribute_in.get_span(); - MutableSpan<Color4f> data_out = attribute_result->get_span_for_write_only<Color4f>(); + MutableSpan<ColorGeometry4f> results = attribute_result.as_span(); ColorBand *color_ramp = &node_storage->color_ramp; - for (const int i : data_in.index_range()) { - BKE_colorband_evaluate(color_ramp, data_in[i], data_out[i]); - } + parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { + for (const int i : range) { + BKE_colorband_evaluate(color_ramp, attribute_in[i], results[i]); + } + }); - attribute_result.apply_span_and_save(); + attribute_result.save(); } static void geo_node_attribute_color_ramp_exec(GeoNodeExecParams params) @@ -106,18 +116,13 @@ static void geo_node_attribute_color_ramp_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { execute_on_component(params, geometry_set.get_component_for_write<PointCloudComponent>()); } + if (geometry_set.has<CurveComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<CurveComponent>()); + } params.set_output("Geometry", std::move(geometry_set)); } -static void geo_node_attribute_color_ramp_init(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeAttributeColorRamp *node_storage = (NodeAttributeColorRamp *)MEM_callocN( - sizeof(NodeAttributeColorRamp), __func__); - BKE_colorband_init(&node_storage->color_ramp, true); - node->storage = node_storage; -} - } // namespace blender::nodes void register_node_type_geo_attribute_color_ramp() diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc index e9e07d34c17..d8c52d16f41 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc @@ -77,9 +77,9 @@ static AttributeDomain get_result_domain(const GeometryComponent &component, StringRef result_name) { /* Use the domain of the result attribute if it already exists. */ - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); - if (result_attribute) { - return result_attribute->domain(); + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(result_name); + if (result_info) { + return result_info->domain; } /* Otherwise use the highest priority domain from existing input attributes, or the default. */ @@ -94,27 +94,25 @@ static void combine_attributes(GeometryComponent &component, const GeoNodeExecPa } const AttributeDomain result_domain = get_result_domain(component, params, result_name); - OutputAttributePtr attribute_result = component.attribute_try_get_for_output( - result_name, result_domain, CD_PROP_FLOAT3); + OutputAttribute_Typed<float3> attribute_result = + component.attribute_try_get_for_output_only<float3>(result_name, result_domain); if (!attribute_result) { return; } - FloatReadAttribute attribute_x = params.get_input_attribute<float>( + GVArray_Typed<float> attribute_x = params.get_input_attribute<float>( "X", component, result_domain, 0.0f); - FloatReadAttribute attribute_y = params.get_input_attribute<float>( + GVArray_Typed<float> attribute_y = params.get_input_attribute<float>( "Y", component, result_domain, 0.0f); - FloatReadAttribute attribute_z = params.get_input_attribute<float>( + GVArray_Typed<float> attribute_z = params.get_input_attribute<float>( "Z", component, result_domain, 0.0f); - MutableSpan<float3> results = attribute_result->get_span_for_write_only<float3>(); - for (const int i : results.index_range()) { + for (const int i : IndexRange(attribute_result->size())) { const float x = attribute_x[i]; const float y = attribute_y[i]; const float z = attribute_z[i]; - const float3 result = float3(x, y, z); - results[i] = result; + attribute_result->set(i, {x, y, z}); } - attribute_result.apply_span_and_save(); + attribute_result.save(); } static void geo_node_attribute_combine_xyz_exec(GeoNodeExecParams params) @@ -129,6 +127,9 @@ static void geo_node_attribute_combine_xyz_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { combine_attributes(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + combine_attributes(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc index fe4045c39a6..57ac68b4cd4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc @@ -81,21 +81,18 @@ static void geo_node_attribute_compare_update(bNodeTree *UNUSED(ntree), bNode *n nodeSetSocketAvailability(socket_threshold, operation_tests_equality(*node_storage)); } -static void do_math_operation(const FloatReadAttribute &input_a, - const FloatReadAttribute &input_b, +static void do_math_operation(const VArray<float> &input_a, + const VArray<float> &input_b, const FloatCompareOperation operation, MutableSpan<bool> span_result) { const int size = input_a.size(); - Span<float> span_a = input_a.get_span(); - Span<float> span_b = input_b.get_span(); - if (try_dispatch_float_math_fl_fl_to_bool( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { for (const int i : IndexRange(size)) { - const float a = span_a[i]; - const float b = span_b[i]; + const float a = input_a[i]; + const float b = input_b[i]; const bool out = math_function(a, b); span_result[i] = out; } @@ -107,8 +104,8 @@ static void do_math_operation(const FloatReadAttribute &input_a, BLI_assert(false); } -static void do_equal_operation_float(const FloatReadAttribute &input_a, - const FloatReadAttribute &input_b, +static void do_equal_operation_float(const VArray<float> &input_a, + const VArray<float> &input_b, const float threshold, MutableSpan<bool> span_result) { @@ -120,8 +117,8 @@ static void do_equal_operation_float(const FloatReadAttribute &input_a, } } -static void do_equal_operation_float3(const Float3ReadAttribute &input_a, - const Float3ReadAttribute &input_b, +static void do_equal_operation_float3(const VArray<float3> &input_a, + const VArray<float3> &input_b, const float threshold, MutableSpan<bool> span_result) { @@ -134,22 +131,22 @@ static void do_equal_operation_float3(const Float3ReadAttribute &input_a, } } -static void do_equal_operation_color4f(const Color4fReadAttribute &input_a, - const Color4fReadAttribute &input_b, +static void do_equal_operation_color4f(const VArray<ColorGeometry4f> &input_a, + const VArray<ColorGeometry4f> &input_b, const float threshold, MutableSpan<bool> span_result) { const float threshold_squared = pow2f(threshold); const int size = input_a.size(); for (const int i : IndexRange(size)) { - const Color4f a = input_a[i]; - const Color4f b = input_b[i]; + const ColorGeometry4f a = input_a[i]; + const ColorGeometry4f b = input_b[i]; span_result[i] = len_squared_v4v4(a, b) < threshold_squared; } } -static void do_equal_operation_bool(const BooleanReadAttribute &input_a, - const BooleanReadAttribute &input_b, +static void do_equal_operation_bool(const VArray<bool> &input_a, + const VArray<bool> &input_b, const float UNUSED(threshold), MutableSpan<bool> span_result) { @@ -161,8 +158,8 @@ static void do_equal_operation_bool(const BooleanReadAttribute &input_a, } } -static void do_not_equal_operation_float(const FloatReadAttribute &input_a, - const FloatReadAttribute &input_b, +static void do_not_equal_operation_float(const VArray<float> &input_a, + const VArray<float> &input_b, const float threshold, MutableSpan<bool> span_result) { @@ -174,8 +171,8 @@ static void do_not_equal_operation_float(const FloatReadAttribute &input_a, } } -static void do_not_equal_operation_float3(const Float3ReadAttribute &input_a, - const Float3ReadAttribute &input_b, +static void do_not_equal_operation_float3(const VArray<float3> &input_a, + const VArray<float3> &input_b, const float threshold, MutableSpan<bool> span_result) { @@ -188,22 +185,22 @@ static void do_not_equal_operation_float3(const Float3ReadAttribute &input_a, } } -static void do_not_equal_operation_color4f(const Color4fReadAttribute &input_a, - const Color4fReadAttribute &input_b, +static void do_not_equal_operation_color4f(const VArray<ColorGeometry4f> &input_a, + const VArray<ColorGeometry4f> &input_b, const float threshold, MutableSpan<bool> span_result) { const float threshold_squared = pow2f(threshold); const int size = input_a.size(); for (const int i : IndexRange(size)) { - const Color4f a = input_a[i]; - const Color4f b = input_b[i]; + const ColorGeometry4f a = input_a[i]; + const ColorGeometry4f b = input_b[i]; span_result[i] = len_squared_v4v4(a, b) >= threshold_squared; } } -static void do_not_equal_operation_bool(const BooleanReadAttribute &input_a, - const BooleanReadAttribute &input_b, +static void do_not_equal_operation_bool(const VArray<bool> &input_a, + const VArray<bool> &input_b, const float UNUSED(threshold), MutableSpan<bool> span_result) { @@ -237,9 +234,9 @@ static AttributeDomain get_result_domain(const GeometryComponent &component, StringRef result_name) { /* Use the domain of the result attribute if it already exists. */ - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); - if (result_attribute) { - return result_attribute->domain(); + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(result_name); + if (result_info) { + return result_info->domain; } /* Otherwise use the highest priority domain from existing input attributes, or the default. */ @@ -254,20 +251,19 @@ static void attribute_compare_calc(GeometryComponent &component, const GeoNodeEx node_storage->operation); const std::string result_name = params.get_input<std::string>("Result"); - const CustomDataType result_type = CD_PROP_BOOL; const AttributeDomain result_domain = get_result_domain(component, params, result_name); - OutputAttributePtr attribute_result = component.attribute_try_get_for_output( - result_name, result_domain, result_type); + OutputAttribute_Typed<bool> attribute_result = component.attribute_try_get_for_output_only<bool>( + result_name, result_domain); if (!attribute_result) { return; } const CustomDataType input_data_type = get_data_type(component, params, *node_storage); - ReadAttributePtr attribute_a = params.get_input_attribute( + GVArrayPtr attribute_a = params.get_input_attribute( "A", component, result_domain, input_data_type, nullptr); - ReadAttributePtr attribute_b = params.get_input_attribute( + GVArrayPtr attribute_b = params.get_input_attribute( "B", component, result_domain, input_data_type, nullptr); if (!attribute_a || !attribute_b) { @@ -275,7 +271,7 @@ static void attribute_compare_calc(GeometryComponent &component, const GeoNodeEx return; } - MutableSpan<bool> result_span = attribute_result->get_span_for_write_only<bool>(); + MutableSpan<bool> result_span = attribute_result.as_span(); /* Use specific types for correct equality operations, but for other operations we use implicit * conversions and float comparison. In other words, the comparison is not element-wise. */ @@ -283,38 +279,51 @@ static void attribute_compare_calc(GeometryComponent &component, const GeoNodeEx const float threshold = params.get_input<float>("Threshold"); if (operation == NODE_FLOAT_COMPARE_EQUAL) { if (input_data_type == CD_PROP_FLOAT) { - do_equal_operation_float(*attribute_a, *attribute_b, threshold, result_span); + do_equal_operation_float( + attribute_a->typed<float>(), attribute_b->typed<float>(), threshold, result_span); } else if (input_data_type == CD_PROP_FLOAT3) { - do_equal_operation_float3(*attribute_a, *attribute_b, threshold, result_span); + do_equal_operation_float3( + attribute_a->typed<float3>(), attribute_b->typed<float3>(), threshold, result_span); } else if (input_data_type == CD_PROP_COLOR) { - do_equal_operation_color4f(*attribute_a, *attribute_b, threshold, result_span); + do_equal_operation_color4f(attribute_a->typed<ColorGeometry4f>(), + attribute_b->typed<ColorGeometry4f>(), + threshold, + result_span); } else if (input_data_type == CD_PROP_BOOL) { - do_equal_operation_bool(*attribute_a, *attribute_b, threshold, result_span); + do_equal_operation_bool( + attribute_a->typed<bool>(), attribute_b->typed<bool>(), threshold, result_span); } } else if (operation == NODE_FLOAT_COMPARE_NOT_EQUAL) { if (input_data_type == CD_PROP_FLOAT) { - do_not_equal_operation_float(*attribute_a, *attribute_b, threshold, result_span); + do_not_equal_operation_float( + attribute_a->typed<float>(), attribute_b->typed<float>(), threshold, result_span); } else if (input_data_type == CD_PROP_FLOAT3) { - do_not_equal_operation_float3(*attribute_a, *attribute_b, threshold, result_span); + do_not_equal_operation_float3( + attribute_a->typed<float3>(), attribute_b->typed<float3>(), threshold, result_span); } else if (input_data_type == CD_PROP_COLOR) { - do_not_equal_operation_color4f(*attribute_a, *attribute_b, threshold, result_span); + do_not_equal_operation_color4f(attribute_a->typed<ColorGeometry4f>(), + attribute_b->typed<ColorGeometry4f>(), + threshold, + result_span); } else if (input_data_type == CD_PROP_BOOL) { - do_not_equal_operation_bool(*attribute_a, *attribute_b, threshold, result_span); + do_not_equal_operation_bool( + attribute_a->typed<bool>(), attribute_b->typed<bool>(), threshold, result_span); } } } else { - do_math_operation(*attribute_a, *attribute_b, operation, result_span); + do_math_operation( + attribute_a->typed<float>(), attribute_b->typed<float>(), operation, result_span); } - attribute_result.apply_span_and_save(); + attribute_result.save(); } static void geo_node_attribute_compare_exec(GeoNodeExecParams params) @@ -329,6 +338,9 @@ static void geo_node_attribute_compare_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { attribute_compare_calc(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + attribute_compare_calc(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc index 19c3aaa9c85..7b40456b180 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc @@ -35,8 +35,10 @@ static void geo_node_attribute_convert_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { - uiItemR(layout, ptr, "domain", 0, "", ICON_NONE); - uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiItemR(layout, ptr, "domain", 0, nullptr, ICON_NONE); + uiItemR(layout, ptr, "data_type", 0, IFACE_("Type"), ICON_NONE); } static void geo_node_attribute_convert_init(bNodeTree *UNUSED(tree), bNode *node) @@ -44,26 +46,27 @@ static void geo_node_attribute_convert_init(bNodeTree *UNUSED(tree), bNode *node NodeAttributeConvert *data = (NodeAttributeConvert *)MEM_callocN(sizeof(NodeAttributeConvert), __func__); - data->data_type = CD_PROP_FLOAT; + data->data_type = CD_AUTO_FROM_NAME; data->domain = ATTR_DOMAIN_AUTO; node->storage = data; } namespace blender::nodes { -static AttributeDomain get_result_domain(const GeometryComponent &component, - StringRef source_name, - StringRef result_name) +static AttributeMetaData get_result_domain_and_type(const GeometryComponent &component, + const StringRef source_name, + const StringRef result_name) { - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); - if (result_attribute) { - return result_attribute->domain(); + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(result_name); + if (result_info) { + return *result_info; } - ReadAttributePtr source_attribute = component.attribute_try_get_for_read(source_name); - if (source_attribute) { - return source_attribute->domain(); + std::optional<AttributeMetaData> source_info = component.attribute_get_meta_data(source_name); + if (source_info) { + return *source_info; } - return ATTR_DOMAIN_POINT; + /* The node won't do anything in this case, but we still have to return a value. */ + return AttributeMetaData{ATTR_DOMAIN_POINT, CD_PROP_BOOL}; } static bool conversion_can_be_skipped(const GeometryComponent &component, @@ -75,14 +78,14 @@ static bool conversion_can_be_skipped(const GeometryComponent &component, if (source_name != result_name) { return false; } - ReadAttributePtr read_attribute = component.attribute_try_get_for_read(source_name); - if (!read_attribute) { + std::optional<AttributeMetaData> info = component.attribute_get_meta_data(result_name); + if (!info) { return false; } - if (read_attribute->domain() != result_domain) { + if (info->domain != result_domain) { return false; } - if (read_attribute->cpp_type() != *bke::custom_data_type_to_cpp_type(result_type)) { + if (info->data_type != result_type) { return false; } return true; @@ -92,19 +95,20 @@ static void attribute_convert_calc(GeometryComponent &component, const GeoNodeExecParams ¶ms, const StringRef source_name, const StringRef result_name, - const CustomDataType result_type, + const CustomDataType data_type, const AttributeDomain domain) { - const AttributeDomain result_domain = (domain == ATTR_DOMAIN_AUTO) ? - get_result_domain( - component, source_name, result_name) : - domain; + const AttributeMetaData auto_info = get_result_domain_and_type( + component, source_name, result_name); + const AttributeDomain result_domain = (domain == ATTR_DOMAIN_AUTO) ? auto_info.domain : domain; + const CustomDataType result_type = (data_type == CD_AUTO_FROM_NAME) ? auto_info.data_type : + data_type; if (conversion_can_be_skipped(component, source_name, result_name, result_domain, result_type)) { return; } - ReadAttributePtr source_attribute = component.attribute_try_get_for_read( + GVArrayPtr source_attribute = component.attribute_try_get_for_read( source_name, result_domain, result_type); if (!source_attribute) { params.error_message_add(NodeWarningType::Error, @@ -112,25 +116,22 @@ static void attribute_convert_calc(GeometryComponent &component, return; } - OutputAttributePtr result_attribute = component.attribute_try_get_for_output( + OutputAttribute result_attribute = component.attribute_try_get_for_output_only( result_name, result_domain, result_type); if (!result_attribute) { return; } - fn::GSpan source_span = source_attribute->get_span(); - fn::GMutableSpan result_span = result_attribute->get_span_for_write_only(); - if (source_span.is_empty() || result_span.is_empty()) { - return; - } + GVArray_GSpan source_span{*source_attribute}; + GMutableSpan result_span = result_attribute.as_span(); + BLI_assert(source_span.size() == result_span.size()); const CPPType *cpp_type = bke::custom_data_type_to_cpp_type(result_type); BLI_assert(cpp_type != nullptr); cpp_type->copy_to_initialized_n(source_span.data(), result_span.data(), result_span.size()); - - result_attribute.apply_span_and_save(); + result_attribute.save(); } static void geo_node_attribute_convert_exec(GeoNodeExecParams params) @@ -166,6 +167,14 @@ static void geo_node_attribute_convert_exec(GeoNodeExecParams params) data_type, domain); } + if (geometry_set.has<CurveComponent>()) { + attribute_convert_calc(geometry_set.get_component_for_write<CurveComponent>(), + params, + source_name, + result_name, + data_type, + domain); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc new file mode 100644 index 00000000000..599c9e58e52 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc @@ -0,0 +1,232 @@ +/* + * 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_blenlib.h" +#include "BLI_task.hh" + +#include "BKE_colortools.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_attribute_curve_map_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_STRING, N_("Attribute")}, + {SOCK_STRING, N_("Result")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_attribute_curve_map_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_attribute_curve_map_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); + bNode *node = (bNode *)ptr->data; + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; + switch (data->data_type) { + case CD_PROP_FLOAT: + uiTemplateCurveMapping(layout, ptr, "curve_vec", 0, false, false, false, false); + break; + case CD_PROP_FLOAT3: + uiTemplateCurveMapping(layout, ptr, "curve_vec", 'v', false, false, false, false); + break; + case CD_PROP_COLOR: + uiTemplateCurveMapping(layout, ptr, "curve_rgb", 'c', false, false, false, false); + break; + } +} + +static void geo_node_attribute_curve_map_free_storage(bNode *node) +{ + if (node->storage) { + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; + BKE_curvemapping_free(data->curve_vec); + BKE_curvemapping_free(data->curve_rgb); + MEM_freeN(node->storage); + } +} + +static void geo_node_attribute_curve_map_copy_storage(bNodeTree *UNUSED(dest_ntree), + bNode *dest_node, + const bNode *src_node) +{ + dest_node->storage = MEM_dupallocN(src_node->storage); + NodeAttributeCurveMap *src_data = (NodeAttributeCurveMap *)src_node->storage; + NodeAttributeCurveMap *dest_data = (NodeAttributeCurveMap *)dest_node->storage; + dest_data->curve_vec = BKE_curvemapping_copy(src_data->curve_vec); + dest_data->curve_rgb = BKE_curvemapping_copy(src_data->curve_rgb); +} + +static void geo_node_attribute_curve_map_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)MEM_callocN(sizeof(NodeAttributeCurveMap), + __func__); + + data->data_type = CD_PROP_FLOAT; + data->curve_vec = BKE_curvemapping_add(4, -1.0f, -1.0f, 1.0f, 1.0f); + data->curve_vec->cur = 3; + data->curve_rgb = BKE_curvemapping_add(4, 0.0f, 0.0f, 1.0f, 1.0f); + node->storage = data; +} + +static void geo_node_attribute_curve_map_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + /* Set the active curve when data type is changed. */ + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)node->storage; + if (data->data_type == CD_PROP_FLOAT) { + data->curve_vec->cur = 3; + } + else if (data->data_type == CD_PROP_FLOAT3) { + data->curve_vec->cur = 0; + } +} + +namespace blender::nodes { + +static AttributeDomain get_result_domain(const GeometryComponent &component, + StringRef input_name, + StringRef result_name) +{ + /* Use the domain of the result attribute if it already exists. */ + ReadAttributeLookup result_attribute = component.attribute_try_get_for_read(result_name); + if (result_attribute) { + return result_attribute.domain; + } + + /* Otherwise use the input attribute's domain if it exists. */ + ReadAttributeLookup input_attribute = component.attribute_try_get_for_read(input_name); + if (input_attribute) { + return input_attribute.domain; + } + + return ATTR_DOMAIN_POINT; +} + +static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryComponent &component) +{ + const bNode &bnode = params.node(); + NodeAttributeCurveMap &node_storage = *(NodeAttributeCurveMap *)bnode.storage; + const std::string result_name = params.get_input<std::string>("Result"); + const std::string input_name = params.get_input<std::string>("Attribute"); + + const CustomDataType result_type = (CustomDataType)node_storage.data_type; + const AttributeDomain result_domain = get_result_domain(component, input_name, result_name); + + OutputAttribute attribute_result = component.attribute_try_get_for_output_only( + result_name, result_domain, result_type); + if (!attribute_result) { + return; + } + + switch (result_type) { + case CD_PROP_FLOAT: { + const CurveMapping *cumap = (CurveMapping *)node_storage.curve_vec; + GVArray_Typed<float> attribute_in = component.attribute_get_for_read<float>( + input_name, result_domain, float(0.0f)); + MutableSpan<float> results = attribute_result.as_span<float>(); + parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { + for (const int i : range) { + results[i] = BKE_curvemapping_evaluateF(cumap, 3, attribute_in[i]); + } + }); + break; + } + case CD_PROP_FLOAT3: { + const CurveMapping *cumap = (CurveMapping *)node_storage.curve_vec; + GVArray_Typed<float3> attribute_in = component.attribute_get_for_read<float3>( + input_name, result_domain, float3(0.0f)); + MutableSpan<float3> results = attribute_result.as_span<float3>(); + parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { + for (const int i : range) { + BKE_curvemapping_evaluate3F(cumap, results[i], attribute_in[i]); + } + }); + break; + } + case CD_PROP_COLOR: { + const CurveMapping *cumap = (CurveMapping *)node_storage.curve_rgb; + GVArray_Typed<ColorGeometry4f> attribute_in = + component.attribute_get_for_read<ColorGeometry4f>( + input_name, result_domain, ColorGeometry4f(0.0f, 0.0f, 0.0f, 1.0f)); + MutableSpan<ColorGeometry4f> results = attribute_result.as_span<ColorGeometry4f>(); + parallel_for(IndexRange(attribute_in.size()), 512, [&](IndexRange range) { + for (const int i : range) { + BKE_curvemapping_evaluateRGBF(cumap, results[i], attribute_in[i]); + } + }); + break; + } + default: { + BLI_assert_unreachable(); + break; + } + } + + attribute_result.save(); +} + +static void geo_node_attribute_curve_map_exec(GeoNodeExecParams params) +{ + const bNode &bnode = params.node(); + NodeAttributeCurveMap *data = (NodeAttributeCurveMap *)bnode.storage; + BKE_curvemapping_init(data->curve_vec); + BKE_curvemapping_init(data->curve_rgb); + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + geometry_set = geometry_set_realize_instances(geometry_set); + + if (geometry_set.has<MeshComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<MeshComponent>()); + } + if (geometry_set.has<PointCloudComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<PointCloudComponent>()); + } + if (geometry_set.has<CurveComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<CurveComponent>()); + } + + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_curve_map() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_ATTRIBUTE_CURVE_MAP, "Attribute Curve Map", NODE_CLASS_ATTRIBUTE, 0); + node_type_socket_templates( + &ntype, geo_node_attribute_curve_map_in, geo_node_attribute_curve_map_out); + node_type_update(&ntype, geo_node_attribute_curve_map_update); + node_type_init(&ntype, geo_node_attribute_curve_map_init); + node_type_size_preset(&ntype, NODE_SIZE_LARGE); + node_type_storage(&ntype, + "NodeAttributeCurveMap", + geo_node_attribute_curve_map_free_storage, + geo_node_attribute_curve_map_copy_storage); + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_curve_map_exec; + ntype.draw_buttons = geo_node_attribute_curve_map_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc index 7b4483a31a1..389abe3b2aa 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc @@ -68,13 +68,12 @@ static void geo_node_attribute_fill_update(bNodeTree *UNUSED(ntree), bNode *node namespace blender::nodes { -static AttributeDomain get_result_domain(const GeometryComponent &component, - StringRef attribute_name) +static AttributeDomain get_result_domain(const GeometryComponent &component, const StringRef name) { /* Use the domain of the result attribute if it already exists. */ - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(attribute_name); - if (result_attribute) { - return result_attribute->domain(); + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(name); + if (result_info) { + return result_info->domain; } return ATTR_DOMAIN_POINT; } @@ -93,7 +92,7 @@ static void fill_attribute(GeometryComponent &component, const GeoNodeExecParams get_result_domain(component, attribute_name) : domain; - OutputAttributePtr attribute = component.attribute_try_get_for_output( + OutputAttribute attribute = component.attribute_try_get_for_output_only( attribute_name, result_domain, data_type); if (!attribute) { return; @@ -102,38 +101,34 @@ static void fill_attribute(GeometryComponent &component, const GeoNodeExecParams switch (data_type) { case CD_PROP_FLOAT: { const float value = params.get_input<float>("Value_001"); - MutableSpan<float> attribute_span = attribute->get_span_for_write_only<float>(); - attribute_span.fill(value); + attribute->fill(&value); break; } case CD_PROP_FLOAT3: { const float3 value = params.get_input<float3>("Value"); - MutableSpan<float3> attribute_span = attribute->get_span_for_write_only<float3>(); - attribute_span.fill(value); + attribute->fill(&value); break; } case CD_PROP_COLOR: { - const Color4f value = params.get_input<Color4f>("Value_002"); - MutableSpan<Color4f> attribute_span = attribute->get_span_for_write_only<Color4f>(); - attribute_span.fill(value); + const ColorGeometry4f value = params.get_input<ColorGeometry4f>("Value_002"); + attribute->fill(&value); break; } case CD_PROP_BOOL: { const bool value = params.get_input<bool>("Value_003"); - MutableSpan<bool> attribute_span = attribute->get_span_for_write_only<bool>(); - attribute_span.fill(value); + attribute->fill(&value); break; } case CD_PROP_INT32: { const int value = params.get_input<int>("Value_004"); - MutableSpan<int> attribute_span = attribute->get_span_for_write_only<int>(); - attribute_span.fill(value); + attribute->fill(&value); + break; } default: break; } - attribute.apply_span_and_save(); + attribute.save(); } static void geo_node_attribute_fill_exec(GeoNodeExecParams params) @@ -148,6 +143,9 @@ static void geo_node_attribute_fill_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { fill_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + fill_attribute(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc index fdbdadf90b6..40fe675bd6c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc @@ -15,6 +15,7 @@ */ #include "BLI_math_base_safe.h" +#include "BLI_task.hh" #include "UI_interface.h" #include "UI_resources.h" @@ -192,8 +193,8 @@ static float map_smootherstep(const float value, return min_to + factor_mapped * (max_to - min_to); } -static void map_range_float(FloatReadAttribute attribute_input, - FloatWriteAttribute attribute_result, +static void map_range_float(const VArray<float> &attribute_input, + MutableSpan<float> results, const GeoNodeExecParams ¶ms) { const bNode &node = params.node(); @@ -204,33 +205,40 @@ static void map_range_float(FloatReadAttribute attribute_input, const float min_to = params.get_input<float>("To Min"); const float max_to = params.get_input<float>("To Max"); - Span<float> span = attribute_input.get_span(); - MutableSpan<float> result_span = attribute_result.get_span(); + VArray_Span<float> span{attribute_input}; switch (interpolation_type) { case NODE_MAP_RANGE_LINEAR: { - for (int i : span.index_range()) { - result_span[i] = map_linear(span[i], min_from, max_from, min_to, max_to); - } + parallel_for(span.index_range(), 2048, [&](IndexRange range) { + for (const int i : range) { + results[i] = map_linear(span[i], min_from, max_from, min_to, max_to); + } + }); break; } case NODE_MAP_RANGE_STEPPED: { const float steps = params.get_input<float>("Steps"); - for (int i : span.index_range()) { - result_span[i] = map_stepped(span[i], min_from, max_from, min_to, max_to, steps); - } + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i] = map_stepped(span[i], min_from, max_from, min_to, max_to, steps); + } + }); break; } case NODE_MAP_RANGE_SMOOTHSTEP: { - for (int i : span.index_range()) { - result_span[i] = map_smoothstep(span[i], min_from, max_from, min_to, max_to); - } + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i] = map_smoothstep(span[i], min_from, max_from, min_to, max_to); + } + }); break; } case NODE_MAP_RANGE_SMOOTHERSTEP: { - for (int i : span.index_range()) { - result_span[i] = map_smootherstep(span[i], min_from, max_from, min_to, max_to); - } + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i] = map_smootherstep(span[i], min_from, max_from, min_to, max_to); + } + }); break; } } @@ -241,14 +249,16 @@ static void map_range_float(FloatReadAttribute attribute_input, const float clamp_min = min_to < max_to ? min_to : max_to; const float clamp_max = min_to < max_to ? max_to : min_to; - for (int i : result_span.index_range()) { - result_span[i] = std::clamp(result_span[i], clamp_min, clamp_max); - } + parallel_for(results.index_range(), 2048, [&](IndexRange range) { + for (const int i : range) { + results[i] = std::clamp(results[i], clamp_min, clamp_max); + } + }); } } -static void map_range_float3(Float3ReadAttribute attribute_input, - Float3WriteAttribute attribute_result, +static void map_range_float3(const VArray<float3> &attribute_input, + const MutableSpan<float3> results, const GeoNodeExecParams ¶ms) { const bNode &node = params.node(); @@ -259,44 +269,51 @@ static void map_range_float3(Float3ReadAttribute attribute_input, const float3 min_to = params.get_input<float3>("To Min_001"); const float3 max_to = params.get_input<float3>("To Max_001"); - Span<float3> span = attribute_input.get_span(); - MutableSpan<float3> result_span = attribute_result.get_span(); + VArray_Span<float3> span{attribute_input}; switch (interpolation_type) { case NODE_MAP_RANGE_LINEAR: { - for (int i : span.index_range()) { - result_span[i].x = map_linear(span[i].x, min_from.x, max_from.x, min_to.x, max_to.x); - result_span[i].y = map_linear(span[i].y, min_from.y, max_from.y, min_to.y, max_to.y); - result_span[i].z = map_linear(span[i].z, min_from.z, max_from.z, min_to.z, max_to.z); - } + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i].x = map_linear(span[i].x, min_from.x, max_from.x, min_to.x, max_to.x); + results[i].y = map_linear(span[i].y, min_from.y, max_from.y, min_to.y, max_to.y); + results[i].z = map_linear(span[i].z, min_from.z, max_from.z, min_to.z, max_to.z); + } + }); break; } case NODE_MAP_RANGE_STEPPED: { const float3 steps = params.get_input<float3>("Steps_001"); - for (int i : span.index_range()) { - result_span[i].x = map_stepped( - span[i].x, min_from.x, max_from.x, min_to.x, max_to.x, steps.x); - result_span[i].y = map_stepped( - span[i].y, min_from.y, max_from.y, min_to.y, max_to.y, steps.y); - result_span[i].z = map_stepped( - span[i].z, min_from.z, max_from.z, min_to.z, max_to.z, steps.z); - } + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i].x = map_stepped( + span[i].x, min_from.x, max_from.x, min_to.x, max_to.x, steps.x); + results[i].y = map_stepped( + span[i].y, min_from.y, max_from.y, min_to.y, max_to.y, steps.y); + results[i].z = map_stepped( + span[i].z, min_from.z, max_from.z, min_to.z, max_to.z, steps.z); + } + }); break; } case NODE_MAP_RANGE_SMOOTHSTEP: { - for (int i : span.index_range()) { - result_span[i].x = map_smoothstep(span[i].x, min_from.x, max_from.x, min_to.x, max_to.x); - result_span[i].y = map_smoothstep(span[i].y, min_from.y, max_from.y, min_to.y, max_to.y); - result_span[i].z = map_smoothstep(span[i].z, min_from.z, max_from.z, min_to.z, max_to.z); - } + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i].x = map_smoothstep(span[i].x, min_from.x, max_from.x, min_to.x, max_to.x); + results[i].y = map_smoothstep(span[i].y, min_from.y, max_from.y, min_to.y, max_to.y); + results[i].z = map_smoothstep(span[i].z, min_from.z, max_from.z, min_to.z, max_to.z); + } + }); break; } case NODE_MAP_RANGE_SMOOTHERSTEP: { - for (int i : span.index_range()) { - result_span[i].x = map_smootherstep(span[i].x, min_from.x, max_from.x, min_to.x, max_to.x); - result_span[i].y = map_smootherstep(span[i].y, min_from.y, max_from.y, min_to.y, max_to.y); - result_span[i].z = map_smootherstep(span[i].z, min_from.z, max_from.z, min_to.z, max_to.z); - } + parallel_for(span.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i].x = map_smootherstep(span[i].x, min_from.x, max_from.x, min_to.x, max_to.x); + results[i].y = map_smootherstep(span[i].y, min_from.y, max_from.y, min_to.y, max_to.y); + results[i].z = map_smootherstep(span[i].z, min_from.z, max_from.z, min_to.z, max_to.z); + } + }); break; } } @@ -313,8 +330,8 @@ static void map_range_float3(Float3ReadAttribute attribute_input, clamp_min.z = min_to.z < max_to.z ? min_to.z : max_to.z; clamp_max.z = min_to.z < max_to.z ? max_to.z : min_to.z; - for (int i : result_span.index_range()) { - clamp_v3_v3v3(result_span[i], clamp_min, clamp_max); + for (int i : results.index_range()) { + clamp_v3_v3v3(results[i], clamp_min, clamp_max); } } } @@ -323,13 +340,13 @@ static AttributeDomain get_result_domain(const GeometryComponent &component, StringRef source_name, StringRef result_name) { - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); - if (result_attribute) { - return result_attribute->domain(); + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(result_name); + if (result_info) { + return result_info->domain; } - ReadAttributePtr source_attribute = component.attribute_try_get_for_read(source_name); - if (source_attribute) { - return source_attribute->domain(); + std::optional<AttributeMetaData> source_info = component.attribute_get_meta_data(source_name); + if (source_info) { + return source_info->domain; } return ATTR_DOMAIN_POINT; } @@ -349,8 +366,7 @@ static void map_range_attribute(GeometryComponent &component, const GeoNodeExecP const AttributeDomain domain = get_result_domain(component, input_name, result_name); - ReadAttributePtr attribute_input = component.attribute_try_get_for_read( - input_name, domain, data_type); + GVArrayPtr attribute_input = component.attribute_try_get_for_read(input_name, domain, data_type); if (!attribute_input) { params.error_message_add(NodeWarningType::Error, @@ -358,7 +374,7 @@ static void map_range_attribute(GeometryComponent &component, const GeoNodeExecP return; } - OutputAttributePtr attribute_result = component.attribute_try_get_for_output( + OutputAttribute attribute_result = component.attribute_try_get_for_output_only( result_name, domain, data_type); if (!attribute_result) { params.error_message_add(NodeWarningType::Error, @@ -369,18 +385,19 @@ static void map_range_attribute(GeometryComponent &component, const GeoNodeExecP switch (data_type) { case CD_PROP_FLOAT: { - map_range_float(*attribute_input, *attribute_result, params); + map_range_float(attribute_input->typed<float>(), attribute_result.as_span<float>(), params); break; } case CD_PROP_FLOAT3: { - map_range_float3(*attribute_input, *attribute_result, params); + map_range_float3( + attribute_input->typed<float3>(), attribute_result.as_span<float3>(), params); break; } default: BLI_assert_unreachable(); } - attribute_result.apply_span_and_save(); + attribute_result.save(); } static void geo_node_attribute_map_range_exec(GeoNodeExecParams params) @@ -393,6 +410,9 @@ static void geo_node_attribute_map_range_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { map_range_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + map_range_attribute(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc index 5ee31e78be2..ce0ca31cc2b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc @@ -14,6 +14,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BLI_task.hh" + #include "UI_interface.h" #include "UI_resources.h" @@ -149,46 +151,52 @@ static void geo_node_attribute_math_update(bNodeTree *UNUSED(ntree), bNode *node operation_use_input_c(operation)); } -static void do_math_operation(Span<float> span_a, - Span<float> span_b, - Span<float> span_c, +static void do_math_operation(const VArray<float> &span_a, + const VArray<float> &span_b, + const VArray<float> &span_c, MutableSpan<float> span_result, const NodeMathOperation operation) { bool success = try_dispatch_float_math_fl_fl_fl_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(span_result.size())) { - span_result[i] = math_function(span_a[i], span_b[i], span_c[i]); - } + parallel_for(IndexRange(span_result.size()), 512, [&](IndexRange range) { + for (const int i : range) { + span_result[i] = math_function(span_a[i], span_b[i], span_c[i]); + } + }); }); BLI_assert(success); UNUSED_VARS_NDEBUG(success); } -static void do_math_operation(Span<float> span_a, - Span<float> span_b, +static void do_math_operation(const VArray<float> &span_a, + const VArray<float> &span_b, MutableSpan<float> span_result, const NodeMathOperation operation) { bool success = try_dispatch_float_math_fl_fl_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(span_result.size())) { - span_result[i] = math_function(span_a[i], span_b[i]); - } + parallel_for(IndexRange(span_result.size()), 1024, [&](IndexRange range) { + for (const int i : range) { + span_result[i] = math_function(span_a[i], span_b[i]); + } + }); }); BLI_assert(success); UNUSED_VARS_NDEBUG(success); } -static void do_math_operation(Span<float> span_input, +static void do_math_operation(const VArray<float> &span_input, MutableSpan<float> span_result, const NodeMathOperation operation) { bool success = try_dispatch_float_math_fl_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(span_result.size())) { - span_result[i] = math_function(span_input[i]); - } + parallel_for(IndexRange(span_result.size()), 1024, [&](IndexRange range) { + for (const int i : range) { + span_result[i] = math_function(span_input[i]); + } + }); }); BLI_assert(success); UNUSED_VARS_NDEBUG(success); @@ -200,9 +208,9 @@ static AttributeDomain get_result_domain(const GeometryComponent &component, StringRef result_name) { /* Use the domain of the result attribute if it already exists. */ - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); - if (result_attribute) { - return result_attribute->domain(); + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(result_name); + if (result_info) { + return result_info->domain; } /* Otherwise use the highest priority domain from existing input attributes, or the default. */ @@ -224,56 +232,39 @@ static void attribute_math_calc(GeometryComponent &component, const GeoNodeExecP const std::string result_name = params.get_input<std::string>("Result"); /* The result type of this node is always float. */ - const CustomDataType result_type = CD_PROP_FLOAT; const AttributeDomain result_domain = get_result_domain( component, params, operation, result_name); - OutputAttributePtr attribute_result = component.attribute_try_get_for_output( - result_name, result_domain, result_type); + OutputAttribute_Typed<float> attribute_result = + component.attribute_try_get_for_output_only<float>(result_name, result_domain); if (!attribute_result) { return; } - ReadAttributePtr attribute_a = params.get_input_attribute( - "A", component, result_domain, result_type, nullptr); - if (!attribute_a) { - return; - } + GVArray_Typed<float> attribute_a = params.get_input_attribute<float>( + "A", component, result_domain, 0.0f); - /* Note that passing the data with `get_span<float>()` works + MutableSpan<float> result_span = attribute_result.as_span(); + + /* Note that passing the data with `get_internal_span<float>()` works * because the attributes were accessed with #CD_PROP_FLOAT. */ if (operation_use_input_b(operation)) { - ReadAttributePtr attribute_b = params.get_input_attribute( - "B", component, result_domain, result_type, nullptr); - if (!attribute_b) { - return; - } + GVArray_Typed<float> attribute_b = params.get_input_attribute<float>( + "B", component, result_domain, 0.0f); if (operation_use_input_c(operation)) { - ReadAttributePtr attribute_c = params.get_input_attribute( - "C", component, result_domain, result_type, nullptr); - if (!attribute_c) { - return; - } - do_math_operation(attribute_a->get_span<float>(), - attribute_b->get_span<float>(), - attribute_c->get_span<float>(), - attribute_result->get_span_for_write_only<float>(), - operation); + GVArray_Typed<float> attribute_c = params.get_input_attribute<float>( + "C", component, result_domain, 0.0f); + do_math_operation(attribute_a, attribute_b, attribute_c, result_span, operation); } else { - do_math_operation(attribute_a->get_span<float>(), - attribute_b->get_span<float>(), - attribute_result->get_span_for_write_only<float>(), - operation); + do_math_operation(attribute_a, attribute_b, result_span, operation); } } else { - do_math_operation(attribute_a->get_span<float>(), - attribute_result->get_span_for_write_only<float>(), - operation); + do_math_operation(attribute_a, result_span, operation); } - attribute_result.apply_span_and_save(); + attribute_result.save(); } static void geo_node_attribute_math_exec(GeoNodeExecParams params) @@ -288,6 +279,9 @@ static void geo_node_attribute_math_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { attribute_math_calc(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + attribute_math_calc(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc index 9d8cd3dfa82..a6bd6c0ee32 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc @@ -14,6 +14,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BLI_task.hh" + #include "BKE_material.h" #include "DNA_material_types.h" @@ -57,73 +59,110 @@ static void geo_node_attribute_mix_layout(uiLayout *layout, bContext *UNUSED(C), namespace blender::nodes { +static void geo_node_attribute_mix_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeMix *data = (NodeAttributeMix *)MEM_callocN(sizeof(NodeAttributeMix), + "attribute mix node"); + data->blend_type = MA_RAMP_BLEND; + data->input_type_factor = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; + data->input_type_a = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; + data->input_type_b = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; + node->storage = data; +} + +static void geo_node_attribute_mix_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeMix *node_storage = (NodeAttributeMix *)node->storage; + update_attribute_input_socket_availabilities( + *node, "Factor", (GeometryNodeAttributeInputMode)node_storage->input_type_factor); + update_attribute_input_socket_availabilities( + *node, "A", (GeometryNodeAttributeInputMode)node_storage->input_type_a); + update_attribute_input_socket_availabilities( + *node, "B", (GeometryNodeAttributeInputMode)node_storage->input_type_b); +} + static void do_mix_operation_float(const int blend_mode, - const FloatReadAttribute &factors, - const FloatReadAttribute &inputs_a, - const FloatReadAttribute &inputs_b, - FloatWriteAttribute results) + const VArray<float> &factors, + const VArray<float> &inputs_a, + const VArray<float> &inputs_b, + VMutableArray<float> &results) { const int size = results.size(); - for (const int i : IndexRange(size)) { - const float factor = factors[i]; - float3 a{inputs_a[i]}; - const float3 b{inputs_b[i]}; - ramp_blend(blend_mode, a, factor, b); - const float result = a.x; - results.set(i, result); - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float factor = factors[i]; + float3 a{inputs_a[i]}; + const float3 b{inputs_b[i]}; + ramp_blend(blend_mode, a, factor, b); + const float result = a.x; + results.set(i, result); + } + }); } static void do_mix_operation_float3(const int blend_mode, - const FloatReadAttribute &factors, - const Float3ReadAttribute &inputs_a, - const Float3ReadAttribute &inputs_b, - Float3WriteAttribute results) + const VArray<float> &factors, + const VArray<float3> &inputs_a, + const VArray<float3> &inputs_b, + VMutableArray<float3> &results) { const int size = results.size(); - for (const int i : IndexRange(size)) { - const float factor = factors[i]; - float3 a = inputs_a[i]; - const float3 b = inputs_b[i]; - ramp_blend(blend_mode, a, factor, b); - results.set(i, a); - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float factor = factors[i]; + float3 a = inputs_a[i]; + const float3 b = inputs_b[i]; + ramp_blend(blend_mode, a, factor, b); + results.set(i, a); + } + }); } static void do_mix_operation_color4f(const int blend_mode, - const FloatReadAttribute &factors, - const Color4fReadAttribute &inputs_a, - const Color4fReadAttribute &inputs_b, - Color4fWriteAttribute results) + const VArray<float> &factors, + const VArray<ColorGeometry4f> &inputs_a, + const VArray<ColorGeometry4f> &inputs_b, + VMutableArray<ColorGeometry4f> &results) { const int size = results.size(); - for (const int i : IndexRange(size)) { - const float factor = factors[i]; - Color4f a = inputs_a[i]; - const Color4f b = inputs_b[i]; - ramp_blend(blend_mode, a, factor, b); - results.set(i, a); - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float factor = factors[i]; + ColorGeometry4f a = inputs_a[i]; + const ColorGeometry4f b = inputs_b[i]; + ramp_blend(blend_mode, a, factor, b); + results.set(i, a); + } + }); } static void do_mix_operation(const CustomDataType result_type, int blend_mode, - const FloatReadAttribute &attribute_factor, - const ReadAttribute &attribute_a, - const ReadAttribute &attribute_b, - WriteAttribute &attribute_result) + const VArray<float> &attribute_factor, + const GVArray &attribute_a, + const GVArray &attribute_b, + GVMutableArray &attribute_result) { if (result_type == CD_PROP_FLOAT) { - do_mix_operation_float( - blend_mode, attribute_factor, attribute_a, attribute_b, attribute_result); + do_mix_operation_float(blend_mode, + attribute_factor, + attribute_a.typed<float>(), + attribute_b.typed<float>(), + attribute_result.typed<float>()); } else if (result_type == CD_PROP_FLOAT3) { - do_mix_operation_float3( - blend_mode, attribute_factor, attribute_a, attribute_b, attribute_result); + do_mix_operation_float3(blend_mode, + attribute_factor, + attribute_a.typed<float3>(), + attribute_b.typed<float3>(), + attribute_result.typed<float3>()); } else if (result_type == CD_PROP_COLOR) { - do_mix_operation_color4f( - blend_mode, attribute_factor, attribute_a, attribute_b, attribute_result); + do_mix_operation_color4f(blend_mode, + attribute_factor, + attribute_a.typed<ColorGeometry4f>(), + attribute_b.typed<ColorGeometry4f>(), + attribute_result.typed<ColorGeometry4f>()); } } @@ -132,9 +171,9 @@ static AttributeDomain get_result_domain(const GeometryComponent &component, StringRef result_name) { /* Use the domain of the result attribute if it already exists. */ - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); - if (result_attribute) { - return result_attribute->domain(); + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(result_name); + if (result_info) { + return result_info->domain; } /* Otherwise use the highest priority domain from existing input attributes, or the default. */ @@ -158,17 +197,17 @@ static void attribute_mix_calc(GeometryComponent &component, const GeoNodeExecPa const AttributeDomain result_domain = get_result_domain(component, params, result_name); - OutputAttributePtr attribute_result = component.attribute_try_get_for_output( + OutputAttribute attribute_result = component.attribute_try_get_for_output_only( result_name, result_domain, result_type); if (!attribute_result) { return; } - FloatReadAttribute attribute_factor = params.get_input_attribute<float>( + GVArray_Typed<float> attribute_factor = params.get_input_attribute<float>( "Factor", component, result_domain, 0.5f); - ReadAttributePtr attribute_a = params.get_input_attribute( + GVArrayPtr attribute_a = params.get_input_attribute( "A", component, result_domain, result_type, nullptr); - ReadAttributePtr attribute_b = params.get_input_attribute( + GVArrayPtr attribute_b = params.get_input_attribute( "B", component, result_domain, result_type, nullptr); do_mix_operation(result_type, @@ -192,32 +231,13 @@ static void geo_node_attribute_mix_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { attribute_mix_calc(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + attribute_mix_calc(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } -static void geo_node_attribute_mix_init(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeAttributeMix *data = (NodeAttributeMix *)MEM_callocN(sizeof(NodeAttributeMix), - "attribute mix node"); - data->blend_type = MA_RAMP_BLEND; - data->input_type_factor = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; - data->input_type_a = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; - data->input_type_b = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; - node->storage = data; -} - -static void geo_node_attribute_mix_update(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeAttributeMix *node_storage = (NodeAttributeMix *)node->storage; - update_attribute_input_socket_availabilities( - *node, "Factor", (GeometryNodeAttributeInputMode)node_storage->input_type_factor); - update_attribute_input_socket_availabilities( - *node, "A", (GeometryNodeAttributeInputMode)node_storage->input_type_a); - update_attribute_input_socket_availabilities( - *node, "B", (GeometryNodeAttributeInputMode)node_storage->input_type_b); -} - } // namespace blender::nodes void register_node_type_geo_attribute_mix() diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc index f09a9bf056e..9c22b7fa87f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc @@ -62,7 +62,7 @@ namespace blender::nodes { static void proximity_calc(MutableSpan<float> distance_span, MutableSpan<float3> location_span, - Span<float3> positions, + const VArray<float3> &positions, BVHTreeFromMesh &tree_data_mesh, BVHTreeFromPointCloud &tree_data_pointcloud, const bool bvh_mesh_success, @@ -169,19 +169,18 @@ static void attribute_calc_proximity(GeometryComponent &component, const AttributeDomain result_domain = ATTR_DOMAIN_POINT; const std::string distance_attribute_name = params.get_input<std::string>("Distance"); - OutputAttributePtr distance_attribute = component.attribute_try_get_for_output( - distance_attribute_name, result_domain, CD_PROP_FLOAT); + OutputAttribute_Typed<float> distance_attribute = + component.attribute_try_get_for_output_only<float>(distance_attribute_name, result_domain); const std::string location_attribute_name = params.get_input<std::string>("Position"); - OutputAttributePtr location_attribute = component.attribute_try_get_for_output( - location_attribute_name, result_domain, CD_PROP_FLOAT3); - - ReadAttributePtr position_attribute = component.attribute_try_get_for_read("position"); - BLI_assert(position_attribute->custom_data_type() == CD_PROP_FLOAT3); + OutputAttribute_Typed<float3> location_attribute = + component.attribute_try_get_for_output_only<float3>(location_attribute_name, result_domain); + ReadAttributeLookup position_attribute = component.attribute_try_get_for_read("position"); if (!position_attribute || (!distance_attribute && !location_attribute)) { return; } + BLI_assert(position_attribute.varray->type().is<float3>()); const bNode &node = params.node(); const NodeGeometryAttributeProximity &storage = *(const NodeGeometryAttributeProximity *) @@ -204,18 +203,15 @@ static void attribute_calc_proximity(GeometryComponent &component, tree_data_pointcloud); } - Span<float3> position_span = position_attribute->get_span<float3>(); - - MutableSpan<float> distance_span = distance_attribute ? - distance_attribute->get_span_for_write_only<float>() : - MutableSpan<float>(); - MutableSpan<float3> location_span = location_attribute ? - location_attribute->get_span_for_write_only<float3>() : - MutableSpan<float3>(); + GVArray_Typed<float3> positions{*position_attribute.varray}; + MutableSpan<float> distance_span = distance_attribute ? distance_attribute.as_span() : + MutableSpan<float>(); + MutableSpan<float3> location_span = location_attribute ? location_attribute.as_span() : + MutableSpan<float3>(); proximity_calc(distance_span, location_span, - position_span, + positions, tree_data_mesh, tree_data_pointcloud, bvh_mesh_success, @@ -231,10 +227,10 @@ static void attribute_calc_proximity(GeometryComponent &component, } if (distance_attribute) { - distance_attribute.apply_span_and_save(); + distance_attribute.save(); } if (location_attribute) { - location_attribute.apply_span_and_save(); + location_attribute.save(); } } @@ -257,6 +253,10 @@ static void geo_node_attribute_proximity_exec(GeoNodeExecParams params) attribute_calc_proximity( geometry_set.get_component_for_write<PointCloudComponent>(), geometry_set_target, params); } + if (geometry_set.has<CurveComponent>()) { + attribute_calc_proximity( + geometry_set.get_component_for_write<CurveComponent>(), geometry_set_target, params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc index 28263287a10..286411b7d28 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc @@ -16,6 +16,7 @@ #include "BLI_hash.h" #include "BLI_rand.hh" +#include "BLI_task.hh" #include "UI_interface.h" #include "UI_resources.h" @@ -125,28 +126,36 @@ static void randomize_attribute(MutableSpan<T> span, /* The operations could be templated too, but it doesn't make the code much shorter. */ switch (operation) { case GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE: - for (const int i : span.index_range()) { - const T random_value = random_value_in_range<T>(ids[i], seed, min, max); - span[i] = random_value; - } + parallel_for(span.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + const T random_value = random_value_in_range<T>(ids[i], seed, min, max); + span[i] = random_value; + } + }); break; case GEO_NODE_ATTRIBUTE_RANDOMIZE_ADD: - for (const int i : span.index_range()) { - const T random_value = random_value_in_range<T>(ids[i], seed, min, max); - span[i] = span[i] + random_value; - } + parallel_for(span.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + const T random_value = random_value_in_range<T>(ids[i], seed, min, max); + span[i] = span[i] + random_value; + } + }); break; case GEO_NODE_ATTRIBUTE_RANDOMIZE_SUBTRACT: - for (const int i : span.index_range()) { - const T random_value = random_value_in_range<T>(ids[i], seed, min, max); - span[i] = span[i] - random_value; - } + parallel_for(span.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + const T random_value = random_value_in_range<T>(ids[i], seed, min, max); + span[i] = span[i] - random_value; + } + }); break; case GEO_NODE_ATTRIBUTE_RANDOMIZE_MULTIPLY: - for (const int i : span.index_range()) { - const T random_value = random_value_in_range<T>(ids[i], seed, min, max); - span[i] = span[i] * random_value; - } + parallel_for(span.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + const T random_value = random_value_in_range<T>(ids[i], seed, min, max); + span[i] = span[i] * random_value; + } + }); break; default: BLI_assert(false); @@ -161,10 +170,12 @@ static void randomize_attribute_bool(MutableSpan<bool> span, { BLI_assert(operation == GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE); UNUSED_VARS_NDEBUG(operation); - for (const int i : span.index_range()) { - const bool random_value = BLI_hash_int_2d_to_float(ids[i], seed) > 0.5f; - span[i] = random_value; - } + parallel_for(span.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + const bool random_value = BLI_hash_int_2d_to_float(ids[i], seed) > 0.5f; + span[i] = random_value; + } + }); } Array<uint32_t> get_geometry_element_ids_as_uints(const GeometryComponent &component, @@ -173,15 +184,17 @@ Array<uint32_t> get_geometry_element_ids_as_uints(const GeometryComponent &compo const int domain_size = component.attribute_domain_size(domain); /* Hash the reserved name attribute "id" as a (hopefully) stable seed for each point. */ - ReadAttributePtr hash_attribute = component.attribute_try_get_for_read("id", domain); + GVArrayPtr hash_attribute = component.attribute_try_get_for_read("id", domain); Array<uint32_t> hashes(domain_size); if (hash_attribute) { BLI_assert(hashes.size() == hash_attribute->size()); - const CPPType &cpp_type = hash_attribute->cpp_type(); - fn::GSpan items = hash_attribute->get_span(); - for (const int i : hashes.index_range()) { - hashes[i] = cpp_type.hash(items[i]); - } + const CPPType &cpp_type = hash_attribute->type(); + GVArray_GSpan items{*hash_attribute}; + parallel_for(hashes.index_range(), 512, [&](IndexRange range) { + for (const int i : range) { + hashes[i] = cpp_type.hash(items[i]); + } + }); } else { /* If there is no "id" attribute for per-point variation, just create it here. */ @@ -196,12 +209,12 @@ Array<uint32_t> get_geometry_element_ids_as_uints(const GeometryComponent &compo static AttributeDomain get_result_domain(const GeometryComponent &component, const GeoNodeExecParams ¶ms, - StringRef attribute_name) + const StringRef name) { /* Use the domain of the result attribute if it already exists. */ - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(attribute_name); - if (result_attribute) { - return result_attribute->domain(); + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(name); + if (result_info) { + return result_info->domain; } /* Otherwise use the input domain chosen in the interface. */ @@ -228,15 +241,13 @@ static void randomize_attribute_on_component(GeometryComponent &component, const AttributeDomain domain = get_result_domain(component, params, attribute_name); - OutputAttributePtr attribute = component.attribute_try_get_for_output( + OutputAttribute attribute = component.attribute_try_get_for_output( attribute_name, domain, data_type); if (!attribute) { return; } - fn::GMutableSpan span = (operation == GEO_NODE_ATTRIBUTE_RANDOMIZE_REPLACE_CREATE) ? - attribute->get_span_for_write_only() : - attribute->get_span(); + GMutableSpan span = attribute.as_span(); Array<uint32_t> hashes = get_geometry_element_ids_as_uints(component, domain); @@ -269,8 +280,8 @@ static void randomize_attribute_on_component(GeometryComponent &component, } } - attribute.apply_span_and_save(); -} // namespace blender::nodes + attribute.save(); +} static void geo_node_random_attribute_exec(GeoNodeExecParams params) { @@ -304,6 +315,14 @@ static void geo_node_random_attribute_exec(GeoNodeExecParams params) operation, seed); } + if (geometry_set.has<CurveComponent>()) { + randomize_attribute_on_component(geometry_set.get_component_for_write<CurveComponent>(), + params, + attribute_name, + data_type, + operation, + seed); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc index 837f0c3629a..e4f3230ebb9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc @@ -70,6 +70,10 @@ static void geo_node_attribute_remove_exec(GeoNodeExecParams params) remove_attribute( geometry_set.get_component_for_write<PointCloudComponent>(), params, attribute_names); } + if (geometry_set.has<CurveComponent>()) { + remove_attribute( + geometry_set.get_component_for_write<CurveComponent>(), params, attribute_names); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc index d0b2595b5b9..d6b1ad3e9e0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc @@ -15,6 +15,7 @@ */ #include "BLI_compiler_attrs.h" +#include "BLI_task.hh" #include "DNA_texture_types.h" @@ -29,6 +30,7 @@ static bNodeSocketTemplate geo_node_attribute_sample_texture_in[] = { {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_TEXTURE, N_("Texture")}, {SOCK_STRING, N_("Mapping")}, {SOCK_STRING, N_("Result")}, {-1, ""}, @@ -39,29 +41,22 @@ static bNodeSocketTemplate geo_node_attribute_sample_texture_out[] = { {-1, ""}, }; -static void geo_node_attribute_sample_texture_layout(uiLayout *layout, - bContext *C, - PointerRNA *ptr) -{ - uiTemplateID(layout, C, ptr, "texture", "texture.new", nullptr, nullptr, 0, ICON_NONE, nullptr); -} - namespace blender::nodes { static AttributeDomain get_result_domain(const GeometryComponent &component, - StringRef result_attribute_name, - StringRef map_attribute_name) + const StringRef result_name, + const StringRef map_name) { /* Use the domain of the result attribute if it already exists. */ - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_attribute_name); - if (result_attribute) { - return result_attribute->domain(); + std::optional<AttributeMetaData> result_info = component.attribute_get_meta_data(result_name); + if (result_info) { + return result_info->domain; } /* Otherwise use the name of the map attribute. */ - ReadAttributePtr map_attribute = component.attribute_try_get_for_read(map_attribute_name); - if (map_attribute) { - return map_attribute->domain(); + std::optional<AttributeMetaData> map_info = component.attribute_get_meta_data(map_name); + if (map_info) { + return map_info->domain; } /* The node won't execute in this case, but we still have to return a value. */ @@ -70,8 +65,7 @@ static AttributeDomain get_result_domain(const GeometryComponent &component, static void execute_on_component(GeometryComponent &component, const GeoNodeExecParams ¶ms) { - const bNode &node = params.node(); - Tex *texture = reinterpret_cast<Tex *>(node.id); + Tex *texture = params.get_input<Tex *>("Texture"); if (texture == nullptr) { return; } @@ -85,25 +79,29 @@ static void execute_on_component(GeometryComponent &component, const GeoNodeExec const AttributeDomain result_domain = get_result_domain( component, result_attribute_name, mapping_name); - OutputAttributePtr attribute_out = component.attribute_try_get_for_output( - result_attribute_name, result_domain, CD_PROP_COLOR); + OutputAttribute_Typed<ColorGeometry4f> attribute_out = + component.attribute_try_get_for_output_only<ColorGeometry4f>(result_attribute_name, + result_domain); if (!attribute_out) { return; } - Float3ReadAttribute mapping_attribute = component.attribute_get_for_read<float3>( + GVArray_Typed<float3> mapping_attribute = component.attribute_get_for_read<float3>( mapping_name, result_domain, {0, 0, 0}); - MutableSpan<Color4f> colors = attribute_out->get_span<Color4f>(); - for (const int i : IndexRange(mapping_attribute.size())) { - TexResult texture_result = {0}; - const float3 position = mapping_attribute[i]; - /* For legacy reasons we have to map [0, 1] to [-1, 1] to support uv mappings. */ - const float3 remapped_position = position * 2.0f - float3(1.0f); - BKE_texture_get_value(nullptr, texture, remapped_position, &texture_result, false); - colors[i] = {texture_result.tr, texture_result.tg, texture_result.tb, texture_result.ta}; - } - attribute_out.apply_span_and_save(); + MutableSpan<ColorGeometry4f> colors = attribute_out.as_span(); + parallel_for(IndexRange(mapping_attribute.size()), 128, [&](IndexRange range) { + for (const int i : range) { + TexResult texture_result = {0}; + const float3 position = mapping_attribute[i]; + /* For legacy reasons we have to map [0, 1] to [-1, 1] to support uv mappings. */ + const float3 remapped_position = position * 2.0f - float3(1.0f); + BKE_texture_get_value(nullptr, texture, remapped_position, &texture_result, false); + colors[i] = {texture_result.tr, texture_result.tg, texture_result.tb, texture_result.ta}; + } + }); + + attribute_out.save(); } static void geo_node_attribute_sample_texture_exec(GeoNodeExecParams params) @@ -118,6 +116,9 @@ static void geo_node_attribute_sample_texture_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { execute_on_component(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + execute_on_component(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } @@ -137,6 +138,5 @@ void register_node_type_geo_sample_texture() node_type_socket_templates( &ntype, geo_node_attribute_sample_texture_in, geo_node_attribute_sample_texture_out); ntype.geometry_node_execute = blender::nodes::geo_node_attribute_sample_texture_exec; - ntype.draw_buttons = geo_node_attribute_sample_texture_layout; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc index 656dc51149e..137a72bb707 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc @@ -71,23 +71,23 @@ static void extract_input(const int index, const Span<float3> &input, MutableSpa static AttributeDomain get_result_domain(const GeometryComponent &component, const GeoNodeExecParams ¶ms, - StringRef result_name_x, - StringRef result_name_y, - StringRef result_name_z) + const StringRef name_x, + const StringRef name_y, + const StringRef name_z) { /* Use the highest priority domain from any existing attribute outputs. */ Vector<AttributeDomain, 3> output_domains; - ReadAttributePtr attribute_x = component.attribute_try_get_for_read(result_name_x); - ReadAttributePtr attribute_y = component.attribute_try_get_for_read(result_name_y); - ReadAttributePtr attribute_z = component.attribute_try_get_for_read(result_name_z); - if (attribute_x) { - output_domains.append(attribute_x->domain()); + std::optional<AttributeMetaData> info_x = component.attribute_get_meta_data(name_x); + std::optional<AttributeMetaData> info_y = component.attribute_get_meta_data(name_y); + std::optional<AttributeMetaData> info_z = component.attribute_get_meta_data(name_z); + if (info_x) { + output_domains.append(info_x->domain); } - if (attribute_y) { - output_domains.append(attribute_y->domain()); + if (info_y) { + output_domains.append(info_y->domain); } - if (attribute_z) { - output_domains.append(attribute_z->domain()); + if (info_z) { + output_domains.append(info_z->domain); } if (output_domains.size() > 0) { return bke::attribute_domain_highest_priority(output_domains); @@ -107,37 +107,32 @@ static void separate_attribute(GeometryComponent &component, const GeoNodeExecPa } /* The node is only for float3 to float conversions. */ - const CustomDataType input_type = CD_PROP_FLOAT3; - const CustomDataType result_type = CD_PROP_FLOAT; const AttributeDomain result_domain = get_result_domain( component, params, result_name_x, result_name_y, result_name_z); - ReadAttributePtr attribute_input = params.get_input_attribute( - "Vector", component, result_domain, input_type, nullptr); - if (!attribute_input) { - return; - } - const Span<float3> input_span = attribute_input->get_span<float3>(); + GVArray_Typed<float3> attribute_input = params.get_input_attribute<float3>( + "Vector", component, result_domain, {0, 0, 0}); + VArray_Span<float3> input_span{*attribute_input}; - OutputAttributePtr attribute_result_x = component.attribute_try_get_for_output( - result_name_x, result_domain, result_type); - OutputAttributePtr attribute_result_y = component.attribute_try_get_for_output( - result_name_y, result_domain, result_type); - OutputAttributePtr attribute_result_z = component.attribute_try_get_for_output( - result_name_z, result_domain, result_type); + OutputAttribute_Typed<float> attribute_result_x = + component.attribute_try_get_for_output_only<float>(result_name_x, result_domain); + OutputAttribute_Typed<float> attribute_result_y = + component.attribute_try_get_for_output_only<float>(result_name_y, result_domain); + OutputAttribute_Typed<float> attribute_result_z = + component.attribute_try_get_for_output_only<float>(result_name_z, result_domain); /* Only extract the components for the outputs with a given attribute. */ if (attribute_result_x) { - extract_input(0, input_span, attribute_result_x->get_span_for_write_only<float>()); - attribute_result_x.apply_span_and_save(); + extract_input(0, input_span, attribute_result_x.as_span()); + attribute_result_x.save(); } if (attribute_result_y) { - extract_input(1, input_span, attribute_result_y->get_span_for_write_only<float>()); - attribute_result_y.apply_span_and_save(); + extract_input(1, input_span, attribute_result_y.as_span()); + attribute_result_y.save(); } if (attribute_result_z) { - extract_input(2, input_span, attribute_result_z->get_span_for_write_only<float>()); - attribute_result_z.apply_span_and_save(); + extract_input(2, input_span, attribute_result_z.as_span()); + attribute_result_z.save(); } } @@ -153,6 +148,9 @@ static void geo_node_attribute_separate_xyz_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { separate_attribute(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + separate_attribute(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc new file mode 100644 index 00000000000..4b677dc5c82 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc @@ -0,0 +1,597 @@ +/* + * 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_kdopbvh.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_pointcloud_types.h" + +#include "BKE_bvhutils.h" +#include "BKE_mesh_runtime.h" +#include "BKE_mesh_sample.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_attribute_transfer_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_GEOMETRY, N_("Source Geometry")}, + {SOCK_STRING, N_("Source")}, + {SOCK_STRING, N_("Destination")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_attribute_transfer_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_attribute_transfer_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiItemR(layout, ptr, "domain", 0, IFACE_("Domain"), ICON_NONE); + uiItemR(layout, ptr, "mapping", 0, IFACE_("Mapping"), ICON_NONE); +} + +namespace blender::nodes { + +static void geo_node_attribute_transfer_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryAttributeTransfer *data = (NodeGeometryAttributeTransfer *)MEM_callocN( + sizeof(NodeGeometryAttributeTransfer), __func__); + data->domain = ATTR_DOMAIN_AUTO; + node->storage = data; +} + +static void get_result_domain_and_data_type(const GeometrySet &src_geometry, + const GeometryComponent &dst_component, + const StringRef attribute_name, + CustomDataType *r_data_type, + AttributeDomain *r_domain) +{ + Vector<CustomDataType> data_types; + Vector<AttributeDomain> domains; + + const PointCloudComponent *pointcloud_component = + src_geometry.get_component_for_read<PointCloudComponent>(); + if (pointcloud_component != nullptr) { + std::optional<AttributeMetaData> meta_data = pointcloud_component->attribute_get_meta_data( + attribute_name); + if (meta_data.has_value()) { + data_types.append(meta_data->data_type); + domains.append(meta_data->domain); + } + } + + const MeshComponent *mesh_component = src_geometry.get_component_for_read<MeshComponent>(); + if (mesh_component != nullptr) { + std::optional<AttributeMetaData> meta_data = mesh_component->attribute_get_meta_data( + attribute_name); + if (meta_data.has_value()) { + data_types.append(meta_data->data_type); + domains.append(meta_data->domain); + } + } + + *r_data_type = bke::attribute_data_type_highest_complexity(data_types); + + if (dst_component.type() == GEO_COMPONENT_TYPE_POINT_CLOUD) { + *r_domain = ATTR_DOMAIN_POINT; + } + else { + *r_domain = bke::attribute_domain_highest_priority(domains); + } +} + +static Span<MLoopTri> get_mesh_looptris(const Mesh &mesh) +{ + /* This only updates a cache and can be considered to be logically const. */ + const MLoopTri *looptris = BKE_mesh_runtime_looptri_ensure(const_cast<Mesh *>(&mesh)); + const int looptris_len = BKE_mesh_runtime_looptri_len(&mesh); + return {looptris, looptris_len}; +} + +static void get_closest_in_bvhtree(BVHTreeFromMesh &tree_data, + const VArray<float3> &positions, + const MutableSpan<int> r_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + BLI_assert(positions.size() == r_indices.size() || r_indices.is_empty()); + BLI_assert(positions.size() == r_distances_sq.size() || r_distances_sq.is_empty()); + BLI_assert(positions.size() == r_positions.size() || r_positions.is_empty()); + + for (const int i : positions.index_range()) { + BVHTreeNearest nearest; + nearest.dist_sq = FLT_MAX; + const float3 position = positions[i]; + BLI_bvhtree_find_nearest( + tree_data.tree, position, &nearest, tree_data.nearest_callback, &tree_data); + if (!r_indices.is_empty()) { + r_indices[i] = nearest.index; + } + if (!r_distances_sq.is_empty()) { + r_distances_sq[i] = nearest.dist_sq; + } + if (!r_positions.is_empty()) { + r_positions[i] = nearest.co; + } + } +} + +static void get_closest_pointcloud_points(const PointCloud &pointcloud, + const VArray<float3> &positions, + const MutableSpan<int> r_indices, + const MutableSpan<float> r_distances_sq) +{ + BLI_assert(positions.size() == r_indices.size()); + BLI_assert(pointcloud.totpoint > 0); + + BVHTreeFromPointCloud tree_data; + BKE_bvhtree_from_pointcloud_get(&tree_data, &pointcloud, 2); + + for (const int i : positions.index_range()) { + BVHTreeNearest nearest; + nearest.dist_sq = FLT_MAX; + const float3 position = positions[i]; + BLI_bvhtree_find_nearest( + tree_data.tree, position, &nearest, tree_data.nearest_callback, &tree_data); + r_indices[i] = nearest.index; + r_distances_sq[i] = nearest.dist_sq; + } + + free_bvhtree_from_pointcloud(&tree_data); +} + +static void get_closest_mesh_points(const Mesh &mesh, + const VArray<float3> &positions, + const MutableSpan<int> r_point_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + BLI_assert(mesh.totvert > 0); + BVHTreeFromMesh tree_data; + BKE_bvhtree_from_mesh_get(&tree_data, const_cast<Mesh *>(&mesh), BVHTREE_FROM_VERTS, 2); + get_closest_in_bvhtree(tree_data, positions, r_point_indices, r_distances_sq, r_positions); + free_bvhtree_from_mesh(&tree_data); +} + +static void get_closest_mesh_edges(const Mesh &mesh, + const VArray<float3> &positions, + const MutableSpan<int> r_edge_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + BLI_assert(mesh.totedge > 0); + BVHTreeFromMesh tree_data; + BKE_bvhtree_from_mesh_get(&tree_data, const_cast<Mesh *>(&mesh), BVHTREE_FROM_EDGES, 2); + get_closest_in_bvhtree(tree_data, positions, r_edge_indices, r_distances_sq, r_positions); + free_bvhtree_from_mesh(&tree_data); +} + +static void get_closest_mesh_looptris(const Mesh &mesh, + const VArray<float3> &positions, + const MutableSpan<int> r_looptri_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + BLI_assert(mesh.totpoly > 0); + BVHTreeFromMesh tree_data; + BKE_bvhtree_from_mesh_get(&tree_data, const_cast<Mesh *>(&mesh), BVHTREE_FROM_LOOPTRI, 2); + get_closest_in_bvhtree(tree_data, positions, r_looptri_indices, r_distances_sq, r_positions); + free_bvhtree_from_mesh(&tree_data); +} + +static void get_closest_mesh_polygons(const Mesh &mesh, + const VArray<float3> &positions, + const MutableSpan<int> r_poly_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + BLI_assert(mesh.totpoly > 0); + + Array<int> looptri_indices(positions.size()); + get_closest_mesh_looptris(mesh, positions, looptri_indices, r_distances_sq, r_positions); + + Span<MLoopTri> looptris = get_mesh_looptris(mesh); + for (const int i : positions.index_range()) { + const MLoopTri &looptri = looptris[looptri_indices[i]]; + r_poly_indices[i] = looptri.poly; + } +} + +/* The closest corner is defined to be the closest corner on the closest face. */ +static void get_closest_mesh_corners(const Mesh &mesh, + const VArray<float3> &positions, + const MutableSpan<int> r_corner_indices, + const MutableSpan<float> r_distances_sq, + const MutableSpan<float3> r_positions) +{ + BLI_assert(mesh.totloop > 0); + Array<int> poly_indices(positions.size()); + get_closest_mesh_polygons(mesh, positions, poly_indices, {}, {}); + + for (const int i : positions.index_range()) { + const float3 position = positions[i]; + const int poly_index = poly_indices[i]; + const MPoly &poly = mesh.mpoly[poly_index]; + + /* Find the closest vertex in the polygon. */ + float min_distance_sq = FLT_MAX; + const MVert *closest_mvert; + int closest_loop_index = 0; + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const MLoop &loop = mesh.mloop[loop_index]; + const int vertex_index = loop.v; + const MVert &mvert = mesh.mvert[vertex_index]; + const float distance_sq = float3::distance_squared(position, mvert.co); + if (distance_sq < min_distance_sq) { + min_distance_sq = distance_sq; + closest_loop_index = loop_index; + closest_mvert = &mvert; + } + } + if (!r_corner_indices.is_empty()) { + r_corner_indices[i] = closest_loop_index; + } + if (!r_positions.is_empty()) { + r_positions[i] = closest_mvert->co; + } + if (!r_distances_sq.is_empty()) { + r_distances_sq[i] = min_distance_sq; + } + } +} + +static void get_barycentric_coords(const Mesh &mesh, + const Span<int> looptri_indices, + const Span<float3> positions, + const MutableSpan<float3> r_bary_coords) +{ + BLI_assert(r_bary_coords.size() == positions.size()); + BLI_assert(r_bary_coords.size() == looptri_indices.size()); + + Span<MLoopTri> looptris = get_mesh_looptris(mesh); + + for (const int i : r_bary_coords.index_range()) { + const int looptri_index = looptri_indices[i]; + const MLoopTri &looptri = looptris[looptri_index]; + + const int v0_index = mesh.mloop[looptri.tri[0]].v; + const int v1_index = mesh.mloop[looptri.tri[1]].v; + const int v2_index = mesh.mloop[looptri.tri[2]].v; + + interp_weights_tri_v3(r_bary_coords[i], + mesh.mvert[v0_index].co, + mesh.mvert[v1_index].co, + mesh.mvert[v2_index].co, + positions[i]); + } +} + +static void transfer_attribute_nearest_face_interpolated(const GeometrySet &src_geometry, + GeometryComponent &dst_component, + const VArray<float3> &dst_positions, + const AttributeDomain dst_domain, + const CustomDataType data_type, + const StringRef src_name, + const StringRef dst_name) +{ + const int tot_samples = dst_positions.size(); + const MeshComponent *component = src_geometry.get_component_for_read<MeshComponent>(); + if (component == nullptr) { + return; + } + const Mesh *mesh = component->get_for_read(); + if (mesh == nullptr) { + return; + } + if (mesh->totpoly == 0) { + return; + } + ReadAttributeLookup src_attribute = component->attribute_try_get_for_read(src_name, data_type); + if (!src_attribute) { + return; + } + + /* Find closest points on the mesh surface. */ + Array<int> looptri_indices(tot_samples); + Array<float3> positions(tot_samples); + get_closest_mesh_looptris(*mesh, dst_positions, looptri_indices, {}, positions); + + OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( + dst_name, dst_domain, data_type); + if (!dst_attribute) { + return; + } + GMutableSpan dst_span = dst_attribute.as_span(); + Array<float3> bary_coords; + + /* Compute barycentric coordinates only when they are needed. */ + if (src_attribute.domain != ATTR_DOMAIN_FACE) { + bary_coords.reinitialize(tot_samples); + get_barycentric_coords(*mesh, looptri_indices, positions, bary_coords); + } + /* Interpolate the source attribute on the surface. */ + switch (src_attribute.domain) { + case ATTR_DOMAIN_POINT: { + bke::mesh_surface_sample::sample_point_attribute( + *mesh, looptri_indices, bary_coords, *src_attribute.varray, dst_span); + break; + } + case ATTR_DOMAIN_FACE: { + bke::mesh_surface_sample::sample_face_attribute( + *mesh, looptri_indices, *src_attribute.varray, dst_span); + break; + } + case ATTR_DOMAIN_CORNER: { + bke::mesh_surface_sample::sample_corner_attribute( + *mesh, looptri_indices, bary_coords, *src_attribute.varray, dst_span); + break; + } + case ATTR_DOMAIN_EDGE: { + /* Not yet supported. */ + break; + } + default: { + BLI_assert_unreachable(); + break; + } + } + dst_attribute.save(); +} + +static void transfer_attribute_nearest(const GeometrySet &src_geometry, + GeometryComponent &dst_component, + const VArray<float3> &dst_positions, + const AttributeDomain dst_domain, + const CustomDataType data_type, + const StringRef src_name, + const StringRef dst_name) +{ + const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type); + + /* Get pointcloud data from geometry. */ + const PointCloudComponent *pointcloud_component = + src_geometry.get_component_for_read<PointCloudComponent>(); + const PointCloud *pointcloud = pointcloud_component ? pointcloud_component->get_for_read() : + nullptr; + + /* Get mesh data from geometry. */ + const MeshComponent *mesh_component = src_geometry.get_component_for_read<MeshComponent>(); + const Mesh *mesh = mesh_component ? mesh_component->get_for_read() : nullptr; + + const int tot_samples = dst_positions.size(); + + Array<int> pointcloud_indices; + Array<float> pointcloud_distances_sq; + bool use_pointcloud = false; + + /* Depending on where what domain the source attribute lives, these indices are either vertex, + * corner, edge or polygon indices. */ + Array<int> mesh_indices; + Array<float> mesh_distances_sq; + bool use_mesh = false; + + /* If there is a pointcloud, find the closest points. */ + if (pointcloud != nullptr && pointcloud->totpoint > 0) { + if (pointcloud_component->attribute_exists(src_name)) { + use_pointcloud = true; + pointcloud_indices.reinitialize(tot_samples); + pointcloud_distances_sq.reinitialize(tot_samples); + get_closest_pointcloud_points( + *pointcloud, dst_positions, pointcloud_indices, pointcloud_distances_sq); + } + } + + /* If there is a mesh, find the closest mesh elements. */ + if (mesh != nullptr) { + ReadAttributeLookup src_attribute = mesh_component->attribute_try_get_for_read(src_name); + if (src_attribute) { + switch (src_attribute.domain) { + case ATTR_DOMAIN_POINT: { + if (mesh->totvert > 0) { + use_mesh = true; + mesh_indices.reinitialize(tot_samples); + mesh_distances_sq.reinitialize(tot_samples); + get_closest_mesh_points(*mesh, dst_positions, mesh_indices, mesh_distances_sq, {}); + } + break; + } + case ATTR_DOMAIN_EDGE: { + if (mesh->totedge > 0) { + use_mesh = true; + mesh_indices.reinitialize(tot_samples); + mesh_distances_sq.reinitialize(tot_samples); + get_closest_mesh_edges(*mesh, dst_positions, mesh_indices, mesh_distances_sq, {}); + } + break; + } + case ATTR_DOMAIN_FACE: { + if (mesh->totpoly > 0) { + use_mesh = true; + mesh_indices.reinitialize(tot_samples); + mesh_distances_sq.reinitialize(tot_samples); + get_closest_mesh_polygons(*mesh, dst_positions, mesh_indices, mesh_distances_sq, {}); + } + break; + } + case ATTR_DOMAIN_CORNER: { + use_mesh = true; + mesh_indices.reinitialize(tot_samples); + mesh_distances_sq.reinitialize(tot_samples); + get_closest_mesh_corners(*mesh, dst_positions, mesh_indices, mesh_distances_sq, {}); + break; + } + default: { + break; + } + } + } + } + + if (!use_pointcloud && !use_mesh) { + return; + } + + OutputAttribute dst_attribute = dst_component.attribute_try_get_for_output_only( + dst_name, dst_domain, data_type); + if (!dst_attribute) { + return; + } + + /* Create a buffer for intermediate values. */ + BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); + + if (use_mesh && use_pointcloud) { + /* When there is a mesh and a pointcloud, we still have to check whether a pointcloud point or + * a mesh element is closer to every point. */ + ReadAttributeLookup pointcloud_src_attribute = + pointcloud_component->attribute_try_get_for_read(src_name, data_type); + ReadAttributeLookup mesh_src_attribute = mesh_component->attribute_try_get_for_read(src_name, + data_type); + for (const int i : IndexRange(tot_samples)) { + if (pointcloud_distances_sq[i] < mesh_distances_sq[i]) { + /* Pointcloud point is closer. */ + const int index = pointcloud_indices[i]; + pointcloud_src_attribute.varray->get(index, buffer); + dst_attribute->set_by_relocate(i, buffer); + } + else { + /* Mesh element is closer. */ + const int index = mesh_indices[i]; + mesh_src_attribute.varray->get(index, buffer); + dst_attribute->set_by_relocate(i, buffer); + } + } + } + else if (use_pointcloud) { + /* The source geometry only has a pointcloud. */ + ReadAttributeLookup src_attribute = pointcloud_component->attribute_try_get_for_read( + src_name, data_type); + for (const int i : IndexRange(tot_samples)) { + const int index = pointcloud_indices[i]; + src_attribute.varray->get(index, buffer); + dst_attribute->set_by_relocate(i, buffer); + } + } + else if (use_mesh) { + /* The source geometry only has a mesh. */ + ReadAttributeLookup src_attribute = mesh_component->attribute_try_get_for_read(src_name, + data_type); + for (const int i : IndexRange(tot_samples)) { + const int index = mesh_indices[i]; + src_attribute.varray->get(index, buffer); + dst_attribute->set_by_relocate(i, buffer); + } + } + + dst_attribute.save(); +} + +static void transfer_attribute(const GeoNodeExecParams ¶ms, + const GeometrySet &src_geometry, + GeometryComponent &dst_component, + const StringRef src_name, + const StringRef dst_name) +{ + const NodeGeometryAttributeTransfer &storage = + *(const NodeGeometryAttributeTransfer *)params.node().storage; + const GeometryNodeAttributeTransferMapMode mapping = (GeometryNodeAttributeTransferMapMode) + storage.mapping; + const AttributeDomain input_domain = (AttributeDomain)storage.domain; + + CustomDataType data_type; + AttributeDomain auto_domain; + get_result_domain_and_data_type(src_geometry, dst_component, src_name, &data_type, &auto_domain); + const AttributeDomain dst_domain = (input_domain == ATTR_DOMAIN_AUTO) ? auto_domain : + input_domain; + + GVArray_Typed<float3> dst_positions = dst_component.attribute_get_for_read<float3>( + "position", dst_domain, {0, 0, 0}); + + switch (mapping) { + case GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST_FACE_INTERPOLATED: { + transfer_attribute_nearest_face_interpolated( + src_geometry, dst_component, dst_positions, dst_domain, data_type, src_name, dst_name); + break; + } + case GEO_NODE_ATTRIBUTE_TRANSFER_NEAREST: { + transfer_attribute_nearest( + src_geometry, dst_component, dst_positions, dst_domain, data_type, src_name, dst_name); + break; + } + } +} + +static void geo_node_attribute_transfer_exec(GeoNodeExecParams params) +{ + GeometrySet dst_geometry_set = params.extract_input<GeometrySet>("Geometry"); + GeometrySet src_geometry_set = params.extract_input<GeometrySet>("Source Geometry"); + const std::string src_attribute_name = params.extract_input<std::string>("Source"); + const std::string dst_attribute_name = params.extract_input<std::string>("Destination"); + + if (src_attribute_name.empty() || dst_attribute_name.empty()) { + params.set_output("Geometry", dst_geometry_set); + return; + } + + dst_geometry_set = bke::geometry_set_realize_instances(dst_geometry_set); + src_geometry_set = bke::geometry_set_realize_instances(src_geometry_set); + + if (dst_geometry_set.has<MeshComponent>()) { + transfer_attribute(params, + src_geometry_set, + dst_geometry_set.get_component_for_write<MeshComponent>(), + src_attribute_name, + dst_attribute_name); + } + if (dst_geometry_set.has<PointCloudComponent>()) { + transfer_attribute(params, + src_geometry_set, + dst_geometry_set.get_component_for_write<PointCloudComponent>(), + src_attribute_name, + dst_attribute_name); + } + + params.set_output("Geometry", dst_geometry_set); +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_transfer() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_ATTRIBUTE_TRANSFER, "Attribute Transfer", NODE_CLASS_ATTRIBUTE, 0); + node_type_socket_templates( + &ntype, geo_node_attribute_transfer_in, geo_node_attribute_transfer_out); + node_type_init(&ntype, blender::nodes::geo_node_attribute_transfer_init); + node_type_storage(&ntype, + "NodeGeometryAttributeTransfer", + node_free_standard_storage, + node_copy_standard_storage); + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_transfer_exec; + ntype.draw_buttons = geo_node_attribute_transfer_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc index 1ae095a27d2..8877af445f9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc @@ -15,6 +15,7 @@ */ #include "BLI_math_base_safe.h" +#include "BLI_task.hh" #include "UI_interface.h" #include "UI_resources.h" @@ -168,196 +169,210 @@ static void geo_node_attribute_vector_math_update(bNodeTree *UNUSED(ntree), bNod operation_use_input_c(operation)); } -static void do_math_operation_fl3_fl3_to_fl3(const Float3ReadAttribute &input_a, - const Float3ReadAttribute &input_b, - Float3WriteAttribute result, +static void do_math_operation_fl3_fl3_to_fl3(const VArray<float3> &input_a, + const VArray<float3> &input_b, + VMutableArray<float3> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); - Span<float3> span_a = input_a.get_span(); - Span<float3> span_b = input_b.get_span(); - MutableSpan<float3> span_result = result.get_span_for_write_only(); + VArray_Span<float3> span_a{input_a}; + VArray_Span<float3> span_b{input_b}; + VMutableArray_Span<float3> span_result{result, false}; bool success = try_dispatch_float_math_fl3_fl3_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 a = span_a[i]; - const float3 b = span_b[i]; - const float3 out = math_function(a, b); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 a = span_a[i]; + const float3 b = span_b[i]; + const float3 out = math_function(a, b); + span_result[i] = out; + } + }); }); - result.apply_span(); + span_result.save(); /* The operation is not supported by this node currently. */ BLI_assert(success); UNUSED_VARS_NDEBUG(success); } -static void do_math_operation_fl3_fl3_fl3_to_fl3(const Float3ReadAttribute &input_a, - const Float3ReadAttribute &input_b, - const Float3ReadAttribute &input_c, - Float3WriteAttribute result, +static void do_math_operation_fl3_fl3_fl3_to_fl3(const VArray<float3> &input_a, + const VArray<float3> &input_b, + const VArray<float3> &input_c, + VMutableArray<float3> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); - Span<float3> span_a = input_a.get_span(); - Span<float3> span_b = input_b.get_span(); - Span<float3> span_c = input_c.get_span(); - MutableSpan<float3> span_result = result.get_span_for_write_only(); + VArray_Span<float3> span_a{input_a}; + VArray_Span<float3> span_b{input_b}; + VArray_Span<float3> span_c{input_c}; + VMutableArray_Span<float3> span_result{result}; bool success = try_dispatch_float_math_fl3_fl3_fl3_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 a = span_a[i]; - const float3 b = span_b[i]; - const float3 c = span_c[i]; - const float3 out = math_function(a, b, c); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 a = span_a[i]; + const float3 b = span_b[i]; + const float3 c = span_c[i]; + const float3 out = math_function(a, b, c); + span_result[i] = out; + } + }); }); - result.apply_span(); + span_result.save(); /* The operation is not supported by this node currently. */ BLI_assert(success); UNUSED_VARS_NDEBUG(success); } -static void do_math_operation_fl3_fl3_fl_to_fl3(const Float3ReadAttribute &input_a, - const Float3ReadAttribute &input_b, - const FloatReadAttribute &input_c, - Float3WriteAttribute result, +static void do_math_operation_fl3_fl3_fl_to_fl3(const VArray<float3> &input_a, + const VArray<float3> &input_b, + const VArray<float> &input_c, + VMutableArray<float3> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); - Span<float3> span_a = input_a.get_span(); - Span<float3> span_b = input_b.get_span(); - Span<float> span_c = input_c.get_span(); - MutableSpan<float3> span_result = result.get_span_for_write_only(); + VArray_Span<float3> span_a{input_a}; + VArray_Span<float3> span_b{input_b}; + VArray_Span<float> span_c{input_c}; + VMutableArray_Span<float3> span_result{result, false}; bool success = try_dispatch_float_math_fl3_fl3_fl_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 a = span_a[i]; - const float3 b = span_b[i]; - const float c = span_c[i]; - const float3 out = math_function(a, b, c); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 a = span_a[i]; + const float3 b = span_b[i]; + const float c = span_c[i]; + const float3 out = math_function(a, b, c); + span_result[i] = out; + } + }); }); - result.apply_span(); + span_result.save(); /* The operation is not supported by this node currently. */ BLI_assert(success); UNUSED_VARS_NDEBUG(success); } -static void do_math_operation_fl3_fl3_to_fl(const Float3ReadAttribute &input_a, - const Float3ReadAttribute &input_b, - FloatWriteAttribute result, +static void do_math_operation_fl3_fl3_to_fl(const VArray<float3> &input_a, + const VArray<float3> &input_b, + VMutableArray<float> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); - Span<float3> span_a = input_a.get_span(); - Span<float3> span_b = input_b.get_span(); - MutableSpan<float> span_result = result.get_span_for_write_only(); + VArray_Span<float3> span_a{input_a}; + VArray_Span<float3> span_b{input_b}; + VMutableArray_Span<float> span_result{result, false}; bool success = try_dispatch_float_math_fl3_fl3_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 a = span_a[i]; - const float3 b = span_b[i]; - const float out = math_function(a, b); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 a = span_a[i]; + const float3 b = span_b[i]; + const float out = math_function(a, b); + span_result[i] = out; + } + }); }); - result.apply_span(); + span_result.save(); /* The operation is not supported by this node currently. */ BLI_assert(success); UNUSED_VARS_NDEBUG(success); } -static void do_math_operation_fl3_fl_to_fl3(const Float3ReadAttribute &input_a, - const FloatReadAttribute &input_b, - Float3WriteAttribute result, +static void do_math_operation_fl3_fl_to_fl3(const VArray<float3> &input_a, + const VArray<float> &input_b, + VMutableArray<float3> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); - Span<float3> span_a = input_a.get_span(); - Span<float> span_b = input_b.get_span(); - MutableSpan<float3> span_result = result.get_span_for_write_only(); + VArray_Span<float3> span_a{input_a}; + VArray_Span<float> span_b{input_b}; + VMutableArray_Span<float3> span_result{result, false}; bool success = try_dispatch_float_math_fl3_fl_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 a = span_a[i]; - const float b = span_b[i]; - const float3 out = math_function(a, b); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 a = span_a[i]; + const float b = span_b[i]; + const float3 out = math_function(a, b); + span_result[i] = out; + } + }); }); - result.apply_span(); + span_result.save(); /* The operation is not supported by this node currently. */ BLI_assert(success); UNUSED_VARS_NDEBUG(success); } -static void do_math_operation_fl3_to_fl3(const Float3ReadAttribute &input_a, - Float3WriteAttribute result, +static void do_math_operation_fl3_to_fl3(const VArray<float3> &input_a, + VMutableArray<float3> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); - Span<float3> span_a = input_a.get_span(); - MutableSpan<float3> span_result = result.get_span_for_write_only(); + VArray_Span<float3> span_a{input_a}; + VMutableArray_Span<float3> span_result{result, false}; bool success = try_dispatch_float_math_fl3_to_fl3( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 in = span_a[i]; - const float3 out = math_function(in); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 in = span_a[i]; + const float3 out = math_function(in); + span_result[i] = out; + } + }); }); - result.apply_span(); + span_result.save(); /* The operation is not supported by this node currently. */ BLI_assert(success); UNUSED_VARS_NDEBUG(success); } -static void do_math_operation_fl3_to_fl(const Float3ReadAttribute &input_a, - FloatWriteAttribute result, +static void do_math_operation_fl3_to_fl(const VArray<float3> &input_a, + VMutableArray<float> &result, const NodeVectorMathOperation operation) { const int size = input_a.size(); - Span<float3> span_a = input_a.get_span(); - MutableSpan<float> span_result = result.get_span_for_write_only(); + VArray_Span<float3> span_a{input_a}; + VMutableArray_Span<float> span_result{result, false}; bool success = try_dispatch_float_math_fl3_to_fl( operation, [&](auto math_function, const FloatMathOperationInfo &UNUSED(info)) { - for (const int i : IndexRange(size)) { - const float3 in = span_a[i]; - const float out = math_function(in); - span_result[i] = out; - } + parallel_for(IndexRange(size), 512, [&](IndexRange range) { + for (const int i : range) { + const float3 in = span_a[i]; + const float out = math_function(in); + span_result[i] = out; + } + }); }); - result.apply_span(); + span_result.save(); /* The operation is not supported by this node currently. */ BLI_assert(success); @@ -370,9 +385,9 @@ static AttributeDomain get_result_domain(const GeometryComponent &component, StringRef result_name) { /* Use the domain of the result attribute if it already exists. */ - ReadAttributePtr result_attribute = component.attribute_try_get_for_read(result_name); + ReadAttributeLookup result_attribute = component.attribute_try_get_for_read(result_name); if (result_attribute) { - return result_attribute->domain(); + return result_attribute.domain; } /* Otherwise use the highest priority domain from existing input attributes, or the default. */ @@ -406,13 +421,13 @@ static void attribute_vector_math_calc(GeometryComponent &component, const AttributeDomain result_domain = get_result_domain( component, params, operation, result_name); - ReadAttributePtr attribute_a = params.get_input_attribute( + GVArrayPtr attribute_a = params.get_input_attribute( "A", component, result_domain, read_type_a, nullptr); if (!attribute_a) { return; } - ReadAttributePtr attribute_b; - ReadAttributePtr attribute_c; + GVArrayPtr attribute_b; + GVArrayPtr attribute_c; if (use_input_b) { attribute_b = params.get_input_attribute("B", component, result_domain, read_type_b, nullptr); if (!attribute_b) { @@ -427,7 +442,7 @@ static void attribute_vector_math_calc(GeometryComponent &component, } /* Get result attribute first, in case it has to overwrite one of the existing attributes. */ - OutputAttributePtr attribute_result = component.attribute_try_get_for_output( + OutputAttribute attribute_result = component.attribute_try_get_for_output_only( result_name, result_domain, result_type); if (!attribute_result) { return; @@ -445,17 +460,27 @@ static void attribute_vector_math_calc(GeometryComponent &component, case NODE_VECTOR_MATH_MODULO: case NODE_VECTOR_MATH_MINIMUM: case NODE_VECTOR_MATH_MAXIMUM: - do_math_operation_fl3_fl3_to_fl3(*attribute_a, *attribute_b, *attribute_result, operation); + do_math_operation_fl3_fl3_to_fl3(attribute_a->typed<float3>(), + attribute_b->typed<float3>(), + attribute_result->typed<float3>(), + operation); break; case NODE_VECTOR_MATH_DOT_PRODUCT: case NODE_VECTOR_MATH_DISTANCE: - do_math_operation_fl3_fl3_to_fl(*attribute_a, *attribute_b, *attribute_result, operation); + do_math_operation_fl3_fl3_to_fl(attribute_a->typed<float3>(), + attribute_b->typed<float3>(), + attribute_result->typed<float>(), + operation); break; case NODE_VECTOR_MATH_LENGTH: - do_math_operation_fl3_to_fl(*attribute_a, *attribute_result, operation); + do_math_operation_fl3_to_fl( + attribute_a->typed<float3>(), attribute_result->typed<float>(), operation); break; case NODE_VECTOR_MATH_SCALE: - do_math_operation_fl3_fl_to_fl3(*attribute_a, *attribute_b, *attribute_result, operation); + do_math_operation_fl3_fl_to_fl3(attribute_a->typed<float3>(), + attribute_b->typed<float>(), + attribute_result->typed<float3>(), + operation); break; case NODE_VECTOR_MATH_NORMALIZE: case NODE_VECTOR_MATH_FLOOR: @@ -465,16 +490,23 @@ static void attribute_vector_math_calc(GeometryComponent &component, case NODE_VECTOR_MATH_SINE: case NODE_VECTOR_MATH_COSINE: case NODE_VECTOR_MATH_TANGENT: - do_math_operation_fl3_to_fl3(*attribute_a, *attribute_result, operation); + do_math_operation_fl3_to_fl3( + attribute_a->typed<float3>(), attribute_result->typed<float3>(), operation); break; case NODE_VECTOR_MATH_WRAP: case NODE_VECTOR_MATH_FACEFORWARD: - do_math_operation_fl3_fl3_fl3_to_fl3( - *attribute_a, *attribute_b, *attribute_c, *attribute_result, operation); + do_math_operation_fl3_fl3_fl3_to_fl3(attribute_a->typed<float3>(), + attribute_b->typed<float3>(), + attribute_c->typed<float3>(), + attribute_result->typed<float3>(), + operation); break; case NODE_VECTOR_MATH_REFRACT: - do_math_operation_fl3_fl3_fl_to_fl3( - *attribute_a, *attribute_b, *attribute_c, *attribute_result, operation); + do_math_operation_fl3_fl3_fl_to_fl3(attribute_a->typed<float3>(), + attribute_b->typed<float3>(), + attribute_c->typed<float>(), + attribute_result->typed<float3>(), + operation); break; } attribute_result.save(); @@ -493,6 +525,9 @@ static void geo_node_attribute_vector_math_exec(GeoNodeExecParams params) attribute_vector_math_calc(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + attribute_vector_math_calc(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc new file mode 100644 index 00000000000..4d568ab5c3a --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc @@ -0,0 +1,352 @@ +/* + * 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 "node_geometry_util.hh" + +#include "BLI_task.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +static bNodeSocketTemplate geo_node_attribute_vector_rotate_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_STRING, N_("Vector")}, + {SOCK_VECTOR, N_("Vector"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE, SOCK_HIDE_VALUE}, + {SOCK_STRING, N_("Center")}, + {SOCK_VECTOR, N_("Center"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_XYZ}, + {SOCK_STRING, N_("Axis")}, + {SOCK_VECTOR, N_("Axis"), 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 1.0f, PROP_XYZ, PROP_NONE}, + {SOCK_STRING, N_("Angle")}, + {SOCK_FLOAT, N_("Angle"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_ANGLE, PROP_NONE}, + {SOCK_STRING, N_("Rotation")}, + {SOCK_VECTOR, N_("Rotation"), 0.0f, 0.0f, 0.0f, 1.0f, -FLT_MAX, FLT_MAX, PROP_EULER}, + {SOCK_BOOLEAN, N_("Invert")}, + {SOCK_STRING, N_("Result")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_attribute_vector_rotate_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_attribute_vector_rotate_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + bNode *node = (bNode *)ptr->data; + const NodeAttributeVectorRotate &node_storage = *(NodeAttributeVectorRotate *)node->storage; + const GeometryNodeAttributeVectorRotateMode mode = (const GeometryNodeAttributeVectorRotateMode) + node_storage.mode; + + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiLayout *column = uiLayoutColumn(layout, false); + + uiItemR(column, ptr, "rotation_mode", 0, "", ICON_NONE); + + uiItemR(column, ptr, "input_type_vector", 0, IFACE_("Vector"), ICON_NONE); + uiItemR(column, ptr, "input_type_center", 0, IFACE_("Center"), ICON_NONE); + if (mode == GEO_NODE_VECTOR_ROTATE_TYPE_AXIS) { + uiItemR(column, ptr, "input_type_axis", 0, IFACE_("Axis"), ICON_NONE); + } + if (mode != GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ) { + uiItemR(column, ptr, "input_type_angle", 0, IFACE_("Angle"), ICON_NONE); + } + if (mode == GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ) { + uiItemR(column, ptr, "input_type_rotation", 0, IFACE_("Rotation"), ICON_NONE); + } +} + +namespace blender::nodes { + +static void geo_node_attribute_vector_rotate_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + const NodeAttributeVectorRotate *node_storage = (NodeAttributeVectorRotate *)node->storage; + const GeometryNodeAttributeVectorRotateMode mode = (const GeometryNodeAttributeVectorRotateMode) + node_storage->mode; + + update_attribute_input_socket_availabilities( + *node, "Vector", (GeometryNodeAttributeInputMode)node_storage->input_type_vector); + update_attribute_input_socket_availabilities( + *node, "Center", (GeometryNodeAttributeInputMode)node_storage->input_type_center); + update_attribute_input_socket_availabilities( + *node, + "Axis", + (GeometryNodeAttributeInputMode)node_storage->input_type_axis, + (mode == GEO_NODE_VECTOR_ROTATE_TYPE_AXIS)); + update_attribute_input_socket_availabilities( + *node, + "Angle", + (GeometryNodeAttributeInputMode)node_storage->input_type_angle, + (mode != GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ)); + update_attribute_input_socket_availabilities( + *node, + "Rotation", + (GeometryNodeAttributeInputMode)node_storage->input_type_rotation, + (mode == GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ)); +} + +static float3 vector_rotate_around_axis(const float3 vector, + const float3 center, + const float3 axis, + const float angle) +{ + float3 result = vector - center; + float mat[3][3]; + axis_angle_to_mat3(mat, axis, angle); + mul_m3_v3(mat, result); + return result + center; +} + +static void geo_node_attribute_vector_rotate_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeAttributeVectorRotate *node_storage = (NodeAttributeVectorRotate *)MEM_callocN( + sizeof(NodeAttributeVectorRotate), __func__); + + node_storage->mode = GEO_NODE_VECTOR_ROTATE_TYPE_AXIS; + node_storage->input_type_vector = GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE; + node_storage->input_type_center = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + node_storage->input_type_axis = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + node_storage->input_type_angle = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; + node_storage->input_type_rotation = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + + node->storage = node_storage; +} + +static float3 vector_rotate_euler(const float3 vector, + const float3 center, + const float3 rotation, + const bool invert) +{ + float mat[3][3]; + float3 result = vector - center; + eul_to_mat3(mat, rotation); + if (invert) { + invert_m3(mat); + } + mul_m3_v3(mat, result); + return result + center; +} + +static void do_vector_rotate_around_axis(const VArray<float3> &vector, + const VArray<float3> ¢er, + const VArray<float3> &axis, + const VArray<float> &angle, + MutableSpan<float3> results, + const bool invert) +{ + VArray_Span<float3> span_vector{vector}; + VArray_Span<float3> span_center{center}; + VArray_Span<float3> span_axis{axis}; + VArray_Span<float> span_angle{angle}; + + parallel_for(IndexRange(results.size()), 1024, [&](IndexRange range) { + for (const int i : range) { + float angle = (invert) ? -span_angle[i] : span_angle[i]; + results[i] = vector_rotate_around_axis(span_vector[i], span_center[i], span_axis[i], angle); + } + }); +} + +static void do_vector_rotate_around_fixed_axis(const VArray<float3> &vector, + const VArray<float3> ¢er, + const float3 axis, + const VArray<float> &angle, + MutableSpan<float3> results, + const bool invert) +{ + VArray_Span<float3> span_vector{vector}; + VArray_Span<float3> span_center{center}; + VArray_Span<float> span_angle{angle}; + + parallel_for(IndexRange(results.size()), 1024, [&](IndexRange range) { + for (const int i : range) { + float angle = (invert) ? -span_angle[i] : span_angle[i]; + results[i] = vector_rotate_around_axis(span_vector[i], span_center[i], axis, angle); + } + }); +} + +static void do_vector_rotate_euler(const VArray<float3> &vector, + const VArray<float3> ¢er, + const VArray<float3> &rotation, + MutableSpan<float3> results, + const bool invert) +{ + VArray_Span<float3> span_vector{vector}; + VArray_Span<float3> span_center{center}; + VArray_Span<float3> span_rotation{rotation}; + + parallel_for(IndexRange(results.size()), 1024, [&](IndexRange range) { + for (const int i : range) { + results[i] = vector_rotate_euler(span_vector[i], span_center[i], span_rotation[i], invert); + } + }); +} + +static AttributeDomain get_result_domain(const GeometryComponent &component, + const GeoNodeExecParams ¶ms, + StringRef result_name) +{ + /* Use the domain of the result attribute if it already exists. */ + std::optional<AttributeMetaData> meta_data = component.attribute_get_meta_data(result_name); + if (meta_data) { + return meta_data->domain; + } + + /* Otherwise use the highest priority domain from existing input attributes, or the default. */ + const AttributeDomain default_domain = ATTR_DOMAIN_POINT; + return params.get_highest_priority_input_domain({"Vector", "Center"}, component, default_domain); +} + +static void execute_on_component(const GeoNodeExecParams ¶ms, GeometryComponent &component) +{ + const bNode &node = params.node(); + const NodeAttributeVectorRotate *node_storage = (const NodeAttributeVectorRotate *)node.storage; + const GeometryNodeAttributeVectorRotateMode mode = (GeometryNodeAttributeVectorRotateMode) + node_storage->mode; + const std::string result_name = params.get_input<std::string>("Result"); + const AttributeDomain result_domain = get_result_domain(component, params, result_name); + const bool invert = params.get_input<bool>("Invert"); + + GVArrayPtr attribute_vector = params.get_input_attribute( + "Vector", component, result_domain, CD_PROP_FLOAT3, nullptr); + if (!attribute_vector) { + return; + } + GVArrayPtr attribute_center = params.get_input_attribute( + "Center", component, result_domain, CD_PROP_FLOAT3, nullptr); + if (!attribute_center) { + return; + } + + OutputAttribute attribute_result = component.attribute_try_get_for_output_only( + result_name, result_domain, CD_PROP_FLOAT3); + if (!attribute_result) { + return; + } + + if (mode == GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ) { + GVArrayPtr attribute_rotation = params.get_input_attribute( + "Rotation", component, result_domain, CD_PROP_FLOAT3, nullptr); + if (!attribute_rotation) { + return; + } + do_vector_rotate_euler(attribute_vector->typed<float3>(), + attribute_center->typed<float3>(), + attribute_rotation->typed<float3>(), + attribute_result.as_span<float3>(), + invert); + attribute_result.save(); + return; + } + + GVArrayPtr attribute_angle = params.get_input_attribute( + "Angle", component, result_domain, CD_PROP_FLOAT, nullptr); + if (!attribute_angle) { + return; + } + + switch (mode) { + case GEO_NODE_VECTOR_ROTATE_TYPE_AXIS: { + GVArrayPtr attribute_axis = params.get_input_attribute( + "Axis", component, result_domain, CD_PROP_FLOAT3, nullptr); + if (!attribute_axis) { + return; + } + do_vector_rotate_around_axis(attribute_vector->typed<float3>(), + attribute_center->typed<float3>(), + attribute_axis->typed<float3>(), + attribute_angle->typed<float>(), + attribute_result.as_span<float3>(), + invert); + } break; + case GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_X: + do_vector_rotate_around_fixed_axis(attribute_vector->typed<float3>(), + attribute_center->typed<float3>(), + float3(1.0f, 0.0f, 0.0f), + attribute_angle->typed<float>(), + attribute_result.as_span<float3>(), + invert); + break; + case GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_Y: + do_vector_rotate_around_fixed_axis(attribute_vector->typed<float3>(), + attribute_center->typed<float3>(), + float3(0.0f, 1.0f, 0.0f), + attribute_angle->typed<float>(), + attribute_result.as_span<float3>(), + invert); + + break; + case GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_Z: + do_vector_rotate_around_fixed_axis(attribute_vector->typed<float3>(), + attribute_center->typed<float3>(), + float3(0.0f, 0.0f, 1.0f), + attribute_angle->typed<float>(), + attribute_result.as_span<float3>(), + invert); + + break; + case GEO_NODE_VECTOR_ROTATE_TYPE_EULER_XYZ: + /* Euler is handled before other modes to avoid processing the unavailable angle socket. */ + BLI_assert_unreachable(); + break; + } + attribute_result.save(); +} + +static void geo_node_attribute_vector_rotate_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + geometry_set = geometry_set_realize_instances(geometry_set); + + if (geometry_set.has<MeshComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<MeshComponent>()); + } + if (geometry_set.has<PointCloudComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<PointCloudComponent>()); + } + if (geometry_set.has<CurveComponent>()) { + execute_on_component(params, geometry_set.get_component_for_write<CurveComponent>()); + } + + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_attribute_vector_rotate() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, + GEO_NODE_ATTRIBUTE_VECTOR_ROTATE, + "Attribute Vector Rotate", + NODE_CLASS_ATTRIBUTE, + 0); + node_type_socket_templates( + &ntype, geo_node_attribute_vector_rotate_in, geo_node_attribute_vector_rotate_out); + node_type_update(&ntype, blender::nodes::geo_node_attribute_vector_rotate_update); + node_type_init(&ntype, blender::nodes::geo_node_attribute_vector_rotate_init); + node_type_size(&ntype, 165, 100, 600); + node_type_storage( + &ntype, "NodeAttributeVectorRotate", node_free_standard_storage, node_copy_standard_storage); + ntype.geometry_node_execute = blender::nodes::geo_node_attribute_vector_rotate_exec; + ntype.draw_buttons = geo_node_attribute_vector_rotate_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_bounding_box.cc b/source/blender/nodes/geometry/nodes/node_geo_bounding_box.cc index 96455f080e7..b6fa4c0d48f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_bounding_box.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_bounding_box.cc @@ -39,15 +39,12 @@ static void compute_min_max_from_position_and_transform(const GeometryComponent float3 &r_min, float3 &r_max) { - ReadAttributePtr position_attribute = component.attribute_try_get_for_read("position"); - if (!position_attribute) { - BLI_assert(component.attribute_domain_size(ATTR_DOMAIN_POINT) == 0); - return; - } - Span<float3> positions = position_attribute->get_span<float3>(); + GVArray_Typed<float3> positions = component.attribute_get_for_read<float3>( + "position", ATTR_DOMAIN_POINT, {0, 0, 0}); for (const float4x4 &transform : transforms) { - for (const float3 &position : positions) { + for (const int i : positions.index_range()) { + const float3 position = positions[i]; const float3 transformed_position = transform * position; minmax_v3v3_v3(r_min, r_max, transformed_position); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc b/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc index 0ad495aa4db..9bc8a4bb4be 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc @@ -40,11 +40,17 @@ static void geo_node_collection_info_layout(uiLayout *layout, bContext *UNUSED(C namespace blender::nodes { +static void geo_node_collection_info_node_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCollectionInfo *data = (NodeGeometryCollectionInfo *)MEM_callocN( + sizeof(NodeGeometryCollectionInfo), __func__); + data->transform_space = GEO_NODE_TRANSFORM_SPACE_ORIGINAL; + node->storage = data; +} + static void geo_node_collection_info_exec(GeoNodeExecParams params) { - bke::PersistentCollectionHandle collection_handle = - params.extract_input<bke::PersistentCollectionHandle>("Collection"); - Collection *collection = params.handle_map().lookup(collection_handle); + Collection *collection = params.get_input<Collection *>("Collection"); GeometrySet geometry_set_out; @@ -58,10 +64,6 @@ static void geo_node_collection_info_exec(GeoNodeExecParams params) const bool transform_space_relative = (node_storage->transform_space == GEO_NODE_TRANSFORM_SPACE_RELATIVE); - InstancedData instance; - instance.type = INSTANCE_DATA_TYPE_COLLECTION; - instance.data.collection = collection; - InstancesComponent &instances = geometry_set_out.get_component_for_write<InstancesComponent>(); float transform_mat[4][4]; @@ -73,17 +75,11 @@ static void geo_node_collection_info_exec(GeoNodeExecParams params) mul_m4_m4_pre(transform_mat, self_object->imat); } - instances.add_instance(instance, transform_mat, -1); - params.set_output("Geometry", geometry_set_out); -} + const int handle = instances.add_reference(*collection); + instances.add_instance(handle, transform_mat, -1); -static void geo_node_collection_info_node_init(bNodeTree *UNUSED(tree), bNode *node) -{ - NodeGeometryCollectionInfo *data = (NodeGeometryCollectionInfo *)MEM_callocN( - sizeof(NodeGeometryCollectionInfo), __func__); - data->transform_space = GEO_NODE_TRANSFORM_SPACE_ORIGINAL; - node->storage = data; + params.set_output("Geometry", geometry_set_out); } } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc new file mode 100644 index 00000000000..684f7d6c702 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc @@ -0,0 +1,240 @@ +/* + * 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_task.hh" +#include "BLI_timeit.hh" + +#include "BKE_attribute_math.hh" +#include "BKE_spline.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +using blender::fn::GVArray_For_GSpan; +using blender::fn::GVArray_For_Span; +using blender::fn::GVArray_Typed; + +static bNodeSocketTemplate geo_node_curve_resample_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_INT, N_("Count"), 10, 0, 0, 0, 1, 100000}, + {SOCK_FLOAT, N_("Length"), 0.1f, 0.0f, 0.0f, 0.0f, 0.001f, FLT_MAX, PROP_DISTANCE}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_curve_resample_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +static void geo_node_curve_resample_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); +} + +static void geo_node_curve_resample_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCurveResample *data = (NodeGeometryCurveResample *)MEM_callocN( + sizeof(NodeGeometryCurveResample), __func__); + + data->mode = GEO_NODE_CURVE_SAMPLE_COUNT; + node->storage = data; +} + +static void geo_node_curve_resample_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryCurveResample &node_storage = *(NodeGeometryCurveResample *)node->storage; + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + + bNodeSocket *count_socket = ((bNodeSocket *)node->inputs.first)->next; + bNodeSocket *length_socket = count_socket->next; + + nodeSetSocketAvailability(count_socket, mode == GEO_NODE_CURVE_SAMPLE_COUNT); + nodeSetSocketAvailability(length_socket, mode == GEO_NODE_CURVE_SAMPLE_LENGTH); +} + +namespace blender::nodes { + +struct SampleModeParam { + GeometryNodeCurveSampleMode mode; + std::optional<float> length; + std::optional<int> count; +}; + +template<typename T> +static void sample_span_to_output_spline(const Spline &input_spline, + Span<float> index_factors, + const VArray<T> &input_data, + MutableSpan<T> output_data) +{ + BLI_assert(input_data.size() == input_spline.evaluated_points_size()); + + parallel_for(output_data.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + const Spline::LookupResult interp = input_spline.lookup_data_from_index_factor( + index_factors[i]); + output_data[i] = blender::attribute_math::mix2(interp.factor, + input_data[interp.evaluated_index], + input_data[interp.next_evaluated_index]); + } + }); +} + +static SplinePtr resample_spline(const Spline &input_spline, const int count) +{ + std::unique_ptr<PolySpline> output_spline = std::make_unique<PolySpline>(); + output_spline->set_cyclic(input_spline.is_cyclic()); + output_spline->normal_mode = input_spline.normal_mode; + + if (input_spline.evaluated_edges_size() < 1) { + output_spline->resize(1); + output_spline->positions().first() = input_spline.positions().first(); + return output_spline; + } + + output_spline->resize(count); + + Array<float> uniform_samples = input_spline.sample_uniform_index_factors(count); + + { + GVArray_For_Span positions(input_spline.evaluated_positions()); + GVArray_Typed<float3> positions_typed(positions); + sample_span_to_output_spline<float3>( + input_spline, uniform_samples, positions_typed, output_spline->positions()); + } + { + GVArrayPtr interpolated_data = input_spline.interpolate_to_evaluated_points( + GVArray_For_Span(input_spline.radii())); + GVArray_Typed<float> interpolated_data_typed{*interpolated_data}; + sample_span_to_output_spline<float>( + input_spline, uniform_samples, interpolated_data_typed, output_spline->radii()); + } + { + GVArrayPtr interpolated_data = input_spline.interpolate_to_evaluated_points( + GVArray_For_Span(input_spline.tilts())); + GVArray_Typed<float> interpolated_data_typed{*interpolated_data}; + sample_span_to_output_spline<float>( + input_spline, uniform_samples, interpolated_data_typed, output_spline->tilts()); + } + + output_spline->attributes.reallocate(count); + input_spline.attributes.foreach_attribute( + [&](StringRefNull name, const AttributeMetaData &meta_data) { + std::optional<GSpan> input_attribute = input_spline.attributes.get_for_read(name); + BLI_assert(input_attribute); + if (!output_spline->attributes.create(name, meta_data.data_type)) { + BLI_assert_unreachable(); + return false; + } + std::optional<GMutableSpan> output_attribute = output_spline->attributes.get_for_write( + name); + if (!output_attribute) { + BLI_assert_unreachable(); + return false; + } + GVArrayPtr interpolated_attribute = input_spline.interpolate_to_evaluated_points( + GVArray_For_GSpan(*input_attribute)); + attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { + using T = decltype(dummy); + GVArray_Typed<T> interpolated_attribute_typed{*interpolated_attribute}; + sample_span_to_output_spline<T>(input_spline, + uniform_samples, + interpolated_attribute_typed, + (*output_attribute).typed<T>()); + }); + return true; + }, + ATTR_DOMAIN_POINT); + + return output_spline; +} + +static std::unique_ptr<CurveEval> resample_curve(const CurveEval &input_curve, + const SampleModeParam &mode_param) +{ + std::unique_ptr<CurveEval> output_curve = std::make_unique<CurveEval>(); + + for (const SplinePtr &spline : input_curve.splines()) { + if (mode_param.mode == GEO_NODE_CURVE_SAMPLE_COUNT) { + BLI_assert(mode_param.count); + output_curve->add_spline(resample_spline(*spline, *mode_param.count)); + } + else if (mode_param.mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { + BLI_assert(mode_param.length); + const float length = spline->length(); + const int count = length / *mode_param.length; + output_curve->add_spline(resample_spline(*spline, count)); + } + } + + output_curve->attributes.reallocate(output_curve->splines().size()); + + return output_curve; +} + +static void geo_node_resample_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + geometry_set = bke::geometry_set_realize_instances(geometry_set); + + if (!geometry_set.has_curve()) { + params.set_output("Geometry", GeometrySet()); + return; + } + + const CurveEval &input_curve = *geometry_set.get_curve_for_read(); + NodeGeometryCurveResample &node_storage = *(NodeGeometryCurveResample *)params.node().storage; + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + SampleModeParam mode_param; + mode_param.mode = mode; + if (mode == GEO_NODE_CURVE_SAMPLE_COUNT) { + const int count = params.extract_input<int>("Count"); + if (count < 1) { + params.set_output("Geometry", GeometrySet()); + return; + } + mode_param.count.emplace(count); + } + else if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { + /* Don't allow asymptotic count increase for low resolution values. */ + const float resolution = std::max(params.extract_input<float>("Length"), 0.0001f); + mode_param.length.emplace(resolution); + } + + std::unique_ptr<CurveEval> output_curve = resample_curve(input_curve, mode_param); + + params.set_output("Geometry", GeometrySet::create_with_curve(output_curve.release())); +} + +} // namespace blender::nodes + +void register_node_type_geo_curve_resample() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_CURVE_RESAMPLE, "Resample Curve", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_curve_resample_in, geo_node_curve_resample_out); + ntype.draw_buttons = geo_node_curve_resample_layout; + node_type_storage( + &ntype, "NodeGeometryCurveResample", node_free_standard_storage, node_copy_standard_storage); + node_type_init(&ntype, geo_node_curve_resample_init); + node_type_update(&ntype, geo_node_curve_resample_update); + ntype.geometry_node_execute = blender::nodes::geo_node_resample_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc new file mode 100644 index 00000000000..f0effdc71f6 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc @@ -0,0 +1,312 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_array.hh" +#include "BLI_float4x4.hh" +#include "BLI_timeit.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_mesh.h" +#include "BKE_spline.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_curve_to_mesh_in[] = { + {SOCK_GEOMETRY, N_("Curve")}, + {SOCK_GEOMETRY, N_("Profile Curve")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_curve_to_mesh_out[] = { + {SOCK_GEOMETRY, N_("Mesh")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static void vert_extrude_to_mesh_data(const Spline &spline, + const float3 profile_vert, + MutableSpan<MVert> r_verts, + MutableSpan<MEdge> r_edges, + int &vert_offset, + int &edge_offset) +{ + Span<float3> positions = spline.evaluated_positions(); + + for (const int i : IndexRange(positions.size() - 1)) { + MEdge &edge = r_edges[edge_offset++]; + edge.v1 = vert_offset + i; + edge.v2 = vert_offset + i + 1; + edge.flag = ME_LOOSEEDGE; + } + + if (spline.is_cyclic() && spline.evaluated_edges_size() > 1) { + MEdge &edge = r_edges[edge_offset++]; + edge.v1 = vert_offset; + edge.v2 = vert_offset + positions.size() - 1; + edge.flag = ME_LOOSEEDGE; + } + + for (const int i : positions.index_range()) { + MVert &vert = r_verts[vert_offset++]; + copy_v3_v3(vert.co, positions[i] + profile_vert); + } +} + +static void mark_edges_sharp(MutableSpan<MEdge> edges) +{ + for (MEdge &edge : edges) { + edge.flag |= ME_SHARP; + } +} + +static void spline_extrude_to_mesh_data(const Spline &spline, + const Spline &profile_spline, + MutableSpan<MVert> r_verts, + MutableSpan<MEdge> r_edges, + MutableSpan<MLoop> r_loops, + MutableSpan<MPoly> r_polys, + int &vert_offset, + int &edge_offset, + int &loop_offset, + int &poly_offset) +{ + const int spline_vert_len = spline.evaluated_points_size(); + const int spline_edge_len = spline.evaluated_edges_size(); + const int profile_vert_len = profile_spline.evaluated_points_size(); + const int profile_edge_len = profile_spline.evaluated_edges_size(); + if (spline_vert_len == 0) { + return; + } + + if (profile_vert_len == 1) { + vert_extrude_to_mesh_data(spline, + profile_spline.evaluated_positions()[0], + r_verts, + r_edges, + vert_offset, + edge_offset); + return; + } + + /* Add the edges running along the length of the curve, starting at each profile vertex. */ + const int spline_edges_start = edge_offset; + for (const int i_profile : IndexRange(profile_vert_len)) { + for (const int i_ring : IndexRange(spline_edge_len)) { + const int i_next_ring = (i_ring == spline_vert_len - 1) ? 0 : i_ring + 1; + + const int ring_vert_offset = vert_offset + profile_vert_len * i_ring; + const int next_ring_vert_offset = vert_offset + profile_vert_len * i_next_ring; + + MEdge &edge = r_edges[edge_offset++]; + edge.v1 = ring_vert_offset + i_profile; + edge.v2 = next_ring_vert_offset + i_profile; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; + } + } + + /* Add the edges running along each profile ring. */ + const int profile_edges_start = edge_offset; + for (const int i_ring : IndexRange(spline_vert_len)) { + const int ring_vert_offset = vert_offset + profile_vert_len * i_ring; + + for (const int i_profile : IndexRange(profile_edge_len)) { + const int i_next_profile = (i_profile == profile_vert_len - 1) ? 0 : i_profile + 1; + + MEdge &edge = r_edges[edge_offset++]; + edge.v1 = ring_vert_offset + i_profile; + edge.v2 = ring_vert_offset + i_next_profile; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; + } + } + + /* Calculate poly and corner indices. */ + for (const int i_ring : IndexRange(spline_edge_len)) { + const int i_next_ring = (i_ring == spline_vert_len - 1) ? 0 : i_ring + 1; + + const int ring_vert_offset = vert_offset + profile_vert_len * i_ring; + const int next_ring_vert_offset = vert_offset + profile_vert_len * i_next_ring; + + const int ring_edge_start = profile_edges_start + profile_edge_len * i_ring; + const int next_ring_edge_offset = profile_edges_start + profile_edge_len * i_next_ring; + + for (const int i_profile : IndexRange(profile_edge_len)) { + const int i_next_profile = (i_profile == profile_vert_len - 1) ? 0 : i_profile + 1; + + const int spline_edge_start = spline_edges_start + spline_edge_len * i_profile; + const int next_spline_edge_start = spline_edges_start + spline_edge_len * i_next_profile; + + MPoly &poly = r_polys[poly_offset++]; + poly.loopstart = loop_offset; + poly.totloop = 4; + poly.flag = ME_SMOOTH; + + MLoop &loop_a = r_loops[loop_offset++]; + loop_a.v = ring_vert_offset + i_profile; + loop_a.e = ring_edge_start + i_profile; + MLoop &loop_b = r_loops[loop_offset++]; + loop_b.v = ring_vert_offset + i_next_profile; + loop_b.e = next_spline_edge_start + i_ring; + MLoop &loop_c = r_loops[loop_offset++]; + loop_c.v = next_ring_vert_offset + i_next_profile; + loop_c.e = next_ring_edge_offset + i_profile; + MLoop &loop_d = r_loops[loop_offset++]; + loop_d.v = next_ring_vert_offset + i_profile; + loop_d.e = spline_edge_start + i_ring; + } + } + + /* Calculate the positions of each profile ring profile along the spline. */ + Span<float3> positions = spline.evaluated_positions(); + Span<float3> tangents = spline.evaluated_tangents(); + Span<float3> normals = spline.evaluated_normals(); + Span<float3> profile_positions = profile_spline.evaluated_positions(); + + GVArray_Typed<float> radii{ + spline.interpolate_to_evaluated_points(blender::fn::GVArray_For_Span(spline.radii()))}; + for (const int i_ring : IndexRange(spline_vert_len)) { + float4x4 point_matrix = float4x4::from_normalized_axis_data( + positions[i_ring], normals[i_ring], tangents[i_ring]); + + point_matrix.apply_scale(radii[i_ring]); + + for (const int i_profile : IndexRange(profile_vert_len)) { + MVert &vert = r_verts[vert_offset++]; + copy_v3_v3(vert.co, point_matrix * profile_positions[i_profile]); + } + } + + /* Mark edge loops from sharp vector control points sharp. */ + if (profile_spline.type() == Spline::Type::Bezier) { + const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(profile_spline); + Span<int> control_point_offsets = bezier_spline.control_point_offsets(); + for (const int i : IndexRange(bezier_spline.size())) { + if (bezier_spline.point_is_sharp(i)) { + mark_edges_sharp(r_edges.slice( + spline_edges_start + spline_edge_len * control_point_offsets[i], spline_edge_len)); + } + } + } +} + +static Mesh *curve_to_mesh_calculate(const CurveEval &curve, const CurveEval &profile_curve) +{ + int profile_vert_total = 0; + int profile_edge_total = 0; + for (const SplinePtr &profile_spline : profile_curve.splines()) { + profile_vert_total += profile_spline->evaluated_points_size(); + profile_edge_total += profile_spline->evaluated_edges_size(); + } + + int vert_total = 0; + int edge_total = 0; + int poly_total = 0; + for (const SplinePtr &spline : curve.splines()) { + const int spline_vert_len = spline->evaluated_points_size(); + const int spline_edge_len = spline->evaluated_edges_size(); + vert_total += spline_vert_len * profile_vert_total; + poly_total += spline_edge_len * profile_edge_total; + + /* Add the ring edges, with one ring for every curve vertex, and the edge loops + * that run along the length of the curve, starting on the first profile. */ + edge_total += profile_edge_total * spline_vert_len + profile_vert_total * spline_edge_len; + } + const int corner_total = poly_total * 4; + + if (vert_total == 0) { + return nullptr; + } + + Mesh *mesh = BKE_mesh_new_nomain(vert_total, edge_total, 0, corner_total, poly_total); + MutableSpan<MVert> verts{mesh->mvert, mesh->totvert}; + MutableSpan<MEdge> edges{mesh->medge, mesh->totedge}; + MutableSpan<MLoop> loops{mesh->mloop, mesh->totloop}; + MutableSpan<MPoly> polys{mesh->mpoly, mesh->totpoly}; + mesh->flag |= ME_AUTOSMOOTH; + mesh->smoothresh = DEG2RADF(180.0f); + + int vert_offset = 0; + int edge_offset = 0; + int loop_offset = 0; + int poly_offset = 0; + for (const SplinePtr &spline : curve.splines()) { + for (const SplinePtr &profile_spline : profile_curve.splines()) { + spline_extrude_to_mesh_data(*spline, + *profile_spline, + verts, + edges, + loops, + polys, + vert_offset, + edge_offset, + loop_offset, + poly_offset); + } + } + + BKE_mesh_calc_normals(mesh); + + return mesh; +} + +static CurveEval get_curve_single_vert() +{ + CurveEval curve; + std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>(); + spline->add_point(float3(0), 0, 0.0f); + curve.add_spline(std::move(spline)); + + return curve; +} + +static void geo_node_curve_to_mesh_exec(GeoNodeExecParams params) +{ + GeometrySet curve_set = params.extract_input<GeometrySet>("Curve"); + GeometrySet profile_set = params.extract_input<GeometrySet>("Profile Curve"); + + curve_set = bke::geometry_set_realize_instances(curve_set); + profile_set = bke::geometry_set_realize_instances(profile_set); + + if (!curve_set.has_curve()) { + params.set_output("Mesh", GeometrySet()); + return; + } + + const CurveEval *profile_curve = profile_set.get_curve_for_read(); + + static const CurveEval vert_curve = get_curve_single_vert(); + + Mesh *mesh = curve_to_mesh_calculate(*curve_set.get_curve_for_read(), + (profile_curve == nullptr) ? vert_curve : *profile_curve); + params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); +} + +} // namespace blender::nodes + +void register_node_type_geo_curve_to_mesh() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_CURVE_TO_MESH, "Curve to Mesh", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_curve_to_mesh_in, geo_node_curve_to_mesh_out); + ntype.geometry_node_execute = blender::nodes::geo_node_curve_to_mesh_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc index 534b7d754ec..740b828d503 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc @@ -44,6 +44,7 @@ static bNodeSocketTemplate geo_node_edge_split_out[] = { }; namespace blender::nodes { + static void geo_node_edge_split_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); @@ -82,6 +83,7 @@ static void geo_node_edge_split_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set)); } + } // namespace blender::nodes void register_node_type_geo_edge_split() diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_material.cc b/source/blender/nodes/geometry/nodes/node_geo_input_material.cc new file mode 100644 index 00000000000..6bad71a62a2 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_material.cc @@ -0,0 +1,51 @@ +/* + * 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 "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_input_material_out[] = { + {SOCK_MATERIAL, N_("Material")}, + {-1, ""}, +}; + +static void geo_node_input_material_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "material", 0, "", ICON_NONE); +} + +namespace blender::nodes { + +static void geo_node_input_material_exec(GeoNodeExecParams params) +{ + Material *material = (Material *)params.node().id; + params.set_output("Material", material); +} + +} // namespace blender::nodes + +void register_node_type_geo_input_material() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_MATERIAL, "Material", NODE_CLASS_INPUT, 0); + node_type_socket_templates(&ntype, nullptr, geo_node_input_material_out); + ntype.draw_buttons = geo_node_input_material_layout; + ntype.geometry_node_execute = blender::nodes::geo_node_input_material_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc index 77d2b0df2a9..adfd924f185 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -14,9 +14,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BKE_material.h" #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" #include "BKE_pointcloud.h" +#include "BKE_spline.hh" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" @@ -56,6 +58,8 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent int64_t cd_dirty_edge = 0; int64_t cd_dirty_loop = 0; + VectorSet<Material *> materials; + for (const MeshComponent *mesh_component : src_components) { const Mesh *mesh = mesh_component->get_for_read(); totverts += mesh->totvert; @@ -66,12 +70,22 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent 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); + } } const Mesh *first_input_mesh = src_components[0]->get_for_read(); Mesh *new_mesh = BKE_mesh_new_nomain(totverts, totedges, 0, totloops, totpolys); BKE_mesh_copy_settings(new_mesh, first_input_mesh); + 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; @@ -87,6 +101,13 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent continue; } + 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 int i : IndexRange(mesh->totvert)) { const MVert &old_vert = mesh->mvert[i]; MVert &new_vert = new_mesh->mvert[vert_offset + i]; @@ -112,6 +133,13 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<const MeshComponent 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; @@ -149,10 +177,10 @@ static void determine_final_data_type_and_domain(Span<const GeometryComponent *> Vector<CustomDataType> data_types; Vector<AttributeDomain> domains; for (const GeometryComponent *component : components) { - ReadAttributePtr attribute = component->attribute_try_get_for_read(attribute_name); + ReadAttributeLookup attribute = component->attribute_try_get_for_read(attribute_name); if (attribute) { - data_types.append(attribute->custom_data_type()); - domains.append(attribute->domain()); + data_types.append(bke::cpp_type_to_custom_data_type(attribute.varray->type())); + domains.append(attribute.domain); } } @@ -164,7 +192,7 @@ static void fill_new_attribute(Span<const GeometryComponent *> src_components, StringRef attribute_name, const CustomDataType data_type, const AttributeDomain domain, - fn::GMutableSpan dst_span) + GMutableSpan dst_span) { const CPPType *cpp_type = bke::custom_data_type_to_cpp_type(data_type); BLI_assert(cpp_type != nullptr); @@ -175,10 +203,10 @@ static void fill_new_attribute(Span<const GeometryComponent *> src_components, if (domain_size == 0) { continue; } - ReadAttributePtr read_attribute = component->attribute_get_for_read( + GVArrayPtr read_attribute = component->attribute_get_for_read( attribute_name, domain, data_type, nullptr); - fn::GSpan src_span = read_attribute->get_span(); + GVArray_GSpan src_span{*read_attribute}; const void *src_buffer = src_span.data(); void *dst_buffer = dst_span[offset]; cpp_type->copy_to_initialized_n(src_buffer, dst_buffer, domain_size); @@ -201,16 +229,14 @@ static void join_attributes(Span<const GeometryComponent *> src_components, AttributeDomain domain; determine_final_data_type_and_domain(src_components, attribute_name, &data_type, &domain); - OutputAttributePtr write_attribute = result.attribute_try_get_for_output( + OutputAttribute write_attribute = result.attribute_try_get_for_output_only( attribute_name, domain, data_type); - if (!write_attribute || - &write_attribute->cpp_type() != bke::custom_data_type_to_cpp_type(data_type) || - write_attribute->domain() != domain) { + if (!write_attribute) { continue; } - fn::GMutableSpan dst_span = write_attribute->get_span_for_write_only(); + GMutableSpan dst_span = write_attribute.as_span(); fill_new_attribute(src_components, attribute_name, data_type, domain, dst_span); - write_attribute.apply_span_and_save(); + write_attribute.save(); } } @@ -244,13 +270,30 @@ static void join_components(Span<const PointCloudComponent *> src_components, Ge static void join_components(Span<const InstancesComponent *> src_components, GeometrySet &result) { InstancesComponent &dst_component = result.get_component_for_write<InstancesComponent>(); - for (const InstancesComponent *component : src_components) { - const int size = component->instances_amount(); - Span<InstancedData> instanced_data = component->instanced_data(); - Span<float4x4> transforms = component->transforms(); - Span<int> ids = component->ids(); - for (const int i : IndexRange(size)) { - dst_component.add_instance(instanced_data[i], transforms[i], ids[i]); + + int tot_instances = 0; + for (const InstancesComponent *src_component : src_components) { + tot_instances += src_component->instances_amount(); + } + dst_component.reserve(tot_instances); + + for (const InstancesComponent *src_component : src_components) { + Span<InstanceReference> src_references = src_component->references(); + Array<int> handle_map(src_references.size()); + for (const int src_handle : src_references.index_range()) { + handle_map[src_handle] = dst_component.add_reference(src_references[src_handle]); + } + + Span<float4x4> src_transforms = src_component->instance_transforms(); + Span<int> src_ids = src_component->instance_ids(); + Span<int> src_reference_handles = src_component->instance_reference_handles(); + + for (const int i : src_transforms.index_range()) { + const int src_handle = src_reference_handles[i]; + const int dst_handle = handle_map[src_handle]; + const float4x4 &transform = src_transforms[i]; + const int id = src_ids[i]; + dst_component.add_instance(dst_handle, transform, id); } } } @@ -263,6 +306,48 @@ static void join_components(Span<const VolumeComponent *> src_components, Geomet UNUSED_VARS(src_components, dst_component); } +static void join_curve_components(MutableSpan<GeometrySet> src_geometry_sets, GeometrySet &result) +{ + Vector<CurveComponent *> src_components; + for (GeometrySet &geometry_set : src_geometry_sets) { + if (geometry_set.has_curve()) { + /* Retrieving with write access seems counterintuitive, but it can allow avoiding a copy + * in the case where the input spline has no other users, because the splines can be + * moved from the source curve rather than copied from a read-only source. Retrieving + * the curve for write will make a copy only when it has a user elsewhere. */ + CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>(); + src_components.append(&component); + } + } + + if (src_components.size() == 0) { + return; + } + if (src_components.size() == 1) { + result.add(*src_components[0]); + return; + } + + CurveComponent &dst_component = result.get_component_for_write<CurveComponent>(); + CurveEval *dst_curve = new CurveEval(); + for (CurveComponent *component : src_components) { + CurveEval *src_curve = component->get_for_write(); + for (SplinePtr &spline : src_curve->splines()) { + dst_curve->add_spline(std::move(spline)); + } + } + + /* For now, remove all custom attributes, since they might have different types, + * or an attribute might not exist on all splines. */ + dst_curve->attributes.reallocate(dst_curve->splines().size()); + CustomData_reset(&dst_curve->attributes.data); + for (SplinePtr &spline : dst_curve->splines()) { + CustomData_reset(&spline->attributes.data); + } + + dst_component.replace(dst_curve); +} + template<typename Component> static void join_component_type(Span<GeometrySet> src_geometry_sets, GeometrySet &result) { @@ -293,6 +378,7 @@ static void geo_node_join_geometry_exec(GeoNodeExecParams params) join_component_type<PointCloudComponent>(geometry_sets, geometry_set_result); join_component_type<InstancesComponent>(geometry_sets, geometry_set_result); join_component_type<VolumeComponent>(geometry_sets, geometry_set_result); + join_curve_components(geometry_sets, geometry_set_result); params.set_output("Geometry", std::move(geometry_set_result)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc b/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc new file mode 100644 index 00000000000..8c245afd3d2 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc @@ -0,0 +1,98 @@ +/* + * 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 "node_geometry_util.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_material.h" + +static bNodeSocketTemplate geo_node_material_assign_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_MATERIAL, N_("Material")}, + {SOCK_STRING, N_("Selection")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_material_assign_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static void assign_material_to_faces(Mesh &mesh, const VArray<bool> &face_mask, Material *material) +{ + int new_material_index = -1; + for (const int i : IndexRange(mesh.totcol)) { + Material *other_material = mesh.mat[i]; + if (other_material == material) { + new_material_index = i; + break; + } + } + if (new_material_index == -1) { + /* Append a new material index. */ + new_material_index = mesh.totcol; + BKE_id_material_eval_assign(&mesh.id, new_material_index + 1, material); + } + + mesh.mpoly = (MPoly *)CustomData_duplicate_referenced_layer(&mesh.pdata, CD_MPOLY, mesh.totpoly); + for (const int i : IndexRange(mesh.totpoly)) { + if (face_mask[i]) { + MPoly &poly = mesh.mpoly[i]; + poly.mat_nr = new_material_index; + } + } +} + +static void geo_node_material_assign_exec(GeoNodeExecParams params) +{ + Material *material = params.extract_input<Material *>("Material"); + const std::string mask_name = params.extract_input<std::string>("Selection"); + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + geometry_set = geometry_set_realize_instances(geometry_set); + + if (geometry_set.has<MeshComponent>()) { + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + Mesh *mesh = mesh_component.get_for_write(); + if (mesh != nullptr) { + GVArray_Typed<bool> face_mask = mesh_component.attribute_get_for_read<bool>( + mask_name, ATTR_DOMAIN_FACE, true); + assign_material_to_faces(*mesh, face_mask, material); + } + } + + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_material_assign() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_MATERIAL_ASSIGN, "Material Assign", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_material_assign_in, geo_node_material_assign_out); + ntype.geometry_node_execute = blender::nodes::geo_node_material_assign_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_material_replace.cc b/source/blender/nodes/geometry/nodes/node_geo_material_replace.cc new file mode 100644 index 00000000000..40ecab98dea --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_material_replace.cc @@ -0,0 +1,75 @@ +/* + * 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 "node_geometry_util.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_material.h" + +static bNodeSocketTemplate geo_node_material_replace_in[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {SOCK_MATERIAL, N_("Old")}, + {SOCK_MATERIAL, N_("New")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_material_replace_out[] = { + {SOCK_GEOMETRY, N_("Geometry")}, + {-1, ""}, +}; + +namespace blender::nodes { + +static void geo_node_material_replace_exec(GeoNodeExecParams params) +{ + Material *old_material = params.extract_input<Material *>("Old"); + Material *new_material = params.extract_input<Material *>("New"); + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + geometry_set = geometry_set_realize_instances(geometry_set); + + if (geometry_set.has<MeshComponent>()) { + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + Mesh *mesh = mesh_component.get_for_write(); + if (mesh != nullptr) { + for (const int i : IndexRange(mesh->totcol)) { + if (mesh->mat[i] == old_material) { + mesh->mat[i] = new_material; + } + } + } + } + + params.set_output("Geometry", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_material_replace() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_MATERIAL_REPLACE, "Material Replace", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_material_replace_in, geo_node_material_replace_out); + ntype.geometry_node_execute = blender::nodes::geo_node_material_replace_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc index 761d5d6c388..2806472286e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc @@ -194,9 +194,9 @@ static void calculate_uvs(Mesh *mesh, { MeshComponent mesh_component; mesh_component.replace(mesh, GeometryOwnershipType::Editable); - OutputAttributePtr uv_attribute = mesh_component.attribute_try_get_for_output( - "uv_map", ATTR_DOMAIN_CORNER, CD_PROP_FLOAT2, nullptr); - MutableSpan<float2> uvs = uv_attribute->get_span_for_write_only<float2>(); + OutputAttribute_Typed<float2> uv_attribute = + mesh_component.attribute_try_get_for_output_only<float2>("uv_map", ATTR_DOMAIN_CORNER); + MutableSpan<float2> uvs = uv_attribute.as_span(); Array<float2> circle(verts_num); float angle = 0.0f; @@ -271,7 +271,7 @@ static void calculate_uvs(Mesh *mesh, } } - uv_attribute.apply_span_and_save(); + uv_attribute.save(); } Mesh *create_cylinder_or_cone_mesh(const float radius_top, diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc index 14c57bc7135..5a4bab86421 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc @@ -44,9 +44,9 @@ static void calculate_uvs( { MeshComponent mesh_component; mesh_component.replace(mesh, GeometryOwnershipType::Editable); - OutputAttributePtr uv_attribute = mesh_component.attribute_try_get_for_output( - "uv_map", ATTR_DOMAIN_CORNER, CD_PROP_FLOAT2, nullptr); - MutableSpan<float2> uvs = uv_attribute->get_span_for_write_only<float2>(); + OutputAttribute_Typed<float2> uv_attribute = + mesh_component.attribute_try_get_for_output_only<float2>("uv_map", ATTR_DOMAIN_CORNER); + MutableSpan<float2> uvs = uv_attribute.as_span(); const float dx = (size_x == 0.0f) ? 0.0f : 1.0f / size_x; const float dy = (size_y == 0.0f) ? 0.0f : 1.0f / size_y; @@ -56,7 +56,7 @@ static void calculate_uvs( uvs[i].y = (co.y + size_y * 0.5f) * dy; } - uv_attribute.apply_span_and_save(); + uv_attribute.save(); } static Mesh *create_grid_mesh(const int verts_x, diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc index fd95cdc81f7..cc93e71a5dd 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc @@ -224,9 +224,9 @@ static void calculate_sphere_uvs(Mesh *mesh, const float segments, const float r { MeshComponent mesh_component; mesh_component.replace(mesh, GeometryOwnershipType::Editable); - OutputAttributePtr uv_attribute = mesh_component.attribute_try_get_for_output( - "uv_map", ATTR_DOMAIN_CORNER, CD_PROP_FLOAT2, nullptr); - MutableSpan<float2> uvs = uv_attribute->get_span_for_write_only<float2>(); + OutputAttribute_Typed<float2> uv_attribute = + mesh_component.attribute_try_get_for_output_only<float2>("uv_map", ATTR_DOMAIN_CORNER); + MutableSpan<float2> uvs = uv_attribute.as_span(); int loop_index = 0; const float dy = 1.0f / rings; @@ -256,7 +256,7 @@ static void calculate_sphere_uvs(Mesh *mesh, const float segments, const float r uvs[loop_index++] = float2(segment / segments, 1.0f - dy); } - uv_attribute.apply_span_and_save(); + uv_attribute.save(); } static Mesh *create_uv_sphere_mesh(const float radius, const int segments, const int rings) diff --git a/source/blender/nodes/geometry/nodes/node_geo_object_info.cc b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc index bd42b4c11d6..16c943b310c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_object_info.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_object_info.cc @@ -47,9 +47,7 @@ static void geo_node_object_info_exec(GeoNodeExecParams params) const bool transform_space_relative = (node_storage->transform_space == GEO_NODE_TRANSFORM_SPACE_RELATIVE); - bke::PersistentObjectHandle object_handle = params.extract_input<bke::PersistentObjectHandle>( - "Object"); - Object *object = params.handle_map().lookup(object_handle); + Object *object = params.get_input<Object *>("Object"); float3 location = {0, 0, 0}; float3 rotation = {0, 0, 0}; @@ -73,14 +71,15 @@ static void geo_node_object_info_exec(GeoNodeExecParams params) if (object != self_object) { InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); + const int handle = instances.add_reference(*object); if (transform_space_relative) { - instances.add_instance(object, transform); + instances.add_instance(handle, transform); } else { float unit_transform[4][4]; unit_m4(unit_transform); - instances.add_instance(object, unit_transform); + instances.add_instance(handle, unit_transform); } } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc index 74cca8a2f3c..772bd8a1080 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc @@ -28,6 +28,7 @@ #include "BKE_geometry_set_instances.hh" #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" +#include "BKE_mesh_sample.hh" #include "BKE_pointcloud.h" #include "UI_interface.h" @@ -91,7 +92,7 @@ static Span<MLoopTri> get_mesh_looptris(const Mesh &mesh) static void sample_mesh_surface(const Mesh &mesh, const float4x4 &transform, const float base_density, - const FloatReadAttribute *density_factors, + const VArray<float> *density_factors, const int seed, Vector<float3> &r_positions, Vector<float3> &r_bary_coords, @@ -113,9 +114,9 @@ static void sample_mesh_surface(const Mesh &mesh, float looptri_density_factor = 1.0f; if (density_factors != nullptr) { - const float v0_density_factor = std::max(0.0f, (*density_factors)[v0_loop]); - const float v1_density_factor = std::max(0.0f, (*density_factors)[v1_loop]); - const float v2_density_factor = std::max(0.0f, (*density_factors)[v2_loop]); + const float v0_density_factor = std::max(0.0f, density_factors->get(v0_loop)); + const float v1_density_factor = std::max(0.0f, density_factors->get(v1_loop)); + const float v2_density_factor = std::max(0.0f, density_factors->get(v2_loop)); looptri_density_factor = (v0_density_factor + v1_density_factor + v2_density_factor) / 3.0f; } const float area = area_tri_v3(v0_pos, v1_pos, v2_pos); @@ -203,7 +204,7 @@ BLI_NOINLINE static void update_elimination_mask_for_close_points( BLI_NOINLINE static void update_elimination_mask_based_on_density_factors( const Mesh &mesh, - const FloatReadAttribute &density_factors, + const VArray<float> &density_factors, Span<float3> bary_coords, Span<int> looptri_indices, MutableSpan<bool> elimination_mask) @@ -249,99 +250,27 @@ BLI_NOINLINE static void eliminate_points_based_on_mask(Span<bool> elimination_m } } -template<typename T> -BLI_NOINLINE static void interpolate_attribute_point(const Mesh &mesh, - const Span<float3> bary_coords, - const Span<int> looptri_indices, - const Span<T> data_in, - MutableSpan<T> data_out) -{ - BLI_assert(data_in.size() == mesh.totvert); - Span<MLoopTri> looptris = get_mesh_looptris(mesh); - - for (const int i : bary_coords.index_range()) { - const int looptri_index = looptri_indices[i]; - const MLoopTri &looptri = looptris[looptri_index]; - const float3 &bary_coord = bary_coords[i]; - - const int v0_index = mesh.mloop[looptri.tri[0]].v; - const int v1_index = mesh.mloop[looptri.tri[1]].v; - const int v2_index = mesh.mloop[looptri.tri[2]].v; - - const T &v0 = data_in[v0_index]; - const T &v1 = data_in[v1_index]; - const T &v2 = data_in[v2_index]; - - const T interpolated_value = attribute_math::mix3(bary_coord, v0, v1, v2); - data_out[i] = interpolated_value; - } -} - -template<typename T> -BLI_NOINLINE static void interpolate_attribute_corner(const Mesh &mesh, - const Span<float3> bary_coords, - const Span<int> looptri_indices, - const Span<T> data_in, - MutableSpan<T> data_out) -{ - BLI_assert(data_in.size() == mesh.totloop); - Span<MLoopTri> looptris = get_mesh_looptris(mesh); - - for (const int i : bary_coords.index_range()) { - const int looptri_index = looptri_indices[i]; - const MLoopTri &looptri = looptris[looptri_index]; - const float3 &bary_coord = bary_coords[i]; - - const int loop_index_0 = looptri.tri[0]; - const int loop_index_1 = looptri.tri[1]; - const int loop_index_2 = looptri.tri[2]; - - const T &v0 = data_in[loop_index_0]; - const T &v1 = data_in[loop_index_1]; - const T &v2 = data_in[loop_index_2]; - - const T interpolated_value = attribute_math::mix3(bary_coord, v0, v1, v2); - data_out[i] = interpolated_value; - } -} - -template<typename T> -BLI_NOINLINE static void interpolate_attribute_face(const Mesh &mesh, - const Span<int> looptri_indices, - const Span<T> data_in, - MutableSpan<T> data_out) -{ - BLI_assert(data_in.size() == mesh.totpoly); - Span<MLoopTri> looptris = get_mesh_looptris(mesh); - - for (const int i : data_out.index_range()) { - const int looptri_index = looptri_indices[i]; - const MLoopTri &looptri = looptris[looptri_index]; - const int poly_index = looptri.poly; - data_out[i] = data_in[poly_index]; - } -} - -template<typename T> BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh, Span<float3> bary_coords, Span<int> looptri_indices, const AttributeDomain source_domain, - Span<T> source_span, - MutableSpan<T> output_span) + const GVArray &source_data, + GMutableSpan output_data) { switch (source_domain) { case ATTR_DOMAIN_POINT: { - interpolate_attribute_point<T>(mesh, bary_coords, looptri_indices, source_span, output_span); + bke::mesh_surface_sample::sample_point_attribute( + mesh, looptri_indices, bary_coords, source_data, output_data); break; } case ATTR_DOMAIN_CORNER: { - interpolate_attribute_corner<T>( - mesh, bary_coords, looptri_indices, source_span, output_span); + bke::mesh_surface_sample::sample_corner_attribute( + mesh, looptri_indices, bary_coords, source_data, output_data); break; } case ATTR_DOMAIN_FACE: { - interpolate_attribute_face<T>(mesh, looptri_indices, source_span, output_span); + bke::mesh_surface_sample::sample_face_attribute( + mesh, looptri_indices, source_data, output_data); break; } default: { @@ -363,13 +292,13 @@ BLI_NOINLINE static void interpolate_existing_attributes( StringRef attribute_name = entry.key; const CustomDataType output_data_type = entry.value.data_type; /* The output domain is always #ATTR_DOMAIN_POINT, since we are creating a point cloud. */ - OutputAttributePtr attribute_out = component.attribute_try_get_for_output( + OutputAttribute attribute_out = component.attribute_try_get_for_output_only( attribute_name, ATTR_DOMAIN_POINT, output_data_type); if (!attribute_out) { continue; } - fn::GMutableSpan out_span = attribute_out->get_span_for_write_only(); + GMutableSpan out_span = attribute_out.as_span(); int i_instance = 0; for (const GeometryInstanceGroup &set_group : set_groups) { @@ -377,47 +306,41 @@ BLI_NOINLINE static void interpolate_existing_attributes( const MeshComponent &source_component = *set.get_component_for_read<MeshComponent>(); const Mesh &mesh = *source_component.get_for_read(); - /* Use a dummy read without specifying a domain or data type in order to - * get the existing attribute's domain. Interpolation is done manually based - * on the bary coords in #interpolate_attribute. */ - ReadAttributePtr dummy_attribute = source_component.attribute_try_get_for_read( + std::optional<AttributeMetaData> attribute_info = component.attribute_get_meta_data( attribute_name); - if (!dummy_attribute) { + if (!attribute_info) { i_instance += set_group.transforms.size(); continue; } - const AttributeDomain source_domain = dummy_attribute->domain(); - ReadAttributePtr source_attribute = source_component.attribute_get_for_read( + const AttributeDomain source_domain = attribute_info->domain; + GVArrayPtr source_attribute = source_component.attribute_get_for_read( attribute_name, source_domain, output_data_type, nullptr); if (!source_attribute) { i_instance += set_group.transforms.size(); continue; } - fn::GSpan source_span = source_attribute->get_span(); + + for (const int UNUSED(i_set_instance) : set_group.transforms.index_range()) { + const int offset = instance_start_offsets[i_instance]; + Span<float3> bary_coords = bary_coords_array[i_instance]; + Span<int> looptri_indices = looptri_indices_array[i_instance]; + + GMutableSpan instance_span = out_span.slice(offset, bary_coords.size()); + interpolate_attribute( + mesh, bary_coords, looptri_indices, source_domain, *source_attribute, instance_span); + + i_instance++; + } attribute_math::convert_to_static_type(output_data_type, [&](auto dummy) { using T = decltype(dummy); - for (const int UNUSED(i_set_instance) : set_group.transforms.index_range()) { - const int offset = instance_start_offsets[i_instance]; - Span<float3> bary_coords = bary_coords_array[i_instance]; - Span<int> looptri_indices = looptri_indices_array[i_instance]; - - MutableSpan<T> instance_span = out_span.typed<T>().slice(offset, bary_coords.size()); - interpolate_attribute<T>(mesh, - bary_coords, - looptri_indices, - source_domain, - source_span.typed<T>(), - instance_span); - - i_instance++; - } + GVArray_Span<T> source_span{*source_attribute}; }); } - attribute_out.apply_span_and_save(); + attribute_out.save(); } } @@ -427,16 +350,16 @@ BLI_NOINLINE static void compute_special_attributes(Span<GeometryInstanceGroup> Span<Vector<float3>> bary_coords_array, Span<Vector<int>> looptri_indices_array) { - OutputAttributePtr id_attribute = component.attribute_try_get_for_output( - "id", ATTR_DOMAIN_POINT, CD_PROP_INT32); - OutputAttributePtr normal_attribute = component.attribute_try_get_for_output( - "normal", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3); - OutputAttributePtr rotation_attribute = component.attribute_try_get_for_output( - "rotation", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3); + OutputAttribute_Typed<int> id_attribute = component.attribute_try_get_for_output_only<int>( + "id", ATTR_DOMAIN_POINT); + OutputAttribute_Typed<float3> normal_attribute = + component.attribute_try_get_for_output_only<float3>("normal", ATTR_DOMAIN_POINT); + OutputAttribute_Typed<float3> rotation_attribute = + component.attribute_try_get_for_output_only<float3>("rotation", ATTR_DOMAIN_POINT); - MutableSpan<int> result_ids = id_attribute->get_span_for_write_only<int>(); - MutableSpan<float3> result_normals = normal_attribute->get_span_for_write_only<float3>(); - MutableSpan<float3> result_rotations = rotation_attribute->get_span_for_write_only<float3>(); + MutableSpan<int> result_ids = id_attribute.as_span(); + MutableSpan<float3> result_normals = normal_attribute.as_span(); + MutableSpan<float3> result_rotations = rotation_attribute.as_span(); int i_instance = 0; for (const GeometryInstanceGroup &set_group : sets) { @@ -480,9 +403,9 @@ BLI_NOINLINE static void compute_special_attributes(Span<GeometryInstanceGroup> } } - id_attribute.apply_span_and_save(); - normal_attribute.apply_span_and_save(); - rotation_attribute.apply_span_and_save(); + id_attribute.save(); + normal_attribute.save(); + rotation_attribute.save(); } BLI_NOINLINE static void add_remaining_point_attributes( @@ -520,7 +443,7 @@ static void distribute_points_random(Span<GeometryInstanceGroup> set_groups, for (const GeometryInstanceGroup &set_group : set_groups) { const GeometrySet &set = set_group.geometry_set; const MeshComponent &component = *set.get_component_for_read<MeshComponent>(); - const FloatReadAttribute density_factors = component.attribute_get_for_read<float>( + GVArray_Typed<float> density_factors = component.attribute_get_for_read<float>( density_attribute_name, ATTR_DOMAIN_CORNER, use_one_default ? 1.0f : 0.0f); const Mesh &mesh = *component.get_for_read(); for (const float4x4 &transform : set_group.transforms) { @@ -530,7 +453,7 @@ static void distribute_points_random(Span<GeometryInstanceGroup> set_groups, sample_mesh_surface(mesh, transform, density, - &density_factors, + &*density_factors, seed, positions, bary_coords, @@ -589,7 +512,7 @@ static void distribute_points_poisson_disk(Span<GeometryInstanceGroup> set_group const GeometrySet &set = set_group.geometry_set; const MeshComponent &component = *set.get_component_for_read<MeshComponent>(); const Mesh &mesh = *component.get_for_read(); - const FloatReadAttribute density_factors = component.attribute_get_for_read<float>( + const GVArray_Typed<float> density_factors = component.attribute_get_for_read<float>( density_attribute_name, ATTR_DOMAIN_CORNER, use_one_default ? 1.0f : 0.0f); for (const int UNUSED(i_set_instance) : set_group.transforms.index_range()) { diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc index 20022e8d29d..44b8b14f4e7 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc @@ -14,11 +14,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "BKE_persistent_data_handle.hh" - #include "DNA_collection_types.h" #include "BLI_hash.h" +#include "BLI_task.hh" #include "UI_interface.h" #include "UI_resources.h" @@ -48,6 +47,15 @@ static void geo_node_point_instance_layout(uiLayout *layout, bContext *UNUSED(C) namespace blender::nodes { +static void geo_node_point_instance_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryPointInstance *data = (NodeGeometryPointInstance *)MEM_callocN( + sizeof(NodeGeometryPointInstance), __func__); + data->instance_type = GEO_NODE_POINT_INSTANCE_TYPE_OBJECT; + data->flag |= GEO_NODE_POINT_INSTANCE_WHOLE_COLLECTION; + node->storage = data; +} + static void geo_node_point_instance_update(bNodeTree *UNUSED(tree), bNode *node) { bNodeSocket *object_socket = (bNodeSocket *)BLI_findlink(&node->inputs, 1); @@ -65,128 +73,133 @@ static void geo_node_point_instance_update(bNodeTree *UNUSED(tree), bNode *node) seed_socket, type == GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION && !use_whole_collection); } -static void get_instanced_data__object(const GeoNodeExecParams ¶ms, - MutableSpan<std::optional<InstancedData>> r_instances_data) +static Vector<InstanceReference> get_instance_references__object(GeoNodeExecParams ¶ms) { - bke::PersistentObjectHandle object_handle = params.get_input<bke::PersistentObjectHandle>( - "Object"); - Object *object = params.handle_map().lookup(object_handle); + Object *object = params.extract_input<Object *>("Object"); if (object == params.self_object()) { - object = nullptr; + return {}; } if (object != nullptr) { - InstancedData instance; - instance.type = INSTANCE_DATA_TYPE_OBJECT; - instance.data.object = object; - r_instances_data.fill(instance); + return {*object}; } + return {}; } -static void get_instanced_data__collection( - const GeoNodeExecParams ¶ms, - const GeometryComponent &component, - MutableSpan<std::optional<InstancedData>> r_instances_data) +static Vector<InstanceReference> get_instance_references__collection(GeoNodeExecParams ¶ms) { const bNode &node = params.node(); NodeGeometryPointInstance *node_storage = (NodeGeometryPointInstance *)node.storage; - bke::PersistentCollectionHandle collection_handle = - params.get_input<bke::PersistentCollectionHandle>("Collection"); - Collection *collection = params.handle_map().lookup(collection_handle); + Collection *collection = params.get_input<Collection *>("Collection"); if (collection == nullptr) { - return; + return {}; } if (BLI_listbase_is_empty(&collection->children) && BLI_listbase_is_empty(&collection->gobject)) { params.error_message_add(NodeWarningType::Info, TIP_("Collection is empty")); - return; + return {}; } - const bool use_whole_collection = (node_storage->flag & - GEO_NODE_POINT_INSTANCE_WHOLE_COLLECTION) != 0; - if (use_whole_collection) { - InstancedData instance; - instance.type = INSTANCE_DATA_TYPE_COLLECTION; - instance.data.collection = collection; - r_instances_data.fill(instance); + if (node_storage->flag & GEO_NODE_POINT_INSTANCE_WHOLE_COLLECTION) { + return {*collection}; } - else { - Vector<InstancedData> possible_instances; - /* Direct child objects are instanced as objects. */ - LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) { - Object *object = cob->ob; - InstancedData instance; - instance.type = INSTANCE_DATA_TYPE_OBJECT; - instance.data.object = object; - possible_instances.append(instance); - } - /* Direct child collections are instanced as collections. */ - LISTBASE_FOREACH (CollectionChild *, child, &collection->children) { - Collection *child_collection = child->collection; - InstancedData instance; - instance.type = INSTANCE_DATA_TYPE_COLLECTION; - instance.data.collection = child_collection; - possible_instances.append(instance); - } - if (!possible_instances.is_empty()) { - const int seed = params.get_input<int>("Seed"); - Array<uint32_t> ids = get_geometry_element_ids_as_uints(component, ATTR_DOMAIN_POINT); - for (const int i : r_instances_data.index_range()) { - const int index = BLI_hash_int_2d(ids[i], seed) % possible_instances.size(); - r_instances_data[i] = possible_instances[index]; - } - } + Vector<InstanceReference> references; + /* Direct child objects are instanced as objects. */ + LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) { + references.append(*cob->ob); + } + /* Direct child collections are instanced as collections. */ + LISTBASE_FOREACH (CollectionChild *, child, &collection->children) { + references.append(*child->collection); } + + return references; } -static Array<std::optional<InstancedData>> get_instanced_data(const GeoNodeExecParams ¶ms, - const GeometryComponent &component, - const int amount) +static Vector<InstanceReference> get_instance_references(GeoNodeExecParams ¶ms) { const bNode &node = params.node(); NodeGeometryPointInstance *node_storage = (NodeGeometryPointInstance *)node.storage; const GeometryNodePointInstanceType type = (GeometryNodePointInstanceType) node_storage->instance_type; - Array<std::optional<InstancedData>> instances_data(amount); switch (type) { case GEO_NODE_POINT_INSTANCE_TYPE_OBJECT: { - get_instanced_data__object(params, instances_data); - break; + return get_instance_references__object(params); } case GEO_NODE_POINT_INSTANCE_TYPE_COLLECTION: { - get_instanced_data__collection(params, component, instances_data); - break; + return get_instance_references__collection(params); } } - return instances_data; + return {}; +} + +/** + * Add the instance references to the component as a separate step from actually creating the + * instances in order to avoid a map lookup for every transform. While this might add some + * unnecessary references if they are not chosen while adding transforms, in the common cases + * there are many more transforms than there are references, so that isn't likely. + */ +static Array<int> add_instance_references(InstancesComponent &instance_component, + Span<InstanceReference> possible_references) +{ + Array<int> possible_handles(possible_references.size()); + for (const int i : possible_references.index_range()) { + possible_handles[i] = instance_component.add_reference(possible_references[i]); + } + return possible_handles; } -static void add_instances_from_geometry_component(InstancesComponent &instances, - const GeometryComponent &src_geometry, - const GeoNodeExecParams ¶ms) +static void add_instances_from_component(InstancesComponent &instances, + const GeometryComponent &src_geometry, + Span<int> possible_handles, + const GeoNodeExecParams ¶ms) { const AttributeDomain domain = ATTR_DOMAIN_POINT; const int domain_size = src_geometry.attribute_domain_size(domain); - Array<std::optional<InstancedData>> instances_data = get_instanced_data( - params, src_geometry, domain_size); - Float3ReadAttribute positions = src_geometry.attribute_get_for_read<float3>( + GVArray_Typed<float3> positions = src_geometry.attribute_get_for_read<float3>( "position", domain, {0, 0, 0}); - Float3ReadAttribute rotations = src_geometry.attribute_get_for_read<float3>( + GVArray_Typed<float3> rotations = src_geometry.attribute_get_for_read<float3>( "rotation", domain, {0, 0, 0}); - Float3ReadAttribute scales = src_geometry.attribute_get_for_read<float3>( + GVArray_Typed<float3> scales = src_geometry.attribute_get_for_read<float3>( "scale", domain, {1, 1, 1}); - Int32ReadAttribute ids = src_geometry.attribute_get_for_read<int>("id", domain, -1); - - for (const int i : IndexRange(domain_size)) { - if (instances_data[i].has_value()) { - const float4x4 matrix = float4x4::from_loc_eul_scale(positions[i], rotations[i], scales[i]); - instances.add_instance(*instances_data[i], matrix, ids[i]); - } + GVArray_Typed<int> id_attribute = src_geometry.attribute_get_for_read<int>("id", domain, -1); + + /* The initial size of the component might be non-zero if there are two component types. */ + const int start_len = instances.instances_amount(); + instances.resize(start_len + domain_size); + MutableSpan<int> handles = instances.instance_reference_handles().slice(start_len, domain_size); + MutableSpan<float4x4> transforms = instances.instance_transforms().slice(start_len, domain_size); + MutableSpan<int> instance_ids = instances.instance_ids().slice(start_len, domain_size); + + /* Skip all of the randomness handling if there is only a single possible instance + * (anything except for collection mode with "Whole Collection" turned off). */ + if (possible_handles.size() == 1) { + const int handle = possible_handles.first(); + parallel_for(IndexRange(domain_size), 1024, [&](IndexRange range) { + for (const int i : range) { + handles[i] = handle; + transforms[i] = float4x4::from_loc_eul_scale(positions[i], rotations[i], scales[i]); + instance_ids[i] = id_attribute[i]; + } + }); + } + else { + const int seed = params.get_input<int>("Seed"); + Array<uint32_t> ids = get_geometry_element_ids_as_uints(src_geometry, ATTR_DOMAIN_POINT); + parallel_for(IndexRange(domain_size), 1024, [&](IndexRange range) { + for (const int i : range) { + const int index = BLI_hash_int_2d(ids[i], seed) % possible_handles.size(); + const int handle = possible_handles[index]; + handles[i] = handle; + transforms[i] = float4x4::from_loc_eul_scale(positions[i], rotations[i], scales[i]); + instance_ids[i] = id_attribute[i]; + } + }); } } @@ -199,28 +212,37 @@ static void geo_node_point_instance_exec(GeoNodeExecParams params) * rather than making the entire input geometry set real. */ geometry_set = geometry_set_realize_instances(geometry_set); + const Vector<InstanceReference> possible_references = get_instance_references(params); + if (possible_references.is_empty()) { + params.set_output("Geometry", std::move(geometry_set_out)); + return; + } + InstancesComponent &instances = geometry_set_out.get_component_for_write<InstancesComponent>(); + Array<int> possible_handles = add_instance_references(instances, possible_references); + if (geometry_set.has<MeshComponent>()) { - add_instances_from_geometry_component( - instances, *geometry_set.get_component_for_read<MeshComponent>(), params); + add_instances_from_component(instances, + *geometry_set.get_component_for_read<MeshComponent>(), + possible_handles, + params); } if (geometry_set.has<PointCloudComponent>()) { - add_instances_from_geometry_component( - instances, *geometry_set.get_component_for_read<PointCloudComponent>(), params); + add_instances_from_component(instances, + *geometry_set.get_component_for_read<PointCloudComponent>(), + possible_handles, + params); + } + if (geometry_set.has<CurveComponent>()) { + add_instances_from_component(instances, + *geometry_set.get_component_for_read<CurveComponent>(), + possible_handles, + params); } params.set_output("Geometry", std::move(geometry_set_out)); } -static void geo_node_point_instance_init(bNodeTree *UNUSED(tree), bNode *node) -{ - NodeGeometryPointInstance *data = (NodeGeometryPointInstance *)MEM_callocN( - sizeof(NodeGeometryPointInstance), __func__); - data->instance_type = GEO_NODE_POINT_INSTANCE_TYPE_OBJECT; - data->flag |= GEO_NODE_POINT_INSTANCE_WHOLE_COLLECTION; - node->storage = data; -} - } // namespace blender::nodes void register_node_type_geo_point_instance() diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc index 2e7fce6ea30..828d3f50551 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc @@ -59,9 +59,43 @@ static void geo_node_point_rotate_layout(uiLayout *layout, bContext *UNUSED(C), namespace blender::nodes { +static void geo_node_point_rotate_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryRotatePoints *node_storage = (NodeGeometryRotatePoints *)MEM_callocN( + sizeof(NodeGeometryRotatePoints), __func__); + + node_storage->type = GEO_NODE_POINT_ROTATE_TYPE_EULER; + node_storage->space = GEO_NODE_POINT_ROTATE_SPACE_OBJECT; + node_storage->input_type_axis = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + node_storage->input_type_angle = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; + node_storage->input_type_rotation = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + + node->storage = node_storage; +} + +static void geo_node_point_rotate_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryRotatePoints *node_storage = (NodeGeometryRotatePoints *)node->storage; + update_attribute_input_socket_availabilities( + *node, + "Axis", + (GeometryNodeAttributeInputMode)node_storage->input_type_axis, + node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE); + update_attribute_input_socket_availabilities( + *node, + "Angle", + (GeometryNodeAttributeInputMode)node_storage->input_type_angle, + node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE); + update_attribute_input_socket_availabilities( + *node, + "Rotation", + (GeometryNodeAttributeInputMode)node_storage->input_type_rotation, + node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_EULER); +} + static void point_rotate__axis_angle__object_space(const int domain_size, - const Float3ReadAttribute &axis, - const FloatReadAttribute &angles, + const VArray<float3> &axis, + const VArray<float> &angles, MutableSpan<float3> rotations) { for (const int i : IndexRange(domain_size)) { @@ -76,8 +110,8 @@ static void point_rotate__axis_angle__object_space(const int domain_size, } static void point_rotate__axis_angle__point_space(const int domain_size, - const Float3ReadAttribute &axis, - const FloatReadAttribute &angles, + const VArray<float3> &axis, + const VArray<float> &angles, MutableSpan<float3> rotations) { for (const int i : IndexRange(domain_size)) { @@ -92,7 +126,7 @@ static void point_rotate__axis_angle__point_space(const int domain_size, } static void point_rotate__euler__object_space(const int domain_size, - const Float3ReadAttribute &eulers, + const VArray<float3> &eulers, MutableSpan<float3> rotations) { for (const int i : IndexRange(domain_size)) { @@ -107,7 +141,7 @@ static void point_rotate__euler__object_space(const int domain_size, } static void point_rotate__euler__point_space(const int domain_size, - const Float3ReadAttribute &eulers, + const VArray<float3> &eulers, MutableSpan<float3> rotations) { for (const int i : IndexRange(domain_size)) { @@ -127,19 +161,19 @@ static void point_rotate_on_component(GeometryComponent &component, const bNode &node = params.node(); const NodeGeometryRotatePoints &storage = *(const NodeGeometryRotatePoints *)node.storage; - OutputAttributePtr rotation_attribute = component.attribute_try_get_for_output( - "rotation", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3); + OutputAttribute_Typed<float3> rotation_attribute = + component.attribute_try_get_for_output<float3>("rotation", ATTR_DOMAIN_POINT, {0, 0, 0}); if (!rotation_attribute) { return; } - MutableSpan<float3> rotations = rotation_attribute->get_span<float3>(); + MutableSpan<float3> rotations = rotation_attribute.as_span(); const int domain_size = rotations.size(); if (storage.type == GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE) { - Float3ReadAttribute axis = params.get_input_attribute<float3>( + GVArray_Typed<float3> axis = params.get_input_attribute<float3>( "Axis", component, ATTR_DOMAIN_POINT, {0, 0, 1}); - FloatReadAttribute angles = params.get_input_attribute<float>( + GVArray_Typed<float> angles = params.get_input_attribute<float>( "Angle", component, ATTR_DOMAIN_POINT, 0); if (storage.space == GEO_NODE_POINT_ROTATE_SPACE_OBJECT) { @@ -150,7 +184,7 @@ static void point_rotate_on_component(GeometryComponent &component, } } else { - Float3ReadAttribute eulers = params.get_input_attribute<float3>( + GVArray_Typed<float3> eulers = params.get_input_attribute<float3>( "Rotation", component, ATTR_DOMAIN_POINT, {0, 0, 0}); if (storage.space == GEO_NODE_POINT_ROTATE_SPACE_OBJECT) { @@ -161,7 +195,7 @@ static void point_rotate_on_component(GeometryComponent &component, } } - rotation_attribute.apply_span_and_save(); + rotation_attribute.save(); } static void geo_node_point_rotate_exec(GeoNodeExecParams params) @@ -176,44 +210,13 @@ static void geo_node_point_rotate_exec(GeoNodeExecParams params) if (geometry_set.has<PointCloudComponent>()) { point_rotate_on_component(geometry_set.get_component_for_write<PointCloudComponent>(), params); } + if (geometry_set.has<CurveComponent>()) { + point_rotate_on_component(geometry_set.get_component_for_write<CurveComponent>(), params); + } params.set_output("Geometry", geometry_set); } -static void geo_node_point_rotate_init(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryRotatePoints *node_storage = (NodeGeometryRotatePoints *)MEM_callocN( - sizeof(NodeGeometryRotatePoints), __func__); - - node_storage->type = GEO_NODE_POINT_ROTATE_TYPE_EULER; - node_storage->space = GEO_NODE_POINT_ROTATE_SPACE_OBJECT; - node_storage->input_type_axis = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; - node_storage->input_type_angle = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; - node_storage->input_type_rotation = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; - - node->storage = node_storage; -} - -static void geo_node_point_rotate_update(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryRotatePoints *node_storage = (NodeGeometryRotatePoints *)node->storage; - update_attribute_input_socket_availabilities( - *node, - "Axis", - (GeometryNodeAttributeInputMode)node_storage->input_type_axis, - node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE); - update_attribute_input_socket_availabilities( - *node, - "Angle", - (GeometryNodeAttributeInputMode)node_storage->input_type_angle, - node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE); - update_attribute_input_socket_availabilities( - *node, - "Rotation", - (GeometryNodeAttributeInputMode)node_storage->input_type_rotation, - node_storage->type == GEO_NODE_POINT_ROTATE_TYPE_EULER); -} - } // namespace blender::nodes void register_node_type_geo_point_rotate() diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc b/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc index 113e2c620f6..655f5475856 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc @@ -43,6 +43,23 @@ static void geo_node_point_scale_layout(uiLayout *layout, bContext *UNUSED(C), P namespace blender::nodes { +static void geo_node_point_scale_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryPointScale *data = (NodeGeometryPointScale *)MEM_callocN( + sizeof(NodeGeometryPointScale), __func__); + + data->input_type = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; + node->storage = data; +} + +static void geo_node_point_scale_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryPointScale &node_storage = *(NodeGeometryPointScale *)node->storage; + + update_attribute_input_socket_availabilities( + *node, "Factor", (GeometryNodeAttributeInputMode)node_storage.input_type); +} + static void execute_on_component(GeoNodeExecParams params, GeometryComponent &component) { /* Note that scale doesn't necessarily need to be created with a vector type-- it could also use @@ -50,7 +67,7 @@ static void execute_on_component(GeoNodeExecParams params, GeometryComponent &co * for the factor. But for it's simpler to simply always use float3, since that is usually * expected anyway. */ static const float3 scale_default = float3(1.0f); - OutputAttributePtr scale_attribute = component.attribute_try_get_for_output( + OutputAttribute_Typed<float3> scale_attribute = component.attribute_try_get_for_output( "scale", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, &scale_default); if (!scale_attribute) { return; @@ -63,27 +80,27 @@ static void execute_on_component(GeoNodeExecParams params, GeometryComponent &co const CustomDataType data_type = (input_type == GEO_NODE_ATTRIBUTE_INPUT_FLOAT) ? CD_PROP_FLOAT : CD_PROP_FLOAT3; - ReadAttributePtr attribute = params.get_input_attribute( + GVArrayPtr attribute = params.get_input_attribute( "Factor", component, ATTR_DOMAIN_POINT, data_type, nullptr); if (!attribute) { return; } - MutableSpan<float3> scale_span = scale_attribute->get_span<float3>(); + MutableSpan<float3> scale_span = scale_attribute.as_span(); if (data_type == CD_PROP_FLOAT) { - Span<float> factors = attribute->get_span<float>(); + GVArray_Typed<float> factors{*attribute}; for (const int i : scale_span.index_range()) { scale_span[i] = scale_span[i] * factors[i]; } } else if (data_type == CD_PROP_FLOAT3) { - Span<float3> factors = attribute->get_span<float3>(); + GVArray_Typed<float3> factors{*attribute}; for (const int i : scale_span.index_range()) { scale_span[i] = scale_span[i] * factors[i]; } } - scale_attribute.apply_span_and_save(); + scale_attribute.save(); } static void geo_node_point_scale_exec(GeoNodeExecParams params) @@ -102,23 +119,6 @@ static void geo_node_point_scale_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set)); } -static void geo_node_point_scale_init(bNodeTree *UNUSED(tree), bNode *node) -{ - NodeGeometryPointScale *data = (NodeGeometryPointScale *)MEM_callocN( - sizeof(NodeGeometryPointScale), __func__); - - data->input_type = GEO_NODE_ATTRIBUTE_INPUT_VECTOR; - node->storage = data; -} - -static void geo_node_point_scale_update(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryPointScale &node_storage = *(NodeGeometryPointScale *)node->storage; - - update_attribute_input_socket_availabilities( - *node, "Factor", (GeometryNodeAttributeInputMode)node_storage.input_type); -} - } // namespace blender::nodes void register_node_type_geo_point_scale() diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc index 522dea4aa0e..312ca5b8c33 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc @@ -58,27 +58,27 @@ static void copy_attributes_based_on_mask(const GeometryComponent &in_component, const bool invert) { for (const std::string &name : in_component.attribute_names()) { - ReadAttributePtr attribute = in_component.attribute_try_get_for_read(name); - const CustomDataType data_type = attribute->custom_data_type(); + ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(name); + const CustomDataType data_type = bke::cpp_type_to_custom_data_type(attribute.varray->type()); /* Only copy point attributes. Theoretically this could interpolate attributes on other * domains to the point domain, but that would conflict with attributes that are built-in * on other domains, which causes creating the attributes to fail. */ - if (attribute->domain() != ATTR_DOMAIN_POINT) { + if (attribute.domain != ATTR_DOMAIN_POINT) { continue; } - OutputAttributePtr result_attribute = result_component.attribute_try_get_for_output( + OutputAttribute result_attribute = result_component.attribute_try_get_for_output_only( name, ATTR_DOMAIN_POINT, data_type); attribute_math::convert_to_static_type(data_type, [&](auto dummy) { using T = decltype(dummy); - Span<T> span = attribute->get_span<T>(); - MutableSpan<T> out_span = result_attribute->get_span_for_write_only<T>(); + GVArray_Span<T> span{*attribute.varray}; + MutableSpan<T> out_span = result_attribute.as_span<T>(); copy_data_based_on_mask(span, masks, invert, out_span); }); - result_attribute.apply_span_and_save(); + result_attribute.save(); } } @@ -107,9 +107,9 @@ static void separate_points_from_component(const GeometryComponent &in_component return; } - const BooleanReadAttribute mask_attribute = in_component.attribute_get_for_read<bool>( + const GVArray_Typed<bool> mask_attribute = in_component.attribute_get_for_read<bool>( mask_name, ATTR_DOMAIN_POINT, false); - Span<bool> masks = mask_attribute.get_span(); + VArray_Span<bool> masks{mask_attribute}; const int total = masks.count(!invert); if (total == 0) { @@ -127,6 +127,10 @@ static GeometrySet separate_geometry_set(const GeometrySet &set_in, { GeometrySet set_out; for (const GeometryComponent *component : set_in.get_components_for_read()) { + if (component->type() == GEO_COMPONENT_TYPE_CURVE) { + /* Don't support the curve component for now, even though it has a point domain. */ + continue; + } GeometryComponent &out_component = set_out.get_component_for_write(component->type()); separate_points_from_component(*component, out_component, mask_name, invert); } @@ -135,15 +139,27 @@ static GeometrySet separate_geometry_set(const GeometrySet &set_in, static void geo_node_point_separate_exec(GeoNodeExecParams params) { - const std::string mask_attribute_name = params.extract_input<std::string>("Mask"); - GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + bool wait_for_inputs = false; + wait_for_inputs |= params.lazy_require_input("Geometry"); + wait_for_inputs |= params.lazy_require_input("Mask"); + if (wait_for_inputs) { + return; + } + const std::string mask_attribute_name = params.get_input<std::string>("Mask"); + GeometrySet geometry_set = params.get_input<GeometrySet>("Geometry"); /* TODO: This is not necessary-- the input geometry set can be read only, * but it must be rewritten to handle instance groups. */ geometry_set = geometry_set_realize_instances(geometry_set); - params.set_output("Geometry 1", separate_geometry_set(geometry_set, mask_attribute_name, true)); - params.set_output("Geometry 2", separate_geometry_set(geometry_set, mask_attribute_name, false)); + if (params.lazy_output_is_required("Geometry 1")) { + params.set_output("Geometry 1", + separate_geometry_set(geometry_set, mask_attribute_name, true)); + } + if (params.lazy_output_is_required("Geometry 2")) { + params.set_output("Geometry 2", + separate_geometry_set(geometry_set, mask_attribute_name, false)); + } } } // namespace blender::nodes @@ -155,5 +171,6 @@ void register_node_type_geo_point_separate() geo_node_type_base(&ntype, GEO_NODE_POINT_SEPARATE, "Point Separate", NODE_CLASS_GEOMETRY, 0); node_type_socket_templates(&ntype, geo_node_point_instance_in, geo_node_point_instance_out); ntype.geometry_node_execute = blender::nodes::geo_node_point_separate_exec; + ntype.geometry_node_execute_supports_laziness = true; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc index 8c7387f7d9b..44203228899 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc @@ -42,24 +42,19 @@ namespace blender::nodes { static void execute_on_component(GeoNodeExecParams params, GeometryComponent &component) { - OutputAttributePtr position_attribute = component.attribute_try_get_for_output( - "position", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3); + OutputAttribute_Typed<float3> position_attribute = + component.attribute_try_get_for_output<float3>("position", ATTR_DOMAIN_POINT, {0, 0, 0}); if (!position_attribute) { return; } - ReadAttributePtr attribute = params.get_input_attribute( - "Translation", component, ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, nullptr); - if (!attribute) { - return; - } + GVArray_Typed<float3> attribute = params.get_input_attribute<float3>( + "Translation", component, ATTR_DOMAIN_POINT, {0, 0, 0}); - Span<float3> data = attribute->get_span<float3>(); - MutableSpan<float3> scale_span = position_attribute->get_span<float3>(); - for (const int i : scale_span.index_range()) { - scale_span[i] = scale_span[i] + data[i]; + for (const int i : IndexRange(attribute.size())) { + position_attribute->set(i, position_attribute->get(i) + attribute[i]); } - position_attribute.apply_span_and_save(); + position_attribute.save(); } static void geo_node_point_translate_exec(GeoNodeExecParams params) diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc index a9eb136597e..65306b1c452 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc @@ -55,6 +55,35 @@ static void geo_node_points_to_volume_layout(uiLayout *layout, namespace blender::nodes { +static void geo_node_points_to_volume_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryPointsToVolume *data = (NodeGeometryPointsToVolume *)MEM_callocN( + sizeof(NodeGeometryPointsToVolume), __func__); + data->resolution_mode = GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT; + data->input_type_radius = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; + node->storage = data; + + bNodeSocket *radius_attribute_socket = nodeFindSocket(node, SOCK_IN, "Radius"); + bNodeSocketValueString *radius_attribute_socket_value = + (bNodeSocketValueString *)radius_attribute_socket->default_value; + STRNCPY(radius_attribute_socket_value->value, "radius"); +} + +static void geo_node_points_to_volume_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryPointsToVolume *data = (NodeGeometryPointsToVolume *)node->storage; + bNodeSocket *voxel_size_socket = nodeFindSocket(node, SOCK_IN, "Voxel Size"); + bNodeSocket *voxel_amount_socket = nodeFindSocket(node, SOCK_IN, "Voxel Amount"); + nodeSetSocketAvailability(voxel_amount_socket, + data->resolution_mode == + GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT); + nodeSetSocketAvailability( + voxel_size_socket, data->resolution_mode == GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_SIZE); + + update_attribute_input_socket_availabilities( + *node, "Radius", (GeometryNodeAttributeInputMode)data->input_type_radius); +} + #ifdef WITH_OPENVDB namespace { /* Implements the interface required by #openvdb::tools::ParticlesToLevelSet. */ @@ -147,13 +176,15 @@ static void gather_point_data_from_component(const GeoNodeExecParams ¶ms, Vector<float3> &r_positions, Vector<float> &r_radii) { - Float3ReadAttribute positions = component.attribute_get_for_read<float3>( + GVArray_Typed<float3> positions = component.attribute_get_for_read<float3>( "position", ATTR_DOMAIN_POINT, {0, 0, 0}); - FloatReadAttribute radii = params.get_input_attribute<float>( + GVArray_Typed<float> radii = params.get_input_attribute<float>( "Radius", component, ATTR_DOMAIN_POINT, 0.0f); - r_positions.extend(positions.get_span()); - r_radii.extend(radii.get_span()); + for (const int i : IndexRange(positions.size())) { + r_positions.append(positions[i]); + r_radii.append(radii[i]); + } } static void convert_to_grid_index_space(const float voxel_size, @@ -184,6 +215,10 @@ static void initialize_volume_component_from_points(const GeometrySet &geometry_ gather_point_data_from_component( params, *geometry_set_in.get_component_for_read<PointCloudComponent>(), positions, radii); } + if (geometry_set_in.has<CurveComponent>()) { + gather_point_data_from_component( + params, *geometry_set_in.get_component_for_read<CurveComponent>(), positions, radii); + } const float max_radius = *std::max_element(radii.begin(), radii.end()); const float voxel_size = compute_voxel_size(params, positions, max_radius); @@ -225,35 +260,6 @@ static void geo_node_points_to_volume_exec(GeoNodeExecParams params) params.set_output("Geometry", std::move(geometry_set_out)); } -static void geo_node_points_to_volume_init(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryPointsToVolume *data = (NodeGeometryPointsToVolume *)MEM_callocN( - sizeof(NodeGeometryPointsToVolume), __func__); - data->resolution_mode = GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT; - data->input_type_radius = GEO_NODE_ATTRIBUTE_INPUT_FLOAT; - node->storage = data; - - bNodeSocket *radius_attribute_socket = nodeFindSocket(node, SOCK_IN, "Radius"); - bNodeSocketValueString *radius_attribute_socket_value = - (bNodeSocketValueString *)radius_attribute_socket->default_value; - STRNCPY(radius_attribute_socket_value->value, "radius"); -} - -static void geo_node_points_to_volume_update(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryPointsToVolume *data = (NodeGeometryPointsToVolume *)node->storage; - bNodeSocket *voxel_size_socket = nodeFindSocket(node, SOCK_IN, "Voxel Size"); - bNodeSocket *voxel_amount_socket = nodeFindSocket(node, SOCK_IN, "Voxel Amount"); - nodeSetSocketAvailability(voxel_amount_socket, - data->resolution_mode == - GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_AMOUNT); - nodeSetSocketAvailability( - voxel_size_socket, data->resolution_mode == GEO_NODE_POINTS_TO_VOLUME_RESOLUTION_MODE_SIZE); - - update_attribute_input_socket_availabilities( - *node, "Radius", (GeometryNodeAttributeInputMode)data->input_type_radius); -} - } // namespace blender::nodes void register_node_type_geo_points_to_volume() diff --git a/source/blender/nodes/geometry/nodes/node_geo_switch.cc b/source/blender/nodes/geometry/nodes/node_geo_switch.cc new file mode 100644 index 00000000000..049ba5d3143 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_switch.cc @@ -0,0 +1,177 @@ +/* + * 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 "node_geometry_util.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +static bNodeSocketTemplate geo_node_switch_in[] = { + {SOCK_BOOLEAN, N_("Switch")}, + + {SOCK_FLOAT, N_("A"), 0.0, 0.0, 0.0, 0.0, -FLT_MAX, FLT_MAX}, + {SOCK_FLOAT, N_("B"), 0.0, 0.0, 0.0, 0.0, -FLT_MAX, FLT_MAX}, + {SOCK_INT, N_("A"), 0, 0, 0, 0, -100000, 100000}, + {SOCK_INT, N_("B"), 0, 0, 0, 0, -100000, 100000}, + {SOCK_BOOLEAN, N_("A")}, + {SOCK_BOOLEAN, N_("B")}, + {SOCK_VECTOR, N_("A"), 0.0, 0.0, 0.0, 0.0, -FLT_MAX, FLT_MAX}, + {SOCK_VECTOR, N_("B"), 0.0, 0.0, 0.0, 0.0, -FLT_MAX, FLT_MAX}, + {SOCK_RGBA, N_("A"), 0.8, 0.8, 0.8, 1.0}, + {SOCK_RGBA, N_("B"), 0.8, 0.8, 0.8, 1.0}, + {SOCK_STRING, N_("A")}, + {SOCK_STRING, N_("B")}, + {SOCK_GEOMETRY, N_("A")}, + {SOCK_GEOMETRY, N_("B")}, + {SOCK_OBJECT, N_("A")}, + {SOCK_OBJECT, N_("B")}, + {SOCK_COLLECTION, N_("A")}, + {SOCK_COLLECTION, N_("B")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_switch_out[] = { + {SOCK_FLOAT, N_("Output")}, + {SOCK_INT, N_("Output")}, + {SOCK_BOOLEAN, N_("Output")}, + {SOCK_VECTOR, N_("Output")}, + {SOCK_RGBA, N_("Output")}, + {SOCK_STRING, N_("Output")}, + {SOCK_GEOMETRY, N_("Output")}, + {SOCK_OBJECT, N_("Output")}, + {SOCK_COLLECTION, N_("Output")}, + {-1, ""}, +}; + +static void geo_node_switch_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "input_type", 0, "", ICON_NONE); +} + +static void geo_node_switch_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeSwitch *data = (NodeSwitch *)MEM_callocN(sizeof(NodeSwitch), __func__); + data->input_type = SOCK_FLOAT; + node->storage = data; +} + +namespace blender::nodes { + +static void geo_node_switch_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeSwitch *node_storage = (NodeSwitch *)node->storage; + int index = 0; + LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { + nodeSetSocketAvailability( + socket, index == 0 || socket->type == (eNodeSocketDatatype)node_storage->input_type); + index++; + } + LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) { + nodeSetSocketAvailability(socket, + socket->type == (eNodeSocketDatatype)node_storage->input_type); + } +} + +template<typename T> +static void output_input(GeoNodeExecParams ¶ms, + const bool input, + const StringRef input_suffix, + const StringRef output_identifier) +{ + const std::string name_a = "A" + input_suffix; + const std::string name_b = "B" + input_suffix; + if (input) { + params.set_input_unused(name_a); + if (params.lazy_require_input(name_b)) { + return; + } + params.set_output(output_identifier, params.extract_input<T>(name_b)); + } + else { + params.set_input_unused(name_b); + if (params.lazy_require_input(name_a)) { + return; + } + params.set_output(output_identifier, params.extract_input<T>(name_a)); + } +} + +static void geo_node_switch_exec(GeoNodeExecParams params) +{ + if (params.lazy_require_input("Switch")) { + return; + } + const NodeSwitch &storage = *(const NodeSwitch *)params.node().storage; + const bool input = params.get_input<bool>("Switch"); + switch ((eNodeSocketDatatype)storage.input_type) { + case SOCK_FLOAT: { + output_input<float>(params, input, "", "Output"); + break; + } + case SOCK_INT: { + output_input<int>(params, input, "_001", "Output_001"); + break; + } + case SOCK_BOOLEAN: { + output_input<bool>(params, input, "_002", "Output_002"); + break; + } + case SOCK_VECTOR: { + output_input<float3>(params, input, "_003", "Output_003"); + break; + } + case SOCK_RGBA: { + output_input<ColorGeometry4f>(params, input, "_004", "Output_004"); + break; + } + case SOCK_STRING: { + output_input<std::string>(params, input, "_005", "Output_005"); + break; + } + case SOCK_GEOMETRY: { + output_input<GeometrySet>(params, input, "_006", "Output_006"); + break; + } + case SOCK_OBJECT: { + output_input<Object *>(params, input, "_007", "Output_007"); + break; + } + case SOCK_COLLECTION: { + output_input<Collection *>(params, input, "_008", "Output_008"); + break; + } + default: + BLI_assert_unreachable(); + break; + } +} + +} // namespace blender::nodes + +void register_node_type_geo_switch() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_SWITCH, "Switch", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates(&ntype, geo_node_switch_in, geo_node_switch_out); + node_type_init(&ntype, geo_node_switch_init); + node_type_update(&ntype, blender::nodes::geo_node_switch_update); + node_type_storage(&ntype, "NodeSwitch", node_free_standard_storage, node_copy_standard_storage); + ntype.geometry_node_execute = blender::nodes::geo_node_switch_exec; + ntype.geometry_node_execute_supports_laziness = true; + ntype.draw_buttons = geo_node_switch_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_transform.cc b/source/blender/nodes/geometry/nodes/node_geo_transform.cc index d54982d16c2..9714a4f8a80 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_transform.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_transform.cc @@ -24,6 +24,7 @@ #include "DNA_volume_types.h" #include "BKE_mesh.h" +#include "BKE_spline.hh" #include "BKE_volume.h" #include "DEG_depsgraph_query.h" @@ -100,7 +101,7 @@ static void transform_instances(InstancesComponent &instances, const float3 rotation, const float3 scale) { - MutableSpan<float4x4> transforms = instances.transforms(); + MutableSpan<float4x4> transforms = instances.instance_transforms(); /* Use only translation if rotation and scale don't apply. */ if (use_translate(rotation, scale)) { @@ -152,6 +153,21 @@ static void transform_volume(Volume *volume, #endif } +static void transform_curve(CurveEval &curve, + const float3 translation, + const float3 rotation, + const float3 scale) +{ + + if (use_translate(rotation, scale)) { + curve.translate(translation); + } + else { + const float4x4 matrix = float4x4::from_loc_eul_scale(translation, rotation, scale); + curve.transform(matrix); + } +} + static void geo_node_transform_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); @@ -163,21 +179,22 @@ static void geo_node_transform_exec(GeoNodeExecParams params) Mesh *mesh = geometry_set.get_mesh_for_write(); transform_mesh(mesh, translation, rotation, scale); } - if (geometry_set.has_pointcloud()) { PointCloud *pointcloud = geometry_set.get_pointcloud_for_write(); transform_pointcloud(pointcloud, translation, rotation, scale); } - if (geometry_set.has_instances()) { InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); transform_instances(instances, translation, rotation, scale); } - if (geometry_set.has_volume()) { Volume *volume = geometry_set.get_volume_for_write(); transform_volume(volume, translation, rotation, scale, params); } + if (geometry_set.has_curve()) { + CurveEval *curve = geometry_set.get_curve_for_write(); + transform_curve(*curve, translation, rotation, scale); + } params.set_output("Geometry", std::move(geometry_set)); } diff --git a/source/blender/nodes/intern/node_geometry_exec.cc b/source/blender/nodes/intern/node_geometry_exec.cc index a4fb99a988e..188d198e159 100644 --- a/source/blender/nodes/intern/node_geometry_exec.cc +++ b/source/blender/nodes/intern/node_geometry_exec.cc @@ -22,6 +22,7 @@ #include "NOD_geometry_exec.hh" #include "NOD_type_callbacks.hh" +#include "NOD_type_conversions.hh" #include "node_geometry_util.hh" @@ -29,22 +30,22 @@ namespace blender::nodes { void GeoNodeExecParams::error_message_add(const NodeWarningType type, std::string message) const { - bNodeTree *btree_cow = node_->btree(); + bNodeTree *btree_cow = provider_->dnode->btree(); BLI_assert(btree_cow != nullptr); if (btree_cow == nullptr) { return; } bNodeTree *btree_original = (bNodeTree *)DEG_get_original_id((ID *)btree_cow); - const NodeTreeEvaluationContext context(*self_object_, *modifier_); + const NodeTreeEvaluationContext context(*provider_->self_object, *provider_->modifier); BKE_nodetree_error_message_add( - *btree_original, context, *node_->bnode(), type, std::move(message)); + *btree_original, context, *provider_->dnode->bnode(), type, std::move(message)); } const bNodeSocket *GeoNodeExecParams::find_available_socket(const StringRef name) const { - for (const InputSocketRef *socket : node_->inputs()) { + for (const InputSocketRef *socket : provider_->dnode->inputs()) { if (socket->is_available() && socket->name() == name) { return socket->bsocket(); } @@ -53,22 +54,29 @@ const bNodeSocket *GeoNodeExecParams::find_available_socket(const StringRef name return nullptr; } -ReadAttributePtr GeoNodeExecParams::get_input_attribute(const StringRef name, - const GeometryComponent &component, - const AttributeDomain domain, - const CustomDataType type, - const void *default_value) const +GVArrayPtr GeoNodeExecParams::get_input_attribute(const StringRef name, + const GeometryComponent &component, + const AttributeDomain domain, + const CustomDataType type, + const void *default_value) const { const bNodeSocket *found_socket = this->find_available_socket(name); BLI_assert(found_socket != nullptr); /* There should always be available socket for the name. */ + const CPPType *cpp_type = bke::custom_data_type_to_cpp_type(type); + const int64_t domain_size = component.attribute_domain_size(domain); + + if (default_value == nullptr) { + default_value = cpp_type->default_value(); + } + if (found_socket == nullptr) { - return component.attribute_get_constant_for_read(domain, type, default_value); + return std::make_unique<fn::GVArray_For_SingleValue>(*cpp_type, domain_size, default_value); } if (found_socket->type == SOCK_STRING) { const std::string name = this->get_input<std::string>(found_socket->identifier); /* Try getting the attribute without the default value. */ - ReadAttributePtr attribute = component.attribute_try_get_for_read(name, domain, type); + GVArrayPtr attribute = component.attribute_try_get_for_read(name, domain, type); if (attribute) { return attribute; } @@ -80,25 +88,30 @@ ReadAttributePtr GeoNodeExecParams::get_input_attribute(const StringRef name, this->error_message_add(NodeWarningType::Error, TIP_("No attribute with name \"") + name + "\""); } - return component.attribute_get_constant_for_read(domain, type, default_value); + return std::make_unique<fn::GVArray_For_SingleValue>(*cpp_type, domain_size, default_value); } + const DataTypeConversions &conversions = get_implicit_type_conversions(); if (found_socket->type == SOCK_FLOAT) { const float value = this->get_input<float>(found_socket->identifier); - return component.attribute_get_constant_for_read_converted( - domain, CD_PROP_FLOAT, type, &value); + BUFFER_FOR_CPP_TYPE_VALUE(*cpp_type, buffer); + conversions.convert_to_uninitialized(CPPType::get<float>(), *cpp_type, &value, buffer); + return std::make_unique<fn::GVArray_For_SingleValue>(*cpp_type, domain_size, buffer); } if (found_socket->type == SOCK_VECTOR) { const float3 value = this->get_input<float3>(found_socket->identifier); - return component.attribute_get_constant_for_read_converted( - domain, CD_PROP_FLOAT3, type, &value); + BUFFER_FOR_CPP_TYPE_VALUE(*cpp_type, buffer); + conversions.convert_to_uninitialized(CPPType::get<float3>(), *cpp_type, &value, buffer); + return std::make_unique<fn::GVArray_For_SingleValue>(*cpp_type, domain_size, buffer); } if (found_socket->type == SOCK_RGBA) { - const Color4f value = this->get_input<Color4f>(found_socket->identifier); - return component.attribute_get_constant_for_read_converted( - domain, CD_PROP_COLOR, type, &value); + const ColorGeometry4f value = this->get_input<ColorGeometry4f>(found_socket->identifier); + BUFFER_FOR_CPP_TYPE_VALUE(*cpp_type, buffer); + conversions.convert_to_uninitialized( + CPPType::get<ColorGeometry4f>(), *cpp_type, &value, buffer); + return std::make_unique<fn::GVArray_For_SingleValue>(*cpp_type, domain_size, buffer); } BLI_assert(false); - return component.attribute_get_constant_for_read(domain, type, default_value); + return std::make_unique<fn::GVArray_For_SingleValue>(*cpp_type, domain_size, default_value); } CustomDataType GeoNodeExecParams::get_input_attribute_data_type( @@ -114,11 +127,11 @@ CustomDataType GeoNodeExecParams::get_input_attribute_data_type( if (found_socket->type == SOCK_STRING) { const std::string name = this->get_input<std::string>(found_socket->identifier); - ReadAttributePtr attribute = component.attribute_try_get_for_read(name); - if (!attribute) { - return default_type; + std::optional<AttributeMetaData> info = component.attribute_get_meta_data(name); + if (info) { + return info->data_type; } - return attribute->custom_data_type(); + return default_type; } if (found_socket->type == SOCK_FLOAT) { return CD_PROP_FLOAT; @@ -157,9 +170,9 @@ AttributeDomain GeoNodeExecParams::get_highest_priority_input_domain( if (found_socket->type == SOCK_STRING) { const std::string name = this->get_input<std::string>(found_socket->identifier); - ReadAttributePtr attribute = component.attribute_try_get_for_read(name); - if (attribute) { - input_domains.append(attribute->domain()); + std::optional<AttributeMetaData> info = component.attribute_get_meta_data(name); + if (info) { + input_domains.append(info->domain); } } } @@ -171,11 +184,11 @@ AttributeDomain GeoNodeExecParams::get_highest_priority_input_domain( return default_domain; } -void GeoNodeExecParams::check_extract_input(StringRef identifier, - const CPPType *requested_type) const +void GeoNodeExecParams::check_input_access(StringRef identifier, + const CPPType *requested_type) const { bNodeSocket *found_socket = nullptr; - for (const InputSocketRef *socket : node_->inputs()) { + for (const InputSocketRef *socket : provider_->dnode->inputs()) { if (socket->identifier() == identifier) { found_socket = socket->bsocket(); break; @@ -185,39 +198,39 @@ void GeoNodeExecParams::check_extract_input(StringRef identifier, if (found_socket == nullptr) { std::cout << "Did not find an input socket with the identifier '" << identifier << "'.\n"; std::cout << "Possible identifiers are: "; - for (const InputSocketRef *socket : node_->inputs()) { + for (const InputSocketRef *socket : provider_->dnode->inputs()) { if (socket->is_available()) { std::cout << "'" << socket->identifier() << "', "; } } std::cout << "\n"; - BLI_assert(false); + BLI_assert_unreachable(); } else if (found_socket->flag & SOCK_UNAVAIL) { std::cout << "The socket corresponding to the identifier '" << identifier << "' is disabled.\n"; - BLI_assert(false); + BLI_assert_unreachable(); } - else if (!input_values_.contains(identifier)) { + else if (!provider_->can_get_input(identifier)) { std::cout << "The identifier '" << identifier << "' is valid, but there is no value for it anymore.\n"; std::cout << "Most likely it has been extracted before.\n"; - BLI_assert(false); + BLI_assert_unreachable(); } else if (requested_type != nullptr) { const CPPType &expected_type = *socket_cpp_type_get(*found_socket->typeinfo); if (*requested_type != expected_type) { std::cout << "The requested type '" << requested_type->name() << "' is incorrect. Expected '" << expected_type.name() << "'.\n"; - BLI_assert(false); + BLI_assert_unreachable(); } } } -void GeoNodeExecParams::check_set_output(StringRef identifier, const CPPType &value_type) const +void GeoNodeExecParams::check_output_access(StringRef identifier, const CPPType &value_type) const { bNodeSocket *found_socket = nullptr; - for (const OutputSocketRef *socket : node_->outputs()) { + for (const OutputSocketRef *socket : provider_->dnode->outputs()) { if (socket->identifier() == identifier) { found_socket = socket->bsocket(); break; @@ -227,29 +240,29 @@ void GeoNodeExecParams::check_set_output(StringRef identifier, const CPPType &va if (found_socket == nullptr) { std::cout << "Did not find an output socket with the identifier '" << identifier << "'.\n"; std::cout << "Possible identifiers are: "; - for (const OutputSocketRef *socket : node_->outputs()) { + for (const OutputSocketRef *socket : provider_->dnode->outputs()) { if (socket->is_available()) { std::cout << "'" << socket->identifier() << "', "; } } std::cout << "\n"; - BLI_assert(false); + BLI_assert_unreachable(); } else if (found_socket->flag & SOCK_UNAVAIL) { std::cout << "The socket corresponding to the identifier '" << identifier << "' is disabled.\n"; - BLI_assert(false); + BLI_assert_unreachable(); } - else if (output_values_.contains(identifier)) { + else if (!provider_->can_set_output(identifier)) { std::cout << "The identifier '" << identifier << "' has been set already.\n"; - BLI_assert(false); + BLI_assert_unreachable(); } else { const CPPType &expected_type = *socket_cpp_type_get(*found_socket->typeinfo); if (value_type != expected_type) { std::cout << "The value type '" << value_type.name() << "' is incorrect. Expected '" << expected_type.name() << "'.\n"; - BLI_assert(false); + BLI_assert_unreachable(); } } } diff --git a/source/blender/nodes/intern/node_socket.cc b/source/blender/nodes/intern/node_socket.cc index b583523da98..052896d2f48 100644 --- a/source/blender/nodes/intern/node_socket.cc +++ b/source/blender/nodes/intern/node_socket.cc @@ -35,9 +35,9 @@ #include "BKE_geometry_set.hh" #include "BKE_lib_id.h" #include "BKE_node.h" -#include "BKE_persistent_data_handle.hh" #include "DNA_collection_types.h" +#include "DNA_material_types.h" #include "RNA_access.h" #include "RNA_types.h" @@ -294,6 +294,21 @@ void node_socket_init_default_value(bNodeSocket *sock) sock->default_value = dval; break; + } + case SOCK_TEXTURE: { + bNodeSocketValueTexture *dval = (bNodeSocketValueTexture *)MEM_callocN( + sizeof(bNodeSocketValueTexture), "node socket value texture"); + dval->value = nullptr; + + sock->default_value = dval; + break; + } + case SOCK_MATERIAL: { + bNodeSocketValueMaterial *dval = (bNodeSocketValueMaterial *)MEM_callocN( + sizeof(bNodeSocketValueMaterial), "node socket value material"); + dval->value = nullptr; + + sock->default_value = dval; break; } } @@ -375,6 +390,20 @@ void node_socket_copy_default_value(bNodeSocket *to, const bNodeSocket *from) id_us_plus(&toval->value->id); break; } + case SOCK_TEXTURE: { + bNodeSocketValueTexture *toval = (bNodeSocketValueTexture *)to->default_value; + bNodeSocketValueTexture *fromval = (bNodeSocketValueTexture *)from->default_value; + *toval = *fromval; + id_us_plus(&toval->value->id); + break; + } + case SOCK_MATERIAL: { + bNodeSocketValueMaterial *toval = (bNodeSocketValueMaterial *)to->default_value; + bNodeSocketValueMaterial *fromval = (bNodeSocketValueMaterial *)from->default_value; + *toval = *fromval; + id_us_plus(&toval->value->id); + break; + } } to->flag |= (from->flag & SOCK_HIDE_VALUE); @@ -616,9 +645,9 @@ static bNodeSocketType *make_socket_type_vector(PropertySubType subtype) static bNodeSocketType *make_socket_type_rgba() { bNodeSocketType *socktype = make_standard_socket_type(SOCK_RGBA, PROP_NONE); - socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<blender::Color4f>(); }; + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<blender::ColorGeometry4f>(); }; socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { - *(blender::Color4f *)r_value = ((bNodeSocketValueRGBA *)socket.default_value)->value; + *(blender::ColorGeometry4f *)r_value = ((bNodeSocketValueRGBA *)socket.default_value)->value; }; return socktype; } @@ -633,63 +662,17 @@ static bNodeSocketType *make_socket_type_string() return socktype; } -class ObjectSocketMultiFunction : public blender::fn::MultiFunction { - private: - Object *object_; - - public: - ObjectSocketMultiFunction(Object *object) : object_(object) - { - static blender::fn::MFSignature signature = create_signature(); - this->set_signature(&signature); - } - - static blender::fn::MFSignature create_signature() - { - blender::fn::MFSignatureBuilder signature{"Object Socket"}; - signature.depends_on_context(); - signature.single_output<blender::bke::PersistentObjectHandle>("Object"); - return signature.build(); - } - - void call(blender::IndexMask mask, - blender::fn::MFParams params, - blender::fn::MFContext context) const override - { - blender::MutableSpan output = - params.uninitialized_single_output<blender::bke::PersistentObjectHandle>(0, "Object"); - - /* Try to get a handle map, so that the object can be converted to a handle. */ - const blender::bke::PersistentDataHandleMap *handle_map = - context.get_global_context<blender::bke::PersistentDataHandleMap>( - "PersistentDataHandleMap"); - - if (handle_map == nullptr) { - /* Return empty handles when there is no handle map. */ - output.fill_indices(mask, blender::bke::PersistentObjectHandle()); - return; - } - - blender::bke::PersistentObjectHandle handle = handle_map->lookup(object_); - for (int64_t i : mask) { - output[i] = handle; - } - } -}; - -MAKE_CPP_TYPE(PersistentObjectHandle, blender::bke::PersistentObjectHandle); -MAKE_CPP_TYPE(PersistentCollectionHandle, blender::bke::PersistentCollectionHandle); +MAKE_CPP_TYPE(Object, Object *) +MAKE_CPP_TYPE(Collection, Collection *) +MAKE_CPP_TYPE(Texture, Tex *) +MAKE_CPP_TYPE(Material, Material *) static bNodeSocketType *make_socket_type_object() { bNodeSocketType *socktype = make_standard_socket_type(SOCK_OBJECT, PROP_NONE); - socktype->get_cpp_type = []() { - /* Objects are not passed along as raw pointers, but as handles. */ - return &blender::fn::CPPType::get<blender::bke::PersistentObjectHandle>(); - }; - socktype->expand_in_mf_network = [](blender::nodes::SocketMFNetworkBuilder &builder) { - Object *object = builder.socket_default_value<bNodeSocketValueObject>()->value; - builder.construct_generator_fn<ObjectSocketMultiFunction>(object); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<Object *>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(Object **)r_value = ((bNodeSocketValueObject *)socket.default_value)->value; }; return socktype; } @@ -707,9 +690,29 @@ static bNodeSocketType *make_socket_type_geometry() static bNodeSocketType *make_socket_type_collection() { bNodeSocketType *socktype = make_standard_socket_type(SOCK_COLLECTION, PROP_NONE); - socktype->get_cpp_type = []() { - /* Objects are not passed along as raw pointers, but as handles. */ - return &blender::fn::CPPType::get<blender::bke::PersistentCollectionHandle>(); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<Collection *>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(Collection **)r_value = ((bNodeSocketValueCollection *)socket.default_value)->value; + }; + return socktype; +} + +static bNodeSocketType *make_socket_type_texture() +{ + bNodeSocketType *socktype = make_standard_socket_type(SOCK_TEXTURE, PROP_NONE); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<Tex *>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(Tex **)r_value = ((bNodeSocketValueTexture *)socket.default_value)->value; + }; + return socktype; +} + +static bNodeSocketType *make_socket_type_material() +{ + bNodeSocketType *socktype = make_standard_socket_type(SOCK_MATERIAL, PROP_NONE); + socktype->get_cpp_type = []() { return &blender::fn::CPPType::get<Material *>(); }; + socktype->get_cpp_value = [](const bNodeSocket &socket, void *r_value) { + *(Material **)r_value = ((bNodeSocketValueMaterial *)socket.default_value)->value; }; return socktype; } @@ -755,5 +758,9 @@ void register_standard_node_socket_types(void) nodeRegisterSocketType(make_socket_type_collection()); + nodeRegisterSocketType(make_socket_type_texture()); + + nodeRegisterSocketType(make_socket_type_material()); + nodeRegisterSocketType(make_socket_type_virtual()); } diff --git a/source/blender/nodes/intern/node_tree_ref.cc b/source/blender/nodes/intern/node_tree_ref.cc index e42572b9cb7..8699736e543 100644 --- a/source/blender/nodes/intern/node_tree_ref.cc +++ b/source/blender/nodes/intern/node_tree_ref.cc @@ -240,7 +240,10 @@ void InputSocketRef::foreach_logical_origin(FunctionRef<void(const OutputSocketR } const OutputSocketRef &origin = link->from(); const NodeRef &origin_node = origin.node(); - if (origin_node.is_reroute_node()) { + if (!origin.is_available()) { + /* Non available sockets are ignored. */ + } + else if (origin_node.is_reroute_node()) { const InputSocketRef &reroute_input = origin_node.input(0); const OutputSocketRef &reroute_output = origin_node.output(0); skipped_fn.call_safe(reroute_input); @@ -281,7 +284,10 @@ void OutputSocketRef::foreach_logical_target( } const InputSocketRef &target = link->to(); const NodeRef &target_node = target.node(); - if (target_node.is_reroute_node()) { + if (!target.is_available()) { + /* Non available sockets are ignored. */ + } + else if (target_node.is_reroute_node()) { const OutputSocketRef &reroute_output = target_node.output(0); skipped_fn.call_safe(target); skipped_fn.call_safe(reroute_output); @@ -291,6 +297,12 @@ void OutputSocketRef::foreach_logical_target( skipped_fn.call_safe(target); for (const InternalLinkRef *internal_link : target_node.internal_links()) { if (&internal_link->from() == &target) { + /* The internal link only forwards the first incoming link. */ + if (target.is_multi_input_socket()) { + if (target.directly_linked_links()[0] != link) { + continue; + } + } const OutputSocketRef &mute_output = internal_link->to(); skipped_fn.call_safe(target); skipped_fn.call_safe(mute_output); diff --git a/source/blender/nodes/intern/node_util.c b/source/blender/nodes/intern/node_util.c index f87e63d195d..1aec280fd2b 100644 --- a/source/blender/nodes/intern/node_util.c +++ b/source/blender/nodes/intern/node_util.c @@ -459,6 +459,22 @@ static int node_datatype_priority(eNodeSocketDatatype from, eNodeSocketDatatype return -1; } } + case SOCK_TEXTURE: { + switch (from) { + case SOCK_TEXTURE: + return 1; + default: + return -1; + } + } + case SOCK_MATERIAL: { + switch (from) { + case SOCK_MATERIAL: + return 1; + default: + return -1; + } + } default: return -1; } diff --git a/source/blender/nodes/intern/type_conversions.cc b/source/blender/nodes/intern/type_conversions.cc index 1c1b7c7feb5..220e5ea9046 100644 --- a/source/blender/nodes/intern/type_conversions.cc +++ b/source/blender/nodes/intern/type_conversions.cc @@ -24,6 +24,9 @@ namespace blender::nodes { +using fn::GVArrayPtr; +using fn::GVMutableArray; +using fn::GVMutableArrayPtr; using fn::MFDataType; template<typename From, typename To, To (*ConversionF)(const From &)> @@ -63,9 +66,9 @@ static bool float_to_bool(const float &a) { return a > 0.0f; } -static Color4f float_to_color(const float &a) +static ColorGeometry4f float_to_color(const float &a) { - return Color4f(a, a, a, 1.0f); + return ColorGeometry4f(a, a, a, 1.0f); } static float3 float2_to_float3(const float2 &a) @@ -84,9 +87,9 @@ static bool float2_to_bool(const float2 &a) { return !is_zero_v2(a); } -static Color4f float2_to_color(const float2 &a) +static ColorGeometry4f float2_to_color(const float2 &a) { - return Color4f(a.x, a.y, 0.0f, 1.0f); + return ColorGeometry4f(a.x, a.y, 0.0f, 1.0f); } static bool float3_to_bool(const float3 &a) @@ -105,9 +108,9 @@ static float2 float3_to_float2(const float3 &a) { return float2(a); } -static Color4f float3_to_color(const float3 &a) +static ColorGeometry4f float3_to_color(const float3 &a) { - return Color4f(a.x, a.y, a.z, 1.0f); + return ColorGeometry4f(a.x, a.y, a.z, 1.0f); } static bool int_to_bool(const int32_t &a) @@ -126,9 +129,9 @@ static float3 int_to_float3(const int32_t &a) { return float3((float)a); } -static Color4f int_to_color(const int32_t &a) +static ColorGeometry4f int_to_color(const int32_t &a) { - return Color4f((float)a, (float)a, (float)a, 1.0f); + return ColorGeometry4f((float)a, (float)a, (float)a, 1.0f); } static float bool_to_float(const bool &a) @@ -147,28 +150,28 @@ static float3 bool_to_float3(const bool &a) { return (a) ? float3(1.0f) : float3(0.0f); } -static Color4f bool_to_color(const bool &a) +static ColorGeometry4f bool_to_color(const bool &a) { - return (a) ? Color4f(1.0f, 1.0f, 1.0f, 1.0f) : Color4f(0.0f, 0.0f, 0.0f, 1.0f); + return (a) ? ColorGeometry4f(1.0f, 1.0f, 1.0f, 1.0f) : ColorGeometry4f(0.0f, 0.0f, 0.0f, 1.0f); } -static bool color_to_bool(const Color4f &a) +static bool color_to_bool(const ColorGeometry4f &a) { return rgb_to_grayscale(a) > 0.0f; } -static float color_to_float(const Color4f &a) +static float color_to_float(const ColorGeometry4f &a) { return rgb_to_grayscale(a); } -static int32_t color_to_int(const Color4f &a) +static int32_t color_to_int(const ColorGeometry4f &a) { return (int)rgb_to_grayscale(a); } -static float2 color_to_float2(const Color4f &a) +static float2 color_to_float2(const ColorGeometry4f &a) { return float2(a.r, a.g); } -static float3 color_to_float3(const Color4f &a) +static float3 color_to_float3(const ColorGeometry4f &a) { return float3(a.r, a.g, a.b); } @@ -181,37 +184,37 @@ static DataTypeConversions create_implicit_conversions() add_implicit_conversion<float, float3, float_to_float3>(conversions); add_implicit_conversion<float, int32_t, float_to_int>(conversions); add_implicit_conversion<float, bool, float_to_bool>(conversions); - add_implicit_conversion<float, Color4f, float_to_color>(conversions); + add_implicit_conversion<float, ColorGeometry4f, float_to_color>(conversions); add_implicit_conversion<float2, float3, float2_to_float3>(conversions); add_implicit_conversion<float2, float, float2_to_float>(conversions); add_implicit_conversion<float2, int32_t, float2_to_int>(conversions); add_implicit_conversion<float2, bool, float2_to_bool>(conversions); - add_implicit_conversion<float2, Color4f, float2_to_color>(conversions); + add_implicit_conversion<float2, ColorGeometry4f, float2_to_color>(conversions); add_implicit_conversion<float3, bool, float3_to_bool>(conversions); add_implicit_conversion<float3, float, float3_to_float>(conversions); add_implicit_conversion<float3, int32_t, float3_to_int>(conversions); add_implicit_conversion<float3, float2, float3_to_float2>(conversions); - add_implicit_conversion<float3, Color4f, float3_to_color>(conversions); + add_implicit_conversion<float3, ColorGeometry4f, float3_to_color>(conversions); add_implicit_conversion<int32_t, bool, int_to_bool>(conversions); add_implicit_conversion<int32_t, float, int_to_float>(conversions); add_implicit_conversion<int32_t, float2, int_to_float2>(conversions); add_implicit_conversion<int32_t, float3, int_to_float3>(conversions); - add_implicit_conversion<int32_t, Color4f, int_to_color>(conversions); + add_implicit_conversion<int32_t, ColorGeometry4f, int_to_color>(conversions); add_implicit_conversion<bool, float, bool_to_float>(conversions); add_implicit_conversion<bool, int32_t, bool_to_int>(conversions); add_implicit_conversion<bool, float2, bool_to_float2>(conversions); add_implicit_conversion<bool, float3, bool_to_float3>(conversions); - add_implicit_conversion<bool, Color4f, bool_to_color>(conversions); + add_implicit_conversion<bool, ColorGeometry4f, bool_to_color>(conversions); - add_implicit_conversion<Color4f, bool, color_to_bool>(conversions); - add_implicit_conversion<Color4f, float, color_to_float>(conversions); - add_implicit_conversion<Color4f, int32_t, color_to_int>(conversions); - add_implicit_conversion<Color4f, float2, color_to_float2>(conversions); - add_implicit_conversion<Color4f, float3, color_to_float3>(conversions); + add_implicit_conversion<ColorGeometry4f, bool, color_to_bool>(conversions); + add_implicit_conversion<ColorGeometry4f, float, color_to_float>(conversions); + add_implicit_conversion<ColorGeometry4f, int32_t, color_to_int>(conversions); + add_implicit_conversion<ColorGeometry4f, float2, color_to_float2>(conversions); + add_implicit_conversion<ColorGeometry4f, float3, color_to_float3>(conversions); return conversions; } @@ -227,6 +230,11 @@ void DataTypeConversions::convert_to_uninitialized(const CPPType &from_type, const void *from_value, void *to_value) const { + if (from_type == to_type) { + from_type.copy_to_uninitialized(from_value, to_value); + return; + } + const ConversionFunctions *functions = this->get_conversion_functions( MFDataType::ForSingle(from_type), MFDataType::ForSingle(to_type)); BLI_assert(functions != nullptr); @@ -234,4 +242,108 @@ void DataTypeConversions::convert_to_uninitialized(const CPPType &from_type, functions->convert_single_to_uninitialized(from_value, to_value); } +class GVArray_For_ConvertedGVArray : public GVArray { + private: + GVArrayPtr varray_; + const CPPType &from_type_; + ConversionFunctions old_to_new_conversions_; + + public: + GVArray_For_ConvertedGVArray(GVArrayPtr varray, + const CPPType &to_type, + const DataTypeConversions &conversions) + : GVArray(to_type, varray->size()), varray_(std::move(varray)), from_type_(varray_->type()) + { + old_to_new_conversions_ = *conversions.get_conversion_functions(from_type_, to_type); + } + + private: + void get_impl(const int64_t index, void *r_value) const override + { + BUFFER_FOR_CPP_TYPE_VALUE(from_type_, buffer); + varray_->get(index, buffer); + old_to_new_conversions_.convert_single_to_initialized(buffer, r_value); + from_type_.destruct(buffer); + } + + void get_to_uninitialized_impl(const int64_t index, void *r_value) const override + { + BUFFER_FOR_CPP_TYPE_VALUE(from_type_, buffer); + varray_->get(index, buffer); + old_to_new_conversions_.convert_single_to_uninitialized(buffer, r_value); + from_type_.destruct(buffer); + } +}; + +class GVMutableArray_For_ConvertedGVMutableArray : public GVMutableArray { + private: + GVMutableArrayPtr varray_; + const CPPType &from_type_; + ConversionFunctions old_to_new_conversions_; + ConversionFunctions new_to_old_conversions_; + + public: + GVMutableArray_For_ConvertedGVMutableArray(GVMutableArrayPtr varray, + const CPPType &to_type, + const DataTypeConversions &conversions) + : GVMutableArray(to_type, varray->size()), + varray_(std::move(varray)), + from_type_(varray_->type()) + { + old_to_new_conversions_ = *conversions.get_conversion_functions(from_type_, to_type); + new_to_old_conversions_ = *conversions.get_conversion_functions(to_type, from_type_); + } + + private: + void get_impl(const int64_t index, void *r_value) const override + { + BUFFER_FOR_CPP_TYPE_VALUE(from_type_, buffer); + varray_->get(index, buffer); + old_to_new_conversions_.convert_single_to_initialized(buffer, r_value); + from_type_.destruct(buffer); + } + + void get_to_uninitialized_impl(const int64_t index, void *r_value) const override + { + BUFFER_FOR_CPP_TYPE_VALUE(from_type_, buffer); + varray_->get(index, buffer); + old_to_new_conversions_.convert_single_to_uninitialized(buffer, r_value); + from_type_.destruct(buffer); + } + + void set_by_move_impl(const int64_t index, void *value) override + { + BUFFER_FOR_CPP_TYPE_VALUE(from_type_, buffer); + new_to_old_conversions_.convert_single_to_uninitialized(value, buffer); + varray_->set_by_relocate(index, buffer); + } +}; + +fn::GVArrayPtr DataTypeConversions::try_convert(fn::GVArrayPtr varray, + const CPPType &to_type) const +{ + const CPPType &from_type = varray->type(); + if (from_type == to_type) { + return varray; + } + if (!this->is_convertible(from_type, to_type)) { + return {}; + } + return std::make_unique<GVArray_For_ConvertedGVArray>(std::move(varray), to_type, *this); +} + +fn::GVMutableArrayPtr DataTypeConversions::try_convert(fn::GVMutableArrayPtr varray, + const CPPType &to_type) const +{ + const CPPType &from_type = varray->type(); + if (from_type == to_type) { + return varray; + } + if (!this->is_convertible(from_type, to_type)) { + return {}; + } + return std::make_unique<GVMutableArray_For_ConvertedGVMutableArray>( + std::move(varray), to_type, *this); +} + } // namespace blender::nodes diff --git a/source/blender/nodes/shader/node_shader_tree.c b/source/blender/nodes/shader/node_shader_tree.c index 83f476884e6..5ec982c4e7f 100644 --- a/source/blender/nodes/shader/node_shader_tree.c +++ b/source/blender/nodes/shader/node_shader_tree.c @@ -184,6 +184,12 @@ static bool shader_validate_link(bNodeTree *UNUSED(ntree), bNodeLink *link) return true; } +static bool shader_node_tree_socket_type_valid(eNodeSocketDatatype socket_type, + bNodeTreeType *UNUSED(ntreetype)) +{ + return ELEM(socket_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_SHADER); +} + bNodeTreeType *ntreeType_Shader; void register_node_tree_type_sh(void) @@ -205,6 +211,7 @@ void register_node_tree_type_sh(void) tt->poll = shader_tree_poll; tt->get_from_context = shader_get_from_context; tt->validate_link = shader_validate_link; + tt->valid_socket_type = shader_node_tree_socket_type_valid; tt->rna_ext.srna = &RNA_ShaderNodeTree; diff --git a/source/blender/nodes/shader/node_shader_util.c b/source/blender/nodes/shader/node_shader_util.c index 04c32574a65..abc2c7008c7 100644 --- a/source/blender/nodes/shader/node_shader_util.c +++ b/source/blender/nodes/shader/node_shader_util.c @@ -332,3 +332,17 @@ void node_shader_gpu_tex_mapping(GPUMaterial *mat, } } } + +void get_XYZ_to_RGB_for_gpu(XYZ_to_RGB *data) +{ + const float *xyz_to_rgb = IMB_colormanagement_get_xyz_to_rgb(); + data->r[0] = xyz_to_rgb[0]; + data->r[1] = xyz_to_rgb[3]; + data->r[2] = xyz_to_rgb[6]; + data->g[0] = xyz_to_rgb[1]; + data->g[1] = xyz_to_rgb[4]; + data->g[2] = xyz_to_rgb[7]; + data->b[0] = xyz_to_rgb[2]; + data->b[1] = xyz_to_rgb[5]; + data->b[2] = xyz_to_rgb[8]; +} diff --git a/source/blender/nodes/shader/node_shader_util.h b/source/blender/nodes/shader/node_shader_util.h index 857a9914354..dc44f0fa98f 100644 --- a/source/blender/nodes/shader/node_shader_util.h +++ b/source/blender/nodes/shader/node_shader_util.h @@ -95,6 +95,11 @@ typedef struct ShaderCallData { int dummy; } ShaderCallData; +typedef struct XYZ_to_RGB /* Transposed #imbuf_xyz_to_rgb, passed as 3x vec3. */ +{ + float r[3], g[3], b[3]; +} XYZ_to_RGB; + void nodestack_get_vec(float *in, short type_in, bNodeStack *ns); void node_gpu_stack_from_data(struct GPUNodeStack *gs, int type, struct bNodeStack *ns); @@ -113,6 +118,7 @@ void node_shader_gpu_tex_mapping(struct GPUMaterial *mat, void ntreeExecGPUNodes(struct bNodeTreeExec *exec, struct GPUMaterial *mat, struct bNode *output_node); +void get_XYZ_to_RGB_for_gpu(XYZ_to_RGB *data); #ifdef __cplusplus } diff --git a/source/blender/nodes/shader/nodes/node_shader_curves.c b/source/blender/nodes/shader/nodes/node_shader_curves.cc index 42299a193e2..f1d5040a292 100644 --- a/source/blender/nodes/shader/nodes/node_shader_curves.c +++ b/source/blender/nodes/shader/nodes/node_shader_curves.cc @@ -47,7 +47,7 @@ static void node_shader_exec_curve_vec(void *UNUSED(data), /* stack order input: vec */ /* stack order output: vec */ nodestack_get_vec(vec, SOCK_VECTOR, in[1]); - BKE_curvemapping_evaluate3F(node->storage, out[0]->vec, vec); + BKE_curvemapping_evaluate3F((CurveMapping *)node->storage, out[0]->vec, vec); } static void node_shader_init_curve_vec(bNodeTree *UNUSED(ntree), bNode *node) @@ -64,7 +64,7 @@ static int gpu_shader_curve_vec(GPUMaterial *mat, float *array, layer; int size; - CurveMapping *cumap = node->storage; + CurveMapping *cumap = (CurveMapping *)node->storage; BKE_curvemapping_table_RGBA(cumap, &array, &size); GPUNodeLink *tex = GPU_color_band(mat, size, array, &layer); @@ -104,17 +104,65 @@ static int gpu_shader_curve_vec(GPUMaterial *mat, GPU_uniform(ext_xyz[2])); } +class CurveVecFunction : public blender::fn::MultiFunction { + private: + const CurveMapping &cumap_; + + public: + CurveVecFunction(const CurveMapping &cumap) : cumap_(cumap) + { + static blender::fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static blender::fn::MFSignature create_signature() + { + blender::fn::MFSignatureBuilder signature{"Curve Vec"}; + signature.single_input<float>("Fac"); + signature.single_input<blender::float3>("Vector"); + signature.single_output<blender::float3>("Vector"); + return signature.build(); + } + + void call(blender::IndexMask mask, + blender::fn::MFParams params, + blender::fn::MFContext UNUSED(context)) const override + { + const blender::VArray<float> &fac = params.readonly_single_input<float>(0, "Fac"); + const blender::VArray<blender::float3> &vec_in = params.readonly_single_input<blender::float3>( + 1, "Vector"); + blender::MutableSpan<blender::float3> vec_out = + params.uninitialized_single_output<blender::float3>(2, "Vector"); + + for (int64_t i : mask) { + BKE_curvemapping_evaluate3F(&cumap_, vec_out[i], vec_in[i]); + if (fac[i] != 1.0f) { + interp_v3_v3v3(vec_out[i], vec_in[i], vec_out[i], fac[i]); + } + } + } +}; + +static void sh_node_curve_vec_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder) +{ + bNode &bnode = builder.bnode(); + CurveMapping *cumap = (CurveMapping *)bnode.storage; + BKE_curvemapping_init(cumap); + builder.construct_and_set_matching_fn<CurveVecFunction>(*cumap); +} + void register_node_type_sh_curve_vec(void) { static bNodeType ntype; - sh_node_type_base(&ntype, SH_NODE_CURVE_VEC, "Vector Curves", NODE_CLASS_OP_VECTOR, 0); + sh_fn_node_type_base(&ntype, SH_NODE_CURVE_VEC, "Vector Curves", NODE_CLASS_OP_VECTOR, 0); node_type_socket_templates(&ntype, sh_node_curve_vec_in, sh_node_curve_vec_out); node_type_init(&ntype, node_shader_init_curve_vec); node_type_size_preset(&ntype, NODE_SIZE_LARGE); node_type_storage(&ntype, "CurveMapping", node_free_curves, node_copy_curves); - node_type_exec(&ntype, node_initexec_curves, NULL, node_shader_exec_curve_vec); + node_type_exec(&ntype, node_initexec_curves, nullptr, node_shader_exec_curve_vec); node_type_gpu(&ntype, gpu_shader_curve_vec); + ntype.expand_in_mf_network = sh_node_curve_vec_expand_in_mf_network; nodeRegisterType(&ntype); } @@ -145,7 +193,7 @@ static void node_shader_exec_curve_rgb(void *UNUSED(data), /* stack order output: vec */ nodestack_get_vec(&fac, SOCK_FLOAT, in[0]); nodestack_get_vec(vec, SOCK_VECTOR, in[1]); - BKE_curvemapping_evaluateRGBF(node->storage, out[0]->vec, vec); + BKE_curvemapping_evaluateRGBF((CurveMapping *)node->storage, out[0]->vec, vec); if (fac != 1.0f) { interp_v3_v3v3(out[0]->vec, vec, out[0]->vec, fac); } @@ -166,7 +214,7 @@ static int gpu_shader_curve_rgb(GPUMaterial *mat, int size; bool use_opti = true; - CurveMapping *cumap = node->storage; + CurveMapping *cumap = (CurveMapping *)node->storage; BKE_curvemapping_init(cumap); BKE_curvemapping_table_RGBA(cumap, &array, &size); @@ -230,17 +278,65 @@ static int gpu_shader_curve_rgb(GPUMaterial *mat, GPU_uniform(ext_rgba[3])); } +class CurveRGBFunction : public blender::fn::MultiFunction { + private: + const CurveMapping &cumap_; + + public: + CurveRGBFunction(const CurveMapping &cumap) : cumap_(cumap) + { + static blender::fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static blender::fn::MFSignature create_signature() + { + blender::fn::MFSignatureBuilder signature{"Curve RGB"}; + signature.single_input<float>("Fac"); + signature.single_input<blender::ColorGeometry4f>("Color"); + signature.single_output<blender::ColorGeometry4f>("Color"); + return signature.build(); + } + + void call(blender::IndexMask mask, + blender::fn::MFParams params, + blender::fn::MFContext UNUSED(context)) const override + { + const blender::VArray<float> &fac = params.readonly_single_input<float>(0, "Fac"); + const blender::VArray<blender::ColorGeometry4f> &col_in = + params.readonly_single_input<blender::ColorGeometry4f>(1, "Color"); + blender::MutableSpan<blender::ColorGeometry4f> col_out = + params.uninitialized_single_output<blender::ColorGeometry4f>(2, "Color"); + + for (int64_t i : mask) { + BKE_curvemapping_evaluateRGBF(&cumap_, col_out[i], col_in[i]); + if (fac[i] != 1.0f) { + interp_v3_v3v3(col_out[i], col_in[i], col_out[i], fac[i]); + } + } + } +}; + +static void sh_node_curve_rgb_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder) +{ + bNode &bnode = builder.bnode(); + CurveMapping *cumap = (CurveMapping *)bnode.storage; + BKE_curvemapping_init(cumap); + builder.construct_and_set_matching_fn<CurveRGBFunction>(*cumap); +} + void register_node_type_sh_curve_rgb(void) { static bNodeType ntype; - sh_node_type_base(&ntype, SH_NODE_CURVE_RGB, "RGB Curves", NODE_CLASS_OP_COLOR, 0); + sh_fn_node_type_base(&ntype, SH_NODE_CURVE_RGB, "RGB Curves", NODE_CLASS_OP_COLOR, 0); node_type_socket_templates(&ntype, sh_node_curve_rgb_in, sh_node_curve_rgb_out); node_type_init(&ntype, node_shader_init_curve_rgb); node_type_size_preset(&ntype, NODE_SIZE_LARGE); node_type_storage(&ntype, "CurveMapping", node_free_curves, node_copy_curves); - node_type_exec(&ntype, node_initexec_curves, NULL, node_shader_exec_curve_rgb); + node_type_exec(&ntype, node_initexec_curves, nullptr, node_shader_exec_curve_rgb); node_type_gpu(&ntype, gpu_shader_curve_rgb); + ntype.expand_in_mf_network = sh_node_curve_rgb_expand_in_mf_network; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/shader/nodes/node_shader_sepcombRGB.cc b/source/blender/nodes/shader/nodes/node_shader_sepcombRGB.cc index 8ca4a6bab5f..a7239154633 100644 --- a/source/blender/nodes/shader/nodes/node_shader_sepcombRGB.cc +++ b/source/blender/nodes/shader/nodes/node_shader_sepcombRGB.cc @@ -70,7 +70,7 @@ class SeparateRGBFunction : public blender::fn::MultiFunction { static blender::fn::MFSignature create_signature() { blender::fn::MFSignatureBuilder signature{"Separate RGB"}; - signature.single_input<blender::Color4f>("Color"); + signature.single_input<blender::ColorGeometry4f>("Color"); signature.single_output<float>("R"); signature.single_output<float>("G"); signature.single_output<float>("B"); @@ -81,14 +81,14 @@ class SeparateRGBFunction : public blender::fn::MultiFunction { blender::fn::MFParams params, blender::fn::MFContext UNUSED(context)) const override { - const blender::VArray<blender::Color4f> &colors = - params.readonly_single_input<blender::Color4f>(0, "Color"); + const blender::VArray<blender::ColorGeometry4f> &colors = + params.readonly_single_input<blender::ColorGeometry4f>(0, "Color"); blender::MutableSpan<float> rs = params.uninitialized_single_output<float>(1, "R"); blender::MutableSpan<float> gs = params.uninitialized_single_output<float>(2, "G"); blender::MutableSpan<float> bs = params.uninitialized_single_output<float>(3, "B"); for (int64_t i : mask) { - blender::Color4f color = colors[i]; + blender::ColorGeometry4f color = colors[i]; rs[i] = color.r; gs[i] = color.g; bs[i] = color.b; @@ -155,8 +155,9 @@ static int gpu_shader_combrgb(GPUMaterial *mat, static void sh_node_combrgb_expand_in_mf_network(blender::nodes::NodeMFNetworkBuilder &builder) { - static blender::fn::CustomMF_SI_SI_SI_SO<float, float, float, blender::Color4f> fn{ - "Combine RGB", [](float r, float g, float b) { return blender::Color4f(r, g, b, 1.0f); }}; + static blender::fn::CustomMF_SI_SI_SI_SO<float, float, float, blender::ColorGeometry4f> fn{ + "Combine RGB", + [](float r, float g, float b) { return blender::ColorGeometry4f(r, g, b, 1.0f); }}; builder.set_matching_fn(fn); } diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_sky.c b/source/blender/nodes/shader/nodes/node_shader_tex_sky.c index 9ef05d781bd..5dc11c4df00 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_sky.c +++ b/source/blender/nodes/shader/nodes/node_shader_tex_sky.c @@ -60,11 +60,6 @@ typedef struct SkyModelPreetham { float radiance[3]; } SkyModelPreetham; -typedef struct XYZ_to_RGB /* transposed imbuf_xyz_to_rgb, passed as 3x vec3 */ -{ - float r[3], g[3], b[3]; -} XYZ_to_RGB; - static float sky_perez_function(const float *lam, float theta, float gamma) { float ctheta = cosf(theta); @@ -119,20 +114,6 @@ static void sky_precompute_old(SkyModelPreetham *sunsky, const float sun_angles[ sunsky->radiance[2] /= sky_perez_function(sunsky->config_y, 0, theta); } -static void get_XYZ_to_RGB_for_gpu(XYZ_to_RGB *data) -{ - const float *xyz_to_rgb = IMB_colormangement_get_xyz_to_rgb(); - data->r[0] = xyz_to_rgb[0]; - data->r[1] = xyz_to_rgb[3]; - data->r[2] = xyz_to_rgb[6]; - data->g[0] = xyz_to_rgb[1]; - data->g[1] = xyz_to_rgb[4]; - data->g[2] = xyz_to_rgb[7]; - data->b[0] = xyz_to_rgb[2]; - data->b[1] = xyz_to_rgb[5]; - data->b[2] = xyz_to_rgb[8]; -} - static int node_shader_gpu_tex_sky(GPUMaterial *mat, bNode *node, bNodeExecData *UNUSED(execdata), diff --git a/source/blender/nodes/shader/nodes/node_shader_valToRgb.cc b/source/blender/nodes/shader/nodes/node_shader_valToRgb.cc index 90e8161c09f..5b2eb300aac 100644 --- a/source/blender/nodes/shader/nodes/node_shader_valToRgb.cc +++ b/source/blender/nodes/shader/nodes/node_shader_valToRgb.cc @@ -140,7 +140,7 @@ class ColorBandFunction : public blender::fn::MultiFunction { { blender::fn::MFSignatureBuilder signature{"Color Band"}; signature.single_input<float>("Value"); - signature.single_output<blender::Color4f>("Color"); + signature.single_output<blender::ColorGeometry4f>("Color"); signature.single_output<float>("Alpha"); return signature.build(); } @@ -150,12 +150,12 @@ class ColorBandFunction : public blender::fn::MultiFunction { blender::fn::MFContext UNUSED(context)) const override { const blender::VArray<float> &values = params.readonly_single_input<float>(0, "Value"); - blender::MutableSpan<blender::Color4f> colors = - params.uninitialized_single_output<blender::Color4f>(1, "Color"); + blender::MutableSpan<blender::ColorGeometry4f> colors = + params.uninitialized_single_output<blender::ColorGeometry4f>(1, "Color"); blender::MutableSpan<float> alphas = params.uninitialized_single_output<float>(2, "Alpha"); for (int64_t i : mask) { - blender::Color4f color; + blender::ColorGeometry4f color; BKE_colorband_evaluate(&color_band_, values[i], color); colors[i] = color; alphas[i] = color.a; diff --git a/source/blender/nodes/shader/nodes/node_shader_wavelength.c b/source/blender/nodes/shader/nodes/node_shader_wavelength.c index 6b7e1399328..30f69557020 100644 --- a/source/blender/nodes/shader/nodes/node_shader_wavelength.c +++ b/source/blender/nodes/shader/nodes/node_shader_wavelength.c @@ -30,6 +30,33 @@ static bNodeSocketTemplate sh_node_wavelength_out[] = { {-1, ""}, }; +static int node_shader_gpu_wavelength(GPUMaterial *mat, + bNode *node, + bNodeExecData *UNUSED(execdata), + GPUNodeStack *in, + GPUNodeStack *out) +{ + const int size = CM_TABLE + 1; + float *data = MEM_mallocN(sizeof(float) * size * 4, "cie_xyz texture"); + + wavelength_to_xyz_table(data, size); + + float layer; + GPUNodeLink *ramp_texture = GPU_color_band(mat, size, data, &layer); + XYZ_to_RGB xyz_to_rgb; + get_XYZ_to_RGB_for_gpu(&xyz_to_rgb); + return GPU_stack_link(mat, + node, + "node_wavelength", + in, + out, + ramp_texture, + GPU_constant(&layer), + GPU_uniform(xyz_to_rgb.r), + GPU_uniform(xyz_to_rgb.g), + GPU_uniform(xyz_to_rgb.b)); +} + /* node type definition */ void register_node_type_sh_wavelength(void) { @@ -40,6 +67,7 @@ void register_node_type_sh_wavelength(void) node_type_socket_templates(&ntype, sh_node_wavelength_in, sh_node_wavelength_out); node_type_init(&ntype, NULL); node_type_storage(&ntype, "", NULL, NULL); + node_type_gpu(&ntype, node_shader_gpu_wavelength); nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/texture/node_texture_tree.c b/source/blender/nodes/texture/node_texture_tree.c index 48027dc847b..2ae722e3cd8 100644 --- a/source/blender/nodes/texture/node_texture_tree.c +++ b/source/blender/nodes/texture/node_texture_tree.c @@ -152,6 +152,12 @@ static void update(bNodeTree *ntree) } } +static bool texture_node_tree_socket_type_valid(eNodeSocketDatatype socket_type, + bNodeTreeType *UNUSED(ntreetype)) +{ + return ELEM(socket_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA); +} + bNodeTreeType *ntreeType_Texture; void register_node_tree_type_tex(void) @@ -171,6 +177,7 @@ void register_node_tree_type_tex(void) tt->local_sync = local_sync; tt->local_merge = local_merge; tt->get_from_context = texture_get_from_context; + tt->valid_socket_type = texture_node_tree_socket_type_valid; tt->rna_ext.srna = &RNA_TextureNodeTree; diff --git a/source/blender/python/bmesh/bmesh_py_types.c b/source/blender/python/bmesh/bmesh_py_types.c index ebe6ed79578..f1a8d450ea5 100644 --- a/source/blender/python/bmesh/bmesh_py_types.c +++ b/source/blender/python/bmesh/bmesh_py_types.c @@ -1087,7 +1087,7 @@ PyDoc_STRVAR( "3.0.\n"); static PyObject *bpy_bmesh_from_object(BPy_BMesh *self, PyObject *args, PyObject *kw) { - static const char *kwlist[] = {"object", "depsgraph", "deform", "cage", "face_normals", NULL}; + static const char *kwlist[] = {"object", "depsgraph", "cage", "face_normals", NULL}; PyObject *py_object; PyObject *py_depsgraph; Object *ob, *ob_eval; @@ -1095,7 +1095,6 @@ static PyObject *bpy_bmesh_from_object(BPy_BMesh *self, PyObject *args, PyObject struct Scene *scene_eval; Mesh *me_eval; BMesh *bm; - bool use_deform = true; bool use_cage = false; bool use_fnorm = true; const CustomData_MeshMasks data_masks = CD_MASK_BMESH; @@ -1104,13 +1103,11 @@ static PyObject *bpy_bmesh_from_object(BPy_BMesh *self, PyObject *args, PyObject if (!PyArg_ParseTupleAndKeywords(args, kw, - "OO|O&O&O&:from_object", + "OO|O&O&:from_object", (char **)kwlist, &py_object, &py_depsgraph, PyC_ParseBool, - &use_deform, - PyC_ParseBool, &use_cage, PyC_ParseBool, &use_fnorm) || @@ -1125,13 +1122,6 @@ static PyObject *bpy_bmesh_from_object(BPy_BMesh *self, PyObject *args, PyObject return NULL; } - if (use_deform == false) { - PyErr_WarnEx(PyExc_FutureWarning, - "from_object(...): the deform parameter is deprecated, assumed to be True, and " - "will be removed in version 3.0", - 1); - } - const bool use_render = DEG_get_mode(depsgraph) == DAG_EVAL_RENDER; scene_eval = DEG_get_evaluated_scene(depsgraph); ob_eval = DEG_get_evaluated_object(depsgraph, ob); diff --git a/source/blender/python/generic/idprop_py_api.c b/source/blender/python/generic/idprop_py_api.c index fc7054f675a..9b6ca7fcec5 100644 --- a/source/blender/python/generic/idprop_py_api.c +++ b/source/blender/python/generic/idprop_py_api.c @@ -42,6 +42,18 @@ extern bool pyrna_id_FromPyObject(PyObject *obj, ID **id); extern PyObject *pyrna_id_CreatePyObject(ID *id); extern bool pyrna_id_CheckPyObject(PyObject *obj); +/* Currently there is no need to expose this publicly. */ +static PyObject *BPy_IDGroup_IterKeys_CreatePyObject(BPy_IDProperty *group, const bool reversed); +static PyObject *BPy_IDGroup_IterValues_CreatePyObject(BPy_IDProperty *group, const bool reversed); +static PyObject *BPy_IDGroup_IterItems_CreatePyObject(BPy_IDProperty *group, const bool reversed); + +static PyObject *BPy_IDGroup_ViewKeys_CreatePyObject(BPy_IDProperty *group); +static PyObject *BPy_IDGroup_ViewValues_CreatePyObject(BPy_IDProperty *group); +static PyObject *BPy_IDGroup_ViewItems_CreatePyObject(BPy_IDProperty *group); + +static BPy_IDGroup_View *IDGroup_View_New_WithType(BPy_IDProperty *group, PyTypeObject *type); +static int BPy_IDGroup_Contains(BPy_IDProperty *self, PyObject *value); + /* -------------------------------------------------------------------- */ /** \name Python from ID-Property (Internal Conversions) * @@ -756,13 +768,7 @@ static int BPy_IDGroup_Map_SetItem(BPy_IDProperty *self, PyObject *key, PyObject static PyObject *BPy_IDGroup_iter(BPy_IDProperty *self) { - BPy_IDGroup_Iter *iter = PyObject_GC_New(BPy_IDGroup_Iter, &BPy_IDGroup_Iter_Type); - iter->group = self; - Py_INCREF(self); - iter->mode = IDPROP_ITER_KEYS; - iter->cur = self->prop->data.group.first; - PyObject_GC_Track(iter); - return (PyObject *)iter; + return BPy_IDGroup_ViewKeys_CreatePyObject(self); } /* for simple, non nested types this is the same as BPy_IDGroup_WrapData */ @@ -875,6 +881,370 @@ PyObject *BPy_IDGroup_MapDataToPy(IDProperty *prop) /** \} */ /* -------------------------------------------------------------------- */ +/** \name ID-Property Group Iterator Type + * \{ */ + +static PyObject *BPy_IDGroup_Iter_repr(BPy_IDGroup_Iter *self) +{ + if (self->group == NULL) { + return PyUnicode_FromFormat("<%s>", Py_TYPE(self)->tp_name); + } + return PyUnicode_FromFormat("<%s \"%s\">", Py_TYPE(self)->tp_name, self->group->prop->name); +} + +static void BPy_IDGroup_Iter_dealloc(BPy_IDGroup_Iter *self) +{ + if (self->group != NULL) { + PyObject_GC_UnTrack(self); + } + Py_CLEAR(self->group); + PyObject_GC_Del(self); +} + +static int BPy_IDGroup_Iter_traverse(BPy_IDGroup_Iter *self, visitproc visit, void *arg) +{ + Py_VISIT(self->group); + return 0; +} + +static int BPy_IDGroup_Iter_clear(BPy_IDGroup_Iter *self) +{ + Py_CLEAR(self->group); + return 0; +} + +static bool BPy_Group_Iter_same_size_or_raise_error(BPy_IDGroup_Iter *self) +{ + if (self->len_init == self->group->prop->len) { + return true; + } + PyErr_SetString(PyExc_RuntimeError, "IDPropertyGroup changed size during iteration"); + return false; +} + +static PyObject *BPy_Group_IterKeys_next(BPy_IDGroup_Iter *self) +{ + if (self->cur != NULL) { + /* When `cur` is set, `group` cannot be NULL. */ + if (!BPy_Group_Iter_same_size_or_raise_error(self)) { + return NULL; + } + IDProperty *cur = self->cur; + self->cur = self->reversed ? self->cur->prev : self->cur->next; + return PyUnicode_FromString(cur->name); + } + PyErr_SetNone(PyExc_StopIteration); + return NULL; +} + +static PyObject *BPy_Group_IterValues_next(BPy_IDGroup_Iter *self) +{ + if (self->cur != NULL) { + /* When `cur` is set, `group` cannot be NULL. */ + if (!BPy_Group_Iter_same_size_or_raise_error(self)) { + return NULL; + } + IDProperty *cur = self->cur; + self->cur = self->reversed ? self->cur->prev : self->cur->next; + return BPy_IDGroup_WrapData(self->group->id, cur, self->group->prop); + } + PyErr_SetNone(PyExc_StopIteration); + return NULL; +} + +static PyObject *BPy_Group_IterItems_next(BPy_IDGroup_Iter *self) +{ + if (self->cur != NULL) { + /* When `cur` is set, `group` cannot be NULL. */ + if (!BPy_Group_Iter_same_size_or_raise_error(self)) { + return NULL; + } + IDProperty *cur = self->cur; + self->cur = self->reversed ? self->cur->prev : self->cur->next; + PyObject *ret = PyTuple_New(2); + PyTuple_SET_ITEMS(ret, + PyUnicode_FromString(cur->name), + BPy_IDGroup_WrapData(self->group->id, cur, self->group->prop)); + return ret; + } + PyErr_SetNone(PyExc_StopIteration); + return NULL; +} + +PyTypeObject BPy_IDGroup_IterKeys_Type = {PyVarObject_HEAD_INIT(NULL, 0)}; +PyTypeObject BPy_IDGroup_IterValues_Type = {PyVarObject_HEAD_INIT(NULL, 0)}; +PyTypeObject BPy_IDGroup_IterItems_Type = {PyVarObject_HEAD_INIT(NULL, 0)}; + +/* ID Property Group Iterator. */ +static void IDGroup_Iter_init_type(void) +{ +#define SHARED_MEMBER_SET(member, value) \ + { \ + k_ty->member = v_ty->member = i_ty->member = value; \ + } \ + ((void)0) + + PyTypeObject *k_ty = &BPy_IDGroup_IterKeys_Type; + PyTypeObject *v_ty = &BPy_IDGroup_IterValues_Type; + PyTypeObject *i_ty = &BPy_IDGroup_IterItems_Type; + + /* Unique members. */ + k_ty->tp_name = "IDPropertyGroupIterKeys"; + v_ty->tp_name = "IDPropertyGroupIterValues"; + i_ty->tp_name = "IDPropertyGroupIterItems"; + + k_ty->tp_iternext = (iternextfunc)BPy_Group_IterKeys_next; + v_ty->tp_iternext = (iternextfunc)BPy_Group_IterValues_next; + i_ty->tp_iternext = (iternextfunc)BPy_Group_IterItems_next; + + /* Shared members. */ + SHARED_MEMBER_SET(tp_basicsize, sizeof(BPy_IDGroup_Iter)); + SHARED_MEMBER_SET(tp_dealloc, (destructor)BPy_IDGroup_Iter_dealloc); + SHARED_MEMBER_SET(tp_repr, (reprfunc)BPy_IDGroup_Iter_repr); + SHARED_MEMBER_SET(tp_flags, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC); + SHARED_MEMBER_SET(tp_traverse, (traverseproc)BPy_IDGroup_Iter_traverse); + SHARED_MEMBER_SET(tp_clear, (inquiry)BPy_IDGroup_Iter_clear); + SHARED_MEMBER_SET(tp_iter, PyObject_SelfIter); + +#undef SHARED_MEMBER_SET +} + +static PyObject *IDGroup_Iter_New_WithType(BPy_IDProperty *group, + const bool reversed, + PyTypeObject *type) +{ + BLI_assert(group ? group->prop->type == IDP_GROUP : true); + BPy_IDGroup_Iter *iter = PyObject_GC_New(BPy_IDGroup_Iter, type); + iter->reversed = reversed; + iter->group = group; + if (group != NULL) { + Py_INCREF(group); + PyObject_GC_Track(iter); + iter->cur = (reversed ? group->prop->data.group.last : group->prop->data.group.first); + iter->len_init = group->prop->len; + } + else { + iter->cur = NULL; + iter->len_init = 0; + } + return (PyObject *)iter; +} + +static PyObject *BPy_IDGroup_IterKeys_CreatePyObject(BPy_IDProperty *group, const bool reversed) +{ + return IDGroup_Iter_New_WithType(group, reversed, &BPy_IDGroup_IterKeys_Type); +} + +static PyObject *BPy_IDGroup_IterValues_CreatePyObject(BPy_IDProperty *group, const bool reversed) +{ + return IDGroup_Iter_New_WithType(group, reversed, &BPy_IDGroup_IterValues_Type); +} + +static PyObject *BPy_IDGroup_IterItems_CreatePyObject(BPy_IDProperty *group, const bool reversed) +{ + return IDGroup_Iter_New_WithType(group, reversed, &BPy_IDGroup_IterItems_Type); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name ID-Property Group View Types (Keys/Values/Items) + * + * This view types is a thin wrapper on keys/values/items, this matches Python's `dict_view` type. + * The is returned by `property.keys()` and is separate from the iterator that loops over keys. + * + * There are some less common features this type could support (matching Python's `dict_view`) + * + * TODO: + * - Efficient contains checks for values and items which currently convert to a list first. + * - Missing `dict_views.isdisjoint`. + * - Missing `tp_as_number` (`nb_subtract`, `nb_and`, `nb_xor`, `nb_or`). + * \{ */ + +static PyObject *BPy_IDGroup_View_repr(BPy_IDGroup_View *self) +{ + if (self->group == NULL) { + return PyUnicode_FromFormat("<%s>", Py_TYPE(self)->tp_name); + } + return PyUnicode_FromFormat("<%s \"%s\">", Py_TYPE(self)->tp_name, self->group->prop->name); +} + +static void BPy_IDGroup_View_dealloc(BPy_IDGroup_View *self) +{ + if (self->group != NULL) { + PyObject_GC_UnTrack(self); + } + Py_CLEAR(self->group); + PyObject_GC_Del(self); +} + +static int BPy_IDGroup_View_traverse(BPy_IDGroup_View *self, visitproc visit, void *arg) +{ + Py_VISIT(self->group); + return 0; +} + +static int BPy_IDGroup_View_clear(BPy_IDGroup_View *self) +{ + Py_CLEAR(self->group); + return 0; +} + +/* View Specific API's (Key/Value/Items). */ + +static PyObject *BPy_Group_ViewKeys_iter(BPy_IDGroup_View *self) +{ + return BPy_IDGroup_IterKeys_CreatePyObject(self->group, self->reversed); +} + +static PyObject *BPy_Group_ViewValues_iter(BPy_IDGroup_View *self) +{ + return BPy_IDGroup_IterValues_CreatePyObject(self->group, self->reversed); +} + +static PyObject *BPy_Group_ViewItems_iter(BPy_IDGroup_View *self) +{ + return BPy_IDGroup_IterItems_CreatePyObject(self->group, self->reversed); +} + +static Py_ssize_t BPy_Group_View_len(BPy_IDGroup_View *self) +{ + if (self->group == NULL) { + return 0; + } + return self->group->prop->len; +} + +static int BPy_Group_ViewKeys_Contains(BPy_IDGroup_View *self, PyObject *value) +{ + if (self->group == NULL) { + return 0; + } + return BPy_IDGroup_Contains(self->group, value); +} + +static int BPy_Group_ViewValues_Contains(BPy_IDGroup_View *self, PyObject *value) +{ + if (self->group == NULL) { + return 0; + } + /* TODO: implement this without first converting to a list. */ + PyObject *list = PySequence_List((PyObject *)self); + const int result = PySequence_Contains(list, value); + Py_DECREF(list); + return result; +} + +static int BPy_Group_ViewItems_Contains(BPy_IDGroup_View *self, PyObject *value) +{ + if (self->group == NULL) { + return 0; + } + /* TODO: implement this without first converting to a list. */ + PyObject *list = PySequence_List((PyObject *)self); + const int result = PySequence_Contains(list, value); + Py_DECREF(list); + return result; +} + +static PySequenceMethods BPy_IDGroup_ViewKeys_as_sequence = { + (lenfunc)BPy_Group_View_len, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + 0, /* sq_item */ + 0, /* sq_slice */ + 0, /* sq_ass_item */ + 0, /* sq_ass_slice */ + (objobjproc)BPy_Group_ViewKeys_Contains, /* sq_contains */ +}; + +static PySequenceMethods BPy_IDGroup_ViewValues_as_sequence = { + (lenfunc)BPy_Group_View_len, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + 0, /* sq_item */ + 0, /* sq_slice */ + 0, /* sq_ass_item */ + 0, /* sq_ass_slice */ + (objobjproc)BPy_Group_ViewValues_Contains, /* sq_contains */ +}; + +static PySequenceMethods BPy_IDGroup_ViewItems_as_sequence = { + (lenfunc)BPy_Group_View_len, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + 0, /* sq_item */ + 0, /* sq_slice */ + 0, /* sq_ass_item */ + 0, /* sq_ass_slice */ + (objobjproc)BPy_Group_ViewItems_Contains, /* sq_contains */ +}; + +/* Methods. */ + +PyDoc_STRVAR(BPy_IDGroup_View_reversed_doc, + "Return a reverse iterator over the ID Property keys values or items."); + +static PyObject *BPy_IDGroup_View_reversed(BPy_IDGroup_View *self, PyObject *UNUSED(ignored)) +{ + BPy_IDGroup_View *result = IDGroup_View_New_WithType(self->group, Py_TYPE(self)); + result->reversed = !self->reversed; + return (PyObject *)result; +} + +static PyMethodDef BPy_IDGroup_View_methods[] = { + {"__reversed__", + (PyCFunction)(void (*)(void))BPy_IDGroup_View_reversed, + METH_NOARGS, + BPy_IDGroup_View_reversed_doc}, + {NULL, NULL}, +}; + +PyTypeObject BPy_IDGroup_ViewKeys_Type = {PyVarObject_HEAD_INIT(NULL, 0)}; +PyTypeObject BPy_IDGroup_ViewValues_Type = {PyVarObject_HEAD_INIT(NULL, 0)}; +PyTypeObject BPy_IDGroup_ViewItems_Type = {PyVarObject_HEAD_INIT(NULL, 0)}; + +/* ID Property Group View. */ +static void IDGroup_View_init_type(void) +{ + PyTypeObject *k_ty = &BPy_IDGroup_ViewKeys_Type; + PyTypeObject *v_ty = &BPy_IDGroup_ViewValues_Type; + PyTypeObject *i_ty = &BPy_IDGroup_ViewItems_Type; + + /* Unique members. */ + k_ty->tp_name = "IDPropertyGroupViewKeys"; + v_ty->tp_name = "IDPropertyGroupViewValues"; + i_ty->tp_name = "IDPropertyGroupViewItems"; + + k_ty->tp_iter = (getiterfunc)BPy_Group_ViewKeys_iter; + v_ty->tp_iter = (getiterfunc)BPy_Group_ViewValues_iter; + i_ty->tp_iter = (getiterfunc)BPy_Group_ViewItems_iter; + + k_ty->tp_as_sequence = &BPy_IDGroup_ViewKeys_as_sequence; + v_ty->tp_as_sequence = &BPy_IDGroup_ViewValues_as_sequence; + i_ty->tp_as_sequence = &BPy_IDGroup_ViewItems_as_sequence; + + /* Shared members. */ +#define SHARED_MEMBER_SET(member, value) \ + { \ + k_ty->member = v_ty->member = i_ty->member = value; \ + } \ + ((void)0) + + SHARED_MEMBER_SET(tp_basicsize, sizeof(BPy_IDGroup_View)); + SHARED_MEMBER_SET(tp_dealloc, (destructor)BPy_IDGroup_View_dealloc); + SHARED_MEMBER_SET(tp_repr, (reprfunc)BPy_IDGroup_View_repr); + SHARED_MEMBER_SET(tp_flags, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC); + SHARED_MEMBER_SET(tp_traverse, (traverseproc)BPy_IDGroup_View_traverse); + SHARED_MEMBER_SET(tp_clear, (inquiry)BPy_IDGroup_View_clear); + SHARED_MEMBER_SET(tp_methods, BPy_IDGroup_View_methods); + +#undef SHARED_MEMBER_SET +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name ID-Property Group Methods * \{ */ @@ -923,22 +1293,6 @@ static PyObject *BPy_IDGroup_pop(BPy_IDProperty *self, PyObject *args) return pyform; } -PyDoc_STRVAR( - BPy_IDGroup_iter_items_doc, - ".. method:: iteritems()\n" - "\n" - " Iterate through the items in the dict; behaves like dictionary method iteritems.\n"); -static PyObject *BPy_IDGroup_iter_items(BPy_IDProperty *self) -{ - BPy_IDGroup_Iter *iter = PyObject_GC_New(BPy_IDGroup_Iter, &BPy_IDGroup_Iter_Type); - iter->group = self; - Py_INCREF(self); - iter->mode = IDPROP_ITER_ITEMS; - iter->cur = self->prop->data.group.first; - PyObject_GC_Track(iter); - return (PyObject *)iter; -} - /* utility function */ static void BPy_IDGroup_CorrectListLen(IDProperty *prop, PyObject *seq, int len, const char *func) { @@ -1023,13 +1377,37 @@ PyObject *BPy_Wrap_GetItems(ID *id, IDProperty *prop) return seq; } +PyObject *BPy_Wrap_GetKeys_View_WithID(ID *id, IDProperty *prop) +{ + PyObject *self = prop ? idprop_py_from_idp_group(id, prop, NULL) : NULL; + PyObject *ret = BPy_IDGroup_ViewKeys_CreatePyObject((BPy_IDProperty *)self); + Py_XDECREF(self); /* Owned by `ret`. */ + return ret; +} + +PyObject *BPy_Wrap_GetValues_View_WithID(ID *id, IDProperty *prop) +{ + PyObject *self = prop ? idprop_py_from_idp_group(id, prop, NULL) : NULL; + PyObject *ret = BPy_IDGroup_ViewValues_CreatePyObject((BPy_IDProperty *)self); + Py_XDECREF(self); /* Owned by `ret`. */ + return ret; +} + +PyObject *BPy_Wrap_GetItems_View_WithID(ID *id, IDProperty *prop) +{ + PyObject *self = prop ? idprop_py_from_idp_group(id, prop, NULL) : NULL; + PyObject *ret = BPy_IDGroup_ViewItems_CreatePyObject((BPy_IDProperty *)self); + Py_XDECREF(self); /* Owned by `ret`. */ + return ret; +} + PyDoc_STRVAR(BPy_IDGroup_keys_doc, ".. method:: keys()\n" "\n" " Return the keys associated with this group as a list of strings.\n"); static PyObject *BPy_IDGroup_keys(BPy_IDProperty *self) { - return BPy_Wrap_GetKeys(self->prop); + return BPy_IDGroup_ViewKeys_CreatePyObject(self); } PyDoc_STRVAR(BPy_IDGroup_values_doc, @@ -1038,16 +1416,16 @@ PyDoc_STRVAR(BPy_IDGroup_values_doc, " Return the values associated with this group.\n"); static PyObject *BPy_IDGroup_values(BPy_IDProperty *self) { - return BPy_Wrap_GetValues(self->id, self->prop); + return BPy_IDGroup_ViewValues_CreatePyObject(self); } PyDoc_STRVAR(BPy_IDGroup_items_doc, ".. method:: items()\n" "\n" - " Return the items associated with this group.\n"); + " Iterate through the items in the dict; behaves like dictionary method items.\n"); static PyObject *BPy_IDGroup_items(BPy_IDProperty *self) { - return BPy_Wrap_GetItems(self->id, self->prop); + return BPy_IDGroup_ViewItems_CreatePyObject(self); } static int BPy_IDGroup_Contains(BPy_IDProperty *self, PyObject *value) @@ -1148,7 +1526,6 @@ static PyObject *BPy_IDGroup_get(BPy_IDProperty *self, PyObject *args) static struct PyMethodDef BPy_IDGroup_methods[] = { {"pop", (PyCFunction)BPy_IDGroup_pop, METH_VARARGS, BPy_IDGroup_pop_doc}, - {"iteritems", (PyCFunction)BPy_IDGroup_iter_items, METH_NOARGS, BPy_IDGroup_iter_items_doc}, {"keys", (PyCFunction)BPy_IDGroup_keys, METH_NOARGS, BPy_IDGroup_keys_doc}, {"values", (PyCFunction)BPy_IDGroup_values, METH_NOARGS, BPy_IDGroup_values_doc}, {"items", (PyCFunction)BPy_IDGroup_items, METH_NOARGS, BPy_IDGroup_items_doc}, @@ -1678,120 +2055,59 @@ PyTypeObject BPy_IDArray_Type = { /** \} */ /* -------------------------------------------------------------------- */ -/** \name ID-Property Group Iterator Type +/** \name Initialize Types * \{ */ -static PyObject *IDGroup_Iter_repr(BPy_IDGroup_Iter *self) +void IDProp_Init_Types(void) { - return PyUnicode_FromFormat("(ID Property Group Iter \"%s\")", self->group->prop->name); -} + IDGroup_Iter_init_type(); + IDGroup_View_init_type(); -static void BPy_IDGroup_Iter_dealloc(BPy_IDGroup_Iter *self) -{ - PyObject_GC_UnTrack(self); - Py_CLEAR(self->group); - PyObject_GC_Del(self); + PyType_Ready(&BPy_IDGroup_Type); + PyType_Ready(&BPy_IDArray_Type); + + PyType_Ready(&BPy_IDGroup_IterKeys_Type); + PyType_Ready(&BPy_IDGroup_IterValues_Type); + PyType_Ready(&BPy_IDGroup_IterItems_Type); + + PyType_Ready(&BPy_IDGroup_ViewKeys_Type); + PyType_Ready(&BPy_IDGroup_ViewValues_Type); + PyType_Ready(&BPy_IDGroup_ViewItems_Type); } -static int BPy_IDGroup_Iter_traverse(BPy_IDGroup_Iter *self, visitproc visit, void *arg) +/** + * \note `group` may be NULL, unlike most other uses of this argument. + * This is supported so RNA keys/values/items methods returns an iterator with the expected type: + * - Without having ID-properties. + * - Without supporting #BPy_IDProperty.prop being NULL, which would incur many more checks. + * Python's own dictionary-views also works this way too. + */ +static BPy_IDGroup_View *IDGroup_View_New_WithType(BPy_IDProperty *group, PyTypeObject *type) { - Py_VISIT(self->group); - return 0; + BLI_assert(group ? group->prop->type == IDP_GROUP : true); + BPy_IDGroup_View *iter = PyObject_GC_New(BPy_IDGroup_View, type); + iter->reversed = false; + iter->group = group; + if (group != NULL) { + Py_INCREF(group); + PyObject_GC_Track(iter); + } + return iter; } -static int BPy_IDGroup_Iter_clear(BPy_IDGroup_Iter *self) +static PyObject *BPy_IDGroup_ViewKeys_CreatePyObject(BPy_IDProperty *group) { - Py_CLEAR(self->group); - return 0; + return (PyObject *)IDGroup_View_New_WithType(group, &BPy_IDGroup_ViewKeys_Type); } -static PyObject *BPy_Group_Iter_Next(BPy_IDGroup_Iter *self) +static PyObject *BPy_IDGroup_ViewValues_CreatePyObject(BPy_IDProperty *group) { - - if (self->cur) { - PyObject *ret; - IDProperty *cur; - - cur = self->cur; - self->cur = self->cur->next; - - if (self->mode == IDPROP_ITER_ITEMS) { - ret = PyTuple_New(2); - PyTuple_SET_ITEMS(ret, - PyUnicode_FromString(cur->name), - BPy_IDGroup_WrapData(self->group->id, cur, self->group->prop)); - return ret; - } - - return PyUnicode_FromString(cur->name); - } - - PyErr_SetNone(PyExc_StopIteration); - return NULL; + return (PyObject *)IDGroup_View_New_WithType(group, &BPy_IDGroup_ViewValues_Type); } -PyTypeObject BPy_IDGroup_Iter_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - /* For printing, in format "<module>.<name>" */ - "IDPropertyGroupIter", /* char *tp_name; */ - sizeof(BPy_IDGroup_Iter), /* int tp_basicsize; */ - 0, /* tp_itemsize; For allocation */ - - /* Methods to implement standard operations */ - - (destructor)BPy_IDGroup_Iter_dealloc, /* tp_dealloc */ - 0, /* tp_vectorcall_offset */ - NULL, /* getattrfunc tp_getattr; */ - NULL, /* setattrfunc tp_setattr; */ - NULL, /* cmpfunc tp_compare; */ - (reprfunc)IDGroup_Iter_repr, /* reprfunc tp_repr; */ - - /* Method suites for standard classes */ - - NULL, /* PyNumberMethods *tp_as_number; */ - NULL, /* PySequenceMethods *tp_as_sequence; */ - NULL, /* PyMappingMethods *tp_as_mapping; */ - - /* More standard operations (here for binary compatibility) */ - - NULL, /* hashfunc tp_hash; */ - NULL, /* ternaryfunc tp_call; */ - NULL, /* reprfunc tp_str; */ - NULL, /* getattrofunc tp_getattro; */ - NULL, /* setattrofunc tp_setattro; */ - - /* Functions to access object as input/output buffer */ - NULL, /* PyBufferProcs *tp_as_buffer; */ - - /*** Flags to define presence of optional/expanded features ***/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* long tp_flags; */ - - NULL, /* char *tp_doc; Documentation string */ - /*** Assigned meaning in release 2.0 ***/ - /* call function for all accessible objects */ - (traverseproc)BPy_IDGroup_Iter_traverse, /* traverseproc tp_traverse; */ - - /* delete references to contained objects */ - (inquiry)BPy_IDGroup_Iter_clear, /* inquiry tp_clear; */ - - /*** Assigned meaning in release 2.1 ***/ - /*** rich comparisons ***/ - NULL, /* richcmpfunc tp_richcompare; */ - - /*** weak reference enabler ***/ - 0, /* long tp_weaklistoffset; */ - - /*** Added in release 2.2 ***/ - /* Iterators */ - PyObject_SelfIter, /* getiterfunc tp_iter; */ - (iternextfunc)BPy_Group_Iter_Next, /* iternextfunc tp_iternext; */ -}; - -void IDProp_Init_Types(void) +static PyObject *BPy_IDGroup_ViewItems_CreatePyObject(BPy_IDProperty *group) { - PyType_Ready(&BPy_IDGroup_Type); - PyType_Ready(&BPy_IDGroup_Iter_Type); - PyType_Ready(&BPy_IDArray_Type); + return (PyObject *)IDGroup_View_New_WithType(group, &BPy_IDGroup_ViewItems_Type); } /** \} */ @@ -1822,7 +2138,15 @@ static PyObject *BPyInit_idprop_types(void) /* bmesh_py_types.c */ PyModule_AddType(submodule, &BPy_IDGroup_Type); - PyModule_AddType(submodule, &BPy_IDGroup_Iter_Type); + + PyModule_AddType(submodule, &BPy_IDGroup_ViewKeys_Type); + PyModule_AddType(submodule, &BPy_IDGroup_ViewValues_Type); + PyModule_AddType(submodule, &BPy_IDGroup_ViewItems_Type); + + PyModule_AddType(submodule, &BPy_IDGroup_IterKeys_Type); + PyModule_AddType(submodule, &BPy_IDGroup_IterValues_Type); + PyModule_AddType(submodule, &BPy_IDGroup_IterItems_Type); + PyModule_AddType(submodule, &BPy_IDArray_Type); return submodule; diff --git a/source/blender/python/generic/idprop_py_api.h b/source/blender/python/generic/idprop_py_api.h index 4cccea3a936..1e8e26a3b6d 100644 --- a/source/blender/python/generic/idprop_py_api.h +++ b/source/blender/python/generic/idprop_py_api.h @@ -25,16 +25,35 @@ struct ID; struct IDProperty; extern PyTypeObject BPy_IDArray_Type; -extern PyTypeObject BPy_IDGroup_Iter_Type; extern PyTypeObject BPy_IDGroup_Type; +extern PyTypeObject BPy_IDGroup_ViewKeys_Type; +extern PyTypeObject BPy_IDGroup_ViewValues_Type; +extern PyTypeObject BPy_IDGroup_ViewItems_Type; + +extern PyTypeObject BPy_IDGroup_IterKeys_Type; +extern PyTypeObject BPy_IDGroup_IterValues_Type; +extern PyTypeObject BPy_IDGroup_IterItems_Type; + #define BPy_IDArray_Check(v) (PyObject_TypeCheck(v, &BPy_IDArray_Type)) #define BPy_IDArray_CheckExact(v) (Py_TYPE(v) == &BPy_IDArray_Type) -#define BPy_IDGroup_Iter_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_Iter_Type)) -#define BPy_IDGroup_Iter_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_Iter_Type) #define BPy_IDGroup_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_Type)) #define BPy_IDGroup_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_Type) +#define BPy_IDGroup_ViewKeys_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_ViewKeys_Type)) +#define BPy_IDGroup_ViewKeys_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_ViewKeys_Type) +#define BPy_IDGroup_ViewValues_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_ViewValues_Type)) +#define BPy_IDGroup_ViewValues_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_ViewValues_Type) +#define BPy_IDGroup_ViewItems_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_ViewItems_Type)) +#define BPy_IDGroup_ViewItems_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_ViewItems_Type) + +#define BPy_IDGroup_IterKeys_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_IterKeys_Type)) +#define BPy_IDGroup_IterKeys_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_IterKeys_Type) +#define BPy_IDGroup_IterValues_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_IterValues_Type)) +#define BPy_IDGroup_IterValues_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_IterValues_Type) +#define BPy_IDGroup_IterItems_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_IterItems_Type)) +#define BPy_IDGroup_IterItems_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_IterItems_Type) + typedef struct BPy_IDProperty { PyObject_VAR_HEAD struct ID *id; /* can be NULL */ @@ -52,12 +71,28 @@ typedef struct BPy_IDGroup_Iter { PyObject_VAR_HEAD BPy_IDProperty *group; struct IDProperty *cur; - int mode; + /** Use for detecting manipulation during iteration (which is not allowed). */ + int len_init; + /** Iterate in the reverse direction. */ + bool reversed; } BPy_IDGroup_Iter; +/** Use to implement `IDPropertyGroup.keys/values/items` */ +typedef struct BPy_IDGroup_View { + PyObject_VAR_HEAD + /** This will be NULL when accessing keys on data that has no ID properties. */ + BPy_IDProperty *group; + bool reversed; +} BPy_IDGroup_View; + PyObject *BPy_Wrap_GetKeys(struct IDProperty *prop); PyObject *BPy_Wrap_GetValues(struct ID *id, struct IDProperty *prop); PyObject *BPy_Wrap_GetItems(struct ID *id, struct IDProperty *prop); + +PyObject *BPy_Wrap_GetKeys_View_WithID(struct ID *id, struct IDProperty *prop); +PyObject *BPy_Wrap_GetValues_View_WithID(struct ID *id, struct IDProperty *prop); +PyObject *BPy_Wrap_GetItems_View_WithID(struct ID *id, struct IDProperty *prop); + int BPy_Wrap_SetMapItem(struct IDProperty *prop, PyObject *key, PyObject *val); PyObject *BPy_IDGroup_MapDataToPy(struct IDProperty *prop); @@ -67,6 +102,3 @@ bool BPy_IDProperty_Map_ValidateAndCreate(PyObject *key, struct IDProperty *grou void IDProp_Init_Types(void); PyObject *BPyInit_idprop(void); - -#define IDPROP_ITER_KEYS 0 -#define IDPROP_ITER_ITEMS 1 diff --git a/source/blender/python/gpu/CMakeLists.txt b/source/blender/python/gpu/CMakeLists.txt index fe5c559fcc0..1424b35a004 100644 --- a/source/blender/python/gpu/CMakeLists.txt +++ b/source/blender/python/gpu/CMakeLists.txt @@ -37,10 +37,12 @@ set(SRC gpu_py_api.c gpu_py_batch.c gpu_py_buffer.c + gpu_py_capabilities.c gpu_py_element.c gpu_py_framebuffer.c gpu_py_matrix.c gpu_py_offscreen.c + gpu_py_platform.c gpu_py_select.c gpu_py_shader.c gpu_py_state.c @@ -54,10 +56,12 @@ set(SRC gpu_py_api.h gpu_py_batch.h gpu_py_buffer.h + gpu_py_capabilities.h gpu_py_element.h gpu_py_framebuffer.h gpu_py_matrix.h gpu_py_offscreen.h + gpu_py_platform.h gpu_py_select.h gpu_py_shader.h gpu_py_state.h diff --git a/source/blender/python/gpu/gpu_py_api.c b/source/blender/python/gpu/gpu_py_api.c index 0bc18e73d0c..5119b3612f8 100644 --- a/source/blender/python/gpu/gpu_py_api.c +++ b/source/blender/python/gpu/gpu_py_api.c @@ -30,7 +30,9 @@ #include "../generic/python_utildefines.h" +#include "gpu_py_capabilities.h" #include "gpu_py_matrix.h" +#include "gpu_py_platform.h" #include "gpu_py_select.h" #include "gpu_py_state.h" #include "gpu_py_types.h" @@ -61,9 +63,15 @@ PyObject *BPyInit_gpu(void) PyModule_AddObject(mod, "types", (submodule = bpygpu_types_init())); PyDict_SetItem(sys_modules, PyModule_GetNameObject(submodule), submodule); + PyModule_AddObject(mod, "capabilities", (submodule = bpygpu_capabilities_init())); + PyDict_SetItem(sys_modules, PyModule_GetNameObject(submodule), submodule); + PyModule_AddObject(mod, "matrix", (submodule = bpygpu_matrix_init())); PyDict_SetItem(sys_modules, PyModule_GetNameObject(submodule), submodule); + PyModule_AddObject(mod, "platform", (submodule = bpygpu_platform_init())); + PyDict_SetItem(sys_modules, PyModule_GetNameObject(submodule), submodule); + PyModule_AddObject(mod, "select", (submodule = bpygpu_select_init())); PyDict_SetItem(sys_modules, PyModule_GetNameObject(submodule), submodule); diff --git a/source/blender/python/gpu/gpu_py_buffer.c b/source/blender/python/gpu/gpu_py_buffer.c index d0965e83e33..e36e3b42617 100644 --- a/source/blender/python/gpu/gpu_py_buffer.c +++ b/source/blender/python/gpu/gpu_py_buffer.c @@ -37,17 +37,90 @@ #include "gpu_py_buffer.h" -// #define PYGPU_BUFFER_PROTOCOL +//#define PYGPU_BUFFER_PROTOCOL +#define MAX_DIMENSIONS 64 /* -------------------------------------------------------------------- */ /** \name Utility Functions * \{ */ -static bool pygpu_buffer_dimensions_compare(int ndim, - const Py_ssize_t *shape_a, - const Py_ssize_t *shape_b) +static Py_ssize_t pygpu_buffer_dimensions_tot_elem(const Py_ssize_t *shape, Py_ssize_t shape_len) +{ + Py_ssize_t tot = shape[0]; + for (int i = 1; i < shape_len; i++) { + tot *= shape[i]; + } + + return tot; +} + +static bool pygpu_buffer_dimensions_tot_len_compare(const Py_ssize_t *shape_a, + const Py_ssize_t shape_a_len, + const Py_ssize_t *shape_b, + const Py_ssize_t shape_b_len) +{ + if (pygpu_buffer_dimensions_tot_elem(shape_a, shape_a_len) != + pygpu_buffer_dimensions_tot_elem(shape_b, shape_b_len)) { + PyErr_Format(PyExc_BufferError, "array size does not match"); + return false; + } + + return true; +} + +static bool pygpu_buffer_pyobj_as_shape(PyObject *shape_obj, + Py_ssize_t r_shape[MAX_DIMENSIONS], + Py_ssize_t *r_shape_len) { - return (bool)memcmp(shape_a, shape_b, ndim * sizeof(Py_ssize_t)); + Py_ssize_t shape_len = 0; + if (PyLong_Check(shape_obj)) { + shape_len = 1; + if (((r_shape[0] = PyLong_AsLong(shape_obj)) < 1)) { + PyErr_SetString(PyExc_AttributeError, "dimension must be greater than or equal to 1"); + return false; + } + } + else if (PySequence_Check(shape_obj)) { + shape_len = PySequence_Size(shape_obj); + if (shape_len > MAX_DIMENSIONS) { + PyErr_SetString(PyExc_AttributeError, + "too many dimensions, max is " STRINGIFY(MAX_DIMENSIONS)); + return false; + } + if (shape_len < 1) { + PyErr_SetString(PyExc_AttributeError, "sequence must have at least one dimension"); + return false; + } + + for (int i = 0; i < shape_len; i++) { + PyObject *ob = PySequence_GetItem(shape_obj, i); + if (!PyLong_Check(ob)) { + PyErr_Format(PyExc_TypeError, + "invalid dimension %i, expected an int, not a %.200s", + i, + Py_TYPE(ob)->tp_name); + Py_DECREF(ob); + return false; + } + + r_shape[i] = PyLong_AsLong(ob); + Py_DECREF(ob); + + if (r_shape[i] < 1) { + PyErr_SetString(PyExc_AttributeError, "dimension must be greater than or equal to 1"); + return false; + } + } + } + else { + PyErr_Format(PyExc_TypeError, + "invalid second argument argument expected a sequence " + "or an int, not a %.200s", + Py_TYPE(shape_obj)->tp_name); + } + + *r_shape_len = shape_len; + return true; } static const char *pygpu_buffer_formatstr(eGPUDataFormat data_format) @@ -174,7 +247,7 @@ static PyObject *pygpu_buffer_to_list_recursive(BPyGPUBuffer *self) return list; } -static PyObject *pygpu_buffer_dimensions(BPyGPUBuffer *self, void *UNUSED(arg)) +static PyObject *pygpu_buffer_dimensions_get(BPyGPUBuffer *self, void *UNUSED(arg)) { PyObject *list = PyList_New(self->shape_len); int i; @@ -186,6 +259,30 @@ static PyObject *pygpu_buffer_dimensions(BPyGPUBuffer *self, void *UNUSED(arg)) return list; } +static int pygpu_buffer_dimensions_set(BPyGPUBuffer *self, PyObject *value, void *UNUSED(type)) +{ + Py_ssize_t shape[MAX_DIMENSIONS]; + Py_ssize_t shape_len = 0; + + if (!pygpu_buffer_pyobj_as_shape(value, shape, &shape_len)) { + return -1; + } + + if (!pygpu_buffer_dimensions_tot_len_compare(shape, shape_len, self->shape, self->shape_len)) { + return -1; + } + + size_t size = shape_len * sizeof(*self->shape); + if (shape_len != self->shape_len) { + MEM_freeN(self->shape); + self->shape = MEM_mallocN(size, __func__); + } + + self->shape_len = shape_len; + memcpy(self->shape, shape, size); + return 0; +} + static int pygpu_buffer__tp_traverse(BPyGPUBuffer *self, visitproc visit, void *arg) { Py_VISIT(self->parent); @@ -280,14 +377,13 @@ static int pygpu_buffer_ass_slice(BPyGPUBuffer *self, return err; } -#define MAX_DIMENSIONS 64 static PyObject *pygpu_buffer__tp_new(PyTypeObject *UNUSED(type), PyObject *args, PyObject *kwds) { PyObject *length_ob, *init = NULL; BPyGPUBuffer *buffer = NULL; Py_ssize_t shape[MAX_DIMENSIONS]; - Py_ssize_t i, shape_len = 0; + Py_ssize_t shape_len = 0; if (kwds && PyDict_Size(kwds)) { PyErr_SetString(PyExc_TypeError, "Buffer(): takes no keyword args"); @@ -300,49 +396,7 @@ static PyObject *pygpu_buffer__tp_new(PyTypeObject *UNUSED(type), PyObject *args return NULL; } - if (PyLong_Check(length_ob)) { - shape_len = 1; - if (((shape[0] = PyLong_AsLong(length_ob)) < 1)) { - PyErr_SetString(PyExc_AttributeError, "dimension must be greater than or equal to 1"); - return NULL; - } - } - else if (PySequence_Check(length_ob)) { - shape_len = PySequence_Size(length_ob); - if (shape_len > MAX_DIMENSIONS) { - PyErr_SetString(PyExc_AttributeError, - "too many dimensions, max is " STRINGIFY(MAX_DIMENSIONS)); - return NULL; - } - if (shape_len < 1) { - PyErr_SetString(PyExc_AttributeError, "sequence must have at least one dimension"); - return NULL; - } - - for (i = 0; i < shape_len; i++) { - PyObject *ob = PySequence_GetItem(length_ob, i); - if (!PyLong_Check(ob)) { - PyErr_Format(PyExc_TypeError, - "invalid dimension %i, expected an int, not a %.200s", - i, - Py_TYPE(ob)->tp_name); - Py_DECREF(ob); - return NULL; - } - shape[i] = PyLong_AsLong(ob); - Py_DECREF(ob); - - if (shape[i] < 1) { - PyErr_SetString(PyExc_AttributeError, "dimension must be greater than or equal to 1"); - return NULL; - } - } - } - else { - PyErr_Format(PyExc_TypeError, - "invalid second argument argument expected a sequence " - "or an int, not a %.200s", - Py_TYPE(length_ob)->tp_name); + if (!pygpu_buffer_pyobj_as_shape(length_ob, shape, &shape_len)) { return NULL; } @@ -354,11 +408,7 @@ static PyObject *pygpu_buffer__tp_new(PyTypeObject *UNUSED(type), PyObject *args return NULL; } - if (shape_len != pybuffer.ndim || - !pygpu_buffer_dimensions_compare(shape_len, shape, pybuffer.shape)) { - PyErr_Format(PyExc_TypeError, "array size does not match"); - } - else { + if (pygpu_buffer_dimensions_tot_len_compare(shape, shape_len, pybuffer.shape, pybuffer.ndim)) { buffer = pygpu_buffer_make_from_data( init, pygpu_dataformat.value_found, pybuffer.ndim, shape, pybuffer.buf); } @@ -518,7 +568,11 @@ static PyMethodDef pygpu_buffer__tp_methods[] = { }; static PyGetSetDef pygpu_buffer_getseters[] = { - {"dimensions", (getter)pygpu_buffer_dimensions, NULL, NULL, NULL}, + {"dimensions", + (getter)pygpu_buffer_dimensions_get, + (setter)pygpu_buffer_dimensions_set, + NULL, + NULL}, {NULL, NULL, NULL, NULL, NULL}, }; @@ -625,13 +679,7 @@ static size_t pygpu_buffer_calc_size(const int format, const int shape_len, const Py_ssize_t *shape) { - size_t r_size = GPU_texture_dataformat_size(format); - - for (int i = 0; i < shape_len; i++) { - r_size *= shape[i]; - } - - return r_size; + return pygpu_buffer_dimensions_tot_elem(shape, shape_len) * GPU_texture_dataformat_size(format); } size_t bpygpu_Buffer_size(BPyGPUBuffer *buffer) diff --git a/source/blender/python/gpu/gpu_py_capabilities.c b/source/blender/python/gpu/gpu_py_capabilities.c new file mode 100644 index 00000000000..cedce485253 --- /dev/null +++ b/source/blender/python/gpu/gpu_py_capabilities.c @@ -0,0 +1,148 @@ +/* + * 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. + */ + +/** \file + * \ingroup bpygpu + * + * - Use ``bpygpu_`` for local API. + * - Use ``BPyGPU`` for public API. + */ + +#include <Python.h> + +#include "BLI_utildefines.h" + +#include "GPU_capabilities.h" + +#include "gpu_py_capabilities.h" /* own include */ + +/* -------------------------------------------------------------------- */ +/** \name Functions + * \{ */ + +static PyObject *pygpu_max_texture_size_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_texture_size()); +} + +static PyObject *pygpu_max_texture_layers_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_texture_layers()); +} + +static PyObject *pygpu_max_textures_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_textures()); +} + +static PyObject *pygpu_max_textures_vert_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_textures_vert()); +} + +static PyObject *pygpu_max_textures_geom_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_textures_geom()); +} + +static PyObject *pygpu_max_textures_frag_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_textures_frag()); +} + +static PyObject *pygpu_max_uniforms_vert_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_uniforms_vert()); +} + +static PyObject *pygpu_max_uniforms_frag_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_uniforms_frag()); +} + +static PyObject *pygpu_max_batch_indices_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_batch_indices()); +} + +static PyObject *pygpu_max_batch_vertices_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_batch_vertices()); +} + +static PyObject *pygpu_max_vertex_attribs_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_vertex_attribs()); +} + +static PyObject *pygpu_max_varying_floats_get(PyObject *UNUSED(self)) +{ + return PyLong_FromLong(GPU_max_varying_floats()); +} + +static PyObject *pygpu_extensions_get(PyObject *UNUSED(self)) +{ + int extensions_len = GPU_extensions_len(); + PyObject *ret = PyTuple_New(extensions_len); + PyObject **ob_items = ((PyTupleObject *)ret)->ob_item; + for (int i = 0; i < extensions_len; i++) { + ob_items[i] = PyUnicode_FromString(GPU_extension_get(i)); + } + + return ret; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Module + * \{ */ + +static struct PyMethodDef pygpu_capabilities__tp_methods[] = { + {"max_texture_size_get", (PyCFunction)pygpu_max_texture_size_get, METH_NOARGS, NULL}, + {"max_texture_layers_get", (PyCFunction)pygpu_max_texture_layers_get, METH_NOARGS, NULL}, + {"max_textures_get", (PyCFunction)pygpu_max_textures_get, METH_NOARGS, NULL}, + {"max_textures_vert_get", (PyCFunction)pygpu_max_textures_vert_get, METH_NOARGS, NULL}, + {"max_textures_geom_get", (PyCFunction)pygpu_max_textures_geom_get, METH_NOARGS, NULL}, + {"max_textures_frag_get", (PyCFunction)pygpu_max_textures_frag_get, METH_NOARGS, NULL}, + {"max_uniforms_vert_get", (PyCFunction)pygpu_max_uniforms_vert_get, METH_NOARGS, NULL}, + {"max_uniforms_frag_get", (PyCFunction)pygpu_max_uniforms_frag_get, METH_NOARGS, NULL}, + {"max_batch_indices_get", (PyCFunction)pygpu_max_batch_indices_get, METH_NOARGS, NULL}, + {"max_batch_vertices_get", (PyCFunction)pygpu_max_batch_vertices_get, METH_NOARGS, NULL}, + {"max_vertex_attribs_get", (PyCFunction)pygpu_max_vertex_attribs_get, METH_NOARGS, NULL}, + {"max_varying_floats_get", (PyCFunction)pygpu_max_varying_floats_get, METH_NOARGS, NULL}, + {"extensions_get", (PyCFunction)pygpu_extensions_get, METH_NOARGS, NULL}, + {NULL, NULL, 0, NULL}, +}; + +PyDoc_STRVAR(pygpu_capabilities__tp_doc, "This module provides access to the GPU capabilities."); +static PyModuleDef pygpu_capabilities_module_def = { + PyModuleDef_HEAD_INIT, + .m_name = "gpu.capabilities", + .m_doc = pygpu_capabilities__tp_doc, + .m_methods = pygpu_capabilities__tp_methods, +}; + +PyObject *bpygpu_capabilities_init(void) +{ + PyObject *submodule; + + submodule = PyModule_Create(&pygpu_capabilities_module_def); + + return submodule; +} + +/** \} */ diff --git a/source/blender/python/gpu/gpu_py_capabilities.h b/source/blender/python/gpu/gpu_py_capabilities.h new file mode 100644 index 00000000000..ac138dda0c9 --- /dev/null +++ b/source/blender/python/gpu/gpu_py_capabilities.h @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** \file + * \ingroup bpygpu + */ + +#pragma once + +PyObject *bpygpu_capabilities_init(void); diff --git a/source/blender/python/gpu/gpu_py_framebuffer.c b/source/blender/python/gpu/gpu_py_framebuffer.c index 77eb4a37624..0efc0713538 100644 --- a/source/blender/python/gpu/gpu_py_framebuffer.c +++ b/source/blender/python/gpu/gpu_py_framebuffer.c @@ -37,6 +37,8 @@ #include "gpu_py.h" #include "gpu_py_texture.h" +#include "gpu_py.h" +#include "gpu_py_buffer.h" #include "gpu_py_framebuffer.h" /* own include */ /* -------------------------------------------------------------------- */ @@ -46,13 +48,7 @@ static int pygpu_framebuffer_valid_check(BPyGPUFrameBuffer *bpygpu_fb) { if (UNLIKELY(bpygpu_fb->fb == NULL)) { - PyErr_SetString(PyExc_ReferenceError, -#ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD - "GPU framebuffer was freed, no further access is valid" -#else - "GPU framebuffer: internal error" -#endif - ); + PyErr_SetString(PyExc_ReferenceError, "GPU framebuffer was freed, no further access is valid"); return -1; } return 0; @@ -68,10 +64,6 @@ static int pygpu_framebuffer_valid_check(BPyGPUFrameBuffer *bpygpu_fb) static void pygpu_framebuffer_free_if_possible(GPUFrameBuffer *fb) { - if (!fb) { - return; - } - if (GPU_is_init()) { GPU_framebuffer_free(fb); } @@ -80,6 +72,21 @@ static void pygpu_framebuffer_free_if_possible(GPUFrameBuffer *fb) } } +static void pygpu_framebuffer_free_safe(BPyGPUFrameBuffer *self) +{ + if (self->fb) { +#ifndef GPU_NO_USE_PY_REFERENCES + GPU_framebuffer_py_reference_set(self->fb, NULL); + if (!self->shared_reference) +#endif + { + pygpu_framebuffer_free_if_possible(self->fb); + } + + self->fb = NULL; + } +} + /* Keep less than or equal to #FRAMEBUFFER_STACK_DEPTH */ #define GPU_PY_FRAMEBUFFER_STACK_LEN 16 @@ -336,7 +343,7 @@ static PyObject *pygpu_framebuffer__tp_new(PyTypeObject *UNUSED(self), GPUFrameBuffer *fb_python = GPU_framebuffer_create("fb_python"); GPU_framebuffer_config_array(fb_python, config, color_attachements_len + 1); - return BPyGPUFrameBuffer_CreatePyObject(fb_python); + return BPyGPUFrameBuffer_CreatePyObject(fb_python, false); } PyDoc_STRVAR(pygpu_framebuffer_is_bound_doc, @@ -450,6 +457,100 @@ static PyObject *pygpu_framebuffer_viewport_get(BPyGPUFrameBuffer *self, void *U return ret; } +PyDoc_STRVAR( + pygpu_framebuffer_read_color_doc, + ".. function:: read_color(x, y, xsize, ysize, channels, slot, format, data=data)\n" + "\n" + " Read a block of pixels from the frame buffer.\n" + "\n" + " :param x, y: Lower left corner of a rectangular block of pixels.\n" + " :param xsize, ysize: Dimensions of the pixel rectangle.\n" + " :type x, y, xsize, ysize: int\n" + " :param channels: Number of components to read.\n" + " :type channels: int\n" + " :param slot: The framebuffer slot to read data from.\n" + " :type slot: int\n" + " :param format: The format that describes the content of a single channel.\n" + " Possible values are `FLOAT`, `INT`, `UINT`, `UBYTE`, `UINT_24_8` and `10_11_11_REV`.\n" + " :type type: str\n" + " :arg data: Optional Buffer object to fill with the pixels values.\n" + " :type data: :class:`gpu.types.Buffer`\n" + " :return: The Buffer with the read pixels.\n" + " :rtype: :class:`gpu.types.Buffer`\n"); +static PyObject *pygpu_framebuffer_read_color(BPyGPUFrameBuffer *self, + PyObject *args, + PyObject *kwds) +{ + PYGPU_FRAMEBUFFER_CHECK_OBJ(self); + int x, y, w, h, channels; + uint slot; + struct PyC_StringEnum pygpu_dataformat = {bpygpu_dataformat_items, GPU_RGBA8}; + BPyGPUBuffer *py_buffer = NULL; + + static const char *_keywords[] = { + "x", "y", "xsize", "ysize", "channels", "slot", "format", "data", NULL}; + static _PyArg_Parser _parser = {"iiiiiIO&|$O!:GPUTexture.__new__", _keywords, 0}; + if (!_PyArg_ParseTupleAndKeywordsFast(args, + kwds, + &_parser, + &x, + &y, + &w, + &h, + &channels, + &slot, + PyC_ParseStringEnum, + &pygpu_dataformat, + &BPyGPU_BufferType, + &py_buffer)) { + return NULL; + } + + if (!IN_RANGE_INCL(channels, 1, 4)) { + PyErr_SetString(PyExc_AttributeError, "Color channels must be 1, 2, 3 or 4"); + return NULL; + } + + if (slot >= BPYGPU_FB_MAX_COLOR_ATTACHMENT) { + PyErr_SetString(PyExc_ValueError, "slot overflow"); + return NULL; + } + + if (py_buffer) { + if (pygpu_dataformat.value_found != py_buffer->format) { + PyErr_SetString(PyExc_AttributeError, + "the format of the buffer is different from that specified"); + return NULL; + } + + size_t size_curr = bpygpu_Buffer_size(py_buffer); + size_t size_expected = w * h * channels * + GPU_texture_dataformat_size(pygpu_dataformat.value_found); + if (size_curr < size_expected) { + PyErr_SetString(PyExc_BufferError, "the buffer size is smaller than expected"); + return NULL; + } + } + else { + py_buffer = BPyGPU_Buffer_CreatePyObject( + pygpu_dataformat.value_found, (Py_ssize_t[3]){h, w, channels}, 3, NULL); + BLI_assert(bpygpu_Buffer_size(py_buffer) == + w * h * channels * GPU_texture_dataformat_size(pygpu_dataformat.value_found)); + } + + GPU_framebuffer_read_color(self->fb, + x, + y, + w, + h, + channels, + (int)slot, + pygpu_dataformat.value_found, + py_buffer->buf.as_void); + + return (PyObject *)py_buffer; +} + #ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD PyDoc_STRVAR(pygpu_framebuffer_free_doc, ".. method:: free()\n" @@ -459,15 +560,14 @@ PyDoc_STRVAR(pygpu_framebuffer_free_doc, static PyObject *pygpu_framebuffer_free(BPyGPUFrameBuffer *self) { PYGPU_FRAMEBUFFER_CHECK_OBJ(self); - pygpu_framebuffer_free_if_possible(self->fb); - self->fb = NULL; + pygpu_framebuffer_free_safe(self); Py_RETURN_NONE; } #endif static void BPyGPUFrameBuffer__tp_dealloc(BPyGPUFrameBuffer *self) { - pygpu_framebuffer_free_if_possible(self->fb); + pygpu_framebuffer_free_safe(self); Py_TYPE(self)->tp_free((PyObject *)self); } @@ -494,6 +594,10 @@ static struct PyMethodDef pygpu_framebuffer__tp_methods[] = { (PyCFunction)pygpu_framebuffer_viewport_get, METH_NOARGS, pygpu_framebuffer_viewport_get_doc}, + {"read_color", + (PyCFunction)pygpu_framebuffer_read_color, + METH_VARARGS | METH_KEYWORDS, + pygpu_framebuffer_read_color_doc}, #ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD {"free", (PyCFunction)pygpu_framebuffer_free, METH_NOARGS, pygpu_framebuffer_free_doc}, #endif @@ -531,13 +635,35 @@ PyTypeObject BPyGPUFrameBuffer_Type = { /** \name Public API * \{ */ -PyObject *BPyGPUFrameBuffer_CreatePyObject(GPUFrameBuffer *fb) +PyObject *BPyGPUFrameBuffer_CreatePyObject(GPUFrameBuffer *fb, bool shared_reference) { BPyGPUFrameBuffer *self; +#ifndef GPU_NO_USE_PY_REFERENCES + if (shared_reference) { + void **ref = GPU_framebuffer_py_reference_get(fb); + if (ref) { + /* Retrieve BPyGPUFrameBuffer reference. */ + self = (BPyGPUFrameBuffer *)POINTER_OFFSET(ref, -offsetof(BPyGPUFrameBuffer, fb)); + BLI_assert(self->fb == fb); + Py_INCREF(self); + return (PyObject *)self; + } + } +#else + UNUSED_VARS(shared_reference); +#endif + self = PyObject_New(BPyGPUFrameBuffer, &BPyGPUFrameBuffer_Type); self->fb = fb; +#ifndef GPU_NO_USE_PY_REFERENCES + self->shared_reference = shared_reference; + + BLI_assert(GPU_framebuffer_py_reference_get(fb) == NULL); + GPU_framebuffer_py_reference_set(fb, (void **)&self->fb); +#endif + return (PyObject *)self; } diff --git a/source/blender/python/gpu/gpu_py_framebuffer.h b/source/blender/python/gpu/gpu_py_framebuffer.h index 7113e7c35aa..6727328518c 100644 --- a/source/blender/python/gpu/gpu_py_framebuffer.h +++ b/source/blender/python/gpu/gpu_py_framebuffer.h @@ -28,6 +28,11 @@ extern PyTypeObject BPyGPUFrameBuffer_Type; typedef struct BPyGPUFrameBuffer { PyObject_HEAD struct GPUFrameBuffer *fb; + +#ifndef GPU_NO_USE_PY_REFERENCES + bool shared_reference; +#endif } BPyGPUFrameBuffer; -PyObject *BPyGPUFrameBuffer_CreatePyObject(struct GPUFrameBuffer *fb) ATTR_NONNULL(1); +PyObject *BPyGPUFrameBuffer_CreatePyObject(struct GPUFrameBuffer *fb, bool shared_reference) + ATTR_NONNULL(1); diff --git a/source/blender/python/gpu/gpu_py_offscreen.c b/source/blender/python/gpu/gpu_py_offscreen.c index 9e9a0b9066a..28bd24a6877 100644 --- a/source/blender/python/gpu/gpu_py_offscreen.c +++ b/source/blender/python/gpu/gpu_py_offscreen.c @@ -53,6 +53,8 @@ #include "../generic/py_capi_utils.h" #include "gpu_py.h" +#include "gpu_py_texture.h" + #include "gpu_py_offscreen.h" /* own include */ /* Define the free method to avoid breakage. */ @@ -264,6 +266,17 @@ static PyObject *pygpu_offscreen_color_texture_get(BPyGPUOffScreen *self, void * return PyLong_FromLong(GPU_texture_opengl_bindcode(texture)); } +PyDoc_STRVAR(pygpu_offscreen_texture_color_doc, + "The color texture attached.\n" + "\n" + ":type: :class:`gpu.types.GPUTexture`"); +static PyObject *pygpu_offscreen_texture_color_get(BPyGPUOffScreen *self, void *UNUSED(type)) +{ + BPY_GPU_OFFSCREEN_CHECK_OBJ(self); + GPUTexture *texture = GPU_offscreen_color_texture(self->ofs); + return BPyGPUTexture_CreatePyObject(texture, true); +} + PyDoc_STRVAR( pygpu_offscreen_draw_view3d_doc, ".. method:: draw_view3d(scene, view_layer, view3d, region, view_matrix, projection_matrix)\n" @@ -385,6 +398,11 @@ static PyGetSetDef pygpu_offscreen__tp_getseters[] = { (setter)NULL, pygpu_offscreen_color_texture_doc, NULL}, + {"texture_color", + (getter)pygpu_offscreen_texture_color_get, + (setter)NULL, + pygpu_offscreen_texture_color_doc, + NULL}, {"width", (getter)pygpu_offscreen_width_get, (setter)NULL, pygpu_offscreen_width_doc, NULL}, {"height", (getter)pygpu_offscreen_height_get, (setter)NULL, pygpu_offscreen_height_doc, NULL}, {NULL, NULL, NULL, NULL, NULL} /* Sentinel */ diff --git a/source/blender/python/gpu/gpu_py_platform.c b/source/blender/python/gpu/gpu_py_platform.c new file mode 100644 index 00000000000..e49ad18dfd8 --- /dev/null +++ b/source/blender/python/gpu/gpu_py_platform.c @@ -0,0 +1,81 @@ +/* + * 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. + */ + +/** \file + * \ingroup bpygpu + * + * - Use ``bpygpu_`` for local API. + * - Use ``BPyGPU`` for public API. + */ + +#include <Python.h> + +#include "BLI_utildefines.h" + +#include "GPU_platform.h" + +#include "gpu_py_platform.h" /* own include */ + +/* -------------------------------------------------------------------- */ +/** \name Functions + * \{ */ + +static PyObject *pygpu_platform_vendor_get(PyObject *UNUSED(self)) +{ + return PyUnicode_FromString(GPU_platform_vendor()); +} + +static PyObject *pygpu_platform_renderer_get(PyObject *UNUSED(self)) +{ + return PyUnicode_FromString(GPU_platform_renderer()); +} + +static PyObject *pygpu_platform_version_get(PyObject *UNUSED(self)) +{ + return PyUnicode_FromString(GPU_platform_version()); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Module + * \{ */ + +static struct PyMethodDef pygpu_platform__tp_methods[] = { + {"vendor_get", (PyCFunction)pygpu_platform_vendor_get, METH_NOARGS, NULL}, + {"renderer_get", (PyCFunction)pygpu_platform_renderer_get, METH_NOARGS, NULL}, + {"version_get", (PyCFunction)pygpu_platform_version_get, METH_NOARGS, NULL}, + {NULL, NULL, 0, NULL}, +}; + +PyDoc_STRVAR(pygpu_platform__tp_doc, "This module provides access to GPU Platform definitions."); +static PyModuleDef pygpu_platform_module_def = { + PyModuleDef_HEAD_INIT, + .m_name = "gpu.platform", + .m_doc = pygpu_platform__tp_doc, + .m_methods = pygpu_platform__tp_methods, +}; + +PyObject *bpygpu_platform_init(void) +{ + PyObject *submodule; + + submodule = PyModule_Create(&pygpu_platform_module_def); + + return submodule; +} + +/** \} */ diff --git a/source/blender/python/gpu/gpu_py_platform.h b/source/blender/python/gpu/gpu_py_platform.h new file mode 100644 index 00000000000..19e3e41fb49 --- /dev/null +++ b/source/blender/python/gpu/gpu_py_platform.h @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** \file + * \ingroup bpygpu + */ + +#pragma once + +PyObject *bpygpu_platform_init(void); diff --git a/source/blender/python/gpu/gpu_py_state.c b/source/blender/python/gpu/gpu_py_state.c index 6b0fade8d1c..173c5afba56 100644 --- a/source/blender/python/gpu/gpu_py_state.c +++ b/source/blender/python/gpu/gpu_py_state.c @@ -25,11 +25,13 @@ #include <Python.h> +#include "GPU_framebuffer.h" #include "GPU_state.h" #include "../generic/py_capi_utils.h" #include "../generic/python_utildefines.h" +#include "gpu_py_framebuffer.h" #include "gpu_py_state.h" /* own include */ /* -------------------------------------------------------------------- */ @@ -334,6 +336,16 @@ static PyObject *pygpu_state_program_point_size_set(PyObject *UNUSED(self), PyOb Py_RETURN_NONE; } +PyDoc_STRVAR(pygpu_state_framebuffer_active_get_doc, + ".. function:: framebuffer_active_get(enable)\n" + "\n" + " Return the active framefuffer in context.\n"); +static PyObject *pygpu_state_framebuffer_active_get(PyObject *UNUSED(self)) +{ + GPUFrameBuffer *fb = GPU_framebuffer_active_get(); + return BPyGPUFrameBuffer_CreatePyObject(fb, true); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -396,6 +408,10 @@ static struct PyMethodDef pygpu_state__tp_methods[] = { (PyCFunction)pygpu_state_program_point_size_set, METH_O, pygpu_state_program_point_size_set_doc}, + {"active_framebuffer_get", + (PyCFunction)pygpu_state_framebuffer_active_get, + METH_NOARGS, + pygpu_state_framebuffer_active_get_doc}, {NULL, NULL, 0, NULL}, }; diff --git a/source/blender/python/gpu/gpu_py_texture.c b/source/blender/python/gpu/gpu_py_texture.c index 257c6d773eb..2181c09b537 100644 --- a/source/blender/python/gpu/gpu_py_texture.c +++ b/source/blender/python/gpu/gpu_py_texture.c @@ -247,7 +247,7 @@ static PyObject *pygpu_texture__tp_new(PyTypeObject *UNUSED(self), PyObject *arg return NULL; } - return BPyGPUTexture_CreatePyObject(tex); + return BPyGPUTexture_CreatePyObject(tex, false); } PyDoc_STRVAR(pygpu_texture_width_doc, "Width of the texture.\n\n:type: `int`"); @@ -417,6 +417,9 @@ static PyObject *pygpu_texture_free(BPyGPUTexture *self) static void BPyGPUTexture__tp_dealloc(BPyGPUTexture *self) { if (self->tex) { +#ifndef GPU_NO_USE_PY_REFERENCES + GPU_texture_py_reference_set(self->tex, NULL); +#endif GPU_texture_free(self->tex); } Py_TYPE(self)->tp_free((PyObject *)self); @@ -540,10 +543,7 @@ static PyObject *pygpu_texture_from_image(PyObject *UNUSED(self), PyObject *arg) BKE_imageuser_default(&iuser); GPUTexture *tex = BKE_image_get_gpu_texture(ima, &iuser, NULL); - /* Increase the texture reference count. */ - GPU_texture_ref(tex); - - return BPyGPUTexture_CreatePyObject(tex); + return BPyGPUTexture_CreatePyObject(tex, true); } static struct PyMethodDef pygpu_texture__m_methods[] = { @@ -600,13 +600,33 @@ PyObject *bpygpu_texture_init(void) /** \name Public API * \{ */ -PyObject *BPyGPUTexture_CreatePyObject(GPUTexture *tex) +PyObject *BPyGPUTexture_CreatePyObject(GPUTexture *tex, bool shared_reference) { BPyGPUTexture *self; + if (shared_reference) { +#ifndef GPU_NO_USE_PY_REFERENCES + void **ref = GPU_texture_py_reference_get(tex); + if (ref) { + /* Retrieve BPyGPUTexture reference. */ + self = (BPyGPUTexture *)POINTER_OFFSET(ref, -offsetof(BPyGPUTexture, tex)); + BLI_assert(self->tex == tex); + Py_INCREF(self); + return (PyObject *)self; + } +#endif + + GPU_texture_ref(tex); + } + self = PyObject_New(BPyGPUTexture, &BPyGPUTexture_Type); self->tex = tex; +#ifndef GPU_NO_USE_PY_REFERENCES + BLI_assert(GPU_texture_py_reference_get(tex) == NULL); + GPU_texture_py_reference_set(tex, (void **)&self->tex); +#endif + return (PyObject *)self; } diff --git a/source/blender/python/gpu/gpu_py_texture.h b/source/blender/python/gpu/gpu_py_texture.h index 5130273f971..3eaaa3411bd 100644 --- a/source/blender/python/gpu/gpu_py_texture.h +++ b/source/blender/python/gpu/gpu_py_texture.h @@ -33,4 +33,5 @@ typedef struct BPyGPUTexture { int bpygpu_ParseTexture(PyObject *o, void *p); PyObject *bpygpu_texture_init(void); -PyObject *BPyGPUTexture_CreatePyObject(struct GPUTexture *tex) ATTR_NONNULL(1); +PyObject *BPyGPUTexture_CreatePyObject(struct GPUTexture *tex, bool shared_reference) + ATTR_NONNULL(1); diff --git a/source/blender/python/intern/CMakeLists.txt b/source/blender/python/intern/CMakeLists.txt index 9ac8d4d9f47..2be2105d327 100644 --- a/source/blender/python/intern/CMakeLists.txt +++ b/source/blender/python/intern/CMakeLists.txt @@ -79,6 +79,7 @@ set(SRC bpy_rna_driver.c bpy_rna_gizmo.c bpy_rna_id_collection.c + bpy_rna_operator.c bpy_rna_types_capi.c bpy_rna_ui.c bpy_traceback.c @@ -118,6 +119,7 @@ set(SRC bpy_rna_driver.h bpy_rna_gizmo.h bpy_rna_id_collection.h + bpy_rna_operator.h bpy_rna_types_capi.h bpy_rna_ui.h bpy_traceback.h diff --git a/source/blender/python/intern/bpy_app_handlers.c b/source/blender/python/intern/bpy_app_handlers.c index 8ecee9b3f2e..a0b543097e6 100644 --- a/source/blender/python/intern/bpy_app_handlers.c +++ b/source/blender/python/intern/bpy_app_handlers.c @@ -74,6 +74,7 @@ static PyStructSequence_Field app_cb_info_fields[] = { {"version_update", "on ending the versioning code"}, {"load_factory_preferences_post", "on loading factory preferences (after)"}, {"load_factory_startup_post", "on loading factory startup (after)"}, + {"xr_session_start_pre", "on starting an xr session (before)"}, /* sets the permanent tag */ #define APP_CB_OTHER_FIELDS 1 diff --git a/source/blender/python/intern/bpy_interface.c b/source/blender/python/intern/bpy_interface.c index 5f31e0bb74d..f95e655d0c6 100644 --- a/source/blender/python/intern/bpy_interface.c +++ b/source/blender/python/intern/bpy_interface.c @@ -167,6 +167,14 @@ void bpy_context_clear(bContext *UNUSED(C), const PyGILState_STATE *gilstate) } } +static void bpy_context_end(bContext *C) +{ + if (UNLIKELY(C == NULL)) { + return; + } + CTX_wm_operator_poll_msg_clear(C); +} + /** * Use for `CTX_*_set(..)` functions need to set values which are later read back as expected. * In this case we don't want the Python context to override the values as it causes problems @@ -392,7 +400,7 @@ void BPY_python_start(bContext *C, int argc, const char **argv) /* Needed for Python's initialization for portable Python installations. * We could use #Py_SetPath, but this overrides Python's internal logic - * for calculating it's own module search paths. + * for calculating its own module search paths. * * `sys.executable` is overwritten after initialization to the Python binary. */ { @@ -524,6 +532,9 @@ void BPY_python_end(void) /* finalizing, no need to grab the state, except when we are a module */ gilstate = PyGILState_Ensure(); + /* Clear Python values in the context so freeing the context after Python exits doesn't crash. */ + bpy_context_end(BPY_context_get()); + /* Decrement user counts of all callback functions. */ BPY_rna_props_clear_all(); diff --git a/source/blender/python/intern/bpy_operator.c b/source/blender/python/intern/bpy_operator.c index 94ad6a8ef78..4a5e2552598 100644 --- a/source/blender/python/intern/bpy_operator.c +++ b/source/blender/python/intern/bpy_operator.c @@ -244,12 +244,16 @@ static PyObject *pyop_call(PyObject *UNUSED(self), PyObject *args) } if (WM_operator_poll_context((bContext *)C, ot, context) == false) { - const char *msg = CTX_wm_operator_poll_msg_get(C); + bool msg_free = false; + const char *msg = CTX_wm_operator_poll_msg_get(C, &msg_free); PyErr_Format(PyExc_RuntimeError, "Operator bpy.ops.%.200s.poll() %.200s", opname, msg ? msg : "failed, context is incorrect"); - CTX_wm_operator_poll_msg_set(C, NULL); /* better set to NULL else it could be used again */ + CTX_wm_operator_poll_msg_clear(C); + if (msg_free) { + MEM_freeN((void *)msg); + } error_val = -1; } else { diff --git a/source/blender/python/intern/bpy_rna.c b/source/blender/python/intern/bpy_rna.c index 1711637458a..fb1cb823964 100644 --- a/source/blender/python/intern/bpy_rna.c +++ b/source/blender/python/intern/bpy_rna.c @@ -3562,7 +3562,7 @@ PyDoc_STRVAR(pyrna_struct_keys_doc, " dictionary function of the same name).\n" "\n" " :return: custom property keys.\n" - " :rtype: list of strings\n" + " :rtype: :class:`idprop.type.IDPropertyGroupViewKeys`\n" "\n" BPY_DOC_ID_PROP_TYPE_NOTE); static PyObject *pyrna_struct_keys(BPy_PropertyRNA *self) { @@ -3573,13 +3573,9 @@ static PyObject *pyrna_struct_keys(BPy_PropertyRNA *self) return NULL; } + /* `group` may be NULL. */ group = RNA_struct_idprops(&self->ptr, 0); - - if (group == NULL) { - return PyList_New(0); - } - - return BPy_Wrap_GetKeys(group); + return BPy_Wrap_GetKeys_View_WithID(self->ptr.owner_id, group); } PyDoc_STRVAR(pyrna_struct_items_doc, @@ -3589,7 +3585,7 @@ PyDoc_STRVAR(pyrna_struct_items_doc, " dictionary function of the same name).\n" "\n" " :return: custom property key, value pairs.\n" - " :rtype: list of key, value tuples\n" + " :rtype: :class:`idprop.type.IDPropertyGroupViewItems`\n" "\n" BPY_DOC_ID_PROP_TYPE_NOTE); static PyObject *pyrna_struct_items(BPy_PropertyRNA *self) { @@ -3600,13 +3596,9 @@ static PyObject *pyrna_struct_items(BPy_PropertyRNA *self) return NULL; } + /* `group` may be NULL. */ group = RNA_struct_idprops(&self->ptr, 0); - - if (group == NULL) { - return PyList_New(0); - } - - return BPy_Wrap_GetItems(self->ptr.owner_id, group); + return BPy_Wrap_GetItems_View_WithID(self->ptr.owner_id, group); } PyDoc_STRVAR(pyrna_struct_values_doc, @@ -3616,7 +3608,7 @@ PyDoc_STRVAR(pyrna_struct_values_doc, " dictionary function of the same name).\n" "\n" " :return: custom property values.\n" - " :rtype: list\n" + " :rtype: :class:`idprop.type.IDPropertyGroupViewValues`\n" "\n" BPY_DOC_ID_PROP_TYPE_NOTE); static PyObject *pyrna_struct_values(BPy_PropertyRNA *self) { @@ -3628,13 +3620,9 @@ static PyObject *pyrna_struct_values(BPy_PropertyRNA *self) return NULL; } + /* `group` may be NULL. */ group = RNA_struct_idprops(&self->ptr, 0); - - if (group == NULL) { - return PyList_New(0); - } - - return BPy_Wrap_GetValues(self->ptr.owner_id, group); + return BPy_Wrap_GetValues_View_WithID(self->ptr.owner_id, group); } PyDoc_STRVAR(pyrna_struct_is_property_set_doc, diff --git a/source/blender/python/intern/bpy_rna_operator.c b/source/blender/python/intern/bpy_rna_operator.c new file mode 100644 index 00000000000..6e0db3eca49 --- /dev/null +++ b/source/blender/python/intern/bpy_rna_operator.c @@ -0,0 +1,150 @@ +/* + * 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. + */ + +/** \file + * \ingroup pythonintern + * + * This file extends `bpy.types.Operator` with C/Python API methods and attributes. + */ + +#include <Python.h> + +#include "BLI_string.h" + +#include "BKE_context.h" + +#include "../generic/python_utildefines.h" + +#include "BPY_extern.h" +#include "bpy_capi_utils.h" + +/* -------------------------------------------------------------------- */ +/** \name Operator `poll_message_set` Method + * \{ */ + +static char *pyop_poll_message_get_fn(bContext *UNUSED(C), void *user_data) +{ + PyGILState_STATE gilstate = PyGILState_Ensure(); + + PyObject *py_args = user_data; + PyObject *py_func_or_msg = PyTuple_GET_ITEM(py_args, 0); + + if (PyUnicode_Check(py_func_or_msg)) { + return BLI_strdup(PyUnicode_AsUTF8(py_func_or_msg)); + } + + PyObject *py_args_after_first = PyTuple_GetSlice(py_args, 1, PY_SSIZE_T_MAX); + PyObject *py_msg = PyObject_CallObject(py_func_or_msg, py_args_after_first); + Py_DECREF(py_args_after_first); + + char *msg = NULL; + bool error = false; + + /* NULL for no string. */ + if (py_msg == NULL) { + error = true; + } + else { + if (py_msg == Py_None) { + /* pass */ + } + else if (PyUnicode_Check(py_msg)) { + msg = BLI_strdup(PyUnicode_AsUTF8(py_msg)); + } + else { + PyErr_Format(PyExc_TypeError, + "poll_message_set(function, ...): expected string or None, got %.200s", + Py_TYPE(py_msg)->tp_name); + error = true; + } + Py_DECREF(py_msg); + } + + if (error) { + PyErr_Print(); + PyErr_Clear(); + } + + PyGILState_Release(gilstate); + return msg; +} + +static void pyop_poll_message_free_fn(bContext *UNUSED(C), void *user_data) +{ + /* Handles the GIL. */ + BPY_DECREF(user_data); +} + +PyDoc_STRVAR(BPY_rna_operator_poll_message_set_doc, + ".. method:: poll_message_set(message, ...)\n" + "\n" + " Set the message to show in the tool-tip when poll fails.\n" + "\n" + " When message is callable, " + "additional user defined positional arguments are passed to the message function.\n" + "\n" + " :param message: The message or a function that returns the message.\n" + " :type message: string or a callable that returns a string or None.\n"); + +static PyObject *BPY_rna_operator_poll_message_set(PyObject *UNUSED(self), PyObject *args) +{ + const ssize_t args_len = PyTuple_GET_SIZE(args); + if (args_len == 0) { + PyErr_SetString(PyExc_ValueError, + "poll_message_set(message, ...): requires a message argument"); + return NULL; + } + + PyObject *py_func_or_msg = PyTuple_GET_ITEM(args, 0); + + if (PyUnicode_Check(py_func_or_msg)) { + if (args_len > 1) { + PyErr_SetString(PyExc_ValueError, + "poll_message_set(message): does not support additional arguments"); + return NULL; + } + } + else if (PyCallable_Check(py_func_or_msg)) { + /* pass */ + } + else { + PyErr_Format(PyExc_TypeError, + "poll_message_set(message, ...): " + "expected at least 1 string or callable argument, got %.200s", + Py_TYPE(py_func_or_msg)->tp_name); + return NULL; + } + + bContext *C = BPY_context_get(); + struct bContextPollMsgDyn_Params params = { + .get_fn = pyop_poll_message_get_fn, + .free_fn = pyop_poll_message_free_fn, + .user_data = Py_INCREF_RET(args), + }; + + CTX_wm_operator_poll_msg_set_dynamic(C, ¶ms); + + Py_RETURN_NONE; +} + +PyMethodDef BPY_rna_operator_poll_message_set_method_def = { + "poll_message_set", + (PyCFunction)BPY_rna_operator_poll_message_set, + METH_VARARGS | METH_STATIC, + BPY_rna_operator_poll_message_set_doc, +}; + +/** \} */ diff --git a/source/blender/python/intern/bpy_rna_operator.h b/source/blender/python/intern/bpy_rna_operator.h new file mode 100644 index 00000000000..8040d8a492a --- /dev/null +++ b/source/blender/python/intern/bpy_rna_operator.h @@ -0,0 +1,31 @@ +/* + * 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. + */ + +/** \file + * \ingroup pythonintern + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +extern PyMethodDef BPY_rna_operator_poll_message_set_method_def; + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/python/intern/bpy_rna_types_capi.c b/source/blender/python/intern/bpy_rna_types_capi.c index 9b15e84663d..2f6e197d1e2 100644 --- a/source/blender/python/intern/bpy_rna_types_capi.c +++ b/source/blender/python/intern/bpy_rna_types_capi.c @@ -41,6 +41,8 @@ #include "bpy_rna_types_capi.h" #include "bpy_rna_ui.h" +#include "bpy_rna_operator.h" + #include "../generic/py_capi_utils.h" #include "RNA_access.h" @@ -87,6 +89,17 @@ static struct PyMethodDef pyrna_uilayout_methods[] = { /** \} */ /* -------------------------------------------------------------------- */ +/** \name Operator + * \{ */ + +static struct PyMethodDef pyrna_operator_methods[] = { + {NULL, NULL, 0, NULL}, /* #BPY_rna_operator_poll_message_set */ + {NULL, NULL, 0, NULL}, +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Window Manager Clipboard Property * * Avoid using the RNA API because this value may change between checking its length @@ -228,6 +241,11 @@ void BPY_rna_types_extend_capi(void) /* Space */ pyrna_struct_type_extend_capi(&RNA_Space, pyrna_space_methods, NULL); + /* wmOperator */ + ARRAY_SET_ITEMS(pyrna_operator_methods, BPY_rna_operator_poll_message_set_method_def); + BLI_assert(ARRAY_SIZE(pyrna_operator_methods) == 2); + pyrna_struct_type_extend_capi(&RNA_Operator, pyrna_operator_methods, NULL); + /* WindowManager */ pyrna_struct_type_extend_capi( &RNA_WindowManager, pyrna_windowmanager_methods, pyrna_windowmanager_getset); diff --git a/source/blender/python/mathutils/mathutils.c b/source/blender/python/mathutils/mathutils.c index 3791a6c2d29..16bf7120606 100644 --- a/source/blender/python/mathutils/mathutils.c +++ b/source/blender/python/mathutils/mathutils.c @@ -152,7 +152,7 @@ int mathutils_array_parse( return -1; } - memcpy(array, ((BaseMathObject *)value)->data, size * sizeof(float)); + memcpy(array, ((const BaseMathObject *)value)->data, size * sizeof(float)); } else #endif @@ -235,7 +235,7 @@ int mathutils_array_parse_alloc(float **array, } *array = PyMem_Malloc(size * sizeof(float)); - memcpy(*array, ((BaseMathObject *)value)->data, size * sizeof(float)); + memcpy(*array, ((const BaseMathObject *)value)->data, size * sizeof(float)); return size; } @@ -471,7 +471,7 @@ int mathutils_any_to_rotmat(float rmat[3][3], PyObject *value, const char *error return -1; } - eulO_to_mat3(rmat, ((EulerObject *)value)->eul, ((EulerObject *)value)->order); + eulO_to_mat3(rmat, ((const EulerObject *)value)->eul, ((const EulerObject *)value)->order); return 0; } if (QuaternionObject_Check(value)) { @@ -480,7 +480,7 @@ int mathutils_any_to_rotmat(float rmat[3][3], PyObject *value, const char *error } float tquat[4]; - normalize_qt_qt(tquat, ((QuaternionObject *)value)->quat); + normalize_qt_qt(tquat, ((const QuaternionObject *)value)->quat); quat_to_mat3(rmat, tquat); return 0; } @@ -530,8 +530,8 @@ int EXPP_FloatsAreEqual(float af, float bf, int maxDiff) { /* solid, fast routine across all platforms * with constant time behavior */ - const int ai = *(int *)(&af); - const int bi = *(int *)(&bf); + const int ai = *(const int *)(&af); + const int bi = *(const int *)(&bf); const int test = SIGNMASK(ai ^ bi); int diff, v1, v2; diff --git a/source/blender/python/mathutils/mathutils_Euler.c b/source/blender/python/mathutils/mathutils_Euler.c index f2a8af18073..b6a0183d04e 100644 --- a/source/blender/python/mathutils/mathutils_Euler.c +++ b/source/blender/python/mathutils/mathutils_Euler.c @@ -88,7 +88,7 @@ short euler_order_from_string(const char *str, const char *error_prefix) # define MAKE_ID3(a, b, c) (((a) << 24) | ((b) << 16) | ((c) << 8)) #endif - switch (*((PY_INT32_T *)str)) { + switch (*((const PY_INT32_T *)str)) { case MAKE_ID3('X', 'Y', 'Z'): return EULER_ORDER_XYZ; case MAKE_ID3('X', 'Z', 'Y'): diff --git a/source/blender/python/mathutils/mathutils_Matrix.c b/source/blender/python/mathutils/mathutils_Matrix.c index 161d2f41592..5d38a3692c3 100644 --- a/source/blender/python/mathutils/mathutils_Matrix.c +++ b/source/blender/python/mathutils/mathutils_Matrix.c @@ -187,7 +187,7 @@ static int mathutils_matrix_col_get(BaseMathObject *bmo, int col) } /* for 'translation' size will always be '3' even on 4x4 vec */ - num_row = min_ii(self->num_row, ((VectorObject *)bmo)->size); + num_row = min_ii(self->num_row, ((const VectorObject *)bmo)->size); for (row = 0; row < num_row; row++) { bmo->data[row] = MATRIX_ITEM(self, row, col); @@ -210,7 +210,7 @@ static int mathutils_matrix_col_set(BaseMathObject *bmo, int col) } /* for 'translation' size will always be '3' even on 4x4 vec */ - num_row = min_ii(self->num_row, ((VectorObject *)bmo)->size); + num_row = min_ii(self->num_row, ((const VectorObject *)bmo)->size); for (row = 0; row < num_row; row++) { MATRIX_ITEM(self, row, col) = bmo->data[row]; @@ -969,6 +969,104 @@ static PyObject *C_Matrix_Shear(PyObject *cls, PyObject *args) return Matrix_CreatePyObject(mat, matSize, matSize, (PyTypeObject *)cls); } +PyDoc_STRVAR( + C_Matrix_LocRotScale_doc, + ".. classmethod:: LocRotScale(location, rotation, scale)\n" + "\n" + " Create a matrix combining translation, rotation and scale,\n" + " acting as the inverse of the decompose() method.\n" + "\n" + " Any of the inputs may be replaced with None if not needed.\n" + "\n" + " :arg location: The translation component.\n" + " :type location: :class:`Vector` or None\n" + " :arg rotation: The rotation component.\n" + " :type rotation: 3x3 :class:`Matrix`, :class:`Quaternion`, :class:`Euler` or None\n" + " :arg scale: The scale component.\n" + " :type scale: :class:`Vector` or None\n" + " :return: Combined transformation matrix. \n" + " :rtype: 4x4 :class:`Matrix`\n"); +static PyObject *C_Matrix_LocRotScale(PyObject *cls, PyObject *args) +{ + PyObject *loc_obj, *rot_obj, *scale_obj; + float mat[4][4], loc[3]; + + if (!PyArg_ParseTuple(args, "OOO:Matrix.LocRotScale", &loc_obj, &rot_obj, &scale_obj)) { + return NULL; + } + + /* Decode location. */ + if (loc_obj == Py_None) { + zero_v3(loc); + } + else if (mathutils_array_parse( + loc, 3, 3, loc_obj, "Matrix.LocRotScale(), invalid location argument") == -1) { + return NULL; + } + + /* Decode rotation. */ + if (rot_obj == Py_None) { + unit_m4(mat); + } + else if (QuaternionObject_Check(rot_obj)) { + QuaternionObject *quat_obj = (QuaternionObject *)rot_obj; + + if (BaseMath_ReadCallback(quat_obj) == -1) { + return NULL; + } + + quat_to_mat4(mat, quat_obj->quat); + } + else if (EulerObject_Check(rot_obj)) { + EulerObject *eul_obj = (EulerObject *)rot_obj; + + if (BaseMath_ReadCallback(eul_obj) == -1) { + return NULL; + } + + eulO_to_mat4(mat, eul_obj->eul, eul_obj->order); + } + else if (MatrixObject_Check(rot_obj)) { + MatrixObject *mat_obj = (MatrixObject *)rot_obj; + + if (BaseMath_ReadCallback(mat_obj) == -1) { + return NULL; + } + + if (mat_obj->num_col == 3 && mat_obj->num_row == 3) { + copy_m4_m3(mat, (const float(*)[3])mat_obj->matrix); + } + else { + PyErr_SetString(PyExc_ValueError, + "Matrix.LocRotScale(): " + "inappropriate rotation matrix size - expects 3x3 matrix"); + return NULL; + } + } + else { + PyErr_SetString(PyExc_ValueError, + "Matrix.LocRotScale(): " + "rotation argument must be Matrix, Quaternion, Euler or None"); + return NULL; + } + + /* Decode scale. */ + if (scale_obj != Py_None) { + float scale[3]; + + if (mathutils_array_parse( + scale, 3, 3, scale_obj, "Matrix.LocRotScale(), invalid scale argument") == -1) { + return NULL; + } + + rescale_m4(mat, scale); + } + + copy_v3_v3(mat[3], loc); + + return Matrix_CreatePyObject(&mat[0][0], 4, 4, (PyTypeObject *)cls); +} + void matrix_as_3x3(float mat[3][3], MatrixObject *self) { copy_v3_v3(mat[0], MATRIX_COL_PTR(self, 0)); @@ -1029,7 +1127,7 @@ static float matrix_determinant_internal(const MatrixObject *self) MATRIX_ITEM(self, 2, 2)); } - return determinant_m4((float(*)[4])self->matrix); + return determinant_m4((const float(*)[4])self->matrix); } static void adjoint_matrix_n(float *mat_dst, const float *mat_src, const ushort dim) @@ -1037,15 +1135,15 @@ static void adjoint_matrix_n(float *mat_dst, const float *mat_src, const ushort /* calculate the classical adjoint */ switch (dim) { case 2: { - adjoint_m2_m2((float(*)[2])mat_dst, (float(*)[2])mat_src); + adjoint_m2_m2((float(*)[2])mat_dst, (const float(*)[2])mat_src); break; } case 3: { - adjoint_m3_m3((float(*)[3])mat_dst, (float(*)[3])mat_src); + adjoint_m3_m3((float(*)[3])mat_dst, (const float(*)[3])mat_src); break; } case 4: { - adjoint_m4_m4((float(*)[4])mat_dst, (float(*)[4])mat_src); + adjoint_m4_m4((float(*)[4])mat_dst, (const float(*)[4])mat_src); break; } default: @@ -1115,7 +1213,7 @@ static void matrix_invert_safe_internal(const MatrixObject *self, float *r_mat) float(*mat)[2] = (float(*)[2])in_mat; if (in_mat != self->matrix) { - copy_m2_m2(mat, (float(*)[2])self->matrix); + copy_m2_m2(mat, (const float(*)[2])self->matrix); } mat[0][0] += eps; mat[1][1] += eps; @@ -1130,7 +1228,7 @@ static void matrix_invert_safe_internal(const MatrixObject *self, float *r_mat) float(*mat)[3] = (float(*)[3])in_mat; if (in_mat != self->matrix) { - copy_m3_m3(mat, (float(*)[3])self->matrix); + copy_m3_m3(mat, (const float(*)[3])self->matrix); } mat[0][0] += eps; mat[1][1] += eps; @@ -1146,7 +1244,7 @@ static void matrix_invert_safe_internal(const MatrixObject *self, float *r_mat) float(*mat)[4] = (float(*)[4])in_mat; if (in_mat != self->matrix) { - copy_m4_m4(mat, (float(*)[4])self->matrix); + copy_m4_m4(mat, (const float(*)[4])self->matrix); } mat[0][0] += eps; mat[1][1] += eps; @@ -1194,7 +1292,7 @@ static PyObject *Matrix_to_quaternion(MatrixObject *self) mat3_to_quat(quat, (float(*)[3])self->matrix); } else { - mat4_to_quat(quat, (float(*)[4])self->matrix); + mat4_to_quat(quat, (const float(*)[4])self->matrix); } return Quaternion_CreatePyObject(quat, NULL); @@ -1243,10 +1341,10 @@ static PyObject *Matrix_to_euler(MatrixObject *self, PyObject *args) /*must be 3-4 cols, 3-4 rows, square matrix */ if (self->num_row == 3 && self->num_col == 3) { - copy_m3_m3(mat, (float(*)[3])self->matrix); + copy_m3_m3(mat, (const float(*)[3])self->matrix); } else if (self->num_row == 4 && self->num_col == 4) { - copy_m3_m4(mat, (float(*)[4])self->matrix); + copy_m3_m4(mat, (const float(*)[4])self->matrix); } else { PyErr_SetString(PyExc_ValueError, @@ -1321,7 +1419,7 @@ static PyObject *Matrix_resize_4x4(MatrixObject *self) memcpy(mat[col], MATRIX_COL_PTR(self, col), self->num_row * sizeof(float)); } - copy_m4_m4((float(*)[4])self->matrix, (float(*)[4])mat); + copy_m4_m4((float(*)[4])self->matrix, (const float(*)[4])mat); self->num_col = 4; self->num_row = 4; @@ -1479,7 +1577,7 @@ static bool matrix_invert_args_check(const MatrixObject *self, PyObject *args, b return true; case 1: if (check_type) { - const MatrixObject *fallback = (MatrixObject *)PyTuple_GET_ITEM(args, 0); + const MatrixObject *fallback = (const MatrixObject *)PyTuple_GET_ITEM(args, 0); if (!MatrixObject_Check(fallback)) { PyErr_SetString(PyExc_TypeError, "Matrix.invert: " @@ -1797,7 +1895,7 @@ static PyObject *Matrix_decompose(MatrixObject *self) return NULL; } - mat4_to_loc_rot_size(loc, rot, size, (float(*)[4])self->matrix); + mat4_to_loc_rot_size(loc, rot, size, (const float(*)[4])self->matrix); mat3_to_quat(quat, rot); ret = PyTuple_New(3); @@ -2059,7 +2157,7 @@ static PyObject *Matrix_identity(MatrixObject *self) static PyObject *Matrix_copy_notest(MatrixObject *self, const float *matrix) { - return Matrix_CreatePyObject((float *)matrix, self->num_col, self->num_row, Py_TYPE(self)); + return Matrix_CreatePyObject((const float *)matrix, self->num_col, self->num_row, Py_TYPE(self)); } PyDoc_STRVAR(Matrix_copy_doc, @@ -2155,7 +2253,7 @@ static PyObject *Matrix_str(MatrixObject *self) for (col = 0; col < self->num_col; col++) { maxsize[col] = 0; for (row = 0; row < self->num_row; row++) { - const int size = BLI_snprintf( + const int size = BLI_snprintf_rlen( dummy_buf, sizeof(dummy_buf), "%.4f", MATRIX_ITEM(self, row, col)); maxsize[col] = max_ii(maxsize[col], size); } @@ -2960,10 +3058,10 @@ static PyObject *Matrix_is_negative_get(MatrixObject *self, void *UNUSED(closure /*must be 3-4 cols, 3-4 rows, square matrix*/ if (self->num_row == 4 && self->num_col == 4) { - return PyBool_FromLong(is_negative_m4((float(*)[4])self->matrix)); + return PyBool_FromLong(is_negative_m4((const float(*)[4])self->matrix)); } if (self->num_row == 3 && self->num_col == 3) { - return PyBool_FromLong(is_negative_m3((float(*)[3])self->matrix)); + return PyBool_FromLong(is_negative_m3((const float(*)[3])self->matrix)); } PyErr_SetString(PyExc_AttributeError, @@ -2982,10 +3080,10 @@ static PyObject *Matrix_is_orthogonal_get(MatrixObject *self, void *UNUSED(closu /*must be 3-4 cols, 3-4 rows, square matrix*/ if (self->num_row == 4 && self->num_col == 4) { - return PyBool_FromLong(is_orthonormal_m4((float(*)[4])self->matrix)); + return PyBool_FromLong(is_orthonormal_m4((const float(*)[4])self->matrix)); } if (self->num_row == 3 && self->num_col == 3) { - return PyBool_FromLong(is_orthonormal_m3((float(*)[3])self->matrix)); + return PyBool_FromLong(is_orthonormal_m3((const float(*)[3])self->matrix)); } PyErr_SetString(PyExc_AttributeError, @@ -3005,10 +3103,10 @@ static PyObject *Matrix_is_orthogonal_axis_vectors_get(MatrixObject *self, void /*must be 3-4 cols, 3-4 rows, square matrix*/ if (self->num_row == 4 && self->num_col == 4) { - return PyBool_FromLong(is_orthogonal_m4((float(*)[4])self->matrix)); + return PyBool_FromLong(is_orthogonal_m4((const float(*)[4])self->matrix)); } if (self->num_row == 3 && self->num_col == 3) { - return PyBool_FromLong(is_orthogonal_m3((float(*)[3])self->matrix)); + return PyBool_FromLong(is_orthogonal_m3((const float(*)[3])self->matrix)); } PyErr_SetString(PyExc_AttributeError, @@ -3111,6 +3209,10 @@ static struct PyMethodDef Matrix_methods[] = { (PyCFunction)C_Matrix_OrthoProjection, METH_VARARGS | METH_CLASS, C_Matrix_OrthoProjection_doc}, + {"LocRotScale", + (PyCFunction)C_Matrix_LocRotScale, + METH_VARARGS | METH_CLASS, + C_Matrix_LocRotScale_doc}, {NULL, NULL, 0, NULL}, }; diff --git a/source/blender/python/mathutils/mathutils_interpolate.c b/source/blender/python/mathutils/mathutils_interpolate.c index 6abb66899d5..bb6a7720c44 100644 --- a/source/blender/python/mathutils/mathutils_interpolate.c +++ b/source/blender/python/mathutils/mathutils_interpolate.c @@ -54,24 +54,15 @@ static PyObject *M_Interpolate_poly_3d_calc(PyObject *UNUSED(self), PyObject *ar PyObject *point, *veclist, *ret; int i; - if (!PyArg_ParseTuple(args, "OO!:poly_3d_calc", &veclist, &vector_Type, &point)) { + if (!PyArg_ParseTuple(args, "OO:poly_3d_calc", &veclist, &point)) { return NULL; } - if (BaseMath_ReadCallback((VectorObject *)point) == -1) { + if (mathutils_array_parse( + fp, 2, 3 | MU_ARRAY_ZERO, point, "pt must be a 2-3 dimensional vector") == -1) { return NULL; } - fp[0] = ((VectorObject *)point)->vec[0]; - fp[1] = ((VectorObject *)point)->vec[1]; - if (((VectorObject *)point)->size > 2) { - fp[2] = ((VectorObject *)point)->vec[2]; - } - else { - /* if its a 2d vector then set the z to be zero */ - fp[2] = 0.0f; - } - len = mathutils_array_parse_alloc_v(((float **)&vecs), 3, veclist, __func__); if (len == -1) { return NULL; diff --git a/source/blender/python/mathutils/mathutils_kdtree.c b/source/blender/python/mathutils/mathutils_kdtree.c index fe8f9ec0334..d54dbc9ab05 100644 --- a/source/blender/python/mathutils/mathutils_kdtree.c +++ b/source/blender/python/mathutils/mathutils_kdtree.c @@ -52,7 +52,7 @@ static void kdtree_nearest_to_py_tuple(const KDTreeNearest_3d *nearest, PyObject BLI_assert(PyTuple_GET_SIZE(py_retval) == 3); PyTuple_SET_ITEMS(py_retval, - Vector_CreatePyObject((float *)nearest->co, 3, NULL), + Vector_CreatePyObject(nearest->co, 3, NULL), PyLong_FromLong(nearest->index), PyFloat_FromDouble(nearest->dist)); } diff --git a/source/blender/render/RE_engine.h b/source/blender/render/RE_engine.h index f6ab7fd9d3c..7352ac7b12e 100644 --- a/source/blender/render/RE_engine.h +++ b/source/blender/render/RE_engine.h @@ -195,7 +195,7 @@ float RE_engine_get_camera_shift_x(RenderEngine *engine, void RE_engine_get_camera_model_matrix(RenderEngine *engine, struct Object *camera, bool use_spherical_stereo, - float *r_modelmat); + float r_modelmat[16]); bool RE_engine_get_spherical_stereo(RenderEngine *engine, struct Object *camera); bool RE_engine_test_break(RenderEngine *engine); diff --git a/source/blender/render/intern/engine.c b/source/blender/render/intern/engine.c index 66d38eb19c7..306d144f79d 100644 --- a/source/blender/render/intern/engine.c +++ b/source/blender/render/intern/engine.c @@ -546,7 +546,7 @@ float RE_engine_get_camera_shift_x(RenderEngine *engine, Object *camera, bool us void RE_engine_get_camera_model_matrix(RenderEngine *engine, Object *camera, bool use_spherical_stereo, - float *r_modelmat) + float r_modelmat[16]) { /* When using spherical stereo, get model matrix without multiview, * leaving stereo to be handled by the engine. */ diff --git a/source/blender/render/intern/zbuf.c b/source/blender/render/intern/zbuf.c index 33af3bbaf29..242c8a199fb 100644 --- a/source/blender/render/intern/zbuf.c +++ b/source/blender/render/intern/zbuf.c @@ -175,7 +175,7 @@ static void zbuf_add_to_span(ZSpan *zspan, const float v1[2], const float v2[2]) /* Functions */ /*-----------------------------------------------------------*/ -/* Scanconvert for strand triangles, calls func for each x, y coordinate +/* Scan-convert for strand triangles, calls function for each x, y coordinate * and gives UV barycentrics and z. */ void zspan_scanconvert(ZSpan *zspan, diff --git a/source/blender/sequencer/SEQ_iterator.h b/source/blender/sequencer/SEQ_iterator.h index 669c55e1f4c..c7c2dc275ee 100644 --- a/source/blender/sequencer/SEQ_iterator.h +++ b/source/blender/sequencer/SEQ_iterator.h @@ -27,39 +27,70 @@ extern "C" { #endif +#include "BLI_ghash.h" + struct Editing; struct Sequence; +struct GSet; +struct GSetIterator; -typedef struct SeqIterator { - struct Sequence **array; - int tot, cur; +#define SEQ_ITERATOR_FOREACH(var, collection) \ + for (SeqIterator iter = {{{NULL}}}; \ + SEQ_iterator_ensure(collection, &iter, &var) && var != NULL; \ + var = SEQ_iterator_yield(&iter)) - struct Sequence *seq; - int valid; -} SeqIterator; - -#define SEQ_ALL_BEGIN(ed, _seq) \ +#define SEQ_ALL_BEGIN(ed, var) \ { \ - SeqIterator iter_macro; \ - for (SEQ_iterator_begin(ed, &iter_macro, false); iter_macro.valid; \ - SEQ_iterator_next(&iter_macro)) { \ - _seq = iter_macro.seq; + if (ed != NULL) { \ + SeqCollection *all_strips = SEQ_query_all_strips_recursive(&ed->seqbase); \ + GSetIterator gsi; \ + GSET_ITER (gsi, all_strips->set) { \ + var = (Sequence *)(BLI_gsetIterator_getKey(&gsi)); #define SEQ_ALL_END \ } \ - SEQ_iterator_end(&iter_macro); \ + SEQ_collection_free(all_strips); \ + } \ } \ ((void)0) -void SEQ_iterator_begin(struct Editing *ed, SeqIterator *iter, const bool use_current_sequences); -void SEQ_iterator_next(SeqIterator *iter); -void SEQ_iterator_end(SeqIterator *iter); -int SEQ_iterator_seqbase_recursive_apply(struct ListBase *seqbase, - int (*apply_fn)(struct Sequence *seq, void *), - void *arg); -int SEQ_iterator_recursive_apply(struct Sequence *seq, - int (*apply_fn)(struct Sequence *, void *), - void *arg); +typedef struct SeqCollection { + struct SeqCollection *next, *prev; + struct GSet *set; +} SeqCollection; + +typedef struct SeqIterator { + GSetIterator gsi; + SeqCollection *collection; + bool iterator_initialized; +} SeqIterator; + +bool SEQ_iterator_ensure(SeqCollection *collection, + SeqIterator *iterator, + struct Sequence **r_seq); +struct Sequence *SEQ_iterator_yield(SeqIterator *iterator); + +SeqCollection *SEQ_collection_create(void); +bool SEQ_collection_append_strip(struct Sequence *seq, SeqCollection *data); +bool SEQ_collection_remove_strip(struct Sequence *seq, SeqCollection *data); +void SEQ_collection_free(SeqCollection *collection); +void SEQ_collection_merge(SeqCollection *collection_dst, SeqCollection *collection_src); +void SEQ_collection_expand(struct ListBase *seqbase, + SeqCollection *collection, + void query_func(struct Sequence *seq_reference, + struct ListBase *seqbase, + SeqCollection *collection)); +SeqCollection *SEQ_query_by_reference(struct Sequence *seq_reference, + struct ListBase *seqbase, + void seq_query_func(struct Sequence *seq_reference, + struct ListBase *seqbase, + SeqCollection *collection)); +SeqCollection *SEQ_query_selected_strips(struct ListBase *seqbase); +SeqCollection *SEQ_query_all_strips(ListBase *seqbase); +SeqCollection *SEQ_query_all_strips_recursive(ListBase *seqbase); +void SEQ_query_strip_effect_chain(struct Sequence *seq_reference, + struct ListBase *seqbase, + SeqCollection *collection); #ifdef __cplusplus } diff --git a/source/blender/sequencer/SEQ_sequencer.h b/source/blender/sequencer/SEQ_sequencer.h index 236ca1a9195..63df886d31f 100644 --- a/source/blender/sequencer/SEQ_sequencer.h +++ b/source/blender/sequencer/SEQ_sequencer.h @@ -44,8 +44,6 @@ enum { SEQ_SIDE_NO_CHANGE, }; -#define SEQ_CACHE_COST_MAX 10.0f - /* seq_dupli' flags */ #define SEQ_DUPE_UNIQUE_NAME (1 << 0) #define SEQ_DUPE_CONTEXT (1 << 1) diff --git a/source/blender/sequencer/SEQ_utils.h b/source/blender/sequencer/SEQ_utils.h index 45f53a64688..9d529089ffc 100644 --- a/source/blender/sequencer/SEQ_utils.h +++ b/source/blender/sequencer/SEQ_utils.h @@ -35,7 +35,7 @@ struct Scene; struct Sequence; struct StripElem; -void SEQ_sort(struct Scene *scene); +void SEQ_sort(struct ListBase *seqbase); void SEQ_sequence_base_unique_name_recursive(struct ListBase *seqbasep, struct Sequence *seq); const char *SEQ_sequence_give_name(struct Sequence *seq); struct ListBase *SEQ_get_seqbase_from_sequence(struct Sequence *seq, int *r_offset); @@ -54,7 +54,13 @@ void SEQ_set_scale_to_fit(const struct Sequence *seq, const int preview_width, const int preview_height, const eSeqImageFitMethod fit_method); - +int SEQ_seqbase_recursive_apply(struct ListBase *seqbase, + int (*apply_fn)(struct Sequence *seq, void *), + void *arg); +int SEQ_recursive_apply(struct Sequence *seq, + int (*apply_fn)(struct Sequence *, void *), + void *arg); +void SEQ_ensure_unique_name(struct Sequence *seq, struct Scene *scene); #ifdef __cplusplus } #endif diff --git a/source/blender/sequencer/intern/effects.c b/source/blender/sequencer/intern/effects.c index 278320d873e..d41a2c19d55 100644 --- a/source/blender/sequencer/intern/effects.c +++ b/source/blender/sequencer/intern/effects.c @@ -3033,10 +3033,9 @@ static ImBuf *do_adjustment_impl(const SeqRenderData *context, Sequence *seq, fl i = seq_render_give_ibuf_seqbase(context, timeline_frame, seq->machine - 1, seqbasep); } - /* found nothing? so let's work the way up the metastrip stack, so + /* Found nothing? so let's work the way up the meta-strip stack, so * that it is possible to group a bunch of adjustment strips into - * a metastrip and have that work on everything below the metastrip - */ + * a meta-strip and have that work on everything below the meta-strip. */ if (!i) { Sequence *meta; diff --git a/source/blender/sequencer/intern/iterator.c b/source/blender/sequencer/intern/iterator.c index f99667dea04..9bbc5362f18 100644 --- a/source/blender/sequencer/intern/iterator.c +++ b/source/blender/sequencer/intern/iterator.c @@ -31,138 +31,268 @@ #include "DNA_scene_types.h" #include "DNA_sequence_types.h" +#include "BLI_ghash.h" #include "BLI_listbase.h" #include "BKE_scene.h" #include "SEQ_iterator.h" -/* ************************* iterator ************************** */ -/* *************** (replaces old WHILE_SEQ) ********************* */ -/* **************** use now SEQ_ALL_BEGIN () SEQ_ALL_END ***************** */ +/* -------------------------------------------------------------------- */ +/** \Iterator API + * \{ */ -/* sequence strip iterator: - * - builds a full array, recursively into meta strips +/** + * Utility function for SEQ_ITERATOR_FOREACH macro. + * Ensure, that iterator is initialized. During initialization return pointer to collection element + * and step gset iterator. When this function is called after iterator has been initialized, it + * will do nothing and return true. + * + * \param collection: collection to iterate + * \param iterator: iterator to be initialized + * \param r_seq: pointer to Sequence pointer + * + * \return false when iterator can not be initialized, true otherwise */ - -static void seq_count(ListBase *seqbase, int *tot) +bool SEQ_iterator_ensure(SeqCollection *collection, SeqIterator *iterator, Sequence **r_seq) { - Sequence *seq; - - for (seq = seqbase->first; seq; seq = seq->next) { - (*tot)++; - - if (seq->seqbase.first) { - seq_count(&seq->seqbase, tot); - } + if (iterator->iterator_initialized) { + return true; } -} -static void seq_build_array(ListBase *seqbase, Sequence ***array, int depth) -{ - Sequence *seq; + if (BLI_gset_len(collection->set) == 0) { + return false; + } - for (seq = seqbase->first; seq; seq = seq->next) { - seq->depth = depth; + iterator->collection = collection; + BLI_gsetIterator_init(&iterator->gsi, iterator->collection->set); + iterator->iterator_initialized = true; - if (seq->seqbase.first) { - seq_build_array(&seq->seqbase, array, depth + 1); - } + *r_seq = BLI_gsetIterator_getKey(&iterator->gsi); + BLI_gsetIterator_step(&iterator->gsi); - **array = seq; - (*array)++; - } + return true; } -static void seq_array(Editing *ed, - const bool use_current_sequences, - Sequence ***r_seqarray, - int *r_seqarray_len) +/** + * Utility function for SEQ_ITERATOR_FOREACH macro. + * Yield collection element + * + * \param iterator: iterator to be initialized + * + * \return collection element or NULL when iteration has ended + */ +Sequence *SEQ_iterator_yield(SeqIterator *iterator) { - Sequence **array; + Sequence *seq = BLI_gsetIterator_done(&iterator->gsi) ? NULL : + BLI_gsetIterator_getKey(&iterator->gsi); + BLI_gsetIterator_step(&iterator->gsi); + return seq; +} - *r_seqarray = NULL; - *r_seqarray_len = 0; +/** + * Free strip collection. + * + * \param collection: collection to be freed + */ +void SEQ_collection_free(SeqCollection *collection) +{ + BLI_gset_free(collection->set, NULL); + MEM_freeN(collection); +} - if (ed == NULL) { - return; - } +/** + * Create new empty strip collection. + * + * \return empty strip collection. + */ +SeqCollection *SEQ_collection_create(void) +{ + SeqCollection *collection = MEM_callocN(sizeof(SeqCollection), "SeqCollection"); + collection->set = BLI_gset_new( + BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, "SeqCollection GSet"); + return collection; +} - if (use_current_sequences) { - seq_count(ed->seqbasep, r_seqarray_len); - } - else { - seq_count(&ed->seqbase, r_seqarray_len); +/** + * Query strips from seqbase. seq_reference is used by query function as filter condition. + * + * \param seq_reference: reference strip for query function + * \param seqbase: ListBase in which strips are queried + * \param seq_query_func: query function callback + * \return strip collection + */ +SeqCollection *SEQ_query_by_reference(Sequence *seq_reference, + ListBase *seqbase, + void seq_query_func(Sequence *seq_reference, + ListBase *seqbase, + SeqCollection *collection)) +{ + SeqCollection *collection = SEQ_collection_create(); + seq_query_func(seq_reference, seqbase, collection); + return collection; +} +/** + * Add strip to collection. + * + * \param seq: strip to be added + * \param collection: collection to which strip will be added + * \return false if strip is already in set, otherwise true + */ +bool SEQ_collection_append_strip(Sequence *seq, SeqCollection *collection) +{ + if (BLI_gset_lookup(collection->set, seq) != NULL) { + return false; } + BLI_gset_insert(collection->set, seq); + return true; +} - if (*r_seqarray_len == 0) { - return; - } +/** + * Remove strip from collection. + * + * \param seq: strip to be removed + * \param collection: collection from which strip will be removed + * \return true if strip exists in set and it was removed from set, otherwise false + */ +bool SEQ_collection_remove_strip(Sequence *seq, SeqCollection *collection) +{ + return BLI_gset_remove(collection->set, seq, NULL); +} - *r_seqarray = array = MEM_mallocN(sizeof(Sequence *) * (*r_seqarray_len), "SeqArray"); - if (use_current_sequences) { - seq_build_array(ed->seqbasep, &array, 0); - } - else { - seq_build_array(&ed->seqbase, &array, 0); +/** + * Move strips from collection_src to collection_dst. Source collection will be freed. + * + * \param collection_dst: destination collection + * \param collection_src: source collection + */ +void SEQ_collection_merge(SeqCollection *collection_dst, SeqCollection *collection_src) +{ + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, collection_src) { + SEQ_collection_append_strip(seq, collection_dst); } + SEQ_collection_free(collection_src); } -void SEQ_iterator_begin(Editing *ed, SeqIterator *iter, const bool use_current_sequences) +/** + * Expand collection by running SEQ_query() for each strip, which will be used as reference. + * Results of these queries will be merged into provided collection. + * + * \param seqbase: ListBase in which strips are queried + * \param collection: SeqCollection to be expanded + * \param seq_query_func: query function callback + */ +void SEQ_collection_expand(ListBase *seqbase, + SeqCollection *collection, + void seq_query_func(Sequence *seq_reference, + ListBase *seqbase, + SeqCollection *collection)) { - memset(iter, 0, sizeof(*iter)); - seq_array(ed, use_current_sequences, &iter->array, &iter->tot); + /* Collect expanded results for each sequence in provided SeqIteratorCollection. */ + ListBase expand_collections = {0}; - if (iter->tot) { - iter->cur = 0; - iter->seq = iter->array[iter->cur]; - iter->valid = 1; + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, collection) { + SeqCollection *expand_collection = SEQ_query_by_reference(seq, seqbase, seq_query_func); + BLI_addtail(&expand_collections, expand_collection); + } + + /* Merge all expanded results in provided SeqIteratorCollection. */ + LISTBASE_FOREACH_MUTABLE (SeqCollection *, expand_collection, &expand_collections) { + BLI_remlink(&expand_collections, expand_collection); + SEQ_collection_merge(collection, expand_collection); } } -void SEQ_iterator_next(SeqIterator *iter) +/** \} */ + +/** + * Query all strips in seqbase and nested meta strips. + * + * \param seqbase: ListBase in which strips are queried + * \return strip collection + */ +SeqCollection *SEQ_query_all_strips_recursive(ListBase *seqbase) { - if (++iter->cur < iter->tot) { - iter->seq = iter->array[iter->cur]; - } - else { - iter->valid = 0; + SeqCollection *collection = SEQ_collection_create(); + LISTBASE_FOREACH (Sequence *, seq, seqbase) { + if (seq->type == SEQ_TYPE_META) { + SEQ_collection_merge(collection, SEQ_query_all_strips_recursive(&seq->seqbase)); + } + SEQ_collection_append_strip(seq, collection); } + return collection; } -void SEQ_iterator_end(SeqIterator *iter) +/** + * Query all strips in seqbase. This does not include strips nested in meta strips. + * + * \param seqbase: ListBase in which strips are queried + * \return strip collection + */ +SeqCollection *SEQ_query_all_strips(ListBase *seqbase) { - if (iter->array) { - MEM_freeN(iter->array); + SeqCollection *collection = SEQ_collection_create(); + LISTBASE_FOREACH (Sequence *, seq, seqbase) { + SEQ_collection_append_strip(seq, collection); } - - iter->valid = 0; + return collection; } -int SEQ_iterator_seqbase_recursive_apply(ListBase *seqbase, - int (*apply_fn)(Sequence *seq, void *), - void *arg) +/** + * Query all selected strips in seqbase. + * + * \param seqbase: ListBase in which strips are queried + * \return strip collection + */ +SeqCollection *SEQ_query_selected_strips(ListBase *seqbase) { - Sequence *iseq; - for (iseq = seqbase->first; iseq; iseq = iseq->next) { - if (SEQ_iterator_recursive_apply(iseq, apply_fn, arg) == -1) { - return -1; /* bail out */ + SeqCollection *collection = SEQ_collection_create(); + LISTBASE_FOREACH (Sequence *, seq, seqbase) { + if ((seq->flag & SELECT) == 0) { + continue; } + SEQ_collection_append_strip(seq, collection); } - return 1; + return collection; } -int SEQ_iterator_recursive_apply(Sequence *seq, int (*apply_fn)(Sequence *, void *), void *arg) +/** + * Query all effect strips that are directly or indirectly connected to seq_reference. + * This includes all effects of seq_reference, strips used by another inputs and their effects, so + * that whole chain is fully independent of other strips. + * + * \param seq_reference: reference strip + * \param seqbase: ListBase in which strips are queried + * \param collection: collection to be filled + */ +void SEQ_query_strip_effect_chain(Sequence *seq_reference, + ListBase *seqbase, + SeqCollection *collection) { - int ret = apply_fn(seq, arg); - - if (ret == -1) { - return -1; /* bail out */ + if (!SEQ_collection_append_strip(seq_reference, collection)) { + return; /* Strip is already in set, so all effects connected to it are as well. */ } - if (ret && seq->seqbase.first) { - ret = SEQ_iterator_seqbase_recursive_apply(&seq->seqbase, apply_fn, arg); + /* Find all strips that seq_reference is connected to. */ + if (seq_reference->type & SEQ_TYPE_EFFECT) { + if (seq_reference->seq1) { + SEQ_query_strip_effect_chain(seq_reference->seq1, seqbase, collection); + } + if (seq_reference->seq2) { + SEQ_query_strip_effect_chain(seq_reference->seq2, seqbase, collection); + } + if (seq_reference->seq3) { + SEQ_query_strip_effect_chain(seq_reference->seq3, seqbase, collection); + } } - return ret; + /* Find all strips connected to seq_reference. */ + LISTBASE_FOREACH (Sequence *, seq_test, seqbase) { + if (seq_test->seq1 == seq_reference || seq_test->seq2 == seq_reference || + seq_test->seq3 == seq_reference) { + SEQ_query_strip_effect_chain(seq_test, seqbase, collection); + } + } } diff --git a/source/blender/sequencer/intern/render.c b/source/blender/sequencer/intern/render.c index f892f1c1b41..d881c90a1e0 100644 --- a/source/blender/sequencer/intern/render.c +++ b/source/blender/sequencer/intern/render.c @@ -65,6 +65,7 @@ #include "RE_pipeline.h" #include "SEQ_effects.h" +#include "SEQ_iterator.h" #include "SEQ_modifier.h" #include "SEQ_proxy.h" #include "SEQ_render.h" @@ -259,120 +260,132 @@ StripElem *SEQ_render_give_stripelem(Sequence *seq, int timeline_frame) return se; } -static int evaluate_seq_frame_gen(Sequence **seq_arr, - ListBase *seqbase, - int timeline_frame, - int chanshown) +static bool seq_is_effect_of(const Sequence *seq_effect, const Sequence *possibly_input) { - /* Use arbitrary sized linked list, the size could be over MAXSEQ. */ - LinkNodePair effect_inputs = {NULL, NULL}; - int totseq = 0; + if (seq_effect->seq1 == possibly_input || seq_effect->seq2 == possibly_input || + seq_effect->seq3 == possibly_input) { + return true; + } + return false; +} - memset(seq_arr, 0, sizeof(Sequence *) * (MAXSEQ + 1)); +/* Check if seq must be rendered. This depends on whole stack in some cases, not only seq itself. + * Order of applying these conditions is important. */ +static bool must_render_strip(const Sequence *seq, SeqCollection *strips_under_playhead) +{ + bool seq_have_effect_in_stack = false; + Sequence *seq_iter; + SEQ_ITERATOR_FOREACH (seq_iter, strips_under_playhead) { + /* Strips is below another strip with replace blending are not rendered. */ + if (seq_iter->blend_mode == SEQ_BLEND_REPLACE && seq->machine < seq_iter->machine) { + return false; + } - LISTBASE_FOREACH (Sequence *, seq, seqbase) { - if ((seq->startdisp <= timeline_frame) && (seq->enddisp > timeline_frame)) { - if ((seq->type & SEQ_TYPE_EFFECT) && !(seq->flag & SEQ_MUTE)) { + if ((seq_iter->type & SEQ_TYPE_EFFECT) != 0 && seq_is_effect_of(seq_iter, seq)) { + /* Strips in same channel or higher than its effect are rendered. */ + if (seq->machine >= seq_iter->machine) { + return true; + } + /* Mark that this strip has effect in stack, that is above the strip. */ + seq_have_effect_in_stack = true; + } + } - if (seq->seq1) { - BLI_linklist_append_alloca(&effect_inputs, seq->seq1); - } + /* All effects are rendered (with respect to conditions above). */ + if ((seq->type & SEQ_TYPE_EFFECT) != 0) { + return true; + } - if (seq->seq2) { - BLI_linklist_append_alloca(&effect_inputs, seq->seq2); - } + /* If strip has effects in stack, and all effects are above this strip, it it not rendered. */ + if (seq_have_effect_in_stack) { + return false; + } - if (seq->seq3) { - BLI_linklist_append_alloca(&effect_inputs, seq->seq3); - } - } + return true; +} + +static SeqCollection *query_strips_at_frame(ListBase *seqbase, const int timeline_frame) +{ + SeqCollection *collection = SEQ_collection_create(); - seq_arr[seq->machine] = seq; - totseq++; + LISTBASE_FOREACH (Sequence *, seq, seqbase) { + if ((seq->startdisp <= timeline_frame) && (seq->enddisp > timeline_frame)) { + SEQ_collection_append_strip(seq, collection); } } + return collection; +} - /* Drop strips which are used for effect inputs, we don't want - * them to blend into render stack in any other way than effect - * string rendering. */ - for (LinkNode *seq_item = effect_inputs.list; seq_item; seq_item = seq_item->next) { - Sequence *seq = seq_item->link; - /* It's possible that effect strip would be placed to the same - * 'machine' as its inputs. We don't want to clear such strips - * from the stack. */ - if (seq_arr[seq->machine] && seq_arr[seq->machine]->type & SEQ_TYPE_EFFECT) { - continue; - } - /* If we're shown a specified channel, then we want to see the strips - * which belongs to this machine. */ - if (chanshown != 0 && chanshown <= seq->machine) { +static void collection_filter_channel_up_to_incl(SeqCollection *collection, const int channel) +{ + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, collection) { + if (seq->machine <= channel) { continue; } - seq_arr[seq->machine] = NULL; + SEQ_collection_remove_strip(seq, collection); } - - return totseq; } -/** - * Count number of strips in timeline at timeline_frame - * - * \param seqbase: ListBase in which strips are located - * \param timeline_frame: frame on timeline from where gaps are searched for - * \return number of strips - */ -int SEQ_render_evaluate_frame(ListBase *seqbase, int timeline_frame) +/* Remove strips we don't want to render from collection. */ +static void collection_filter_rendered_strips(SeqCollection *collection) { - Sequence *seq_arr[MAXSEQ + 1]; - return evaluate_seq_frame_gen(seq_arr, seqbase, timeline_frame, 0); + Sequence *seq; + + /* Remove sound strips and muted strips from collection, because these are not rendered. + * Function must_render_strip() don't have to check for these strips anymore. */ + SEQ_ITERATOR_FOREACH (seq, collection) { + if (seq->type == SEQ_TYPE_SOUND_RAM || (seq->flag & SEQ_MUTE) != 0) { + SEQ_collection_remove_strip(seq, collection); + } + } + + SEQ_ITERATOR_FOREACH (seq, collection) { + if (must_render_strip(seq, collection)) { + continue; + } + SEQ_collection_remove_strip(seq, collection); + } } -static bool video_seq_is_rendered(Sequence *seq) +static int seq_channel_cmp_fn(const void *a, const void *b) { - return (seq && !(seq->flag & SEQ_MUTE) && seq->type != SEQ_TYPE_SOUND_RAM); + return (*(Sequence **)a)->machine - (*(Sequence **)b)->machine; } -int seq_get_shown_sequences(ListBase *seqbasep, - int timeline_frame, - int chanshown, - Sequence **seq_arr_out) +int seq_get_shown_sequences(ListBase *seqbase, + const int timeline_frame, + const int chanshown, + Sequence **r_seq_arr) { - Sequence *seq_arr[MAXSEQ + 1]; - int b = chanshown; - int cnt = 0; + SeqCollection *collection = query_strips_at_frame(seqbase, timeline_frame); - if (b > MAXSEQ) { - return 0; - } - - if (evaluate_seq_frame_gen(seq_arr, seqbasep, timeline_frame, chanshown)) { - if (b == 0) { - b = MAXSEQ; - } - for (; b > 0; b--) { - if (video_seq_is_rendered(seq_arr[b])) { - break; - } - } + if (chanshown != 0) { + collection_filter_channel_up_to_incl(collection, chanshown); } + collection_filter_rendered_strips(collection); - chanshown = b; + const int strip_count = BLI_gset_len(collection->set); - for (; b > 0; b--) { - if (video_seq_is_rendered(seq_arr[b])) { - if (seq_arr[b]->blend_mode == SEQ_BLEND_REPLACE) { - break; - } - } + if (strip_count > MAXSEQ) { + BLI_assert(!"Too many strips, this shouldn't happen"); + return 0; } - for (; b <= chanshown && b >= 0; b++) { - if (video_seq_is_rendered(seq_arr[b])) { - seq_arr_out[cnt++] = seq_arr[b]; - } + /* Copy collection elements into array. */ + memset(r_seq_arr, 0, sizeof(Sequence *) * (MAXSEQ + 1)); + Sequence *seq; + int index = 0; + SEQ_ITERATOR_FOREACH (seq, collection) { + r_seq_arr[index] = seq; + index++; } + SEQ_collection_free(collection); + + /* Sort array by channel. */ + qsort(r_seq_arr, strip_count, sizeof(Sequence *), seq_channel_cmp_fn); - return cnt; + return strip_count; } /** \} */ @@ -1495,7 +1508,7 @@ static ImBuf *seq_render_scene_strip(const SeqRenderData *context, goto finally; } - if (seq->flag & SEQ_SCENE_NO_GPENCIL) { + if (seq->flag & SEQ_SCENE_NO_ANNOTATION) { use_gpencil = false; } @@ -1961,7 +1974,7 @@ static ImBuf *seq_render_strip_stack(const SeqRenderData *context, /** * \return The image buffer or NULL. * - * \note The returned #ImBuf is has it's reference increased, free after usage! + * \note The returned #ImBuf has its reference increased, free after usage! */ ImBuf *SEQ_render_give_ibuf(const SeqRenderData *context, float timeline_frame, int chanshown) { diff --git a/source/blender/sequencer/intern/render.h b/source/blender/sequencer/intern/render.h index 1147516b8ec..a0cdf24d84b 100644 --- a/source/blender/sequencer/intern/render.h +++ b/source/blender/sequencer/intern/render.h @@ -60,10 +60,10 @@ struct ImBuf *seq_render_effect_execute_threaded(struct SeqEffectHandle *sh, struct ImBuf *ibuf2, struct ImBuf *ibuf3); void seq_imbuf_to_sequencer_space(struct Scene *scene, struct ImBuf *ibuf, bool make_float); -int seq_get_shown_sequences(struct ListBase *seqbasep, +int seq_get_shown_sequences(struct ListBase *seqbase, int timeline_frame, int chanshown, - struct Sequence **seq_arr_out); + struct Sequence **r_seq_arr); struct ImBuf *seq_render_strip(const struct SeqRenderData *context, struct SeqRenderState *state, struct Sequence *seq, diff --git a/source/blender/sequencer/intern/sequencer.c b/source/blender/sequencer/intern/sequencer.c index b00c36ad8e4..4acb6a206be 100644 --- a/source/blender/sequencer/intern/sequencer.c +++ b/source/blender/sequencer/intern/sequencer.c @@ -459,8 +459,8 @@ static Sequence *seq_dupli(const Scene *scene_src, seqn->strip->stripdata = NULL; BLI_listbase_clear(&seqn->seqbase); - /* WATCH OUT!!! - This metastrip is not recursively duplicated here - do this after!!! */ - /* - seq_dupli_recursive(&seq->seqbase, &seqn->seqbase);*/ + /* WARNING: This meta-strip is not recursively duplicated here - do this after! */ + // seq_dupli_recursive(&seq->seqbase, &seqn->seqbase); } else if (seq->type == SEQ_TYPE_SCENE) { seqn->strip->stripdata = NULL; diff --git a/source/blender/sequencer/intern/strip_add.c b/source/blender/sequencer/intern/strip_add.c index 1106f47c477..5ec2269b993 100644 --- a/source/blender/sequencer/intern/strip_add.c +++ b/source/blender/sequencer/intern/strip_add.c @@ -100,7 +100,7 @@ static void seq_add_generic_update(Scene *scene, ListBase *seqbase, Sequence *se { SEQ_sequence_base_unique_name_recursive(seqbase, seq); SEQ_time_update_sequence_bounds(scene, seq); - SEQ_sort(scene); + SEQ_sort(seqbase); SEQ_relations_invalidate_cache_composite(scene, seq); } diff --git a/source/blender/sequencer/intern/strip_edit.c b/source/blender/sequencer/intern/strip_edit.c index 4a27fb3a087..4de6ec3583c 100644 --- a/source/blender/sequencer/intern/strip_edit.c +++ b/source/blender/sequencer/intern/strip_edit.c @@ -39,10 +39,12 @@ #include "BKE_sound.h" #include "strip_time.h" +#include "utils.h" #include "SEQ_add.h" #include "SEQ_edit.h" #include "SEQ_effects.h" +#include "SEQ_iterator.h" #include "SEQ_relations.h" #include "SEQ_sequencer.h" #include "SEQ_time.h" @@ -149,7 +151,7 @@ void SEQ_edit_update_muting(Editing *ed) static void sequencer_flag_users_for_removal(Scene *scene, ListBase *seqbase, Sequence *seq) { LISTBASE_FOREACH (Sequence *, user_seq, seqbase) { - /* Look in metas for usage of seq. */ + /* Look in meta-strips for usage of seq. */ if (user_seq->type == SEQ_TYPE_META) { sequencer_flag_users_for_removal(scene, &user_seq->seqbase, seq); } @@ -251,22 +253,26 @@ bool SEQ_edit_move_strip_to_meta(Scene *scene, return false; } - /* Remove users of src_seq. Ideally these could be moved into meta as well, but this would be - * best to do with generalized iterator as described in D10337. */ - sequencer_flag_users_for_removal(scene, seqbase, src_seq); - SEQ_edit_remove_flagged_sequences(scene, seqbase); + SeqCollection *collection = SEQ_collection_create(); + SEQ_collection_append_strip(src_seq, collection); + SEQ_collection_expand(seqbase, collection, SEQ_query_strip_effect_chain); - /* Move to meta. */ - BLI_remlink(seqbase, src_seq); - BLI_addtail(&dst_seqm->seqbase, src_seq); - SEQ_relations_invalidate_cache_preprocessed(scene, src_seq); - - /* Update meta. */ - SEQ_time_update_sequence(scene, dst_seqm); - if (SEQ_transform_test_overlap(&dst_seqm->seqbase, src_seq)) { - SEQ_transform_seqbase_shuffle(&dst_seqm->seqbase, src_seq, scene); + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, collection) { + /* Move to meta. */ + BLI_remlink(seqbase, seq); + BLI_addtail(&dst_seqm->seqbase, seq); + SEQ_relations_invalidate_cache_preprocessed(scene, seq); + + /* Update meta. */ + SEQ_time_update_sequence(scene, dst_seqm); + if (SEQ_transform_test_overlap(&dst_seqm->seqbase, seq)) { + SEQ_transform_seqbase_shuffle(&dst_seqm->seqbase, seq, scene); + } } + SEQ_collection_free(collection); + return true; } @@ -343,6 +349,29 @@ static void seq_split_set_left_offset(Sequence *seq, int timeline_frame) SEQ_transform_set_left_handle_frame(seq, timeline_frame); } +static void seq_edit_split_handle_strip_offsets(Main *bmain, + Scene *scene, + Sequence *left_seq, + Sequence *right_seq, + const int timeline_frame, + const eSeqSplitMethod method) +{ + switch (method) { + case SEQ_SPLIT_SOFT: + seq_split_set_left_offset(right_seq, timeline_frame); + seq_split_set_right_offset(left_seq, timeline_frame); + break; + case SEQ_SPLIT_HARD: + seq_split_set_right_hold_offset(left_seq, timeline_frame); + seq_split_set_left_hold_offset(right_seq, timeline_frame); + SEQ_add_reload_new_file(bmain, scene, left_seq, false); + SEQ_add_reload_new_file(bmain, scene, right_seq, false); + break; + } + SEQ_time_update_sequence(scene, left_seq); + SEQ_time_update_sequence(scene, right_seq); +} + /** * Split Sequence at timeline_frame in two. * @@ -365,33 +394,44 @@ Sequence *SEQ_edit_strip_split(Main *bmain, return NULL; } - if (method == SEQ_SPLIT_HARD) { - /* Precaution, needed because the length saved on-disk may not match the length saved in the - * blend file, or our code may have minor differences reading file length between versions. - * This causes hard-split to fail, see: T47862. */ - SEQ_add_reload_new_file(bmain, scene, seq, true); - SEQ_time_update_sequence(scene, seq); + SeqCollection *collection = SEQ_collection_create(); + SEQ_collection_append_strip(seq, collection); + SEQ_collection_expand(seqbase, collection, SEQ_query_strip_effect_chain); + + /* Move strips in collection from seqbase to new ListBase. */ + ListBase left_strips = {NULL, NULL}; + SEQ_ITERATOR_FOREACH (seq, collection) { + BLI_remlink(seqbase, seq); + BLI_addtail(&left_strips, seq); } - Sequence *left_seq = seq; - Sequence *right_seq = SEQ_sequence_dupli_recursive( - scene, scene, seqbase, seq, SEQ_DUPE_UNIQUE_NAME | SEQ_DUPE_ANIM); + /* Sort list, so that no strip can depend on next strip in list. + * This is important for SEQ_time_update_sequence functionality. */ + SEQ_sort(&left_strips); + + /* Duplicate ListBase. */ + ListBase right_strips = {NULL, NULL}; + SEQ_sequence_base_dupli_recursive(scene, scene, &right_strips, &left_strips, SEQ_DUPE_ALL, 0); + + /* Split strips. */ + Sequence *left_seq = left_strips.first; + Sequence *right_seq = right_strips.first; + Sequence *return_seq = right_strips.first; + while (left_seq && right_seq) { + seq_edit_split_handle_strip_offsets(bmain, scene, left_seq, right_seq, timeline_frame, method); + left_seq = left_seq->next; + right_seq = right_seq->next; + } - switch (method) { - case SEQ_SPLIT_SOFT: - seq_split_set_left_offset(right_seq, timeline_frame); - seq_split_set_right_offset(left_seq, timeline_frame); - break; - case SEQ_SPLIT_HARD: - seq_split_set_right_hold_offset(left_seq, timeline_frame); - seq_split_set_left_hold_offset(right_seq, timeline_frame); - SEQ_add_reload_new_file(bmain, scene, left_seq, false); - SEQ_add_reload_new_file(bmain, scene, right_seq, false); - break; + seq = right_strips.first; + BLI_movelisttolist(seqbase, &left_strips); + BLI_movelisttolist(seqbase, &right_strips); + + for (; seq; seq = seq->next) { + SEQ_ensure_unique_name(seq, scene); } - SEQ_time_update_sequence(scene, left_seq); - SEQ_time_update_sequence(scene, right_seq); - return right_seq; + + return return_seq; } /** diff --git a/source/blender/sequencer/intern/strip_time.c b/source/blender/sequencer/intern/strip_time.c index 4a01b0e1938..40d7fade308 100644 --- a/source/blender/sequencer/intern/strip_time.c +++ b/source/blender/sequencer/intern/strip_time.c @@ -36,6 +36,7 @@ #include "IMB_imbuf.h" +#include "SEQ_iterator.h" #include "SEQ_render.h" #include "SEQ_sequencer.h" #include "SEQ_time.h" @@ -187,7 +188,7 @@ static void seq_time_update_meta_strip_range(Scene *scene, Sequence *seq_meta) { seq_time_update_meta_strip(scene, seq_meta); - /* Prevent metastrip to move in timeline. */ + /* Prevent meta-strip to move in timeline. */ SEQ_transform_set_left_handle_frame(seq_meta, seq_meta->startdisp); SEQ_transform_set_right_handle_frame(seq_meta, seq_meta->enddisp); } @@ -196,7 +197,7 @@ void SEQ_time_update_sequence(Scene *scene, Sequence *seq) { Sequence *seqm; - /* check all metas recursively */ + /* Check all meta-strips recursively. */ seqm = seq->seqbase.first; while (seqm) { if (seqm->seqbase.first) { @@ -404,6 +405,17 @@ void SEQ_timeline_boundbox(const Scene *scene, const ListBase *seqbase, rctf *re } } +static bool strip_exists_at_frame(SeqCollection *all_strips, const int timeline_frame) +{ + Sequence *seq; + SEQ_ITERATOR_FOREACH (seq, all_strips) { + if ((seq->startdisp <= timeline_frame) && (seq->enddisp > timeline_frame)) { + return true; + } + } + return false; +} + /** * Find first gap between strips after initial_frame and describe it by filling data of r_gap_info * @@ -425,10 +437,12 @@ void seq_time_gap_info_get(const Scene *scene, int timeline_frame = initial_frame; r_gap_info->gap_exists = false; - if (SEQ_render_evaluate_frame(seqbase, initial_frame) == 0) { + SeqCollection *collection = SEQ_query_all_strips(seqbase); + + if (!strip_exists_at_frame(collection, initial_frame)) { /* Search backward for gap_start_frame. */ for (; timeline_frame >= sfra; timeline_frame--) { - if (SEQ_render_evaluate_frame(seqbase, timeline_frame) != 0) { + if (strip_exists_at_frame(collection, timeline_frame)) { break; } } @@ -438,7 +452,7 @@ void seq_time_gap_info_get(const Scene *scene, else { /* Search forward for gap_start_frame. */ for (; timeline_frame <= efra; timeline_frame++) { - if (SEQ_render_evaluate_frame(seqbase, timeline_frame) == 0) { + if (!strip_exists_at_frame(collection, timeline_frame)) { r_gap_info->gap_start_frame = timeline_frame; break; } @@ -446,7 +460,7 @@ void seq_time_gap_info_get(const Scene *scene, } /* Search forward for gap_end_frame. */ for (; timeline_frame <= efra; timeline_frame++) { - if (SEQ_render_evaluate_frame(seqbase, timeline_frame) != 0) { + if (strip_exists_at_frame(collection, timeline_frame)) { const int gap_end_frame = timeline_frame; r_gap_info->gap_length = gap_end_frame - r_gap_info->gap_start_frame; r_gap_info->gap_exists = true; diff --git a/source/blender/sequencer/intern/utils.c b/source/blender/sequencer/intern/utils.c index a15465eb3c0..cf1d7d66476 100644 --- a/source/blender/sequencer/intern/utils.c +++ b/source/blender/sequencer/intern/utils.c @@ -33,10 +33,7 @@ #include "DNA_scene_types.h" #include "DNA_sequence_types.h" -#include "BLI_listbase.h" -#include "BLI_path_util.h" -#include "BLI_string.h" -#include "BLI_utildefines.h" +#include "BLI_blenlib.h" #include "BKE_image.h" #include "BKE_main.h" @@ -55,21 +52,27 @@ #include "proxy.h" #include "utils.h" -void SEQ_sort(Scene *scene) +/** + * Sort strips in provided seqbase. Effect strips are trailing the list and they are sorted by + * channel position as well. + * This is important for SEQ_time_update_sequence to work properly + * + * \param seqbase: ListBase with strips + */ +void SEQ_sort(ListBase *seqbase) { - /* all strips together per kind, and in order of y location ("machine") */ - ListBase seqbase, effbase; - Editing *ed = SEQ_editing_get(scene, false); - Sequence *seq, *seqt; - - if (ed == NULL) { + if (seqbase == NULL) { return; } - BLI_listbase_clear(&seqbase); + /* all strips together per kind, and in order of y location ("machine") */ + ListBase inputbase, effbase; + Sequence *seq, *seqt; + + BLI_listbase_clear(&inputbase); BLI_listbase_clear(&effbase); - while ((seq = BLI_pophead(ed->seqbasep))) { + while ((seq = BLI_pophead(seqbase))) { if (seq->type & SEQ_TYPE_EFFECT) { seqt = effbase.first; @@ -85,22 +88,22 @@ void SEQ_sort(Scene *scene) } } else { - seqt = seqbase.first; + seqt = inputbase.first; while (seqt) { if (seqt->machine >= seq->machine) { - BLI_insertlinkbefore(&seqbase, seqt, seq); + BLI_insertlinkbefore(&inputbase, seqt, seq); break; } seqt = seqt->next; } if (seqt == NULL) { - BLI_addtail(&seqbase, seq); + BLI_addtail(&inputbase, seq); } } } - BLI_movelisttolist(&seqbase, &effbase); - *(ed->seqbasep) = seqbase; + BLI_movelisttolist(seqbase, &inputbase); + BLI_movelisttolist(seqbase, &effbase); } typedef struct SeqUniqueInfo { @@ -160,7 +163,7 @@ void SEQ_sequence_base_unique_name_recursive(ListBase *seqbasep, Sequence *seq) while (sui.match) { sui.match = 0; seqbase_unique_name(seqbasep, &sui); - SEQ_iterator_seqbase_recursive_apply(seqbasep, seqbase_unique_name_recursive_fn, &sui); + SEQ_seqbase_recursive_apply(seqbasep, seqbase_unique_name_recursive_fn, &sui); } BLI_strncpy(seq->name + 2, sui.name_dest, sizeof(seq->name) - 2); @@ -584,3 +587,53 @@ void SEQ_set_scale_to_fit(const Sequence *seq, break; } } + +int SEQ_seqbase_recursive_apply(ListBase *seqbase, + int (*apply_fn)(Sequence *seq, void *), + void *arg) +{ + Sequence *iseq; + for (iseq = seqbase->first; iseq; iseq = iseq->next) { + if (SEQ_recursive_apply(iseq, apply_fn, arg) == -1) { + return -1; /* bail out */ + } + } + return 1; +} + +int SEQ_recursive_apply(Sequence *seq, int (*apply_fn)(Sequence *, void *), void *arg) +{ + int ret = apply_fn(seq, arg); + + if (ret == -1) { + return -1; /* bail out */ + } + + if (ret && seq->seqbase.first) { + ret = SEQ_seqbase_recursive_apply(&seq->seqbase, apply_fn, arg); + } + + return ret; +} + +/** + * Ensure, that provided Sequence has unique name. If animation data exists for this Sequence, it + * will be duplicated and mapped onto new name + * + * \param seq: Sequence which name will be ensured to be unique + * \param scene: Scene in which name must be unique + */ +void SEQ_ensure_unique_name(Sequence *seq, Scene *scene) +{ + char name[SEQ_NAME_MAXSTR]; + + BLI_strncpy_utf8(name, seq->name + 2, sizeof(name)); + SEQ_sequence_base_unique_name_recursive(&scene->ed->seqbase, seq); + SEQ_dupe_animdata(scene, name, seq->name + 2); + + if (seq->type == SEQ_TYPE_META) { + LISTBASE_FOREACH (Sequence *, seq_child, &seq->seqbase) { + SEQ_ensure_unique_name(seq_child, scene); + } + } +} diff --git a/source/blender/shader_fx/intern/FX_shader_glow.c b/source/blender/shader_fx/intern/FX_shader_glow.c index 30eaa35a049..fc75771cd62 100644 --- a/source/blender/shader_fx/intern/FX_shader_glow.c +++ b/source/blender/shader_fx/intern/FX_shader_glow.c @@ -71,12 +71,11 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel) uiItemR(layout, ptr, "mode", 0, NULL, ICON_NONE); - if (mode == eShaderFxGlowMode_Luminance) { - uiItemR(layout, ptr, "threshold", 0, NULL, ICON_NONE); - } - else { + uiItemR(layout, ptr, "threshold", 0, NULL, ICON_NONE); + if (mode == eShaderFxGlowMode_Color) { uiItemR(layout, ptr, "select_color", 0, NULL, ICON_NONE); } + uiItemR(layout, ptr, "glow_color", 0, NULL, ICON_NONE); uiItemS(layout); diff --git a/source/blender/windowmanager/CMakeLists.txt b/source/blender/windowmanager/CMakeLists.txt index 0f26ec50816..183b22c9791 100644 --- a/source/blender/windowmanager/CMakeLists.txt +++ b/source/blender/windowmanager/CMakeLists.txt @@ -203,6 +203,7 @@ if(WITH_XR_OPENXR) list(APPEND SRC xr/intern/wm_xr.c + xr/intern/wm_xr_actions.c xr/intern/wm_xr_draw.c xr/intern/wm_xr_session.c diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index 280ee75a50f..edd5b555e2f 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -71,6 +71,11 @@ struct wmTabletData; struct wmNDOFMotionData; #endif +#ifdef WITH_XR_OPENXR +struct wmXrActionState; +struct wmXrPose; +#endif + typedef struct wmGizmo wmGizmo; typedef struct wmGizmoMap wmGizmoMap; typedef struct wmGizmoMapType wmGizmoMapType; @@ -929,7 +934,7 @@ void WM_generic_user_data_free(struct wmGenericUserData *wm_userdata); bool WM_region_use_viewport(struct ScrArea *area, struct ARegion *region); #ifdef WITH_XR_OPENXR -/* wm_xr.c */ +/* wm_xr_session.c */ bool WM_xr_session_exists(const wmXrData *xr); bool WM_xr_session_is_ready(const wmXrData *xr); struct wmXrSessionState *WM_xr_session_state_handle_get(const wmXrData *xr); @@ -939,7 +944,74 @@ bool WM_xr_session_state_viewer_pose_rotation_get(const wmXrData *xr, float r_ro bool WM_xr_session_state_viewer_pose_matrix_info_get(const wmXrData *xr, float r_viewmat[4][4], float *r_focal_len); -#endif +bool WM_xr_session_state_controller_pose_location_get(const wmXrData *xr, + unsigned int subaction_idx, + float r_location[3]); +bool WM_xr_session_state_controller_pose_rotation_get(const wmXrData *xr, + unsigned int subaction_idx, + float r_rotation[4]); + +/* wm_xr_actions.c */ +/* XR action functions to be called pre-XR session start. + * Note: The "destroy" functions can also be called post-session start. */ +bool WM_xr_action_set_create(wmXrData *xr, const char *action_set_name); +void WM_xr_action_set_destroy(wmXrData *xr, const char *action_set_name); +bool WM_xr_action_create(wmXrData *xr, + const char *action_set_name, + const char *action_name, + eXrActionType type, + unsigned int count_subaction_paths, + const char **subaction_paths, + const float *float_threshold, + struct wmOperatorType *ot, + struct IDProperty *op_properties, + eXrOpFlag op_flag); +void WM_xr_action_destroy(wmXrData *xr, const char *action_set_name, const char *action_name); +bool WM_xr_action_space_create(wmXrData *xr, + const char *action_set_name, + const char *action_name, + unsigned int count_subaction_paths, + const char **subaction_paths, + const struct wmXrPose *poses); +void WM_xr_action_space_destroy(wmXrData *xr, + const char *action_set_name, + const char *action_name, + unsigned int count_subaction_paths, + const char **subaction_paths); +bool WM_xr_action_binding_create(wmXrData *xr, + const char *action_set_name, + const char *profile_path, + const char *action_name, + unsigned int count_interaction_paths, + const char **interaction_paths); +void WM_xr_action_binding_destroy(wmXrData *xr, + const char *action_set_name, + const char *profile_path, + const char *action_name, + unsigned int count_interaction_paths, + const char **interaction_paths); + +bool WM_xr_active_action_set_set( + wmXrData *xr, const char *action_set_name); /* If action_set_name is NULL, then + * all action sets will be treated as active. */ +bool WM_xr_controller_pose_action_set(wmXrData *xr, + const char *action_set_name, + const char *action_name); + +/* XR action functions to be called post-XR session start. */ +bool WM_xr_action_state_get(const wmXrData *xr, + const char *action_set_name, + const char *action_name, + const char *subaction_path, + struct wmXrActionState *r_state); +bool WM_xr_haptic_action_apply(wmXrData *xr, + const char *action_set_name, + const char *action_name, + const long long *duration, + const float *frequency, + const float *amplitude); +void WM_xr_haptic_action_stop(wmXrData *xr, const char *action_set_name, const char *action_name); +#endif /* WITH_XR_OPENXR */ #ifdef __cplusplus } diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index d54925272de..168b775d16d 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -132,17 +132,21 @@ struct wmWindowManager; extern "C" { #endif +typedef void (*wmGenericUserDataFreeFn)(void *data); + typedef struct wmGenericUserData { void *data; /** When NULL, use #MEM_freeN. */ - void (*free_fn)(void *data); + wmGenericUserDataFreeFn free_fn; bool use_free; } wmGenericUserData; +typedef void (*wmGenericCallbackFn)(struct bContext *C, void *user_data); + typedef struct wmGenericCallback { - void (*exec)(struct bContext *C, void *user_data); + wmGenericCallbackFn exec; void *user_data; - void (*free_user_data)(void *user_data); + wmGenericUserDataFreeFn free_user_data; } wmGenericCallback; /* ************** wmOperatorType ************************ */ @@ -680,6 +684,25 @@ typedef struct wmNDOFMotionData { } wmNDOFMotionData; #endif /* WITH_INPUT_NDOF */ +#ifdef WITH_XR_OPENXR +/* Similar to GHOST_XrPose. */ +typedef struct wmXrPose { + float position[3]; + /* Blender convention (w, x, y, z) */ + float orientation_quat[4]; +} wmXrPose; + +typedef struct wmXrActionState { + union { + bool state_boolean; + float state_float; + float state_vector2f[2]; + wmXrPose state_pose; + }; + int type; /* eXrActionType */ +} wmXrActionState; +#endif + /** Timer flags. */ typedef enum { /** Do not attempt to free customdata pointer even if non-NULL. */ diff --git a/source/blender/windowmanager/intern/wm_cursors.c b/source/blender/windowmanager/intern/wm_cursors.c index cdb7b591907..11783ae3517 100644 --- a/source/blender/windowmanager/intern/wm_cursors.c +++ b/source/blender/windowmanager/intern/wm_cursors.c @@ -264,7 +264,7 @@ void WM_cursor_grab_enable(wmWindow *win, int wrap, bool hide, int bounds[4]) if (wrap == WM_CURSOR_WRAP_X) { mode_axis = GHOST_kAxisX; } - if (wrap == WM_CURSOR_WRAP_Y) { + else if (wrap == WM_CURSOR_WRAP_Y) { mode_axis = GHOST_kGrabAxisY; } } diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index 0d1f4cc4830..a6588c40f0f 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -1048,7 +1048,7 @@ static int wm_operator_exec(bContext *C, wmOperator *op, const bool repeat, cons wmWindowManager *wm = CTX_wm_manager(C); int retval = OPERATOR_CANCELLED; - CTX_wm_operator_poll_msg_set(C, NULL); + CTX_wm_operator_poll_msg_clear(C); if (op == NULL || op->type == NULL) { return retval; @@ -1469,7 +1469,7 @@ static int wm_operator_call_internal(bContext *C, { int retval; - CTX_wm_operator_poll_msg_set(C, NULL); + CTX_wm_operator_poll_msg_clear(C); /* Dummy test. */ if (ot) { @@ -4478,16 +4478,21 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void event.prevtype = event.type; event.prevval = event.val; - /* Ensure the event state is correct, any deviation from this may cause bugs. */ + /* Ensure the event state is correct, any deviation from this may cause bugs. + * + * NOTE: #EVENT_NONE is set when unknown keys are pressed, + * while not common, avoid a false alarm. */ #ifndef NDEBUG if ((event_state->type || event_state->val) && /* Ignore cleared event state. */ - !(ISMOUSE_BUTTON(event_state->type) || ISKEYBOARD(event_state->type))) { + !(ISMOUSE_BUTTON(event_state->type) || ISKEYBOARD(event_state->type) || + (event_state->type == EVENT_NONE))) { CLOG_WARN(WM_LOG_HANDLERS, "Non-keyboard/mouse button found in 'win->eventstate->type = %d'", event_state->type); } if ((event_state->prevtype || event_state->prevval) && /* Ignore cleared event state. */ - !(ISMOUSE_BUTTON(event_state->prevtype) || ISKEYBOARD(event_state->prevtype))) { + !(ISMOUSE_BUTTON(event_state->prevtype) || ISKEYBOARD(event_state->prevtype) || + (event_state->type == EVENT_NONE))) { CLOG_WARN(WM_LOG_HANDLERS, "Non-keyboard/mouse button found in 'win->eventstate->prevtype = %d'", event_state->prevtype); diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c index bbcb0669cce..d0ee7075516 100644 --- a/source/blender/windowmanager/intern/wm_files.c +++ b/source/blender/windowmanager/intern/wm_files.c @@ -2157,21 +2157,9 @@ static void wm_homefile_read_after_dialog_callback(bContext *C, void *user_data) C, "WM_OT_read_homefile", WM_OP_EXEC_DEFAULT, (IDProperty *)user_data); } -static void wm_free_operator_properties_callback(void *user_data) -{ - IDProperty *properties = (IDProperty *)user_data; - IDP_FreeProperty(properties); -} - static int wm_homefile_read_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { - if (U.uiflag & USER_SAVE_PROMPT && - wm_file_or_image_is_modified(CTX_data_main(C), CTX_wm_manager(C))) { - wmGenericCallback *callback = MEM_callocN(sizeof(*callback), __func__); - callback->exec = wm_homefile_read_after_dialog_callback; - callback->user_data = IDP_CopyProperty(op->properties); - callback->free_user_data = wm_free_operator_properties_callback; - wm_close_file_dialog(C, callback); + if (wm_operator_close_file_dialog_if_needed(C, op, wm_homefile_read_after_dialog_callback)) { return OPERATOR_INTERFACE; } return wm_homefile_read_exec(C, op); @@ -2331,13 +2319,7 @@ static int wm_open_mainfile__discard_changes(bContext *C, wmOperator *op) set_next_operator_state(op, OPEN_MAINFILE_STATE_OPEN); } - if (U.uiflag & USER_SAVE_PROMPT && - wm_file_or_image_is_modified(CTX_data_main(C), CTX_wm_manager(C))) { - wmGenericCallback *callback = MEM_callocN(sizeof(*callback), __func__); - callback->exec = wm_open_mainfile_after_dialog_callback; - callback->user_data = IDP_CopyProperty(op->properties); - callback->free_user_data = wm_free_operator_properties_callback; - wm_close_file_dialog(C, callback); + if (wm_operator_close_file_dialog_if_needed(C, op, wm_open_mainfile_after_dialog_callback)) { return OPERATOR_INTERFACE; } return wm_open_mainfile_dispatch(C, op); @@ -2637,12 +2619,25 @@ static int wm_recover_last_session_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } -static int wm_recover_last_session_invoke(bContext *C, wmOperator *op, const wmEvent *event) +static void wm_recover_last_session_after_dialog_callback(bContext *C, void *user_data) +{ + WM_operator_name_call_with_properties( + C, "WM_OT_recover_last_session", WM_OP_EXEC_DEFAULT, (IDProperty *)user_data); +} + +static int wm_recover_last_session_invoke(bContext *C, + wmOperator *op, + const wmEvent *UNUSED(event)) { /* Keep the current setting instead of using the preferences since a file selector * doesn't give us the option to change the setting. */ wm_open_init_use_scripts(op, false); - return WM_operator_confirm(C, op, event); + + if (wm_operator_close_file_dialog_if_needed( + C, op, wm_recover_last_session_after_dialog_callback)) { + return OPERATOR_INTERFACE; + } + return wm_recover_last_session_exec(C, op); } void WM_OT_recover_last_session(wmOperatorType *ot) @@ -3270,7 +3265,10 @@ static void wm_block_file_close_save(bContext *C, void *arg_block, void *arg_dat bool file_has_been_saved_before = BKE_main_blendfile_path(bmain)[0] != '\0'; if (file_has_been_saved_before) { - WM_operator_name_call(C, "WM_OT_save_mainfile", WM_OP_EXEC_DEFAULT, NULL); + if (WM_operator_name_call(C, "WM_OT_save_mainfile", WM_OP_EXEC_DEFAULT, NULL) & + OPERATOR_CANCELLED) { + execute_callback = false; + } } else { WM_operator_name_call(C, "WM_OT_save_mainfile", WM_OP_INVOKE_DEFAULT, NULL); @@ -3456,4 +3454,31 @@ void wm_close_file_dialog(bContext *C, wmGenericCallback *post_action) } } +static void wm_free_operator_properties_callback(void *user_data) +{ + IDProperty *properties = (IDProperty *)user_data; + IDP_FreeProperty(properties); +} + +/** + * \return True if the dialog was created, the calling operator should return #OPERATOR_INTERFACE + * then. + */ +bool wm_operator_close_file_dialog_if_needed(bContext *C, + wmOperator *op, + wmGenericCallbackFn post_action_fn) +{ + if (U.uiflag & USER_SAVE_PROMPT && + wm_file_or_image_is_modified(CTX_data_main(C), CTX_wm_manager(C))) { + wmGenericCallback *callback = MEM_callocN(sizeof(*callback), __func__); + callback->exec = post_action_fn; + callback->user_data = IDP_CopyProperty(op->properties); + callback->free_user_data = wm_free_operator_properties_callback; + wm_close_file_dialog(C, callback); + return true; + } + + return false; +} + /** \} */ diff --git a/source/blender/windowmanager/intern/wm_init_exit.c b/source/blender/windowmanager/intern/wm_init_exit.c index 56fd51ac6fd..0dcb817ad15 100644 --- a/source/blender/windowmanager/intern/wm_init_exit.c +++ b/source/blender/windowmanager/intern/wm_init_exit.c @@ -343,8 +343,13 @@ void WM_init(bContext *C, int argc, const char **argv) (void)argv; /* unused */ #endif - if (!G.background && !wm_start_with_console) { - GHOST_toggleConsole(3); + if (!G.background) { + if (wm_start_with_console) { + GHOST_toggleConsole(1); + } + else { + GHOST_toggleConsole(3); + } } BKE_material_copybuf_clear(); diff --git a/source/blender/windowmanager/intern/wm_playanim.c b/source/blender/windowmanager/intern/wm_playanim.c index de563cd309d..5300649a0cd 100644 --- a/source/blender/windowmanager/intern/wm_playanim.c +++ b/source/blender/windowmanager/intern/wm_playanim.c @@ -97,45 +97,69 @@ static AUD_Device *audio_device = NULL; struct PlayState; static void playanim_window_zoom(struct PlayState *ps, const float zoom_offset); +/** + * The current state of the player. + * + * \warning Don't store results of parsing command-line arguments + * in this struct if they need to persist across playing back different + * files as these will be cleared when playing other files (drag & drop). + */ typedef struct PlayState { - /* window and viewport size */ + /** Window and viewport size. */ int win_x, win_y; - /* current zoom level */ + /** Current zoom level. */ float zoom; - /* playback state */ + /** Playback direction (-1, 1). */ short direction; + /** Set the next frame to implement frame stepping (using shortcuts). */ short next_frame; + /** Playback once then wait. */ bool once; - bool turbo; + /** Play forwards/backwards. */ bool pingpong; + /** Disable frame skipping. */ bool noskip; + /** Display current frame over the window. */ bool indicator; + /** Single-frame stepping has been enabled (frame loading and update pending). */ bool sstep; + /** Playback has stopped the image has been displayed. */ bool wait2; + /** Playback stopped state once stop/start variables have been handled. */ bool stopped; + /** + * When disabled the current animation will exit, + * after this either the application exits or a new animation window is opened. + * + * This is used so drag & drop can load new files which setup a newly created animation window. + */ bool go; - /* waiting for images to load */ + /** True when waiting for images to load. */ bool loading; - /* x/y image flip */ + /** X/Y image flip (set via key bindings). */ bool draw_flip[2]; + /** The number of frames to step each update (default to 1, command line argument). */ int fstep; - /* current picture */ + /** Current frame (picture). */ struct PlayAnimPict *picture; - /* set once at the start */ + /** Image size in pixels, set once at the start. */ int ibufx, ibufy; + /** Mono-space font ID. */ int fontid; - /* restarts player for file drop */ + /** Restarts player for file drop (drag & drop). */ char dropped_file[FILE_MAX]; + /** Force update when scrubbing with the cursor. */ bool need_frame_update; + /** The current frame calculated by scrubbing the mouse cursor. */ int frame_cursor_x; ColorManagedViewSettings view_settings; @@ -144,17 +168,14 @@ typedef struct PlayState { /* for debugging */ #if 0 -void print_ps(PlayState *ps) +static void print_ps(PlayState *ps) { printf("ps:\n"); printf(" direction=%d,\n", (int)ps->direction); - printf(" next=%d,\n", ps->next); printf(" once=%d,\n", ps->once); - printf(" turbo=%d,\n", ps->turbo); printf(" pingpong=%d,\n", ps->pingpong); printf(" noskip=%d,\n", ps->noskip); printf(" sstep=%d,\n", ps->sstep); - printf(" pause=%d,\n", ps->pause); printf(" wait2=%d,\n", ps->wait2); printf(" stopped=%d,\n", ps->stopped); printf(" go=%d,\n\n", ps->go); @@ -869,16 +890,14 @@ static void change_frame(PlayState *ps) static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) { PlayState *ps = (PlayState *)ps_void; - GHOST_TEventType type = GHOST_GetEventType(evt); - int val; + const GHOST_TEventType type = GHOST_GetEventType(evt); + /* Convert ghost event into value keyboard or mouse. */ + const int val = ELEM(type, GHOST_kEventKeyDown, GHOST_kEventButtonDown); // print_ps(ps); playanim_event_qual_update(); - /* convert ghost event into value keyboard or mouse */ - val = ELEM(type, GHOST_kEventKeyDown, GHOST_kEventButtonDown); - /* first check if we're busy loading files */ if (ps->loading) { switch (type) { @@ -902,8 +921,8 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) return 1; } - if (ps->wait2 && ps->stopped) { - ps->stopped = false; + if (ps->wait2 && ps->stopped == false) { + ps->stopped = true; } if (ps->wait2) { @@ -1379,7 +1398,9 @@ static void playanim_window_zoom(PlayState *ps, const float zoom_offset) GHOST_SetClientSize(g_WS.ghost_window, sizex, sizey); } -/* return path for restart */ +/** + * \return The a path used to restart the animation player or NULL to exit. + */ static char *wm_main_playanim_intern(int argc, const char **argv) { struct ImBuf *ibuf = NULL; @@ -1398,7 +1419,6 @@ static char *wm_main_playanim_intern(int argc, const char **argv) ps.direction = true; ps.next_frame = 1; ps.once = false; - ps.turbo = false; ps.pingpong = false; ps.noskip = false; ps.sstep = false; @@ -1420,6 +1440,7 @@ static char *wm_main_playanim_intern(int argc, const char **argv) IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DEFAULT_BYTE)); IMB_colormanagement_init_default_view_settings(&ps.view_settings, &ps.display_settings); + /* Skip the first argument which is assumed to be '-a' (used to launch this player). */ while (argc > 1) { if (argv[1][0] == '-') { switch (argv[1][1]) { @@ -1720,14 +1741,15 @@ static char *wm_main_playanim_intern(int argc, const char **argv) ps.wait2 = ps.sstep; - if (ps.wait2 == false && ps.stopped == false) { - ps.stopped = true; + if (ps.wait2 == false && ps.stopped) { + ps.stopped = false; } pupdate_time(); if (ps.picture && ps.next_frame) { - /* always at least set one step */ + /* Advance to the next frame, always at least set one step. + * Implement frame-skipping when enabled and playback is not fast enough. */ while (ps.picture) { ps.picture = playanim_step(ps.picture, ps.next_frame); @@ -1740,7 +1762,7 @@ static char *wm_main_playanim_intern(int argc, const char **argv) } } - if (ps.wait2 || ptottime < swaptime || ps.turbo || ps.noskip) { + if (ps.wait2 || ptottime < swaptime || ps.noskip) { break; } ptottime -= swaptime; diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c index 2e9fd1b1b16..cdd5ea12df8 100644 --- a/source/blender/windowmanager/intern/wm_window.c +++ b/source/blender/windowmanager/intern/wm_window.c @@ -133,7 +133,7 @@ static struct WMInitStruct { * \{ */ static void wm_window_set_drawable(wmWindowManager *wm, wmWindow *win, bool activate); -static int wm_window_timer(const bContext *C); +static bool wm_window_timer(const bContext *C); /* XXX this one should correctly check for apple top header... * done for Cocoa : returns window contents (and not frame) max size*/ @@ -1498,12 +1498,12 @@ static int ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_ptr * Timer handlers should check for delta to decide if they just update, or follow real time. * Timer handlers can also set duration to match frames passed */ -static int wm_window_timer(const bContext *C) +static bool wm_window_timer(const bContext *C) { Main *bmain = CTX_data_main(C); wmWindowManager *wm = CTX_wm_manager(C); double time = PIL_check_seconds_timer(); - int retval = 0; + bool has_event = false; /* Mutable in case the timer gets removed. */ LISTBASE_FOREACH_MUTABLE (wmTimer *, wt, &wm->timers) { @@ -1540,31 +1540,34 @@ static int wm_window_timer(const bContext *C) event.customdata = wt; wm_event_add(win, &event); - retval = 1; + has_event = true; } } } - return retval; + return has_event; } void wm_window_process_events(const bContext *C) { BLI_assert(BLI_thread_is_main()); - int hasevent = GHOST_ProcessEvents(g_system, 0); /* 0 is no wait */ + bool has_event = GHOST_ProcessEvents(g_system, false); /* `false` is no wait. */ - if (hasevent) { + if (has_event) { GHOST_DispatchEvents(g_system); } - hasevent |= wm_window_timer(C); + has_event |= wm_window_timer(C); #ifdef WITH_XR_OPENXR /* XR events don't use the regular window queues. So here we don't only trigger * processing/dispatching but also handling. */ - hasevent |= wm_xr_events_handle(CTX_wm_manager(C)); + has_event |= wm_xr_events_handle(CTX_wm_manager(C)); #endif - /* no event, we sleep 5 milliseconds */ - if (hasevent == 0) { + /* When there is no event, sleep 5 milliseconds not to use too much CPU when idle. + * + * Skip sleeping when simulating events so tests don't idle unnecessarily as simulated + * events are typically generated from a timer that runs in the main loop. */ + if ((has_event == false) && !(G.f & G_FLAG_EVENT_SIMULATE)) { PIL_sleep_ms(5); } } diff --git a/source/blender/windowmanager/wm_files.h b/source/blender/windowmanager/wm_files.h index d54090a6025..c7fe07cad7f 100644 --- a/source/blender/windowmanager/wm_files.h +++ b/source/blender/windowmanager/wm_files.h @@ -45,6 +45,9 @@ void wm_homefile_read(struct bContext *C, void wm_file_read_report(bContext *C, struct Main *bmain); void wm_close_file_dialog(bContext *C, struct wmGenericCallback *post_action); +bool wm_operator_close_file_dialog_if_needed(bContext *C, + wmOperator *op, + wmGenericCallbackFn exec_fn); bool wm_file_or_image_is_modified(const Main *bmain, const wmWindowManager *wm); void WM_OT_save_homefile(struct wmOperatorType *ot); diff --git a/source/blender/windowmanager/xr/intern/wm_xr.c b/source/blender/windowmanager/xr/intern/wm_xr.c index 439d611b085..2a67c2bee9f 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr.c +++ b/source/blender/windowmanager/xr/intern/wm_xr.c @@ -128,6 +128,11 @@ bool wm_xr_events_handle(wmWindowManager *wm) if (wm->xr.runtime && wm->xr.runtime->context) { GHOST_XrEventsHandle(wm->xr.runtime->context); + /* Process OpenXR action events. */ + if (WM_xr_session_is_ready(&wm->xr)) { + wm_xr_session_actions_update(&wm->xr); + } + /* wm_window_process_events() uses the return value to determine if it can put the main thread * to sleep for some milliseconds. We never want that to happen while the VR session runs on * the main thread. So always return true. */ diff --git a/source/blender/windowmanager/xr/intern/wm_xr_actions.c b/source/blender/windowmanager/xr/intern/wm_xr_actions.c new file mode 100644 index 00000000000..51ed3dcfd3c --- /dev/null +++ b/source/blender/windowmanager/xr/intern/wm_xr_actions.c @@ -0,0 +1,480 @@ +/* + * 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. + */ + +/** \file + * \ingroup wm + * + * \name Window-Manager XR Actions + * + * Uses the Ghost-XR API to manage OpenXR actions. + * All functions are designed to be usable by RNA / the Python API. + */ + +#include "BLI_math.h" + +#include "GHOST_C-api.h" + +#include "MEM_guardedalloc.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "wm_xr_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name XR-Action API + * + * API functions for managing OpenXR actions. + * + * \{ */ + +static wmXrActionSet *action_set_create(const char *action_set_name) +{ + wmXrActionSet *action_set = MEM_callocN(sizeof(*action_set), __func__); + action_set->name = MEM_mallocN(strlen(action_set_name) + 1, "XrActionSet_Name"); + strcpy(action_set->name, action_set_name); + + return action_set; +} + +static void action_set_destroy(void *val) +{ + wmXrActionSet *action_set = val; + + MEM_SAFE_FREE(action_set->name); + + MEM_freeN(action_set); +} + +static wmXrActionSet *action_set_find(wmXrData *xr, const char *action_set_name) +{ + return GHOST_XrGetActionSetCustomdata(xr->runtime->context, action_set_name); +} + +static wmXrAction *action_create(const char *action_name, + eXrActionType type, + unsigned int count_subaction_paths, + const char **subaction_paths, + const float *float_threshold, + wmOperatorType *ot, + IDProperty *op_properties, + eXrOpFlag op_flag) +{ + wmXrAction *action = MEM_callocN(sizeof(*action), __func__); + action->name = MEM_mallocN(strlen(action_name) + 1, "XrAction_Name"); + strcpy(action->name, action_name); + action->type = type; + + const unsigned int count = count_subaction_paths; + action->count_subaction_paths = count; + + action->subaction_paths = MEM_mallocN(sizeof(*action->subaction_paths) * count, + "XrAction_SubactionPaths"); + for (unsigned int i = 0; i < count; ++i) { + action->subaction_paths[i] = MEM_mallocN(strlen(subaction_paths[i]) + 1, + "XrAction_SubactionPath"); + strcpy(action->subaction_paths[i], subaction_paths[i]); + } + + size_t size; + switch (type) { + case XR_BOOLEAN_INPUT: + size = sizeof(bool); + break; + case XR_FLOAT_INPUT: + size = sizeof(float); + break; + case XR_VECTOR2F_INPUT: + size = sizeof(float) * 2; + break; + case XR_POSE_INPUT: + size = sizeof(GHOST_XrPose); + break; + case XR_VIBRATION_OUTPUT: + return action; + } + action->states = MEM_calloc_arrayN(count, size, "XrAction_States"); + action->states_prev = MEM_calloc_arrayN(count, size, "XrAction_StatesPrev"); + + if (float_threshold) { + BLI_assert(type == XR_FLOAT_INPUT || type == XR_VECTOR2F_INPUT); + action->float_threshold = *float_threshold; + CLAMP(action->float_threshold, 0.0f, 1.0f); + } + + action->ot = ot; + action->op_properties = op_properties; + action->op_flag = op_flag; + + return action; +} + +static void action_destroy(void *val) +{ + wmXrAction *action = val; + + MEM_SAFE_FREE(action->name); + + const unsigned int count = action->count_subaction_paths; + char **subaction_paths = action->subaction_paths; + if (subaction_paths) { + for (unsigned int i = 0; i < count; ++i) { + MEM_SAFE_FREE(subaction_paths[i]); + } + MEM_freeN(subaction_paths); + } + + MEM_SAFE_FREE(action->states); + MEM_SAFE_FREE(action->states_prev); + + MEM_freeN(action); +} + +static wmXrAction *action_find(wmXrData *xr, const char *action_set_name, const char *action_name) +{ + return GHOST_XrGetActionCustomdata(xr->runtime->context, action_set_name, action_name); +} + +bool WM_xr_action_set_create(wmXrData *xr, const char *action_set_name) +{ + if (action_set_find(xr, action_set_name)) { + return false; + } + + wmXrActionSet *action_set = action_set_create(action_set_name); + + GHOST_XrActionSetInfo info = { + .name = action_set_name, + .customdata_free_fn = action_set_destroy, + .customdata = action_set, + }; + + if (!GHOST_XrCreateActionSet(xr->runtime->context, &info)) { + return false; + } + + return true; +} + +void WM_xr_action_set_destroy(wmXrData *xr, const char *action_set_name) +{ + wmXrActionSet *action_set = action_set_find(xr, action_set_name); + if (!action_set) { + return; + } + + wmXrSessionState *session_state = &xr->runtime->session_state; + + if (action_set == session_state->active_action_set) { + if (action_set->controller_pose_action) { + wm_xr_session_controller_data_clear(session_state); + action_set->controller_pose_action = NULL; + } + if (action_set->active_modal_action) { + action_set->active_modal_action = NULL; + } + session_state->active_action_set = NULL; + } + + GHOST_XrDestroyActionSet(xr->runtime->context, action_set_name); +} + +bool WM_xr_action_create(wmXrData *xr, + const char *action_set_name, + const char *action_name, + eXrActionType type, + unsigned int count_subaction_paths, + const char **subaction_paths, + const float *float_threshold, + wmOperatorType *ot, + IDProperty *op_properties, + eXrOpFlag op_flag) +{ + if (action_find(xr, action_set_name, action_name)) { + return false; + } + + wmXrAction *action = action_create(action_name, + type, + count_subaction_paths, + subaction_paths, + float_threshold, + ot, + op_properties, + op_flag); + + GHOST_XrActionInfo info = { + .name = action_name, + .count_subaction_paths = count_subaction_paths, + .subaction_paths = subaction_paths, + .states = action->states, + .customdata_free_fn = action_destroy, + .customdata = action, + }; + + switch (type) { + case XR_BOOLEAN_INPUT: + info.type = GHOST_kXrActionTypeBooleanInput; + break; + case XR_FLOAT_INPUT: + info.type = GHOST_kXrActionTypeFloatInput; + break; + case XR_VECTOR2F_INPUT: + info.type = GHOST_kXrActionTypeVector2fInput; + break; + case XR_POSE_INPUT: + info.type = GHOST_kXrActionTypePoseInput; + break; + case XR_VIBRATION_OUTPUT: + info.type = GHOST_kXrActionTypeVibrationOutput; + break; + } + + if (!GHOST_XrCreateActions(xr->runtime->context, action_set_name, 1, &info)) { + return false; + } + + return true; +} + +void WM_xr_action_destroy(wmXrData *xr, const char *action_set_name, const char *action_name) +{ + wmXrActionSet *action_set = action_set_find(xr, action_set_name); + if (!action_set) { + return; + } + + if (action_set->controller_pose_action && + STREQ(action_set->controller_pose_action->name, action_name)) { + if (action_set == xr->runtime->session_state.active_action_set) { + wm_xr_session_controller_data_clear(&xr->runtime->session_state); + } + action_set->controller_pose_action = NULL; + } + if (action_set->active_modal_action && + STREQ(action_set->active_modal_action->name, action_name)) { + action_set->active_modal_action = NULL; + } + + wmXrAction *action = action_find(xr, action_set_name, action_name); + if (!action) { + return; + } +} + +bool WM_xr_action_space_create(wmXrData *xr, + const char *action_set_name, + const char *action_name, + unsigned int count_subaction_paths, + const char **subaction_paths, + const wmXrPose *poses) +{ + GHOST_XrActionSpaceInfo info = { + .action_name = action_name, + .count_subaction_paths = count_subaction_paths, + .subaction_paths = subaction_paths, + }; + + GHOST_XrPose *ghost_poses = MEM_malloc_arrayN( + count_subaction_paths, sizeof(*ghost_poses), __func__); + for (unsigned int i = 0; i < count_subaction_paths; ++i) { + const wmXrPose *pose = &poses[i]; + GHOST_XrPose *ghost_pose = &ghost_poses[i]; + copy_v3_v3(ghost_pose->position, pose->position); + copy_qt_qt(ghost_pose->orientation_quat, pose->orientation_quat); + } + info.poses = ghost_poses; + + bool ret = GHOST_XrCreateActionSpaces(xr->runtime->context, action_set_name, 1, &info) ? true : + false; + MEM_freeN(ghost_poses); + return ret; +} + +void WM_xr_action_space_destroy(wmXrData *xr, + const char *action_set_name, + const char *action_name, + unsigned int count_subaction_paths, + const char **subaction_paths) +{ + GHOST_XrActionSpaceInfo info = { + .action_name = action_name, + .count_subaction_paths = count_subaction_paths, + .subaction_paths = subaction_paths, + }; + + GHOST_XrDestroyActionSpaces(xr->runtime->context, action_set_name, 1, &info); +} + +bool WM_xr_action_binding_create(wmXrData *xr, + const char *action_set_name, + const char *profile_path, + const char *action_name, + unsigned int count_interaction_paths, + const char **interaction_paths) +{ + GHOST_XrActionBindingInfo binding_info = { + .action_name = action_name, + .count_interaction_paths = count_interaction_paths, + .interaction_paths = interaction_paths, + }; + + GHOST_XrActionProfileInfo profile_info = { + .profile_path = profile_path, + .count_bindings = 1, + .bindings = &binding_info, + }; + + return GHOST_XrCreateActionBindings(xr->runtime->context, action_set_name, 1, &profile_info); +} + +void WM_xr_action_binding_destroy(wmXrData *xr, + const char *action_set_name, + const char *profile_path, + const char *action_name, + unsigned int count_interaction_paths, + const char **interaction_paths) +{ + GHOST_XrActionBindingInfo binding_info = { + .action_name = action_name, + .count_interaction_paths = count_interaction_paths, + .interaction_paths = interaction_paths, + }; + + GHOST_XrActionProfileInfo profile_info = { + .profile_path = profile_path, + .count_bindings = 1, + .bindings = &binding_info, + }; + + GHOST_XrDestroyActionBindings(xr->runtime->context, action_set_name, 1, &profile_info); +} + +bool WM_xr_active_action_set_set(wmXrData *xr, const char *action_set_name) +{ + wmXrActionSet *action_set = action_set_find(xr, action_set_name); + if (!action_set) { + return false; + } + + { + /* Unset active modal action (if any). */ + wmXrActionSet *active_action_set = xr->runtime->session_state.active_action_set; + if (active_action_set) { + wmXrAction *active_modal_action = active_action_set->active_modal_action; + if (active_modal_action) { + if (active_modal_action->active_modal_path) { + active_modal_action->active_modal_path = NULL; + } + active_action_set->active_modal_action = NULL; + } + } + } + + xr->runtime->session_state.active_action_set = action_set; + + if (action_set->controller_pose_action) { + wm_xr_session_controller_data_populate(action_set->controller_pose_action, xr); + } + + return true; +} + +bool WM_xr_controller_pose_action_set(wmXrData *xr, + const char *action_set_name, + const char *action_name) +{ + wmXrActionSet *action_set = action_set_find(xr, action_set_name); + if (!action_set) { + return false; + } + + wmXrAction *action = action_find(xr, action_set_name, action_name); + if (!action) { + return false; + } + + action_set->controller_pose_action = action; + + if (action_set == xr->runtime->session_state.active_action_set) { + wm_xr_session_controller_data_populate(action, xr); + } + + return true; +} + +bool WM_xr_action_state_get(const wmXrData *xr, + const char *action_set_name, + const char *action_name, + const char *subaction_path, + wmXrActionState *r_state) +{ + const wmXrAction *action = action_find((wmXrData *)xr, action_set_name, action_name); + if (!action) { + return false; + } + + BLI_assert(action->type == (eXrActionType)r_state->type); + + /* Find the action state corresponding to the subaction path. */ + for (unsigned int i = 0; i < action->count_subaction_paths; ++i) { + if (STREQ(subaction_path, action->subaction_paths[i])) { + switch ((eXrActionType)r_state->type) { + case XR_BOOLEAN_INPUT: + r_state->state_boolean = ((bool *)action->states)[i]; + break; + case XR_FLOAT_INPUT: + r_state->state_float = ((float *)action->states)[i]; + break; + case XR_VECTOR2F_INPUT: + copy_v2_v2(r_state->state_vector2f, ((float(*)[2])action->states)[i]); + break; + case XR_POSE_INPUT: { + const GHOST_XrPose *pose = &((GHOST_XrPose *)action->states)[i]; + copy_v3_v3(r_state->state_pose.position, pose->position); + copy_qt_qt(r_state->state_pose.orientation_quat, pose->orientation_quat); + break; + } + case XR_VIBRATION_OUTPUT: + BLI_assert_unreachable(); + break; + } + return true; + } + } + + return false; +} + +bool WM_xr_haptic_action_apply(wmXrData *xr, + const char *action_set_name, + const char *action_name, + const long long *duration, + const float *frequency, + const float *amplitude) +{ + return GHOST_XrApplyHapticAction( + xr->runtime->context, action_set_name, action_name, duration, frequency, amplitude) ? + true : + false; +} + +void WM_xr_haptic_action_stop(wmXrData *xr, const char *action_set_name, const char *action_name) +{ + GHOST_XrStopHapticAction(xr->runtime->context, action_set_name, action_name); +} + +/** \} */ /* XR-Action API */ diff --git a/source/blender/windowmanager/xr/intern/wm_xr_draw.c b/source/blender/windowmanager/xr/intern/wm_xr_draw.c index cc4a7e41e82..1f722855696 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_draw.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_draw.c @@ -45,6 +45,12 @@ void wm_xr_pose_to_viewmat(const GHOST_XrPose *pose, float r_viewmat[4][4]) translate_m4(r_viewmat, -pose->position[0], -pose->position[1], -pose->position[2]); } +void wm_xr_controller_pose_to_mat(const GHOST_XrPose *pose, float r_mat[4][4]) +{ + quat_to_mat4(r_mat, pose->orientation_quat); + copy_v3_v3(r_mat[3], pose->position); +} + static void wm_xr_draw_matrices_create(const wmXrDrawData *draw_data, const GHOST_XrDrawViewInfo *draw_view, const XrSessionSettings *session_settings, diff --git a/source/blender/windowmanager/xr/intern/wm_xr_intern.h b/source/blender/windowmanager/xr/intern/wm_xr_intern.h index 25e3da3ffb4..9bf63be61dd 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_intern.h +++ b/source/blender/windowmanager/xr/intern/wm_xr_intern.h @@ -24,6 +24,21 @@ #include "wm_xr.h" +struct wmXrActionSet; + +typedef struct wmXrControllerData { + /** OpenXR path identifier. Length is dependent on OpenXR's XR_MAX_PATH_LENGTH (256). + This subaction path will later be combined with a component path, and that combined path should + also have a max of XR_MAX_PATH_LENGTH (e.g. subaction_path = /user/hand/left, component_path = + /input/trigger/value, interaction_path = /user/hand/left/input/trigger/value). + */ + char subaction_path[64]; + /** Last known controller pose (in world space) stored for queries. */ + GHOST_XrPose pose; + /** The last known controller matrix, calculated from above's controller pose. */ + float mat[4][4]; +} wmXrControllerData; + typedef struct wmXrSessionState { bool is_started; @@ -39,11 +54,23 @@ typedef struct wmXrSessionState { Object *prev_base_pose_object; /** Copy of XrSessionSettings.flag created on the last draw call, stored to detect changes. */ int prev_settings_flag; + /** Copy of wmXrDrawData.base_pose. */ + GHOST_XrPose prev_base_pose; + /** Copy of GHOST_XrDrawViewInfo.local_pose. */ + GHOST_XrPose prev_local_pose; /** Copy of wmXrDrawData.eye_position_ofs. */ float prev_eye_position_ofs[3]; bool force_reset_to_base_pose; bool is_view_data_set; + + /** Last known controller data. */ + wmXrControllerData controllers[2]; + + /** The currently active action set that will be updated on calls to + * wm_xr_session_actions_update(). If NULL, all action sets will be treated as active and + * updated. */ + struct wmXrActionSet *active_action_set; } wmXrSessionState; typedef struct wmXrRuntimeData { @@ -79,6 +106,40 @@ typedef struct wmXrDrawData { float eye_position_ofs[3]; /* Local/view space. */ } wmXrDrawData; +typedef struct wmXrAction { + char *name; + eXrActionType type; + unsigned int count_subaction_paths; + char **subaction_paths; + /** States for each subaction path. */ + void *states; + /** Previous states, stored to determine XR events. */ + void *states_prev; + + /** Input threshold for float/vector2f actions. */ + float float_threshold; + + /** The currently active subaction path (if any) for modal actions. */ + char **active_modal_path; + + /** Operator to be called on XR events. */ + struct wmOperatorType *ot; + IDProperty *op_properties; + eXrOpFlag op_flag; +} wmXrAction; + +typedef struct wmXrActionSet { + char *name; + + /** The XR pose action that determines the controller + * transforms. This is usually identified by the OpenXR path "/grip/pose" or "/aim/pose", + * although it could differ depending on the specification and hardware. */ + wmXrAction *controller_pose_action; + + /** The currently active modal action (if any). */ + wmXrAction *active_modal_action; +} wmXrActionSet; + wmXrRuntimeData *wm_xr_runtime_data_create(void); void wm_xr_runtime_data_free(wmXrRuntimeData **runtime); @@ -95,5 +156,12 @@ bool wm_xr_session_surface_offscreen_ensure(wmXrSurfaceData *surface_data, void *wm_xr_session_gpu_binding_context_create(void); void wm_xr_session_gpu_binding_context_destroy(GHOST_ContextHandle context); +void wm_xr_session_actions_init(wmXrData *xr); +void wm_xr_session_actions_update(wmXrData *xr); +void wm_xr_session_controller_data_populate(const wmXrAction *controller_pose_action, + wmXrData *xr); +void wm_xr_session_controller_data_clear(wmXrSessionState *state); + void wm_xr_pose_to_viewmat(const GHOST_XrPose *pose, float r_viewmat[4][4]); +void wm_xr_controller_pose_to_mat(const GHOST_XrPose *pose, float r_mat[4][4]); void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata); diff --git a/source/blender/windowmanager/xr/intern/wm_xr_session.c b/source/blender/windowmanager/xr/intern/wm_xr_session.c index b9ef40e3398..1ddbe228e05 100644 --- a/source/blender/windowmanager/xr/intern/wm_xr_session.c +++ b/source/blender/windowmanager/xr/intern/wm_xr_session.c @@ -18,7 +18,9 @@ * \ingroup wm */ +#include "BKE_callbacks.h" #include "BKE_context.h" +#include "BKE_global.h" #include "BKE_main.h" #include "BKE_scene.h" @@ -49,11 +51,24 @@ static CLG_LogRef LOG = {"wm.xr"}; /* -------------------------------------------------------------------- */ +static void wm_xr_session_create_cb(void) +{ + Main *bmain = G_MAIN; + wmWindowManager *wm = bmain->wm.first; + wmXrData *xr_data = &wm->xr; + + /* Get action set data from Python. */ + BKE_callback_exec_null(bmain, BKE_CB_EVT_XR_SESSION_START_PRE); + + wm_xr_session_actions_init(xr_data); +} + static void wm_xr_session_exit_cb(void *customdata) { wmXrData *xr_data = customdata; xr_data->runtime->session_state.is_started = false; + if (xr_data->runtime->exit_fn) { xr_data->runtime->exit_fn(xr_data); } @@ -65,6 +80,10 @@ static void wm_xr_session_exit_cb(void *customdata) static void wm_xr_session_begin_info_create(wmXrData *xr_data, GHOST_XrSessionBeginInfo *r_begin_info) { + /* Callback for when the session is created. This is needed to create and bind OpenXR actions + * after the session is created but before it is started. */ + r_begin_info->create_fn = wm_xr_session_create_cb; + /* WM-XR exit function, does some own stuff and calls callback passed to wm_xr_session_toggle(), * to allow external code to execute its own session-exit logic. */ r_begin_info->exit_fn = wm_xr_session_exit_cb; @@ -289,6 +308,7 @@ void wm_xr_session_draw_data_update(const wmXrSessionState *state, /** * Update information that is only stored for external state queries. E.g. for Python API to * request the current (as in, last known) viewer pose. + * Controller data and action sets will be updated separately via wm_xr_session_actions_update(). */ void wm_xr_session_state_update(const XrSessionSettings *settings, const wmXrDrawData *draw_data, @@ -322,6 +342,8 @@ void wm_xr_session_state_update(const XrSessionSettings *settings, DEFAULT_SENSOR_WIDTH); copy_v3_v3(state->prev_eye_position_ofs, draw_data->eye_position_ofs); + memcpy(&state->prev_base_pose, &draw_data->base_pose, sizeof(state->prev_base_pose)); + memcpy(&state->prev_local_pose, &draw_view->local_pose, sizeof(state->prev_local_pose)); state->prev_settings_flag = settings->flag; state->prev_base_pose_type = settings->base_pose_type; state->prev_base_pose_object = settings->base_pose_object; @@ -373,6 +395,132 @@ bool WM_xr_session_state_viewer_pose_matrix_info_get(const wmXrData *xr, return true; } +bool WM_xr_session_state_controller_pose_location_get(const wmXrData *xr, + unsigned int subaction_idx, + float r_location[3]) +{ + if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set || + subaction_idx >= ARRAY_SIZE(xr->runtime->session_state.controllers)) { + zero_v3(r_location); + return false; + } + + copy_v3_v3(r_location, xr->runtime->session_state.controllers[subaction_idx].pose.position); + return true; +} + +bool WM_xr_session_state_controller_pose_rotation_get(const wmXrData *xr, + unsigned int subaction_idx, + float r_rotation[4]) +{ + if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set || + subaction_idx >= ARRAY_SIZE(xr->runtime->session_state.controllers)) { + unit_qt(r_rotation); + return false; + } + + copy_v4_v4(r_rotation, + xr->runtime->session_state.controllers[subaction_idx].pose.orientation_quat); + return true; +} + +/* -------------------------------------------------------------------- */ +/** \name XR-Session Actions + * + * XR action processing and event dispatching. + * + * \{ */ + +void wm_xr_session_actions_init(wmXrData *xr) +{ + if (!xr->runtime) { + return; + } + + GHOST_XrAttachActionSets(xr->runtime->context); +} + +static void wm_xr_session_controller_mats_update(const XrSessionSettings *settings, + const wmXrAction *controller_pose_action, + wmXrSessionState *state) +{ + const unsigned int count = (unsigned int)min_ii( + (int)controller_pose_action->count_subaction_paths, (int)ARRAY_SIZE(state->controllers)); + + float view_ofs[3]; + float base_inv[4][4]; + float tmp[4][4]; + + zero_v3(view_ofs); + if ((settings->flag & XR_SESSION_USE_POSITION_TRACKING) == 0) { + add_v3_v3(view_ofs, state->prev_local_pose.position); + } + + wm_xr_pose_to_viewmat(&state->prev_base_pose, base_inv); + invert_m4(base_inv); + + for (unsigned int i = 0; i < count; ++i) { + wmXrControllerData *controller = &state->controllers[i]; + + /* Calculate controller matrix in world space. */ + wm_xr_controller_pose_to_mat(&((GHOST_XrPose *)controller_pose_action->states)[i], tmp); + + /* Apply eye position and base pose offsets. */ + sub_v3_v3(tmp[3], view_ofs); + mul_m4_m4m4(controller->mat, base_inv, tmp); + + /* Save final pose. */ + mat4_to_loc_quat( + controller->pose.position, controller->pose.orientation_quat, controller->mat); + } +} + +void wm_xr_session_actions_update(wmXrData *xr) +{ + if (!xr->runtime) { + return; + } + + GHOST_XrContextHandle xr_context = xr->runtime->context; + wmXrSessionState *state = &xr->runtime->session_state; + wmXrActionSet *active_action_set = state->active_action_set; + + int ret = GHOST_XrSyncActions(xr_context, active_action_set ? active_action_set->name : NULL); + if (!ret) { + return; + } + + /* Only update controller mats for active action set. */ + if (active_action_set) { + if (active_action_set->controller_pose_action) { + wm_xr_session_controller_mats_update( + &xr->session_settings, active_action_set->controller_pose_action, state); + } + } +} + +void wm_xr_session_controller_data_populate(const wmXrAction *controller_pose_action, wmXrData *xr) +{ + wmXrSessionState *state = &xr->runtime->session_state; + + const unsigned int count = (unsigned int)min_ii( + (int)ARRAY_SIZE(state->controllers), (int)controller_pose_action->count_subaction_paths); + + for (unsigned int i = 0; i < count; ++i) { + wmXrControllerData *c = &state->controllers[i]; + strcpy(c->subaction_path, controller_pose_action->subaction_paths[i]); + memset(&c->pose, 0, sizeof(c->pose)); + zero_m4(c->mat); + } +} + +void wm_xr_session_controller_data_clear(wmXrSessionState *state) +{ + memset(state->controllers, 0, sizeof(state->controllers)); +} + +/** \} */ /* XR-Session Actions */ + /* -------------------------------------------------------------------- */ /** \name XR-Session Surface * diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt index cb5fc538e69..6a768106d9e 100644 --- a/source/creator/CMakeLists.txt +++ b/source/creator/CMakeLists.txt @@ -130,14 +130,11 @@ set(SRC # MSVC 2010 gives linking errors with the manifest if(WIN32 AND NOT UNIX) - string(SUBSTRING ${BLENDER_VERSION} 0 1 bver1) - string(SUBSTRING ${BLENDER_VERSION} 2 1 bver2) - string(SUBSTRING ${BLENDER_VERSION} 3 1 bver3) add_definitions( -DBLEN_VER_RC_STR="${BLENDER_VERSION}" - -DBLEN_VER_RC_1=${bver1} - -DBLEN_VER_RC_2=${bver2} - -DBLEN_VER_RC_3=${bver3} + -DBLEN_VER_RC_1=${BLENDER_VERSION_MAJOR} + -DBLEN_VER_RC_2=${BLENDER_VERSION_MINOR} + -DBLEN_VER_RC_3=${BLENDER_VERSION_PATCH} -DBLEN_VER_RC_4=0 ) @@ -188,7 +185,7 @@ if(WITH_BUILDINFO) # -------------------------------------------------------------------------- # write header for values that change each build - # note, generaed file is in build dir's source/creator + # note, generated file is in build dir's source/creator # except when used as an include path. add_definitions(-DWITH_BUILDINFO_HEADER) @@ -288,6 +285,15 @@ if(WITH_PYTHON_MODULE) else() add_executable(blender ${EXETYPE} ${SRC}) + if(WIN32) + add_executable(blender-launcher WIN32 + blender_launcher_win32.c + ${CMAKE_SOURCE_DIR}/release/windows/icons/winblender.rc + ${CMAKE_BINARY_DIR}/blender.exe.manifest + ) + target_compile_definitions (blender-launcher PRIVATE -D_UNICODE -DUNICODE) + target_link_libraries(blender-launcher Pathcch.lib) + endif() endif() if(WITH_BUILDINFO) @@ -1215,7 +1221,7 @@ endif() if(WIN32 AND NOT WITH_PYTHON_MODULE) install( - TARGETS blender + TARGETS blender blender-launcher COMPONENT Blender DESTINATION "." ) diff --git a/source/creator/blender_launcher_win32.c b/source/creator/blender_launcher_win32.c new file mode 100644 index 00000000000..86b0f4f3b97 --- /dev/null +++ b/source/creator/blender_launcher_win32.c @@ -0,0 +1,92 @@ +/* + * 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 <Windows.h> +#include <strsafe.h> + +#include <PathCch.h> + +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) +{ + STARTUPINFO siStartInfo = {0}; + PROCESS_INFORMATION procInfo; + wchar_t path[MAX_PATH]; + + siStartInfo.wShowWindow = SW_HIDE; + siStartInfo.dwFlags = STARTF_USESHOWWINDOW; + + /* Get the path to the currently running executable (blender-launcher.exe) */ + + DWORD nSize = GetModuleFileName(NULL, path, MAX_PATH); + if (!nSize) { + return -1; + } + + /* GetModuleFileName returns the number of characters written, but GetLastError needs to be + * called to see if it ran out of space or not. However where would we be without exceptions + * to the rule: "If the buffer is too small to hold the module name, the function returns nSize. + * The last error code remains ERROR_SUCCESS." - source: MSDN. */ + + if (GetLastError() == ERROR_SUCCESS && nSize == MAX_PATH) { + return -1; + } + + /* Remove the filename (blender-launcher.exe) from path. */ + if (PathCchRemoveFileSpec(path, MAX_PATH) != S_OK) { + return -1; + } + + /* Add blender.exe to path, resulting in the full path to the blender executable. */ + if (PathCchCombine(path, MAX_PATH, path, L"blender.exe") != S_OK) { + return -1; + } + + int required_size_chars = lstrlenW(path) + /* Module name */ + 3 + /* 2 quotes + Space */ + lstrlenW(pCmdLine) + /* Original command line */ + 1; /* Zero terminator */ + size_t required_size_bytes = required_size_chars * sizeof(wchar_t); + wchar_t *buffer = (wchar_t *)malloc(required_size_bytes); + if (!buffer) { + return -1; + } + + if (StringCbPrintfEx(buffer, + required_size_bytes, + NULL, + NULL, + STRSAFE_NULL_ON_FAILURE, + L"\"%s\" %s", + path, + pCmdLine) != S_OK) { + free(buffer); + return -1; + } + + BOOL success = CreateProcess( + path, buffer, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &siStartInfo, &procInfo); + + if (success) { + /* Handles in PROCESS_INFORMATION must be closed with CloseHandle when they are no longer + * needed - MSDN. Closing the handles will NOT terminate the thread/process that we just + * started. */ + CloseHandle(procInfo.hThread); + CloseHandle(procInfo.hProcess); + } + + free(buffer); + return success ? 0 : -1; +} diff --git a/source/creator/creator.c b/source/creator/creator.c index b40718d1f7c..51efadf5e56 100644 --- a/source/creator/creator.c +++ b/source/creator/creator.c @@ -132,7 +132,6 @@ struct ApplicationState app_state = { /** \name Application Level Callbacks * * Initialize callbacks for the modules that need them. - * * \{ */ static void callback_mem_error(const char *errorStr) diff --git a/source/creator/creator_args.c b/source/creator/creator_args.c index 9c7b7dc3f34..36fdaef507b 100644 --- a/source/creator/creator_args.c +++ b/source/creator/creator_args.c @@ -1195,15 +1195,17 @@ static const char arg_handle_playback_mode_doc[] = "\t\tZero disables (clamping to a fixed number of frames instead)."; static int arg_handle_playback_mode(int argc, const char **argv, void *UNUSED(data)) { - /* not if -b was given first */ + /* Ignore the animation player if `-b` was given first. */ if (G.background == 0) { # ifdef WITH_FFMPEG /* Setup FFmpeg with current debug flags. */ IMB_ffmpeg_init(); # endif - WM_main_playanim(argc, argv); /* not the same argc and argv as before */ - exit(0); /* 2.4x didn't do this */ + /* This function knows to skip this argument ('-a'). */ + WM_main_playanim(argc, argv); + + exit(0); } return -2; diff --git a/source/creator/creator_intern.h b/source/creator/creator_intern.h index bcc8a15355a..2260da8db11 100644 --- a/source/creator/creator_intern.h +++ b/source/creator/creator_intern.h @@ -72,7 +72,7 @@ enum { /* for the callbacks: */ #ifndef WITH_PYTHON_MODULE -# define BLEND_VERSION_FMT "Blender %d.%02d.%d" +# define BLEND_VERSION_FMT "Blender %d.%d.%d" # define BLEND_VERSION_ARG (BLENDER_VERSION / 100), (BLENDER_VERSION % 100), BLENDER_VERSION_PATCH #endif |