Files
test/source/blender/animrig/ANIM_action.hh
Campbell Barton 7f7648c6ed Cleanup: spelling in code comments & minor edits
- Use uppercase NOTE: tags.
- Correct bNote -> bNode.
- Use colon after parameters.
- Use doxy-style doc-strings.
2024-06-06 09:55:13 +10:00

660 lines
21 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup animrig
*
* \brief Functions and classes to work with Actions.
*/
#pragma once
#include "ANIM_fcurve.hh"
#include "ANIM_keyframing.hh"
#include "DNA_action_types.h"
#include "DNA_anim_types.h"
#include "BLI_math_vector.hh"
#include "BLI_set.hh"
#include "BLI_string_ref.hh"
#include "RNA_types.hh"
struct AnimationEvalContext;
struct FCurve;
struct FCurve;
struct ID;
struct Main;
struct PointerRNA;
namespace blender::animrig {
/* Forward declarations for the types defined later in this file. */
class Layer;
class Strip;
class Binding;
/* Use an alias for the Binding handle type to help disambiguate function parameters. */
using binding_handle_t = decltype(::ActionBinding::handle);
/**
* Container of animation data for one or more animated IDs.
*
* Broadly an Action consists of Layers, each Layer has Strips, and it's the
* Strips that eventually contain the animation data.
*
* Temporary limitation: each Action can only contain one Layer.
*
* Which sub-set of that data drives the animation of which ID is determined by
* which Binding is associated with that ID.
*
* \note This wrapper class for the `bAction` DNA struct only has functionality
* for the layered animation data. The legacy F-Curves (in `bAction::curves`)
* and their groups (in `bAction::groups`) are not managed here. To see whether
* an Action uses this legacy data, or has been converted to the current layered
* structure, use `Action::is_action_legacy()` and
* `Action::is_action_layered()`. Note that an empty Action is considered valid
* for both.
*
* \see AnimData::action
* \see AnimData::binding_handle
*/
class Action : public ::bAction {
public:
Action() = default;
/**
* Copy constructor is deleted, as code should use regular ID library
* management functions to duplicate this data-block.
*/
Action(const Action &other) = delete;
/* Discriminators for 'legacy' and 'layered' Actions. */
/**
* Return whether this Action has any data at all.
*
* \return true when `bAction::layer_array` and `bAction::binding_array`, as well as
* the legacy `curves` list, are empty.
*/
bool is_empty() const;
/**
* Return whether this is a legacy Action.
*
* - Animation data is stored in `bAction::curves`.
* - Evaluated equally for all data-blocks that reference this Action.
* - Binding handle is ignored.
*
* \note An empty Action is valid as both a legacy and layered Action. Code that only supports
* layered Actions should assert on `is_action_layered()`.
*/
bool is_action_legacy() const;
/**
* Return whether this is a layered Action.
*
* - Animation data is stored in `bAction::layer_array`.
* - Evaluated for data-blocks based on their binding handle.
*
* \note An empty Action is valid as both a legacy and layered Action.
*/
bool is_action_layered() const;
/* Animation Layers access. */
blender::Span<const Layer *> layers() const;
blender::MutableSpan<Layer *> layers();
const Layer *layer(int64_t index) const;
Layer *layer(int64_t index);
Layer &layer_add(StringRefNull name);
/**
* Remove the layer from this animation.
*
* After this call, the passed reference is no longer valid, as the memory
* will have been freed. Any strips on the layer will be freed too.
*
* \return true when the layer was found & removed, false if it wasn't found.
*/
bool layer_remove(Layer &layer_to_remove);
/* Animation Binding access. */
blender::Span<const Binding *> bindings() const;
blender::MutableSpan<Binding *> bindings();
const Binding *binding(int64_t index) const;
Binding *binding(int64_t index);
Binding *binding_for_handle(binding_handle_t handle);
const Binding *binding_for_handle(binding_handle_t handle) const;
/**
* Set the binding name, ensure it is unique, and propagate the new name to
* all data-blocks that use it.
*
* This has to be done on the Animation level to ensure each binding has a
* unique name within the Animation.
*
* \note This does NOT ensure the first two characters match the ID type of
* this binding. This is the caller's responsibility.
*
* \see Action::binding_name_define
* \see Action::binding_name_propagate
*/
void binding_name_set(Main &bmain, Binding &binding, StringRefNull new_name);
/**
* Set the binding name, and ensure it is unique.
*
* \note This does NOT ensure the first two characters match the ID type of
* this binding. This is the caller's responsibility.
*
* \see Action::binding_name_set
* \see Action::binding_name_propagate
*/
void binding_name_define(Binding &binding, StringRefNull new_name);
/**
* Update the `AnimData::action_binding_name` field of any ID that is animated by
* this Binding.
*
* Should be called after `binding_name_define(binding)`. This is implemented as a separate
* function due to the need to access `bmain`, which is available in the RNA on-property-update
* handler, but not in the RNA property setter.
*/
void binding_name_propagate(Main &bmain, const Binding &binding);
Binding *binding_find_by_name(StringRefNull binding_name);
/**
* Create a new, unused Binding.
*
* The returned binding will be suitable for any ID type. After binding to an
* ID, it be limited to that ID's type.
*/
Binding &binding_add();
/**
* Create a new binding, named after the given ID, and limited to the ID's type.
*
* Note that this assigns neither this Animation nor the new Binding to the ID. This function
* merely initializes the Binding itself to suitable values to start animating this ID.
*/
Binding &binding_add_for_id(const ID &animated_id);
/** Assign this animation to the ID.
*
* \param binding: The binding this ID should be animated by, may be nullptr if it is to be
* assigned later. In that case, the ID will not actually receive any animation.
* \param animated_id: The ID that should be animated by this Animation data-block.
*
* \return whether the assignment was successful.
*/
bool assign_id(Binding *binding, ID &animated_id);
/**
* Unassign this Animation from the animated ID.
*
* \param animated_id: ID that is animated by this Animation. Calling this
* function when this ID is _not_ animated by this Animation is not allowed,
* and considered a bug.
*/
void unassign_id(ID &animated_id);
/**
* Find the binding that best matches the animated ID.
*
* If the ID is already animated by this Animation, by matching this
* Animation's bindings with (in order):
*
* - `animated_id.adt->binding_handle`,
* - `animated_id.adt->binding_name`,
* - `animated_id.name`.
*
* Note that this is different from #binding_for_id, which does not use the
* binding name, and only works when this Animation is already assigned. */
Binding *find_suitable_binding_for(const ID &animated_id);
/**
* Return whether this Animation actually has any animation data for the given binding.
*/
bool is_binding_animated(binding_handle_t binding_handle) const;
protected:
/** Return the layer's index, or -1 if not found in this animation. */
int64_t find_layer_index(const Layer &layer) const;
private:
Binding &binding_allocate();
/**
* Ensure the binding name prefix matches its ID type.
*
* This ensures that the first two characters match the ID type of
* this binding.
*
* \see Action::binding_name_propagate
*/
void binding_name_ensure_prefix(Binding &binding);
/**
* Set the binding's ID type to that of the animated ID, ensure the name
* prefix is set accordingly, and that the name is unique within the
* Animation.
*
* \note This assumes that the binding has no ID type set yet. If it does, it
* is considered a bug to call this function.
*/
void binding_setup_for_id(Binding &binding, const ID &animated_id);
};
static_assert(sizeof(Action) == sizeof(::bAction),
"DNA struct and its C++ wrapper must have the same size");
/**
* Strips contain the actual animation data.
*
* Although the data model allows for different strip types, currently only a
* single type is implemented: keyframe strips.
*/
class Strip : public ::ActionStrip {
public:
/**
* Strip instances should not be created via this constructor. Create a sub-class like
* #KeyframeStrip instead.
*
* The reason is that various functions will assume that the `Strip` is actually a down-cast
* instance of another strip class, and that `Strip::type()` will say which type. To avoid having
* to explicitly deal with an 'invalid' type everywhere, creating a `Strip` directly is simply
* not allowed.
*/
Strip() = delete;
/**
* Strip cannot be duplicated via the copy constructor. Either use a concrete
* strip type's copy constructor, or use Strip::duplicate().
*
* The reason why the copy constructor won't work is due to the double nature
* of the inheritance at play here:
*
* C-style inheritance: `KeyframeActionStrip` "inherits" `ActionStrip"
* by embedding the latter. This means that any `KeyframeActionStrip *`
* can be reinterpreted as `ActionStrip *`.
*
* C++-style inheritance: the C++ wrappers inherit the DNA structs, so
* `animrig::Strip` inherits `::ActionStrip`, and
* `animrig::KeyframeStrip` inherits `::KeyframeActionStrip`.
*/
Strip(const Strip &other) = delete;
~Strip();
Strip *duplicate(StringRefNull allocation_name) const;
enum class Type : int8_t { Keyframe = 0 };
/**
* Strip type, so it's known which subclass this can be wrapped in without
* having to rely on C++ RTTI.
*/
Type type() const
{
return Type(this->strip_type);
}
template<typename T> bool is() const;
template<typename T> T &as();
template<typename T> const T &as() const;
bool contains_frame(float frame_time) const;
bool is_last_frame(float frame_time) const;
/**
* Set the start and end frame.
*
* Note that this does not do anything else. There is no check whether the
* frame numbers are valid (i.e. frame_start <= frame_end). Infinite values
* (negative for frame_start, positive for frame_end) are supported.
*/
void resize(float frame_start, float frame_end);
};
static_assert(sizeof(Strip) == sizeof(::ActionStrip),
"DNA struct and its C++ wrapper must have the same size");
/**
* Layers can be stacked on top of each other to define the animation. Each
* layer has a mix mode and an influence (0-1), which define how it is mixed
* with the layers below it.
*
* Layers contain one or more Strips, which in turn contain the animation data
* itself.
*
* Temporary limitation: at most one strip may exist on a layer, and it extends
* from negative to positive infinity.
*/
class Layer : public ::ActionLayer {
public:
Layer() = default;
Layer(const Layer &other);
~Layer();
enum class Flags : uint8_t {
/* Set by default, cleared to mute. */
Enabled = (1 << 0),
/* When adding/removing a flag, also update the ENUM_OPERATORS() invocation below. */
};
Flags flags() const
{
return static_cast<Flags>(this->layer_flags);
}
enum class MixMode : int8_t {
/** Channels in this layer override the same channels from underlying layers. */
Replace = 0,
/** Channels in this layer are added to underlying layers as sequential operations. */
Offset = 1,
/** Channels in this layer are added to underlying layers on a per-channel basis. */
Add = 2,
/** Channels in this layer are subtracted to underlying layers on a per-channel basis. */
Subtract = 3,
/** Channels in this layer are multiplied with underlying layers on a per-channel basis. */
Multiply = 4,
};
MixMode mix_mode() const
{
return static_cast<MixMode>(this->layer_mix_mode);
}
/* Strip access. */
blender::Span<const Strip *> strips() const;
blender::MutableSpan<Strip *> strips();
const Strip *strip(int64_t index) const;
Strip *strip(int64_t index);
Strip &strip_add(Strip::Type strip_type);
/**
* Remove the strip from this layer.
*
* After this call, the passed reference is no longer valid, as the memory
* will have been freed.
*
* \return true when the strip was found & removed, false if it wasn't found.
*/
bool strip_remove(Strip &strip);
protected:
/** Return the strip's index, or -1 if not found in this layer. */
int64_t find_strip_index(const Strip &strip) const;
};
static_assert(sizeof(Layer) == sizeof(::ActionLayer),
"DNA struct and its C++ wrapper must have the same size");
ENUM_OPERATORS(Layer::Flags, Layer::Flags::Enabled);
/**
* Identifier for a sub-set of the animation data inside an Animation data-block.
*
* An animatable ID specifies both an `Animation*` and an `ActionBinding::handle`
* to identify which F-Curves (and in the future other animation data) it will
* be animated by.
*
* This is called a 'binding' because it binds the animatable ID to the sub-set
* of animation data that should animate it.
*
* \see AnimData::binding_handle
*/
class Binding : public ::ActionBinding {
public:
Binding() = default;
Binding(const Binding &other) = default;
~Binding() = default;
/**
* Binding handle value indicating that there is no binding assigned.
*/
constexpr static binding_handle_t unassigned = 0;
/**
* Binding names consist of a two-character ID code, then the display name.
* This means that the minimum length of a valid name is 3 characters.
*/
constexpr static int name_length_min = 3;
/**
* Return the name prefix for the Binding's type.
*
* This is the ID name prefix, so "OB" for objects, "CA" for cameras, etc.
*/
std::string name_prefix_for_idtype() const;
/**
* Return the name without the prefix.
*
* \see name_prefix_for_idtype
*/
StringRefNull name_without_prefix() const;
/** Return whether this Binding is usable by this ID type. */
bool is_suitable_for(const ID &animated_id) const;
/** Return whether this Binding has an `idtype` set. */
bool has_idtype() const;
protected:
friend Action;
/**
* Ensure the first two characters of the name match the ID type.
*
* \note This does NOT ensure name uniqueness within the Animation. That is
* the responsibility of the caller.
*/
void name_ensure_prefix();
};
static_assert(sizeof(Binding) == sizeof(::ActionBinding),
"DNA struct and its C++ wrapper must have the same size");
/**
* KeyframeStrips effectively contain a bag of F-Curves for each Binding.
*/
class KeyframeStrip : public ::KeyframeActionStrip {
public:
KeyframeStrip() = default;
KeyframeStrip(const KeyframeStrip &other);
~KeyframeStrip();
/* ChannelBag array access. */
blender::Span<const ChannelBag *> channelbags() const;
blender::MutableSpan<ChannelBag *> channelbags();
const ChannelBag *channelbag(int64_t index) const;
ChannelBag *channelbag(int64_t index);
/**
* Find the animation channels for this binding.
*
* \return nullptr if there is none yet for this binding.
*/
const ChannelBag *channelbag_for_binding(const Binding &binding) const;
ChannelBag *channelbag_for_binding(const Binding &binding);
const ChannelBag *channelbag_for_binding(binding_handle_t binding_handle) const;
ChannelBag *channelbag_for_binding(binding_handle_t binding_handle);
/**
* Add the animation channels for this binding.
*
* Should only be called when there is no `ChannelBag` for this binding yet.
*/
ChannelBag &channelbag_for_binding_add(const Binding &binding);
/**
* Find an FCurve for this binding + RNA path + array index combination.
*
* If it cannot be found, `nullptr` is returned.
*/
FCurve *fcurve_find(const Binding &binding, StringRefNull rna_path, int array_index);
/**
* Find an FCurve for this binding + RNA path + array index combination.
*
* If it cannot be found, a new one is created.
*/
FCurve &fcurve_find_or_create(const Binding &binding, StringRefNull rna_path, int array_index);
SingleKeyingResult keyframe_insert(const Binding &binding,
StringRefNull rna_path,
int array_index,
float2 time_value,
const KeyframeSettings &settings);
};
static_assert(sizeof(KeyframeStrip) == sizeof(::KeyframeActionStrip),
"DNA struct and its C++ wrapper must have the same size");
template<> KeyframeStrip &Strip::as<KeyframeStrip>();
template<> const KeyframeStrip &Strip::as<KeyframeStrip>() const;
/**
* Collection of F-Curves, intended for a specific Binding handle.
*/
class ChannelBag : public ::ActionChannelBag {
public:
ChannelBag() = default;
ChannelBag(const ChannelBag &other);
~ChannelBag();
/* FCurves access. */
blender::Span<const FCurve *> fcurves() const;
blender::MutableSpan<FCurve *> fcurves();
const FCurve *fcurve(int64_t index) const;
FCurve *fcurve(int64_t index);
const FCurve *fcurve_find(StringRefNull rna_path, int array_index) const;
};
static_assert(sizeof(ChannelBag) == sizeof(::ActionChannelBag),
"DNA struct and its C++ wrapper must have the same size");
/**
* Assign the animation to the ID.
*
* This will will make a best-effort guess as to which binding to use, in this
* order;
*
* - By binding handle.
* - By fallback string.
* - By the ID's name (matching against the binding name).
* - If the above do not find a suitable binding, the animated ID will not
* receive any animation and the caller is responsible for creating a binding
* and assigning it.
*
* \return `false` if the assignment was not possible (for example the ID is of a type that cannot
* be animated). If the above fall-through case of "no binding found" is reached, this function
* will still return `true` as the Animation was successfully assigned.
*/
bool assign_animation(Action &anim, ID &animated_id);
/**
* Ensure that this ID is no longer animated.
*/
void unassign_animation(ID &animated_id);
/**
* Clear the animation binding of this ID.
*
* `adt.binding_handle_name` is updated to reflect the current name of the
* binding, before un-assigning. This is to ensure that the stored name reflects
* the actual binding that was used, making re-binding trivial.
*
* \param adt: the AnimData of the animated ID.
*
* \note this does not clear the Animation pointer, just the binding handle.
*/
void unassign_binding(AnimData &adt);
/**
* Return the Animation of this ID, or nullptr if it has none.
*/
Action *get_animation(ID &animated_id);
/**
* Return the F-Curves for this specific binding handle.
*
* This is just a utility function, that's intended to become obsolete when multi-layer animation
* is introduced. However, since Blender currently only supports a single layer with a single
* strip, of a single type, this function can be used.
*
* The use of this function is also an indicator for code that will have to be altered when
* multi-layered animation is getting implemented.
*/
Span<FCurve *> fcurves_for_animation(Action &anim, binding_handle_t binding_handle);
Span<const FCurve *> fcurves_for_animation(const Action &anim, binding_handle_t binding_handle);
/**
* Get (or add relevant data to be able to do so) F-Curve from the given Action,
* for the given Animation Data block. This assumes that all the destinations are valid.
* \param ptr: can be a null pointer.
*/
FCurve *action_fcurve_ensure(Main *bmain,
bAction *act,
const char group[],
PointerRNA *ptr,
const char rna_path[],
int array_index);
/**
* Find the F-Curve from the given Action. This assumes that all the destinations are valid.
*/
FCurve *action_fcurve_find(bAction *act, const char rna_path[], int array_index);
} // namespace blender::animrig
/* Wrap functions for the DNA structs. */
inline blender::animrig::Action &bAction::wrap()
{
return *reinterpret_cast<blender::animrig::Action *>(this);
}
inline const blender::animrig::Action &bAction::wrap() const
{
return *reinterpret_cast<const blender::animrig::Action *>(this);
}
inline blender::animrig::Layer &ActionLayer::wrap()
{
return *reinterpret_cast<blender::animrig::Layer *>(this);
}
inline const blender::animrig::Layer &ActionLayer::wrap() const
{
return *reinterpret_cast<const blender::animrig::Layer *>(this);
}
inline blender::animrig::Binding &ActionBinding::wrap()
{
return *reinterpret_cast<blender::animrig::Binding *>(this);
}
inline const blender::animrig::Binding &ActionBinding::wrap() const
{
return *reinterpret_cast<const blender::animrig::Binding *>(this);
}
inline blender::animrig::Strip &ActionStrip::wrap()
{
return *reinterpret_cast<blender::animrig::Strip *>(this);
}
inline const blender::animrig::Strip &ActionStrip::wrap() const
{
return *reinterpret_cast<const blender::animrig::Strip *>(this);
}
inline blender::animrig::KeyframeStrip &KeyframeActionStrip::wrap()
{
return *reinterpret_cast<blender::animrig::KeyframeStrip *>(this);
}
inline const blender::animrig::KeyframeStrip &KeyframeActionStrip::wrap() const
{
return *reinterpret_cast<const blender::animrig::KeyframeStrip *>(this);
}
inline blender::animrig::ChannelBag &ActionChannelBag::wrap()
{
return *reinterpret_cast<blender::animrig::ChannelBag *>(this);
}
inline const blender::animrig::ChannelBag &ActionChannelBag::wrap() const
{
return *reinterpret_cast<const blender::animrig::ChannelBag *>(this);
}