GPv3: Add function to insert frames with duration

Adds a new function to insert a frame with a duration on a layer.
Also adds tests for the edge cases.

This introduces the concept of a `null-frame`.
It can be created with `GreasePencilFrame::null()` and checked for with
`is_null()`.
The purpose of a `null` frame is to indicate the end of whatever frame
comes before it. This way, the frames map does not need to store
the duration of frames. The duration is always implied by the distance
to the next frame.
This commit is contained in:
Falk David
2023-07-04 16:36:53 +02:00
parent 715bc6b200
commit 61d5ff1409
4 changed files with 168 additions and 4 deletions

View File

@@ -223,11 +223,21 @@ class Layer : public ::GreasePencilLayer {
bool is_locked() const;
/**
* Inserts the frame into the layer. Fails if there exists a frame at \a frame_number already.
* Inserts the frame into the layer frames map. Will not overwrite existing frames at \a
* frame_number, except null-frames.
* \returns true on success.
*/
bool insert_frame(int frame_number, const GreasePencilFrame &frame);
/**
* Inserts the frame into the layer frames map. Will not overwrite existing frames at \a
* frame_number, except null-frames.
* Inserts an additional null-frame at \a frame_number + \a duration, if necessary, to indicate
* the end of the inserted frame.
* \returns true on success.
*/
bool insert_frame(int frame_number, int duration, const GreasePencilFrame &frame);
/**
* Inserts the frame into the layer. If there exists a frame at \a frame_number already, it is
* overwritten.
@@ -442,6 +452,16 @@ inline const blender::bke::greasepencil::Drawing &GreasePencilDrawing::wrap() co
return *reinterpret_cast<const blender::bke::greasepencil::Drawing *>(this);
}
inline GreasePencilFrame GreasePencilFrame::null()
{
return GreasePencilFrame{-1, 0, 0};
}
inline bool GreasePencilFrame::is_null() const
{
return this->drawing_index == -1;
}
inline blender::bke::greasepencil::TreeNode &GreasePencilLayerTreeNode::wrap()
{
return *reinterpret_cast<blender::bke::greasepencil::TreeNode *>(this);

View File

@@ -498,10 +498,55 @@ bool Layer::is_locked() const
return this->parent_group().is_locked() || (this->base.flag & GP_LAYER_TREE_NODE_LOCKED) != 0;
}
bool Layer::insert_frame(int frame_number, const GreasePencilFrame &frame)
bool Layer::insert_frame(const int frame_number, const GreasePencilFrame &frame)
{
this->tag_frames_map_changed();
return this->frames_for_write().add(frame_number, frame);
BLI_assert(!frame.is_null());
if (!this->frames().contains(frame_number)) {
this->frames_for_write().add(frame_number, frame);
this->tag_frames_map_keys_changed();
return true;
}
/* Overwrite null-frames. */
if (this->frames().lookup(frame_number).is_null()) {
this->frames_for_write().add_overwrite(frame_number, frame);
this->tag_frames_map_changed();
return true;
}
return false;
}
bool Layer::insert_frame(const int frame_number,
const int duration,
const GreasePencilFrame &frame)
{
BLI_assert(duration > 0);
if (!this->insert_frame(frame_number, frame)) {
return false;
}
Span<int> sorted_keys = this->sorted_keys();
const int end_frame_number = frame_number + duration;
/* Finds the next greater frame_number that is stored in the map. */
auto next_frame_number_it = std::upper_bound(
sorted_keys.begin(), sorted_keys.end(), frame_number);
/* If the next frame we found is at the end of the frame we're inserting, then we are done. */
if (next_frame_number_it != sorted_keys.end() && *next_frame_number_it == end_frame_number) {
return true;
}
/* While the next frame is a null frame, remove it. */
while (next_frame_number_it != sorted_keys.end() &&
this->frames().lookup(*next_frame_number_it).is_null())
{
this->frames_for_write().remove(*next_frame_number_it);
this->tag_frames_map_keys_changed();
next_frame_number_it = std::next(next_frame_number_it);
}
/* If the next frame comes after the end of the frame we're inserting (or if there are no more
* frames), add a null-frame. */
if (next_frame_number_it == sorted_keys.end() || *next_frame_number_it > end_frame_number) {
this->frames_for_write().add(end_frame_number, GreasePencilFrame::null());
this->tag_frames_map_keys_changed();
}
return true;
}
bool Layer::overwrite_frame(int frame_number, const GreasePencilFrame &frame)

View File

