paint: timeline layer tween

This commit is contained in:
luboslenco
2026-06-04 18:39:23 +02:00
parent e57de8fc79
commit 4dd9bdd588
6 changed files with 201 additions and 19 deletions
+42
View File
@@ -0,0 +1,42 @@
#[set(everything)]
const constants: {
factor: float;
};
#[set(everything)]
const sampler_linear: sampler;
#[set(everything)]
const tex0: tex2d;
#[set(everything)]
const tex1: tex2d;
struct vert_in {
pos: float2;
}
struct vert_out {
pos: float4;
tex: float2;
}
fun layer_tween_vert(input: vert_in): vert_out {
var output: vert_out;
output.tex = input.pos.xy * 0.5 + 0.5;
output.tex.y = 1.0 - output.tex.y;
output.pos = float4(input.pos.xy, 0.0, 1.0);
return output;
}
fun layer_tween_frag(input: vert_out): float4 {
var col0: float4 = sample_lod(tex0, sampler_linear, input.tex, 0.0);
var col1: float4 = sample_lod(tex1, sampler_linear, input.tex, 0.0);
return lerp4(col0, col1, constants.factor);
}
#[pipe]
struct pipe {
vertex = layer_tween_vert;
fragment = layer_tween_frag;
}
+1
View File
@@ -252,6 +252,7 @@ project_t *import_arm_from_map_to_arm(any_map_t *old) {
d->path_points_world = any_map_get(old, "path_points_world");
d->path_points_camera = any_map_get(old, "path_points_camera");
d->path_points_parent = any_map_get(old, "path_points_parent");
d->tween = armpack_map_get_i32(old, "tween") > 0;
any_array_push(project->timeline_layers, d);
}
}
+1
View File
@@ -631,6 +631,7 @@ typedef struct timeline_layer_keyframe_data {
struct f32_array *path_points_world;
struct f32_array *path_points_camera;
struct i32_array *path_points_parent;
bool tween;
} timeline_layer_keyframe_data_t;
typedef struct timeline_mesh_keyframe_data {
+4
View File
@@ -13,6 +13,10 @@ void tab_scripts_prepare() {
string_array_push(g_project->script_datas, "void main() {\n \n}\n");
string_array_push(g_project->script_names, "main.c");
}
// A script may have been removed
if (tab_scripts_selected >= g_project->script_datas->length) {
tab_scripts_selected = g_project->script_datas->length - 1;
}
}
char *tab_scripts_get() {
+150 -18
View File
@@ -11,6 +11,7 @@ typedef struct {
f32_array_t *path_points_world;
f32_array_t *path_points_camera;
i32_array_t *path_points_parent;
bool tween;
} tab_timeline_keyframe_t;
typedef struct {
@@ -42,13 +43,13 @@ static i32 tab_timeline_last_frame = 0;
static any_array_t *tab_timeline_mesh_keyframes = NULL;
static any_array_t *tab_timeline_mesh_origins = NULL;
static i32 tab_timeline_pending_from = -1;
static i32 tab_timeline_pending_to = -1;
static i32 tab_timeline_pending_kf_frame = -1;
static i32 tab_timeline_pending_kf_layer = -1;
static i32 tab_timeline_pending_rm_frame = -1;
static i32 tab_timeline_pending_rm_layer = -1;
static i32 tab_timeline_pending_from = -1;
static i32 tab_timeline_pending_to = -1;
static i32 tab_timeline_pending_kf_frame = -1;
static i32 tab_timeline_pending_kf_layer = -1;
static i32 tab_timeline_pending_rm_frame = -1;
static i32 tab_timeline_pending_rm_layer = -1;
static f32 tab_timeline_pending_tween_f = -1.0f;
static i32 tab_timeline_pending_mesh_add_frame = -1;
static i32 tab_timeline_pending_mesh_add_index = -1;
static i32 tab_timeline_pending_mesh_rm_frame = -1;
@@ -58,6 +59,11 @@ static f64 tab_timeline_last_click_time = 0.0;
static i32 tab_timeline_last_click_frame = -1;
static i32 tab_timeline_last_click_row = -1;
static gpu_pipeline_t *tab_timeline_tween_pipe = NULL;
static i32 tab_timeline_tween_tex0 = 0;
static i32 tab_timeline_tween_tex1 = 0;
static i32 tab_timeline_tween_factor = 0;
static void tab_timeline_copy_tex(gpu_texture_t *dst, gpu_texture_t *src) {
draw_begin(dst, true, 0x00000000);
draw_set_pipeline(pipes_copy);
@@ -118,6 +124,50 @@ static i32 tab_timeline_find_origin(i32 layer_index) {
return -1;
}
static i32 tab_timeline_find_next_keyframe(i32 frame, i32 layer_index) {
i32 best = -1;
i32 best_frame = tab_timeline_max_frames + 1;
for (i32 i = 0; i < tab_timeline_keyframes->length; i++) {
tab_timeline_keyframe_t *kf = tab_timeline_keyframes->buffer[i];
if (kf->layer_index == layer_index && kf->frame > frame && kf->frame < best_frame) {
best_frame = kf->frame;
best = i;
}
}
return best;
}
static void tab_timeline_init_tween_pipe() {
if (tab_timeline_tween_pipe != NULL) {
return;
}
tab_timeline_tween_pipe = gpu_create_pipeline();
gc_root(tab_timeline_tween_pipe);
tab_timeline_tween_pipe->vertex_shader = sys_get_shader("layer_tween.vert");
tab_timeline_tween_pipe->fragment_shader = sys_get_shader("layer_tween.frag");
gpu_vertex_structure_t *vs = GC_ALLOC_INIT(gpu_vertex_structure_t, {0});
gpu_vertex_structure_add(vs, "pos", GPU_VERTEX_DATA_F32_2X);
tab_timeline_tween_pipe->input_layout = vs;
gpu_pipeline_compile(tab_timeline_tween_pipe);
tab_timeline_tween_tex0 = 0;
tab_timeline_tween_tex1 = 1;
pipes_offset = 0;
tab_timeline_tween_factor = pipes_get_constant_location("float");
}
static void tab_timeline_tween_tex(gpu_texture_t *dst, gpu_texture_t *from, gpu_texture_t *to, f32 t) {
tab_timeline_init_tween_pipe();
_gpu_begin(dst, NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
gpu_set_pipeline(tab_timeline_tween_pipe);
gpu_set_texture(tab_timeline_tween_tex0, from);
gpu_set_texture(tab_timeline_tween_tex1, to);
gpu_set_float(tab_timeline_tween_factor, t);
gpu_set_vertex_buffer(const_data_screen_aligned_vb);
gpu_set_index_buffer(const_data_screen_aligned_ib);
gpu_draw();
gpu_end();
}
static void tab_timeline_save_origins() {
gpu_texture_format_t fmt = tab_timeline_tex_format();
i32 w = config_get_texture_res_x();
@@ -217,6 +267,68 @@ static void tab_timeline_load_from_keyframes(i32 frame) {
}
}
static void tab_timeline_tween_from_keyframes(f32 frame_f) {
bool any = false;
i32 frame_i = (i32)frame_f;
for (i32 li = 0; li < project_layers->length; li++) {
slot_layer_t *l = project_layers->buffer[li];
if (!slot_layer_is_layer(l)) {
continue;
}
i32 nxt_kfi = tab_timeline_find_next_keyframe(frame_i, li);
if (nxt_kfi < 0) {
continue;
}
tab_timeline_keyframe_t *nxt = tab_timeline_keyframes->buffer[nxt_kfi];
if (!nxt->tween) {
continue;
}
gpu_texture_t *from_texpaint = NULL;
gpu_texture_t *from_texpaint_nor = NULL;
gpu_texture_t *from_texpaint_pack = NULL;
i32 from_frame = 0;
i32 act_kfi = tab_timeline_find_active_keyframe(frame_i, li);
if (act_kfi >= 0) {
tab_timeline_keyframe_t *act = tab_timeline_keyframes->buffer[act_kfi];
from_texpaint = act->texpaint;
from_texpaint_nor = act->texpaint_nor;
from_texpaint_pack = act->texpaint_pack;
from_frame = act->frame;
}
else {
i32 oi = tab_timeline_find_origin(li);
if (oi < 0) {
continue;
}
tab_timeline_origin_t *o = tab_timeline_origins->buffer[oi];
from_texpaint = o->texpaint;
from_texpaint_nor = o->texpaint_nor;
from_texpaint_pack = o->texpaint_pack;
from_frame = 0;
}
f32 t = (frame_f - (f32)from_frame) / (f32)(nxt->frame - from_frame);
tab_timeline_tween_tex(l->texpaint, from_texpaint, nxt->texpaint, t);
tab_timeline_tween_tex(l->texpaint_nor, from_texpaint_nor, nxt->texpaint_nor, t);
tab_timeline_tween_tex(l->texpaint_pack, from_texpaint_pack, nxt->texpaint_pack, t);
any = true;
}
if (any) {
g_context->ddirty = 2;
g_context->layers_preview_dirty = true;
}
}
static void tab_timeline_tween_on_next_frame(void *_) {
f32 frame_f = tab_timeline_pending_tween_f;
tab_timeline_pending_tween_f = -1.0f;
if (frame_f < 0.0f) {
return;
}
tab_timeline_tween_from_keyframes(frame_f);
}
static i32 tab_timeline_find_mesh_keyframe(i32 frame, i32 mesh_index) {
for (i32 i = 0; i < tab_timeline_mesh_keyframes->length; i++) {
tab_timeline_mesh_keyframe_t *kf = tab_timeline_mesh_keyframes->buffer[i];
@@ -365,8 +477,11 @@ static void tab_timeline_frame_change_on_next_frame(void *_) {
tab_timeline_pending_from = -1;
tab_timeline_pending_to = -1;
from == 0 ? tab_timeline_save_origins() : tab_timeline_save_to_keyframes(from);
if (!tab_timeline_playing) {
from == 0 ? tab_timeline_save_origins() : tab_timeline_save_to_keyframes(from);
}
to == 0 ? tab_timeline_load_origins() : tab_timeline_load_from_keyframes(to);
tab_timeline_tween_from_keyframes((f32)to);
if (!tab_timeline_playing) {
from == 0 ? tab_timeline_save_mesh_origins() : tab_timeline_save_mesh_to_keyframes(from);
to == 0 ? tab_timeline_load_mesh_origins() : tab_timeline_load_mesh_from_keyframes((float)to);
@@ -563,6 +678,7 @@ void tab_timeline_export(project_t *raw) {
.path_points_world = kf->path_points_world,
.path_points_camera = kf->path_points_camera,
.path_points_parent = kf->path_points_parent,
.tween = kf->tween,
});
any_array_push(layers, d);
}
@@ -619,6 +735,7 @@ void tab_timeline_import(project_t *raw) {
kf->path_points_world = d->path_points_world;
kf->path_points_camera = d->path_points_camera;
kf->path_points_parent = d->path_points_parent;
kf->tween = d->tween;
any_array_push(tab_timeline_keyframes, kf);
}
}
@@ -753,16 +870,23 @@ void tab_timeline_player_update() {
tab_timeline_load_from_keyframes(frame_i);
tab_timeline_run_frame_scripts(frame_i);
}
if (tab_timeline_keyframes != NULL) {
tab_timeline_tween_from_keyframes(frame_f);
}
}
void tab_timeline_draw_frame_context_menu() {
i32 layer_count = project_layers->length;
bool is_mesh = tab_timeline_selected_row >= layer_count;
bool has_kf;
i32 mesh_kfi = -1;
i32 mesh_kfi = -1;
i32 layer_kfi = -1;
if (!is_mesh) {
has_kf = tab_timeline_keyframes != NULL && tab_timeline_selected_frame > 0 &&
tab_timeline_find_keyframe(tab_timeline_selected_frame, tab_timeline_selected_row) >= 0;
layer_kfi = tab_timeline_keyframes != NULL && tab_timeline_selected_frame > 0
? tab_timeline_find_keyframe(tab_timeline_selected_frame, tab_timeline_selected_row)
: -1;
has_kf = layer_kfi >= 0;
}
else {
i32 mi = tab_timeline_selected_row - layer_count;
@@ -775,17 +899,21 @@ void tab_timeline_draw_frame_context_menu() {
tab_timeline_edit_script(tab_timeline_selected_row, tab_timeline_selected_frame);
}
ui->enabled = has_kf && is_mesh;
ui_handle_t *h_tween = ui_handle(__ID__);
h_tween->b = false;
tab_timeline_mesh_keyframe_t *kf = NULL;
ui->enabled = has_kf;
ui_handle_t *h_tween = ui_handle(__ID__);
h_tween->b = false;
if (ui->enabled) {
kf = tab_timeline_mesh_keyframes->buffer[mesh_kfi];
h_tween->b = kf->tween;
h_tween->b = is_mesh ? ((tab_timeline_mesh_keyframe_t *)tab_timeline_mesh_keyframes->buffer[mesh_kfi])->tween
: ((tab_timeline_keyframe_t *)tab_timeline_keyframes->buffer[layer_kfi])->tween;
}
ui_check(h_tween, tr("Tween"), "");
if (ui->enabled && h_tween->changed) {
kf->tween = h_tween->b;
if (is_mesh) {
((tab_timeline_mesh_keyframe_t *)tab_timeline_mesh_keyframes->buffer[mesh_kfi])->tween = h_tween->b;
}
else {
((tab_timeline_keyframe_t *)tab_timeline_keyframes->buffer[layer_kfi])->tween = h_tween->b;
}
ui_menu_keep_open = true;
}
@@ -909,6 +1037,10 @@ void tab_timeline_draw(ui_handle_t *htab) {
if (tab_timeline_mesh_keyframes != NULL) {
tab_timeline_load_mesh_from_keyframes(frame_f);
}
if (tab_timeline_keyframes != NULL && tab_timeline_pending_tween_f < 0.0f) {
tab_timeline_pending_tween_f = frame_f;
sys_notify_on_next_frame(&tab_timeline_tween_on_next_frame, NULL);
}
}
// Frame number labels every 5 frames
+3 -1
View File
@@ -481,7 +481,7 @@ buffer_t *util_encode_project(project_t *raw) {
if (raw->timeline_layers != NULL) {
armpack_encode_array(raw->timeline_layers->length);
for (i32 i = 0; i < raw->timeline_layers->length; ++i) {
armpack_encode_map(9);
armpack_encode_map(10);
armpack_encode_string("frame");
armpack_encode_i32(raw->timeline_layers->buffer[i]->frame);
armpack_encode_string("layer_index");
@@ -500,6 +500,8 @@ buffer_t *util_encode_project(project_t *raw) {
armpack_encode_array_f32(raw->timeline_layers->buffer[i]->path_points_camera);
armpack_encode_string("path_points_parent");
armpack_encode_array_i32(raw->timeline_layers->buffer[i]->path_points_parent);
armpack_encode_string("tween");
armpack_encode_bool(raw->timeline_layers->buffer[i]->tween);
}
}
else {