@@ -179,4 +179,99 @@ TEST(greasepencil, layer_tree_node_types)
}
}
/* --------------------------------------------------------------------------------------------- */
/* Frames Tests. */
struct GreasePencilLayerFramesExample {
/**
* | | | | | | | | | | |1|1|1|1|1|1|1|
* Scene Frame: |0|1|2|3|4|5|6|7|8|9|0|1|2|3|4|5|6|...
* Drawing: [#0 ][#1 ] [#2 ]
*/
const int sorted_keys[5] = {0, 5, 10, 12, 16};
GreasePencilFrame sorted_values[5] = {{0}, {1}, {-1}, {2}, {-1}};
Layer layer;
GreasePencilLayerFramesExample()
{
for (int i = 0; i < 5; i++) {
layer.frames_for_write().add(this->sorted_keys[i], this->sorted_values[i]);
}
}
};
TEST(greasepencil, frame_is_null)
{
GreasePencilLayerFramesExample ex;
EXPECT_TRUE(ex.layer.frames().lookup(10).is_null());
}
TEST(greasepencil, drawing_index_at)
{
GreasePencilLayerFramesExample ex;
EXPECT_EQ(ex.layer.drawing_index_at(-100), -1);
EXPECT_EQ(ex.layer.drawing_index_at(100), -1);
EXPECT_EQ(ex.layer.drawing_index_at(0), 0);
EXPECT_EQ(ex.layer.drawing_index_at(1), 0);
EXPECT_EQ(ex.layer.drawing_index_at(5), 1);
}
TEST(greasepencil, insert_frame)
{
GreasePencilLayerFramesExample ex;
GreasePencilFrame frame{3, 0, 0};
EXPECT_FALSE(ex.layer.insert_frame(0, frame));
EXPECT_TRUE(ex.layer.insert_frame(10, frame));
EXPECT_EQ(ex.layer.drawing_index_at(10), 3);
EXPECT_EQ(ex.layer.drawing_index_at(11), 3);
EXPECT_EQ(ex.layer.drawing_index_at(12), 2);
}
TEST(greasepencil, insert_frame_duration_fail)
{
GreasePencilLayerFramesExample ex;
GreasePencilFrame frame{3, 0, 0};
EXPECT_FALSE(ex.layer.insert_frame(0, 10, frame));
}
TEST(greasepencil, insert_frame_duration_override_start_null_frame)
{
GreasePencilLayerFramesExample ex;
GreasePencilFrame frame{3, 0, 0};
EXPECT_TRUE(ex.layer.insert_frame(10, 2, frame));
EXPECT_EQ(ex.layer.drawing_index_at(10), 3);
EXPECT_EQ(ex.layer.drawing_index_at(11), 3);
EXPECT_EQ(ex.layer.drawing_index_at(12), 2);
}
TEST(greasepencil, insert_frame_duration_check_duration)
{
GreasePencilLayerFramesExample ex;
GreasePencilFrame frame{3, 0, 0};
EXPECT_TRUE(ex.layer.insert_frame(17, 10, frame));
Span<int> sorted_keys = ex.layer.sorted_keys();
EXPECT_EQ(sorted_keys.size(), 7);
EXPECT_EQ(sorted_keys[6] - sorted_keys[5], 10);
}
TEST(greasepencil, insert_frame_duration_override_null_frames)
{
Layer layer;
layer.frames_for_write().add(0, {1});
layer.frames_for_write().add(1, {-1});
layer.frames_for_write().add(2, {-1});
layer.frames_for_write().add(3, {-1});
GreasePencilFrame frame{3, 0, 0};
EXPECT_TRUE(layer.insert_frame(1, 10, frame));
EXPECT_EQ(layer.drawing_index_at(0), 1);
EXPECT_EQ(layer.drawing_index_at(1), 3);
EXPECT_EQ(layer.drawing_index_at(11), -1);
Span<int> sorted_keys = layer.sorted_keys();
EXPECT_EQ(sorted_keys.size(), 3);
EXPECT_EQ(sorted_keys[0], 0);
EXPECT_EQ(sorted_keys[1], 1);
EXPECT_EQ(sorted_keys[2], 11);
}
} // namespace blender::bke::greasepencil::tests

View File

@@ -148,6 +148,10 @@ typedef struct GreasePencilFrame {
*/
int8_t type;
char _pad[3];
#ifdef __cplusplus
static GreasePencilFrame null();
bool is_null() const;
#endif
} GreasePencilFrame;
typedef enum GreasePencilLayerFramesMapStorageFlag {