From 65192290e69486972571298e6cea9f1fe021fc79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Costa?= Date: Thu, 26 Feb 2026 15:23:47 +0100 Subject: [PATCH] feat(gltf): allow sharing models across gltf objects (#9681) --- demos/gltf/lv_demo_gltf.c | 47 +++-- docs/src/libs/gltf.rst | 2 +- examples/libs/gltf/index.rst | 7 + examples/libs/gltf/lv_example_gltf.h | 1 + examples/libs/gltf/lv_example_gltf_2.c | 5 +- examples/libs/gltf/lv_example_gltf_4.c | 117 +++++++++++ examples/libs/gltf/lvgl_logo.glb | Bin 0 -> 117588 bytes src/libs/gltf/gltf_data/lv_gltf_data.cpp | 125 ++++++++---- .../gltf/gltf_data/lv_gltf_data_cache.cpp | 29 +-- .../gltf/gltf_data/lv_gltf_data_injest.cpp | 27 ++- .../gltf/gltf_data/lv_gltf_data_internal.h | 46 +---- .../gltf/gltf_data/lv_gltf_data_internal.hpp | 74 ++----- src/libs/gltf/gltf_data/lv_gltf_model.h | 51 +++++ .../gltf/gltf_data/lv_gltf_model_loader.c | 101 ++++++++++ .../gltf/gltf_data/lv_gltf_model_loader.h | 75 +++++++ .../gltf/gltf_data/lv_gltf_model_node.cpp | 5 +- src/libs/gltf/gltf_data/lv_gltf_model_node.h | 9 + src/libs/gltf/gltf_view/lv_gltf.h | 30 +-- src/libs/gltf/gltf_view/lv_gltf_view.cpp | 147 +++++++------- .../gltf/gltf_view/lv_gltf_view_internal.h | 14 +- .../gltf/gltf_view/lv_gltf_view_render.cpp | 184 ++++++++++-------- 21 files changed, 765 insertions(+), 331 deletions(-) create mode 100644 examples/libs/gltf/lv_example_gltf_4.c create mode 100644 examples/libs/gltf/lvgl_logo.glb create mode 100644 src/libs/gltf/gltf_data/lv_gltf_model_loader.c create mode 100644 src/libs/gltf/gltf_data/lv_gltf_model_loader.h diff --git a/demos/gltf/lv_demo_gltf.c b/demos/gltf/lv_demo_gltf.c index 10acef9ca5..079325511a 100644 --- a/demos/gltf/lv_demo_gltf.c +++ b/demos/gltf/lv_demo_gltf.c @@ -40,19 +40,25 @@ typedef struct { lv_obj_t * label; } play_pause_event_data_t; -typedef void (*lv_gltf_set_float_fn)(lv_obj_t *, float); -typedef void (*lv_gltf_set_int_fn)(lv_obj_t *, uint32_t); +typedef void (*lv_gltf_set_float_cb_t)(lv_obj_t *, float); +typedef void (*lv_gltf_set_int_cb_t)(lv_obj_t *, uint32_t); +typedef void (*lv_gltf_model_set_int_fn)(lv_gltf_model_t *, uint32_t); typedef union { void * ptr; - lv_gltf_set_float_fn fn; + lv_gltf_set_float_cb_t cb; } lv_gltf_set_float_fn_union_t; typedef union { void * ptr; - lv_gltf_set_int_fn fn; + lv_gltf_set_int_cb_t cb; } lv_gltf_set_int_fn_union_t; +typedef union { + void * ptr; + lv_gltf_set_int_cb_t cb; +} lv_gltf_model_set_int_fn_union_t; + /********************** * STATIC PROTOTYPES **********************/ @@ -77,6 +83,7 @@ static lv_obj_t * add_dropdown_to_row(lv_obj_t * row); static void viewer_observer_float_cb(lv_observer_t * observer, lv_subject_t * subject); static void viewer_observer_int_cb(lv_observer_t * observer, lv_subject_t * subject); static void animation_observer_cb(lv_observer_t * observer, lv_subject_t * subject); +static void animation_speed_observer_cb(lv_observer_t * observer, lv_subject_t * subject); static void style_dropdown(lv_obj_t * dropdown); static void style_slider(lv_obj_t * slider, lv_color_t accent_color); static void style_control_panel(lv_obj_t * panel); @@ -85,16 +92,15 @@ static void style_control_panel(lv_obj_t * panel); * STATIC VARIABLES **********************/ -static lv_gltf_set_float_fn_union_t pitch_fn = { .fn = lv_gltf_set_pitch }; -static lv_gltf_set_float_fn_union_t yaw_fn = { .fn = lv_gltf_set_yaw }; -static lv_gltf_set_float_fn_union_t distance_fn = { .fn = lv_gltf_set_distance }; -static lv_gltf_set_int_fn_union_t camera_fn = { .fn = lv_gltf_set_camera }; -static lv_gltf_set_int_fn_union_t animation_speed_fn = { .fn = lv_gltf_set_animation_speed }; -static lv_gltf_set_int_fn_union_t background_mode_fn = { .fn = lv_gltf_set_background_mode }; -static lv_gltf_set_int_fn_union_t antialiasing_mode_fn = { .fn = lv_gltf_set_antialiasing_mode }; +static lv_gltf_set_float_fn_union_t pitch_fn = { .cb = lv_gltf_set_pitch }; +static lv_gltf_set_float_fn_union_t yaw_fn = { .cb = lv_gltf_set_yaw }; +static lv_gltf_set_float_fn_union_t distance_fn = { .cb = lv_gltf_set_distance }; +static lv_gltf_set_int_fn_union_t camera_fn = { .cb = lv_gltf_set_camera }; +static lv_gltf_set_int_fn_union_t background_mode_fn = { .cb = lv_gltf_set_background_mode }; +static lv_gltf_set_int_fn_union_t antialiasing_mode_fn = { .cb = lv_gltf_set_antialiasing_mode }; -static lv_gltf_set_int_fn_union_t env_brightness_fn = { .fn = lv_gltf_set_env_brightness }; -static lv_gltf_set_int_fn_union_t bg_blur_fn = { .fn = lv_gltf_set_background_blur }; +static lv_gltf_set_int_fn_union_t env_brightness_fn = { .cb = lv_gltf_set_env_brightness }; +static lv_gltf_set_int_fn_union_t bg_blur_fn = { .cb = lv_gltf_set_background_blur }; static lv_subject_t yaw_subject; static lv_subject_t pitch_subject; @@ -165,7 +171,7 @@ static void init_subjects(lv_obj_t * viewer) lv_subject_add_observer_obj(&distance_subject, viewer_observer_float_cb, viewer, distance_fn.ptr); lv_subject_add_observer(&animation_subject, animation_observer_cb, viewer); - lv_subject_add_observer_obj(&animation_speed_subject, viewer_observer_int_cb, viewer, animation_speed_fn.ptr); + lv_subject_add_observer(&animation_speed_subject, animation_speed_observer_cb, viewer); lv_subject_add_observer_obj(&background_subject, viewer_observer_int_cb, viewer, background_mode_fn.ptr); lv_subject_add_observer_obj(&env_brightness_subject, viewer_observer_int_cb, viewer, env_brightness_fn.ptr); @@ -497,7 +503,7 @@ static void viewer_observer_float_cb(lv_observer_t * observer, lv_subject_t * su float value = lv_subject_get_float(subject); lv_gltf_set_float_fn_union_t fn_union = { .ptr = lv_observer_get_user_data(observer) }; - fn_union.fn(viewer, value); + fn_union.cb(viewer, value); } static void viewer_observer_int_cb(lv_observer_t * observer, lv_subject_t * subject) @@ -506,7 +512,7 @@ static void viewer_observer_int_cb(lv_observer_t * observer, lv_subject_t * subj lv_gltf_set_int_fn_union_t fn_union = { .ptr = lv_observer_get_user_data(observer) }; lv_obj_t * viewer = lv_observer_get_target_obj(observer); - fn_union.fn(viewer, value); + fn_union.cb(viewer, value); } static void animation_observer_cb(lv_observer_t * observer, lv_subject_t * subject) @@ -517,6 +523,15 @@ static void animation_observer_cb(lv_observer_t * observer, lv_subject_t * subje lv_gltf_model_play_animation(model, value); } + +static void animation_speed_observer_cb(lv_observer_t * observer, lv_subject_t * subject) +{ + int value = lv_subject_get_int(subject); + lv_obj_t * viewer = lv_observer_get_user_data(observer); + lv_gltf_model_t * model = lv_gltf_get_primary_model(viewer); + + lv_gltf_model_set_animation_speed(model, value); +} static void style_slider(lv_obj_t * slider, lv_color_t accent_color) { lv_obj_set_style_bg_color(slider, lv_color_hex(0x1A1A1A), LV_PART_MAIN); diff --git a/docs/src/libs/gltf.rst b/docs/src/libs/gltf.rst index 353249c0cd..4f340f644a 100644 --- a/docs/src/libs/gltf.rst +++ b/docs/src/libs/gltf.rst @@ -291,7 +291,7 @@ Control model animations with these functions: lv_gltf_model_play_animation(model, 0); /* Control animation speed */ - lv_gltf_set_animation_speed(gltf, LV_GLTF_ANIM_SPEED_2X); + lv_gltf_model_set_animation_speed(model, LV_GLTF_ANIM_SPEED_2X); /* Pause/resume animation */ lv_gltf_model_pause_animation(model); diff --git a/examples/libs/gltf/index.rst b/examples/libs/gltf/index.rst index b5dc520c18..f829de7d21 100644 --- a/examples/libs/gltf/index.rst +++ b/examples/libs/gltf/index.rst @@ -19,3 +19,10 @@ Load multiple models in a single glTF object and modify their position, rotation .. lv_example:: libs/gltf/lv_example_gltf_3 :language: c + + +Share a model across different glTF objects +------------------------------------------- + +.. lv_example:: libs/gltf/lv_example_gltf_4 + :language: c diff --git a/examples/libs/gltf/lv_example_gltf.h b/examples/libs/gltf/lv_example_gltf.h index dc0bc1e7d1..18fa1c7d3c 100644 --- a/examples/libs/gltf/lv_example_gltf.h +++ b/examples/libs/gltf/lv_example_gltf.h @@ -30,6 +30,7 @@ extern "C" { void lv_example_gltf_1(void); void lv_example_gltf_2(void); void lv_example_gltf_3(void); +void lv_example_gltf_4(void); /********************** * MACROS diff --git a/examples/libs/gltf/lv_example_gltf_2.c b/examples/libs/gltf/lv_example_gltf_2.c index 503f83ccbc..a223578c8e 100644 --- a/examples/libs/gltf/lv_example_gltf_2.c +++ b/examples/libs/gltf/lv_example_gltf_2.c @@ -14,6 +14,7 @@ static uint32_t current_speed = LV_GLTF_ANIM_SPEED_HALF; static void timer_cb(lv_timer_t * timer) { lv_obj_t * gltf = (lv_obj_t *) lv_timer_get_user_data(timer); + lv_gltf_model_t * model = lv_gltf_get_primary_model(gltf); current_camera = (current_camera + 1) % (camera_count + 1); current_speed *= 2; @@ -23,7 +24,7 @@ static void timer_cb(lv_timer_t * timer) LV_LOG_USER("Setting camera %zu and animation speed %" PRIu32, current_camera, current_speed); lv_gltf_set_camera(gltf, current_camera); - lv_gltf_set_animation_speed(gltf, current_speed); + lv_gltf_model_set_animation_speed(model, current_speed); } /** @@ -36,7 +37,7 @@ void lv_example_gltf_2(void) "A:lvgl/examples/libs/gltf/webp_diffuse_transmission_plant.glb"); - lv_gltf_set_animation_speed(gltf, current_speed); + lv_gltf_model_set_animation_speed(model, current_speed); lv_gltf_model_play_animation(model, 0); lv_obj_set_size(gltf, LV_PCT(100), LV_PCT(100)); camera_count = lv_gltf_get_camera_count(gltf); diff --git a/examples/libs/gltf/lv_example_gltf_4.c b/examples/libs/gltf/lv_example_gltf_4.c new file mode 100644 index 0000000000..38d67c18ab --- /dev/null +++ b/examples/libs/gltf/lv_example_gltf_4.c @@ -0,0 +1,117 @@ +#include "lv_example_gltf.h" + +#if LV_BUILD_EXAMPLES + +#if LV_USE_GLTF + +static lv_gltf_model_node_t * logo_root; + +static void anim_scale_cb(void * var, int32_t v) +{ + lv_gltf_model_node_t * node = (lv_gltf_model_node_t *)var; + + /* Convert integer animation value to float scale (v ranges from 10 to 100, representing 0.1 to 1.0) */ + float scale = v / 100.0f; + + /* Apply uniform scale to all axes */ + lv_gltf_model_node_set_scale_x(node, scale); + lv_gltf_model_node_set_scale_y(node, scale); + lv_gltf_model_node_set_scale_z(node, scale); +} + +static void logo_scale_cb(lv_event_t * e) +{ + lv_3dpoint_t scale; + + /* Read current scale values */ + lv_gltf_model_node_get_scale(e, &scale); + + LV_LOG_USER("logo scale: %.2f, %.2f, %.2f", + scale.x, scale.y, scale.z); +} + +/** + * Load a logo model once and share it across four glTF viewers in a 2x2 grid. + * Each viewer shows the logo from a different angle. + * Animate the shared model's growth - changes affect all four viewers. + */ +void lv_example_gltf_4(void) +{ + /* Load the logo model once */ + lv_gltf_model_t * logo_model = lv_gltf_data_load_from_file( + "A:lvgl/examples/libs/gltf/lvgl_logo.glb", NULL); + + LV_ASSERT_NULL(logo_model); + /* Create first glTF viewer - Top Left */ + lv_obj_t * gltf1 = lv_gltf_create(lv_screen_active()); + lv_obj_set_size(gltf1, LV_PCT(50), LV_PCT(50)); + lv_obj_align(gltf1, LV_ALIGN_TOP_LEFT, 0, 0); + lv_gltf_set_pitch(gltf1, -30.f); + lv_gltf_set_yaw(gltf1, -30.f); + lv_gltf_set_distance(gltf1, 0.5f); + lv_gltf_add_model(gltf1, logo_model); + + /* Create second glTF viewer - Top Right */ + lv_obj_t * gltf2 = lv_gltf_create(lv_screen_active()); + lv_obj_set_size(gltf2, LV_PCT(50), LV_PCT(50)); + lv_obj_align(gltf2, LV_ALIGN_TOP_RIGHT, 0, 0); + lv_gltf_set_pitch(gltf2, -30.f); + lv_gltf_set_yaw(gltf2, 30.f); + lv_gltf_set_distance(gltf2, 0.5f); + lv_gltf_add_model(gltf2, logo_model); + + /* Create third glTF viewer - Bottom Left */ + lv_obj_t * gltf3 = lv_gltf_create(lv_screen_active()); + lv_obj_set_size(gltf3, LV_PCT(50), LV_PCT(50)); + lv_obj_align(gltf3, LV_ALIGN_BOTTOM_LEFT, 0, 0); + lv_gltf_set_pitch(gltf3, 30.f); + lv_gltf_set_yaw(gltf3, -30.f); + lv_gltf_set_distance(gltf3, 0.5f); + lv_gltf_add_model(gltf3, logo_model); + + /* Create fourth glTF viewer - Bottom Right */ + lv_obj_t * gltf4 = lv_gltf_create(lv_screen_active()); + lv_obj_set_size(gltf4, LV_PCT(50), LV_PCT(50)); + lv_obj_align(gltf4, LV_ALIGN_BOTTOM_RIGHT, 0, 0); + lv_gltf_set_pitch(gltf4, 30.f); + lv_gltf_set_yaw(gltf4, 30.f); + lv_gltf_set_distance(gltf4, 0.5f); + lv_gltf_add_model(gltf4, logo_model); + + /* Get the root node of the shared logo */ + logo_root = lv_gltf_model_node_get_by_numeric_path(logo_model, ".0"); + + /* Register event callback to monitor scale changes */ + lv_gltf_model_node_add_event_cb(logo_root, logo_scale_cb, + LV_EVENT_VALUE_CHANGED, NULL); + + /* Create animation for logo growth */ + /* Since the model is shared, animating the node affects all four viewers */ + lv_anim_t a; + lv_anim_init(&a); + lv_anim_set_var(&a, logo_root); + lv_anim_set_values(&a, 10, 100); /* Scale from 0.1 to 1.0 */ + lv_anim_set_duration(&a, 2000); /* 2 seconds to grow */ + lv_anim_set_reverse_delay(&a, 1000); /* Stay big for 1 second */ + lv_anim_set_reverse_duration(&a, 1500); /* 1.5 seconds to shrink */ + lv_anim_set_repeat_delay(&a, 500); /* Wait 0.5 seconds before repeating */ + lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE); + lv_anim_set_path_cb(&a, lv_anim_path_ease_in_out); + lv_anim_set_exec_cb(&a, anim_scale_cb); + lv_anim_start(&a); +} + +#else + +void lv_example_gltf_4(void) +{ + /*TODO + *fallback for online examples*/ + + lv_obj_t * label = lv_label_create(lv_screen_active()); + lv_label_set_text(label, "glTF web support is coming soon"); + lv_obj_center(label); +} + +#endif +#endif diff --git a/examples/libs/gltf/lvgl_logo.glb b/examples/libs/gltf/lvgl_logo.glb new file mode 100644 index 0000000000000000000000000000000000000000..5300ce77e73a22c66be7b37ce03c3e731b4595a5 GIT binary patch literal 117588 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 literal 0 HcmV?d00001 diff --git a/src/libs/gltf/gltf_data/lv_gltf_data.cpp b/src/libs/gltf/gltf_data/lv_gltf_data.cpp index 1c554be809..52dbaf4583 100644 --- a/src/libs/gltf/gltf_data/lv_gltf_data.cpp +++ b/src/libs/gltf/gltf_data/lv_gltf_data.cpp @@ -15,7 +15,6 @@ #include "../../../misc/lv_assert.h" #include "../../../core/lv_obj_pos.h" #include "../../../misc/lv_timer.h" -#include "../gltf_view/lv_gltf_view_internal.h" /********************* @@ -52,20 +51,17 @@ lv_gltf_model_t * lv_gltf_data_create_internal(const char * gltf_path, new(data) lv_gltf_model_t; new(&data->asset) fastgltf::Asset(std::move(asset)); data->filename = gltf_path; - data->last_camera_index = -5; data->last_anim_num = -5; data->current_animation_max_time = 0; data->local_timestamp = 0.0f; data->last_material_index = 99999; - data->last_frame_was_antialiased = false; - data->last_frame_no_motion = false; - data->_last_frame_no_motion = false; + data->animation_speed_ratio = LV_GLTF_ANIM_SPEED_NORMAL; data->animation_update_timer = lv_timer_create(update_animation_cb, LV_DEF_REFR_PERIOD, data); lv_timer_pause(data->animation_update_timer); LV_ASSERT_NULL(data->animation_update_timer); - new(&data->node_transform_cache) NodeTransformMap(); + new(&data->transforms) NodeTransformMap(); new(&data->opaque_nodes_by_material_index) MaterialIndexMap(); new(&data->blended_nodes_by_material_index) MaterialIndexMap(); new(&data->validated_skins) LongVector(); @@ -75,40 +71,46 @@ lv_gltf_model_t * lv_gltf_data_create_internal(const char * gltf_path, new(&data->node_by_light_index) NodeVector(); new(&data->meshes) std::vector(); new(&data->textures) std::vector(); + new(&data->ibm_by_skin_then_node) std::map>; + lv_array_init(&data->viewers, 1, sizeof(lv_gltf_t *)); lv_array_init(&data->compiled_shaders, 1, sizeof(lv_gltf_compiled_shader_t)); return data; } -void lv_gltf_data_delete(lv_gltf_model_t * data) +void lv_gltf_model_delete(lv_gltf_model_t * model) { - LV_ASSERT_NULL(data); - lv_timer_delete(data->animation_update_timer); - data->animation_update_timer = NULL; + if(!model) { + return; + } + lv_timer_delete(model->animation_update_timer); + model->animation_update_timer = NULL; - lv_gltf_data_delete_textures(data); - uint32_t node_count = lv_array_size(&data->nodes); + lv_gltf_data_delete_textures(model); + uint32_t node_count = lv_array_size(&model->nodes); for(uint32_t i = 0; i < node_count; ++i) { - lv_gltf_model_node_t * node = (lv_gltf_model_node_t *) lv_array_at(&data->nodes, i); + lv_gltf_model_node_t * node = (lv_gltf_model_node_t *) lv_array_at(&model->nodes, i); lv_gltf_model_node_deinit(node); } - lv_array_deinit(&data->nodes); - lv_array_deinit(&data->compiled_shaders); + lv_array_deinit(&model->viewers); + lv_array_deinit(&model->nodes); + lv_array_deinit(&model->compiled_shaders); /* Explicitly call destructors for C++ objects initialized with placement new */ - data->textures.~vector(); - data->meshes.~vector(); - data->node_by_light_index.~vector(); - data->local_mesh_to_center_points_by_primitive.~map(); - data->skin_tex.~vector(); - data->validated_skins.~vector(); - data->blended_nodes_by_material_index.~map(); - data->opaque_nodes_by_material_index.~map(); - data->node_transform_cache.~map(); - data->channel_set_cache.~map(); - data->asset.~Asset(); + model->ibm_by_skin_then_node.~IbmBySkinThenNodeMap(); + model->textures.~vector(); + model->meshes.~vector(); + model->node_by_light_index.~vector(); + model->local_mesh_to_center_points_by_primitive.~map(); + model->skin_tex.~vector(); + model->validated_skins.~vector(); + model->blended_nodes_by_material_index.~map(); + model->opaque_nodes_by_material_index.~map(); + model->transforms.~map(); + model->channel_set_cache.~map(); + model->asset.~Asset(); - lv_free(data); + lv_free(model); } const char * lv_gltf_get_filename(const lv_gltf_model_t * data) @@ -206,18 +208,16 @@ size_t lv_gltf_model_get_animation(lv_gltf_model_t * model) return model->current_animation; } -lv_gltf_model_t * -lv_gltf_data_load_from_file(const char * file_path, - lv_opengl_shader_manager_t * shader_manager) + +lv_gltf_model_t * lv_gltf_data_load_from_file(const char * file_path, lv_gltf_model_loader_t * loader) { - return lv_gltf_data_load_internal(file_path, 0, shader_manager); + return lv_gltf_data_load_internal(file_path, 0, loader); } -lv_gltf_model_t * -lv_gltf_data_load_from_bytes(const uint8_t * data, size_t data_size, - lv_opengl_shader_manager_t * shader_manager) + +lv_gltf_model_t * lv_gltf_data_load_from_bytes(const uint8_t * data, size_t data_size, lv_gltf_model_loader_t * loader) { - return lv_gltf_data_load_internal(data, data_size, shader_manager); + return lv_gltf_data_load_internal(data, data_size, loader); } fastgltf::Asset * lv_gltf_data_get_asset(lv_gltf_model_t * data) @@ -266,6 +266,56 @@ void lv_gltf_data_copy_bounds_info(lv_gltf_model_t * to, lv_gltf_model_t * from) to->bound_radius = from->bound_radius; } +void lv_gltf_model_set_animation_speed(lv_gltf_model_t * model, uint32_t value) +{ + if(!model) { + return; + } + if(model->animation_speed_ratio == value) { + return; + } + model->animation_speed_ratio = value; + lv_gltf_model_invalidate(model); +} +uint32_t lv_gltf_model_get_animation_speed(const lv_gltf_model_t * model) +{ + if(!model) { + return 0; + } + return model->animation_speed_ratio; +} + +lv_result_t lv_gltf_model_add_viewer(lv_gltf_model_t * model, lv_obj_t * viewer) +{ + LV_ASSERT_NULL(model); + LV_ASSERT_NULL(viewer); + return lv_array_push_back(&model->viewers, &viewer); +} + +void lv_gltf_model_remove_viewer(lv_gltf_model_t * model, lv_obj_t * target_viewer) +{ + LV_ASSERT_NULL(model); + LV_ASSERT_NULL(target_viewer); + const uint32_t viewer_count = lv_array_size(&model->viewers); + for(uint32_t i = 0; i < viewer_count; ++i) { + lv_obj_t * viewer = *(lv_obj_t **)lv_array_at(&model->viewers, i); + if(viewer == target_viewer) { + lv_array_remove_unordered(&model->viewers, i); + return; + } + } +} + +void lv_gltf_model_invalidate(lv_gltf_model_t * model) +{ + LV_ASSERT_NULL(model); + const uint32_t viewer_count = lv_array_size(&model->viewers); + for(uint32_t i = 0; i < viewer_count; ++i) { + lv_obj_t * viewer = *(lv_obj_t **)lv_array_at(&model->viewers, i); + lv_obj_invalidate(viewer); + } +} + /********************** * STATIC FUNCTIONS **********************/ @@ -278,12 +328,13 @@ static void update_animation_cb(lv_timer_t * timer) const uint32_t delta = lv_tick_diff(current_tick, model->last_tick); model->last_tick = current_tick; - model->local_timestamp += (delta * model->viewer->desc.animation_speed_ratio) / 1000; + model->local_timestamp += (delta * model->animation_speed_ratio) / 1000; if(model->local_timestamp >= model->current_animation_max_time) { model->local_timestamp = 50; } - lv_obj_invalidate((lv_obj_t *)model->viewer); + lv_gltf_model_invalidate(model); } + #endif /*LV_USE_GLTF*/ diff --git a/src/libs/gltf/gltf_data/lv_gltf_data_cache.cpp b/src/libs/gltf/gltf_data/lv_gltf_data_cache.cpp index 69bae4e7ad..5c88cfd99d 100644 --- a/src/libs/gltf/gltf_data/lv_gltf_data_cache.cpp +++ b/src/libs/gltf/gltf_data/lv_gltf_data_cache.cpp @@ -34,31 +34,34 @@ * GLOBAL FUNCTIONS **********************/ -fastgltf::math::fmat4x4 lv_gltf_data_get_cached_transform(lv_gltf_model_t * data, - fastgltf::Node * node) +fastgltf::math::fmat4x4 lv_gltf_data_get_node_transform(lv_gltf_model_t * model, + fastgltf::Node * node) { - return data->node_transform_cache[node]; + return model->transforms[node]; } -bool lv_gltf_data_has_cached_transform(lv_gltf_model_t * data, fastgltf::Node * node) +bool lv_gltf_model_has_node_transform(lv_gltf_model_t * model, fastgltf::Node * node) { - return (data->node_transform_cache.find(node) != - data->node_transform_cache.end()); + return model->transforms.find(node) != + model->transforms.end(); } -void lv_gltf_data_set_cached_transform(lv_gltf_model_t * data, fastgltf::Node * node, - fastgltf::math::fmat4x4 M) +void lv_gltf_model_set_transforms(lv_gltf_model_t * model, fastgltf::Node * node, + fastgltf::math::fmat4x4 M) { - data->node_transform_cache[node] = M; + model->transforms[node] = M; + model->transforms_changed = true; } -void lv_gltf_data_clear_transform_cache(lv_gltf_model_t * data) +void lv_gltf_model_clear_transforms(lv_gltf_model_t * model) { - data->node_transform_cache.clear(); + model->transforms.clear(); + model->transforms_changed = true; } -bool lv_gltf_data_transform_cache_is_empty(lv_gltf_model_t * data) +bool lv_gltf_model_needs_transforms(lv_gltf_model_t * model) { - return data->node_transform_cache.size() == 0; + return model->transforms.size() == 0 || model->transforms_changed; } + void recache_centerpoint(lv_gltf_model_t * data, size_t index_mesh, int32_t primitive) { data->local_mesh_to_center_points_by_primitive[index_mesh][primitive] = diff --git a/src/libs/gltf/gltf_data/lv_gltf_data_injest.cpp b/src/libs/gltf/gltf_data/lv_gltf_data_injest.cpp index dda74251fb..3b0efc5acd 100644 --- a/src/libs/gltf/gltf_data/lv_gltf_data_injest.cpp +++ b/src/libs/gltf/gltf_data/lv_gltf_data_injest.cpp @@ -81,7 +81,7 @@ static void injest_grow_bounds_to_include(lv_gltf_model_t * data, const fastgltf static void injest_set_initial_bounds(lv_gltf_model_t * data, const fastgltf::math::fmat4x4 & matrix, const fastgltf::Mesh & mesh); -static bool injest_image(lv_opengl_shader_manager_t * shader_manager, lv_gltf_model_t * data, fastgltf::Image & image, +static bool injest_image(lv_gltf_model_loader_t * loader, lv_gltf_model_t * data, fastgltf::Image & image, uint32_t index); static bool injest_image_from_buffer_view(lv_gltf_model_t * data, fastgltf::sources::BufferView & view, @@ -158,7 +158,7 @@ static void load_mesh_texture(lv_gltf_model_t * data, **********************/ lv_gltf_model_t * lv_gltf_data_load_internal(const void * data_source, size_t data_size, - lv_opengl_shader_manager_t * shaders) + lv_gltf_model_loader_t * loader) { lv_gltf_model_t * model = NULL; if(data_size > 0) { @@ -211,11 +211,23 @@ lv_gltf_model_t * lv_gltf_data_load_internal(const void * data_source, size_t da }); { + bool owns_loader = loader == NULL; + if(!loader) { + loader = lv_gltf_model_loader_create(); + if(!loader) { + LV_LOG_ERROR("Failed to create gltf model loader"); + lv_gltf_model_delete(model); + return NULL; + } + } uint32_t i = 0; for(auto & image : model->asset.images) { - injest_image(shaders, model, image, i); + injest_image(loader, model, image, i); i++; } + if(owns_loader) { + lv_gltf_model_loader_delete(loader); + } } uint16_t lightnum = 0; for(auto & light : model->asset.lights) { @@ -477,15 +489,16 @@ static void injest_set_initial_bounds(lv_gltf_model_t * data, const fastgltf::ma set_bounds_info(data, v_min, v_max, v_cen, radius); } -bool injest_image(lv_opengl_shader_manager_t * shader_manager, lv_gltf_model_t * data, fastgltf::Image & image, - uint32_t index) +static bool injest_image(lv_gltf_model_loader_t * loader, lv_gltf_model_t * data, fastgltf::Image & image, + uint32_t index) { + LV_ASSERT_NULL(loader); std::string _tex_id = std::string(lv_gltf_get_filename(data)) + "_IMG" + std::to_string(index); char tmp[512]; lv_snprintf(tmp, sizeof(tmp), "%s_img_%u", data->filename, index); const uint32_t hash = lv_opengl_shader_hash(tmp); - GLuint texture_id = lv_opengl_shader_manager_get_texture(shader_manager, hash); + GLuint texture_id = lv_gltf_model_loader_get_texture(loader, hash); if(texture_id != GL_NONE) { LV_LOG_TRACE("Emplacing back already cached texture from previous injest iteration %u", texture_id); @@ -547,7 +560,7 @@ bool injest_image(lv_opengl_shader_manager_t * shader_manager, lv_gltf_model_t * LV_LOG_ERROR("Failed to load image %s", image.name.c_str()); } LV_LOG_TRACE("Storing texture with hash: %u %u", hash, texture_id); - lv_opengl_shader_manager_store_texture(shader_manager, hash, texture_id); + lv_gltf_model_loader_store_texture(loader, hash, texture_id); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); data->textures.emplace_back(texture_id); return true; diff --git a/src/libs/gltf/gltf_data/lv_gltf_data_internal.h b/src/libs/gltf/gltf_data/lv_gltf_data_internal.h index 2df78ad155..ca1c770644 100644 --- a/src/libs/gltf/gltf_data/lv_gltf_data_internal.h +++ b/src/libs/gltf/gltf_data/lv_gltf_data_internal.h @@ -1,17 +1,15 @@ #ifndef LV_GLTFDATA_PRIVATE_H #define LV_GLTFDATA_PRIVATE_H -#include "../../../lv_conf_internal.h" - #ifdef __cplusplus extern "C" { #endif +#include "../../../lv_conf_internal.h" + #if LV_USE_GLTF #include "../../../drivers/opengles/opengl_shader/lv_opengl_shader_internal.h" -#include "../../../draw/lv_image_dsc.h" #include "../../../misc/lv_types.h" -#include "../../../misc/lv_array.h" typedef struct { @@ -177,6 +175,10 @@ typedef struct { } lv_gltf_uniform_locations_t; +struct _lv_gltf_model_loader_t { + lv_rb_t textures_map; +}; + lv_gltf_uniform_locations_t lv_gltf_uniform_locations_create(GLuint program); typedef struct { @@ -187,31 +189,6 @@ typedef struct { void lv_gltf_store_compiled_shader(lv_gltf_model_t * data, size_t identifier, lv_gltf_compiled_shader_t * shader); lv_gltf_compiled_shader_t * lv_gltf_get_compiled_shader(lv_gltf_model_t * data, size_t identifier); -/** - * @brief Load the gltf file at the specified filepath - * - * @param gltf_path The gltf filename - * @param ret_data Pointer to the data container that will be populated. - * @param shaders Pointer to the shader cache object this file uses. - */ -lv_gltf_model_t * -lv_gltf_data_load_from_file(const char * file_path, - lv_opengl_shader_manager_t * shader_manager); - -/** - * @brief Load the gltf file encoded within the supplied byte array - * - * @param gltf_path The gltf filename - * @param gltf_data_size if gltf_path is instead a byte array, pass the size of that array in through this variable (or 0 if it's a file path). - * @param ret_data Pointer to the data container that will be populated. - * @param shaders Pointer to the shader cache object this file uses. - */ - -lv_gltf_model_t * -lv_gltf_data_load_from_bytes(const uint8_t * data, size_t data_size, - lv_opengl_shader_manager_t * shader_manager); - - /** * @brief Retrieve the radius of the GLTF data object. * @@ -221,13 +198,6 @@ lv_gltf_data_load_from_bytes(const uint8_t * data, size_t data_size, double lv_gltf_data_get_radius(const lv_gltf_model_t * model); -/** - * @brief Destroy a GLTF data object and free associated resources. - * - * @param _data Pointer to the lv_gltf_data_t object to be destroyed. - */ -void lv_gltf_data_delete(lv_gltf_model_t * _data); - /** * @brief Copy the bounds information from one GLTF data object to another. * @@ -247,6 +217,10 @@ void lv_gltf_data_rgb_to_bgr(uint8_t * pixel_buffer, size_t byte_total_count, bool has_alpha); +lv_result_t lv_gltf_model_add_viewer(lv_gltf_model_t * model, lv_obj_t * viewer); +void lv_gltf_model_remove_viewer(lv_gltf_model_t * model, lv_obj_t * target_viewer); +void lv_gltf_model_invalidate(lv_gltf_model_t * model); + #endif /*LV_USE_GLTF*/ #ifdef __cplusplus diff --git a/src/libs/gltf/gltf_data/lv_gltf_data_internal.hpp b/src/libs/gltf/gltf_data/lv_gltf_data_internal.hpp index 648ca58c55..d0d5b602f8 100644 --- a/src/libs/gltf/gltf_data/lv_gltf_data_internal.hpp +++ b/src/libs/gltf/gltf_data/lv_gltf_data_internal.hpp @@ -8,11 +8,10 @@ #include "../gltf_view/lv_gltf.h" #include "lv_gltf_data_internal.h" -#include "../../../misc/lv_array.h" #include "../../../drivers/opengles/lv_opengles_private.h" #include "../../../misc/lv_types.h" -#include "../../../misc/lv_ll.h" +#include "../../../misc/lv_array.h" #include "../../../misc/lv_event.h" #ifdef __cplusplus @@ -43,8 +42,6 @@ using NodePairVector = std::vector; using NodeDistanceVector = std::vector; // Map of uint32_t to NodePairVector using MaterialIndexMap = std::map; -// Map of Node Pointers to Transforms -using NodeTransformMap = std::map; // Map of Nodes by string (name) using StringNodeMap = std::map; // Map of Nodes by string (name) @@ -53,6 +50,10 @@ using NodeIntMap = std::map; using NodeVector = std::vector; // Map of Node Index to Map of Prim Index to CenterXYZ+RadiusW Vec4 using NodePrimCenterMap = std::map >; +// Map of Node Pointers to Transforms +using NodeTransformMap = std::map; + +using IbmBySkinThenNodeMap = std::map>; #define LV_GLTF_NODE_CHANNEL_X 0 #define LV_GLTF_NODE_CHANNEL_Y 1 @@ -105,13 +106,14 @@ struct _lv_gltf_model_t { fastgltf::Asset asset; lv_array_t nodes; NodeVector node_by_light_index; - NodeTransformMap node_transform_cache; + NodeTransformMap transforms; MaterialIndexMap opaque_nodes_by_material_index; MaterialIndexMap blended_nodes_by_material_index; + IbmBySkinThenNodeMap ibm_by_skin_then_node; std::vector validated_skins; std::vector skin_tex; NodePrimCenterMap local_mesh_to_center_points_by_primitive; - lv_gltf_t * viewer; + lv_array_t viewers; std::vector meshes; std::vector textures; @@ -128,7 +130,7 @@ struct _lv_gltf_model_t { size_t current_animation; size_t last_material_index; - uint32_t last_camera_index; + int32_t animation_speed_ratio; int32_t last_anim_num; float bound_radius; @@ -138,11 +140,9 @@ struct _lv_gltf_model_t { uint32_t last_tick; uint32_t camera; + bool transforms_changed; bool is_animation_enabled; bool last_pass_was_transmission; - bool last_frame_was_antialiased; - bool last_frame_no_motion; - bool _last_frame_no_motion; bool write_ops_pending; bool write_ops_flushed; struct _lv_gltf_model_t * linked_view_source; @@ -274,48 +274,6 @@ void lv_gltf_data_add_opaque_node_primitive(lv_gltf_model_t * data, size_t index void lv_gltf_data_add_blended_node_primitive(lv_gltf_model_t * data, size_t mesh_index, fastgltf::Node * node, size_t primitive_index); -/** - * @brief Set the cached transformation matrix for a specific node in the GLTF model data. - * - * @param D Pointer to the lv_gltf_data_t object containing the model data. - * @param N Pointer to the NodePtr representing the node for which to set the transformation. - * @param M The transformation matrix to cache. - */ -void lv_gltf_data_set_cached_transform(lv_gltf_model_t * data, fastgltf::Node * node, fastgltf::math::fmat4x4 M); - -/** - * @brief Clear the transformation cache for the GLTF model data. - * - * @param D Pointer to the lv_gltf_data_t object containing the model data. - */ -void lv_gltf_data_clear_transform_cache(lv_gltf_model_t * data); - -/** - * @brief Retrieve the cached transformation matrix for a specific node in the GLTF model data. - * - * @param D Pointer to the lv_gltf_data_t object containing the model data. - * @param N Pointer to the NodePtr representing the node for which to retrieve the transformation. - * @return The cached transformation matrix. - */ -fastgltf::math::fmat4x4 lv_gltf_data_get_cached_transform(lv_gltf_model_t * data, fastgltf::Node * node); - -/** - * @brief Check if a cached transformation matrix exists for a given node. - * - * @param D Pointer to the lv_gltf_data_t object containing the model data. - * @param N Pointer to the NodePtr representing the node for which to retrieve the transformation. - * @return true if a cache item exists, false otherwise - int32_t*/ -bool lv_gltf_data_has_cached_transform(lv_gltf_model_t * data, fastgltf::Node * node); - -/** - * @brief Check if the transformation cache is empty. - * - * @param D Pointer to the lv_gltf_data_t object containing the model data. - * @return True if the transformation cache is empty, false otherwise. - */ -bool lv_gltf_data_transform_cache_is_empty(lv_gltf_model_t * data); - /** * @brief Retrieve the size of the skins in the GLTF model data. * @@ -361,7 +319,7 @@ lv_gltf_mesh_data_t * lv_gltf_get_new_meshdata(lv_gltf_model_t * _data); lv_gltf_model_t * lv_gltf_data_create_internal(const char * gltf_path, fastgltf::Asset); lv_gltf_model_t * lv_gltf_data_load_internal(const void * data_source, size_t data_size, - lv_opengl_shader_manager_t * shaders); + lv_gltf_model_loader_t * loader); fastgltf::math::fvec4 lv_gltf_get_primitive_centerpoint(lv_gltf_model_t * data, fastgltf::Mesh & mesh, uint32_t prim_num); @@ -405,6 +363,16 @@ lv_gltf_model_node_t * lv_gltf_model_node_get_by_internal_node(lv_gltf_model_t * void lv_gltf_model_send_new_values(lv_gltf_model_t * model); +void lv_gltf_model_set_transforms(lv_gltf_model_t * model, fastgltf::Node * node, fastgltf::math::fmat4x4 M); + +void lv_gltf_model_clear_transforms(lv_gltf_model_t * model); + +fastgltf::math::fmat4x4 lv_gltf_data_get_node_transform(lv_gltf_model_t * model, fastgltf::Node * node); + +bool lv_gltf_model_has_node_transform(lv_gltf_model_t * model, fastgltf::Node * node); + +bool lv_gltf_model_needs_transforms(lv_gltf_model_t * model); + #endif diff --git a/src/libs/gltf/gltf_data/lv_gltf_model.h b/src/libs/gltf/gltf_data/lv_gltf_model.h index 5c4608ac86..74a9750534 100644 --- a/src/libs/gltf/gltf_data/lv_gltf_model.h +++ b/src/libs/gltf/gltf_data/lv_gltf_model.h @@ -7,10 +7,40 @@ #include "lv_gltf_model_node.h" #include "../../../misc/lv_types.h" #include "../../../misc/lv_event.h" +#include "lv_gltf_model_loader.h" #ifdef __cplusplus extern "C" { #endif + +/** + * Load a glTF model from a file + * @param file_path path to the glTF file to load + * @param loader pointer to the glTF model loader instance, or NULL to create a new one + * @return pointer to the loaded glTF model, or NULL on failure + * @note If loader is NULL, an internal loader will be created and managed automatically + */ +lv_gltf_model_t * lv_gltf_data_load_from_file(const char * file_path, + lv_gltf_model_loader_t * loader); + +/** + * Load a glTF model from a byte array + * @param data pointer to the glTF data buffer + * @param data_size size of the data buffer in bytes + * @param loader pointer to the glTF model loader instance, or NULL to create a new one + * @return pointer to the loaded glTF model, or NULL on failure + * @note If loader is NULL, an internal loader will be created and managed automatically + */ +lv_gltf_model_t * lv_gltf_data_load_from_bytes(const uint8_t * data, size_t data_size, + lv_gltf_model_loader_t * loader); + + +/** + * Delete a glTF model + * @param model the gltf model to delete + */ +void lv_gltf_model_delete(lv_gltf_model_t * model); + /** * @brief Get the number of images in the glTF model * @@ -119,6 +149,27 @@ bool lv_gltf_model_is_animation_paused(lv_gltf_model_t * model); */ size_t lv_gltf_model_get_animation(lv_gltf_model_t * model); +/** + * Set the animation speed ratio + * + * The actual ratio is the value parameter / LV_GLTF_ANIM_SPEED_NORMAL + * Values greater than LV_GLTF_ANIM_SPEED_NORMAL will speed-up the animation + * Values less than LV_GLTF_ANIM_SPEED_NORMAL will slow down the animation + * + * @param model pointer to a glTF model + * @param value speed-up ratio of the animation + */ +void lv_gltf_model_set_animation_speed(lv_gltf_model_t * model, uint32_t value); + +/** + * Get the animation speed ratio + * + * The actual ratio is the return value / LV_GLTF_ANIM_SPEED_NORMAL + * + * @param model pointer to a glTF model + */ +uint32_t lv_gltf_model_get_animation_speed(const lv_gltf_model_t * model); + #ifdef __cplusplus } /*extern "C"*/ #endif diff --git a/src/libs/gltf/gltf_data/lv_gltf_model_loader.c b/src/libs/gltf/gltf_data/lv_gltf_model_loader.c new file mode 100644 index 0000000000..be22547481 --- /dev/null +++ b/src/libs/gltf/gltf_data/lv_gltf_model_loader.c @@ -0,0 +1,101 @@ +/** + * @file lv_gltf_model_loader.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_gltf_model_loader.h" +#include "lv_gltf_data_internal.h" +#if LV_USE_GLTF + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + uint32_t hash; + uint32_t id; +} lv_opengl_texture_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +static inline lv_rb_compare_res_t opengl_texture_compare_cb(const lv_opengl_texture_t * lhs, + const lv_opengl_texture_t * rhs) +{ + return lhs->hash - rhs->hash; +} + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_gltf_model_loader_t * lv_gltf_model_loader_create(void) +{ + lv_gltf_model_loader_t * loader = lv_zalloc(sizeof(*loader)); + if(!loader) { + return NULL; + } + + lv_rb_init(&loader->textures_map, + (lv_rb_compare_t)opengl_texture_compare_cb, + sizeof(lv_opengl_texture_t)); + return loader; +} + +void lv_gltf_model_loader_delete(lv_gltf_model_loader_t * loader) +{ + if(!loader) { + return; + } + lv_rb_destroy(&loader->textures_map); + lv_free(loader); +} + +void lv_gltf_model_loader_store_texture(lv_gltf_model_loader_t * loader, uint32_t texture_hash, uint32_t texture_id) +{ + lv_opengl_texture_t key = { .id = texture_id, .hash = texture_hash }; + lv_rb_node_t * node = lv_rb_insert(&loader->textures_map, &key); + if(!node) { + LV_LOG_WARN("Failed to cache texture hash: %d id: %d", + texture_hash, texture_id); + return; + } + lv_memcpy(node->data, &key, sizeof(key)); +} + + +uint32_t lv_gltf_model_loader_get_texture(lv_gltf_model_loader_t * loader, uint32_t texture_hash) +{ + lv_opengl_texture_t key = { .hash = texture_hash }; + lv_rb_node_t * node = lv_rb_find(&loader->textures_map, &key); + if(!node) { + LV_LOG_INFO("Couldn't find texture with hash %" LV_PRIu32 " in cache", + texture_hash); + return GL_NONE; + } + return ((lv_opengl_texture_t *)node->data)->id; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + + +#endif /*LV_USE_GLTF*/ diff --git a/src/libs/gltf/gltf_data/lv_gltf_model_loader.h b/src/libs/gltf/gltf_data/lv_gltf_model_loader.h new file mode 100644 index 0000000000..50bfe38b5e --- /dev/null +++ b/src/libs/gltf/gltf_data/lv_gltf_model_loader.h @@ -0,0 +1,75 @@ +/** + * @file lv_gltf_model_loader.h + * + */ + +#ifndef LV_GLTF_MODEL_LOADER_H +#define LV_GLTF_MODEL_LOADER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "../../../lv_conf_internal.h" + +#if LV_USE_GLTF + +#include "../../../misc/lv_types.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct _lv_gltf_model_loader_t lv_gltf_model_loader_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a new glTF model loader instance + * @return pointer to the newly created glTF model loader instance + */ +lv_gltf_model_loader_t * lv_gltf_model_loader_create(void); + +/** + * Store a texture ID associated with a texture hash in the loader + * @param loader pointer to the glTF model loader instance + * @param texture_hash hash value identifying the texture + * @param texture_id the texture ID to associate with the hash + */ +void lv_gltf_model_loader_store_texture(lv_gltf_model_loader_t * loader, uint32_t texture_hash, uint32_t texture_id); + +/** + * Retrieve a texture ID by its hash from the loader + * @param loader pointer to the glTF model loader instance + * @param texture_hash hash value of the texture to retrieve + * @return the texture ID associated with the hash, or 0 if not found + */ +uint32_t lv_gltf_model_loader_get_texture(lv_gltf_model_loader_t * loader, uint32_t texture_hash); + +/** + * Delete a glTF model loader instance and free its resources + * @param loader pointer to the glTF model loader instance to delete + */ +void lv_gltf_model_loader_delete(lv_gltf_model_loader_t * loader); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_GLTF*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_GLTF_MODEL_LOADER_H*/ diff --git a/src/libs/gltf/gltf_data/lv_gltf_model_node.cpp b/src/libs/gltf/gltf_data/lv_gltf_model_node.cpp index f2367d9617..c1b9f18ef0 100644 --- a/src/libs/gltf/gltf_data/lv_gltf_model_node.cpp +++ b/src/libs/gltf/gltf_data/lv_gltf_model_node.cpp @@ -12,14 +12,12 @@ #include #include "fastgltf/types.hpp" -#include "lv_gltf_model.h" #include "../../../misc/lv_array.h" #include "../../../misc/lv_assert.h" #include "../../../misc/lv_types.h" #include "../../../misc/lv_event_private.h" #include "../../../stdlib/lv_string.h" #include "../../../stdlib/lv_sprintf.h" -#include "../../../core/lv_obj_pos.h" /********************* * DEFINES @@ -170,6 +168,7 @@ void lv_gltf_model_send_new_values(lv_gltf_model_t * model) node->read_attrs->value_changed = false; } model->write_ops_flushed = false; + model->transforms_changed = false; } lv_event_dsc_t * lv_gltf_model_node_add_event_cb(lv_gltf_model_node_t * node, lv_event_cb_t cb, @@ -335,7 +334,7 @@ lv_result_t lv_gltf_model_node_get_euler_rotation(lv_event_t * e, lv_3dpoint_t * static void invalidate_model(lv_gltf_model_t * model) { - lv_obj_invalidate((lv_obj_t *)model->viewer); + lv_gltf_model_invalidate(model); model->write_ops_pending = true; } diff --git a/src/libs/gltf/gltf_data/lv_gltf_model_node.h b/src/libs/gltf/gltf_data/lv_gltf_model_node.h index 8a60151cb3..3e1c05d0a0 100644 --- a/src/libs/gltf/gltf_data/lv_gltf_model_node.h +++ b/src/libs/gltf/gltf_data/lv_gltf_model_node.h @@ -26,6 +26,15 @@ extern "C" { * DEFINES *********************/ +#define LV_GLTF_ANIM_SPEED_TENTH 100 +#define LV_GLTF_ANIM_SPEED_QUARTER 250 +#define LV_GLTF_ANIM_SPEED_HALF 500 +#define LV_GLTF_ANIM_SPEED_NORMAL 1000 +#define LV_GLTF_ANIM_SPEED_2X 2000 +#define LV_GLTF_ANIM_SPEED_3X 3000 +#define LV_GLTF_ANIM_SPEED_4X 4000 +#define LV_GLTF_DEFAULT_CAMERA 0 + /********************** * TYPEDEFS **********************/ diff --git a/src/libs/gltf/gltf_view/lv_gltf.h b/src/libs/gltf/gltf_view/lv_gltf.h index b9cc2b35ad..c27df476cd 100644 --- a/src/libs/gltf/gltf_view/lv_gltf.h +++ b/src/libs/gltf/gltf_view/lv_gltf.h @@ -27,15 +27,6 @@ extern "C" { * DEFINES *********************/ -#define LV_GLTF_ANIM_SPEED_TENTH 100 -#define LV_GLTF_ANIM_SPEED_QUARTER 250 -#define LV_GLTF_ANIM_SPEED_HALF 500 -#define LV_GLTF_ANIM_SPEED_NORMAL 1000 -#define LV_GLTF_ANIM_SPEED_2X 2000 -#define LV_GLTF_ANIM_SPEED_3X 3000 -#define LV_GLTF_ANIM_SPEED_4X 4000 -#define LV_GLTF_DEFAULT_CAMERA 0 - /********************** * TYPEDEFS **********************/ @@ -90,6 +81,19 @@ lv_gltf_model_t * lv_gltf_load_model_from_file(lv_obj_t * obj, const char * path */ lv_gltf_model_t * lv_gltf_load_model_from_bytes(lv_obj_t * obj, const uint8_t * bytes, size_t len); +/** + * Add a glTF model to the viewer. + * + * Contrary to `lv_gltf_load_model_from_file` and `lv_gltf_load_model_from_bytes`, the model + * is owned by the caller of this function meaning that it's the caller's responsibility + * to delete the model when it is no longer needed, that is, the model must outlive the viewer's lifetime. + * + * @param obj pointer to a glTF viewer object + * @param model glTF model to add to the viewer + * @return LV_RESULT_OK if the model was added to the viewer or LV_RESULT_INVALID on failure + */ +lv_result_t lv_gltf_add_model(lv_obj_t * obj, lv_gltf_model_t * model); + /** * Get the number of models loaded in the glTF viewer * @param obj pointer to a glTF viewer object @@ -103,7 +107,7 @@ size_t lv_gltf_get_model_count(lv_obj_t * obj); * @param id index of the model to retrieve (0-based) * @return pointer to the model at the specified index, or NULL if index is invalid */ -lv_gltf_model_t * lv_gltf_get_model_by_index(lv_obj_t * obj, size_t id); +lv_gltf_model_t * lv_gltf_get_model_by_index(const lv_obj_t * obj, size_t id); /** * Get the primary model from the glTF viewer @@ -112,7 +116,7 @@ lv_gltf_model_t * lv_gltf_get_model_by_index(lv_obj_t * obj, size_t id); * @param obj pointer to a glTF viewer object * @return pointer to the primary model, or NULL if no models are loaded */ -lv_gltf_model_t * lv_gltf_get_primary_model(lv_obj_t * obj); +lv_gltf_model_t * lv_gltf_get_primary_model(const lv_obj_t * obj); /** * Set the yaw (horizontal rotation) of the camera @@ -269,6 +273,8 @@ uint32_t lv_gltf_get_camera(const lv_obj_t * obj); uint32_t lv_gltf_get_camera_count(const lv_obj_t * obj); /** + * DEPRECATED. See `lv_gltf_model_set_animation_speed` + * * Set the animation speed ratio * * The actual ratio is the value parameter / LV_GLTF_ANIM_SPEED_NORMAL @@ -281,6 +287,8 @@ uint32_t lv_gltf_get_camera_count(const lv_obj_t * obj); void lv_gltf_set_animation_speed(lv_obj_t * obj, uint32_t value); /** + * DEPRECATED. See `lv_gltf_model_get_animation_speed` + * * Get the animation speed ratio * * The actual ratio is the return value / LV_GLTF_ANIM_SPEED_NORMAL diff --git a/src/libs/gltf/gltf_view/lv_gltf_view.cpp b/src/libs/gltf/gltf_view/lv_gltf_view.cpp index 71d0f9c91a..61e4faf566 100644 --- a/src/libs/gltf/gltf_view/lv_gltf_view.cpp +++ b/src/libs/gltf/gltf_view/lv_gltf_view.cpp @@ -13,6 +13,7 @@ #include "../gltf_data/lv_gltf_model.h" #include "../gltf_data/lv_gltf_data_internal.hpp" +#include "../gltf_data/lv_gltf_data_internal.h" #include "../../../draw/lv_draw_3d.h" #include "../fastgltf/lv_fastgltf.hpp" #include "../../../core/lv_obj_class_private.h" @@ -42,7 +43,7 @@ * STATIC PROTOTYPES **********************/ -static lv_gltf_model_t * lv_gltf_add_model(lv_gltf_t * viewer, lv_gltf_model_t * model); +static lv_gltf_model_t * add_model(lv_gltf_t * viewer, lv_gltf_model_t * model, bool owned); static void lv_gltf_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); static void lv_gltf_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); static void lv_gltf_event(const lv_obj_class_t * class_p, lv_event_t * e); @@ -120,8 +121,8 @@ lv_gltf_model_t * lv_gltf_load_model_from_file(lv_obj_t * obj, const char * path } } - lv_gltf_model_t * model = lv_gltf_data_load_from_file(path, &viewer->shader_manager); - return lv_gltf_add_model(viewer, model); + lv_gltf_model_t * model = lv_gltf_data_load_from_file(path, viewer->model_loader); + return add_model(viewer, model, true); } lv_gltf_model_t * lv_gltf_load_model_from_bytes(lv_obj_t * obj, const uint8_t * bytes, size_t len) @@ -137,9 +138,28 @@ lv_gltf_model_t * lv_gltf_load_model_from_bytes(lv_obj_t * obj, const uint8_t * } } - lv_gltf_model_t * model = lv_gltf_data_load_from_bytes(bytes, len, &viewer->shader_manager); - return lv_gltf_add_model(viewer, model); + lv_gltf_model_t * model = lv_gltf_data_load_from_bytes(bytes, len, viewer->model_loader); + return add_model(viewer, model, true); } + +lv_result_t lv_gltf_add_model(lv_obj_t * obj, lv_gltf_model_t * model) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + if(!model) { + return LV_RESULT_INVALID; + } + + lv_gltf_t * viewer = (lv_gltf_t *)obj; + + if(!viewer->environment) { + lv_result_t res = create_default_environment(viewer); + if(res != LV_RESULT_OK) { + return LV_RESULT_INVALID; + } + } + return add_model(viewer, model, false) != NULL ? LV_RESULT_OK : LV_RESULT_INVALID; +} + void lv_gltf_set_environment(lv_obj_t * obj, lv_gltf_environment_t * env) { LV_ASSERT_OBJ(obj, MY_CLASS); @@ -159,34 +179,31 @@ void lv_gltf_set_environment(lv_obj_t * obj, lv_gltf_environment_t * env) size_t lv_gltf_get_model_count(lv_obj_t * obj) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); return lv_array_size(&((lv_gltf_t *)obj)->models); } -lv_gltf_model_t * lv_gltf_get_model_by_index(lv_obj_t * obj, size_t id) +lv_gltf_model_t * lv_gltf_get_model_by_index(const lv_obj_t * obj, size_t id) { - - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *) obj; if(id >= lv_array_size(&viewer->models)) { return NULL; } - return *(lv_gltf_model_t **)lv_array_at(&((lv_gltf_t *)obj)->models, id); + lv_gltf_model_data_t * modeld = *(lv_gltf_model_data_t **)lv_array_at(&((lv_gltf_t *)obj)->models, id); + LV_ASSERT_NULL(modeld); + return modeld->model; } -lv_gltf_model_t * lv_gltf_get_primary_model(lv_obj_t * obj) +lv_gltf_model_t * lv_gltf_get_primary_model(const lv_obj_t * obj) { - return lv_gltf_get_model_by_index(obj, 0); } void lv_gltf_set_yaw(lv_obj_t * obj, float yaw) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; viewer->desc.yaw = yaw; @@ -195,7 +212,6 @@ void lv_gltf_set_yaw(lv_obj_t * obj, float yaw) float lv_gltf_get_yaw(const lv_obj_t * obj) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; return viewer->desc.yaw; @@ -203,7 +219,6 @@ float lv_gltf_get_yaw(const lv_obj_t * obj) void lv_gltf_set_pitch(lv_obj_t * obj, float pitch) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; viewer->desc.pitch = pitch; @@ -212,7 +227,6 @@ void lv_gltf_set_pitch(lv_obj_t * obj, float pitch) float lv_gltf_get_pitch(const lv_obj_t * obj) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; return viewer->desc.pitch; @@ -220,7 +234,6 @@ float lv_gltf_get_pitch(const lv_obj_t * obj) void lv_gltf_set_fov(lv_obj_t * obj, float value) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; viewer->desc.fov = value; @@ -229,7 +242,6 @@ void lv_gltf_set_fov(lv_obj_t * obj, float value) float lv_gltf_get_fov(const lv_obj_t * obj) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; return viewer->desc.fov; @@ -237,7 +249,6 @@ float lv_gltf_get_fov(const lv_obj_t * obj) void lv_gltf_set_distance(lv_obj_t * obj, float value) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; viewer->desc.distance = value; @@ -246,7 +257,6 @@ void lv_gltf_set_distance(lv_obj_t * obj, float value) float lv_gltf_get_distance(const lv_obj_t * obj) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; return viewer->desc.distance; @@ -254,37 +264,41 @@ float lv_gltf_get_distance(const lv_obj_t * obj) float lv_gltf_get_world_distance(const lv_obj_t * obj) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; lv_gltf_view_desc_t * view_desc = &viewer->desc; if(viewer->models.size == 0) { return 0.0f; } - lv_gltf_model_t * model = *(lv_gltf_model_t **)lv_array_at(&viewer->models, 0); + lv_gltf_model_data_t * modeld = *(lv_gltf_model_data_t **)lv_array_at(&viewer->models, 0); + lv_gltf_model_t * model = modeld->model; return (lv_gltf_data_get_radius(model) * LV_GLTF_DISTANCE_SCALE_FACTOR) * view_desc->distance; } void lv_gltf_set_animation_speed(lv_obj_t * obj, uint32_t value) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); - lv_gltf_t * viewer = (lv_gltf_t *)obj; - viewer->desc.animation_speed_ratio = value; - lv_obj_invalidate(obj); + LV_LOG_WARN("Deprecated. lv_gltf_set_animation_speed should now be set on the model directly via `lv_gltf_model_set_animation_speed`. Setting it on the main model as a fallback"); + lv_gltf_model_t * model = lv_gltf_get_primary_model(obj); + if(!model) { + return; + } + lv_gltf_model_set_animation_speed(model, value); } uint32_t lv_gltf_get_animation_speed(const lv_obj_t * obj) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); - lv_gltf_t * viewer = (lv_gltf_t *)obj; - return viewer->desc.animation_speed_ratio; + LV_LOG_WARN("Deprecated. lv_gltf_get_animation_speed should now be called on the model directly via `lv_gltf_model_get_animation_speed`. Getting the animation speed from the main model as a fallback"); + lv_gltf_model_t * model = lv_gltf_get_primary_model(obj); + if(!model) { + return 0; + } + return lv_gltf_model_get_animation_speed(model); } void lv_gltf_set_focal_x(lv_obj_t * obj, float value) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; viewer->desc.focal_x = value; @@ -293,7 +307,6 @@ void lv_gltf_set_focal_x(lv_obj_t * obj, float value) float lv_gltf_get_focal_x(const lv_obj_t * obj) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; return viewer->desc.focal_x; @@ -301,7 +314,6 @@ float lv_gltf_get_focal_x(const lv_obj_t * obj) void lv_gltf_set_focal_y(lv_obj_t * obj, float value) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; viewer->desc.focal_y = value; @@ -310,7 +322,6 @@ void lv_gltf_set_focal_y(lv_obj_t * obj, float value) float lv_gltf_get_focal_y(const lv_obj_t * obj) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; return viewer->desc.focal_y; @@ -318,7 +329,6 @@ float lv_gltf_get_focal_y(const lv_obj_t * obj) void lv_gltf_set_focal_z(lv_obj_t * obj, float value) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; viewer->desc.focal_z = value; @@ -327,7 +337,6 @@ void lv_gltf_set_focal_z(lv_obj_t * obj, float value) float lv_gltf_get_focal_z(const lv_obj_t * obj) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; return viewer->desc.focal_z; @@ -335,7 +344,6 @@ float lv_gltf_get_focal_z(const lv_obj_t * obj) void lv_gltf_set_camera(lv_obj_t * obj, uint32_t value) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; @@ -343,7 +351,8 @@ void lv_gltf_set_camera(lv_obj_t * obj, uint32_t value) return; } - lv_gltf_model_t * model = *(lv_gltf_model_t **) lv_array_at(&viewer->models, 0); + lv_gltf_model_data_t * modeld = *(lv_gltf_model_data_t **)lv_array_at(&viewer->models, 0); + lv_gltf_model_t * model = modeld->model; if(value > model->asset.cameras.size()) { return; @@ -355,7 +364,6 @@ void lv_gltf_set_camera(lv_obj_t * obj, uint32_t value) uint32_t lv_gltf_get_camera(const lv_obj_t * obj) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; @@ -369,7 +377,6 @@ uint32_t lv_gltf_get_camera(const lv_obj_t * obj) uint32_t lv_gltf_get_camera_count(const lv_obj_t * obj) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; @@ -382,7 +389,6 @@ uint32_t lv_gltf_get_camera_count(const lv_obj_t * obj) void lv_gltf_set_antialiasing_mode(lv_obj_t * obj, lv_gltf_aa_mode_t value) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; viewer->desc.aa_mode = value; @@ -391,7 +397,6 @@ void lv_gltf_set_antialiasing_mode(lv_obj_t * obj, lv_gltf_aa_mode_t value) lv_gltf_aa_mode_t lv_gltf_get_antialiasing_mode(const lv_obj_t * obj) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; return viewer->desc.aa_mode; @@ -399,7 +404,6 @@ lv_gltf_aa_mode_t lv_gltf_get_antialiasing_mode(const lv_obj_t * obj) void lv_gltf_set_background_mode(lv_obj_t * obj, lv_gltf_bg_mode_t value) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; viewer->desc.bg_mode = value; @@ -408,7 +412,6 @@ void lv_gltf_set_background_mode(lv_obj_t * obj, lv_gltf_bg_mode_t value) lv_gltf_bg_mode_t lv_gltf_get_background_mode(const lv_obj_t * obj) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; return viewer->desc.bg_mode; @@ -416,7 +419,6 @@ lv_gltf_bg_mode_t lv_gltf_get_background_mode(const lv_obj_t * obj) void lv_gltf_set_background_blur(lv_obj_t * obj, uint32_t value) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; if(value > 100) { @@ -428,7 +430,6 @@ void lv_gltf_set_background_blur(lv_obj_t * obj, uint32_t value) uint32_t lv_gltf_get_background_blur(const lv_obj_t * obj) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; return viewer->desc.blur_bg * 100; @@ -436,7 +437,6 @@ uint32_t lv_gltf_get_background_blur(const lv_obj_t * obj) void lv_gltf_set_env_brightness(lv_obj_t * obj, uint32_t value) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; viewer->desc.env_pow = value / 100.; @@ -445,7 +445,6 @@ void lv_gltf_set_env_brightness(lv_obj_t * obj, uint32_t value) uint32_t lv_gltf_get_env_brightness(const lv_obj_t * obj) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; return viewer->desc.env_pow * 100; @@ -453,7 +452,6 @@ uint32_t lv_gltf_get_env_brightness(const lv_obj_t * obj) void lv_gltf_set_image_exposure(lv_obj_t * obj, float value) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; viewer->desc.exposure = value; @@ -462,14 +460,12 @@ void lv_gltf_set_image_exposure(lv_obj_t * obj, float value) float lv_gltf_get_image_exposure(const lv_obj_t * obj) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; return viewer->desc.exposure; } void lv_gltf_recenter(lv_obj_t * obj, lv_gltf_model_t * model) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; if(model == NULL) { @@ -485,7 +481,6 @@ void lv_gltf_recenter(lv_obj_t * obj, lv_gltf_model_t * model) lv_3dray_t lv_gltf_get_ray_from_2d_coordinate(lv_obj_t * obj, const lv_point_t * screen_pos) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; @@ -542,7 +537,6 @@ lv_result_t lv_intersect_ray_with_plane(const lv_3dray_t * ray, const lv_3dplane lv_3dplane_t lv_gltf_get_current_view_plane(lv_obj_t * obj, float distance) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; lv_3dplane_t outplane = {{0, 0, 0}, {0, 0, 0}}; @@ -561,7 +555,6 @@ lv_3dplane_t lv_gltf_get_current_view_plane(lv_obj_t * obj, float distance) lv_result_t lv_gltf_world_to_screen(lv_obj_t * obj, const lv_3dpoint_t world_pos, lv_point_t * screen_pos) { - LV_ASSERT_NULL(obj); LV_ASSERT_OBJ(obj, MY_CLASS); lv_gltf_t * viewer = (lv_gltf_t *)obj; @@ -589,19 +582,32 @@ lv_result_t lv_gltf_world_to_screen(lv_obj_t * obj, const lv_3dpoint_t world_pos * STATIC FUNCTIONS **********************/ -static lv_gltf_model_t * lv_gltf_add_model(lv_gltf_t * viewer, lv_gltf_model_t * model) +static lv_gltf_model_t * add_model(lv_gltf_t * viewer, lv_gltf_model_t * model, bool owned) { if(!model) { return NULL; } - if(lv_array_push_back(&viewer->models, &model) == LV_RESULT_INVALID) { - lv_gltf_data_delete(model); + lv_gltf_model_data_t model_data; + lv_memset(&model_data, 0, sizeof(model_data)); + model_data.model = model; + model_data.owned = owned; + lv_array_init(&model_data.skin_textures, 0, sizeof(GLuint)); + + if(lv_array_push_back(&viewer->models, &model_data) != LV_RESULT_OK) { + if(owned) { + lv_gltf_model_delete(model); + } + return NULL; + } + if(lv_gltf_model_add_viewer(model, (lv_obj_t *)viewer) != LV_RESULT_OK) { + if(owned) { + lv_gltf_model_delete(model); + } + lv_array_remove(&viewer->models, lv_array_size(&viewer->models) - 1); return NULL; } - model->viewer = viewer; lv_gltf_parse_model(viewer, model); - if(lv_array_size(&viewer->models) == 1) { lv_gltf_recenter((lv_obj_t *)viewer, model); } @@ -635,8 +641,6 @@ static void lv_gltf_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) view->camera_pos = fastgltf::math::fvec3(0.0f); view->texture.h_flip = false; view->texture.v_flip = true; - new(&view->ibm_by_skin_then_node) std::map>; - lv_opengl_shader_portions_t portions; lv_gltf_view_shader_get_src(&portions); char * vertex_shader = lv_gltf_view_shader_get_vertex(); @@ -645,7 +649,9 @@ static void lv_gltf_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) lv_free(vertex_shader); lv_free(frag_shader); - lv_array_init(&view->models, LV_GLTF_INITIAL_MODEL_CAPACITY, sizeof(lv_gltf_model_t *)); + view->model_loader = lv_gltf_model_loader_create(); + + lv_array_init(&view->models, LV_GLTF_INITIAL_MODEL_CAPACITY, sizeof(lv_gltf_model_data_t)); LV_TRACE_OBJ_CREATE("end"); } @@ -675,17 +681,22 @@ static void lv_gltf_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) LV_UNUSED(class_p); lv_gltf_t * view = (lv_gltf_t *)obj; lv_opengl_shader_manager_deinit(&view->shader_manager); - using IbmBySkinThenNodeMap = std::map>; - view->ibm_by_skin_then_node.~IbmBySkinThenNodeMap(); const size_t n = lv_array_size(&view->models); for(size_t i = 0; i < n; ++i) { - lv_gltf_data_delete(*(lv_gltf_model_t **)lv_array_at(&view->models, i)); + lv_gltf_model_data_t * model_data = (lv_gltf_model_data_t *)lv_array_at(&view->models, i); + if(model_data->owned) { + lv_gltf_model_delete(model_data->model); + } + else { + lv_gltf_model_remove_viewer(model_data->model, obj); + } } lv_array_deinit(&view->models); if(view->environment && view->owns_environment) { lv_gltf_environment_delete(view->environment); } + lv_gltf_model_loader_delete(view->model_loader); lv_display_t * disp = lv_obj_get_display(obj); LV_ASSERT_NULL(disp); lv_display_remove_event_cb_with_user_data(disp, display_refr_end_event_cb, obj); @@ -710,7 +721,6 @@ static void lv_gltf_view_desc_init(lv_gltf_view_desc_t * desc) desc->bg_mode = LV_GLTF_BG_MODE_ENVIRONMENT; desc->aa_mode = LV_GLTF_AA_MODE_OFF; desc->fov = 45.f; - desc->animation_speed_ratio = LV_GLTF_ANIM_SPEED_NORMAL; desc->frame_was_antialiased = false; } static void lv_gltf_parse_model(lv_gltf_t * viewer, lv_gltf_model_t * model) @@ -734,7 +744,7 @@ static void lv_gltf_parse_model(lv_gltf_t * viewer, lv_gltf_model_t * model) model->asset, ibm_accessor, [&](fastgltf::math::fmat4x4 _matrix, std::size_t idx) { auto & joint_node = model->asset.nodes[skin.joints[idx]]; - viewer->ibm_by_skin_then_node[skin_index][&joint_node] = _matrix; + model->ibm_by_skin_then_node[skin_index][&joint_node] = _matrix; }); } } @@ -856,8 +866,9 @@ static void display_refr_end_event_cb(lv_event_t * e) lv_gltf_t * viewer = (lv_gltf_t *) lv_event_get_user_data(e); uint32_t model_count = lv_array_size(&viewer->models); for(uint32_t i = 0; i < model_count; ++i) { - lv_gltf_model_t * model = *(lv_gltf_model_t **)lv_array_at(&viewer->models, i); - lv_gltf_model_send_new_values(model); + lv_gltf_model_data_t * modeld = *(lv_gltf_model_data_t **)lv_array_at(&viewer->models, i); + lv_gltf_model_send_new_values(modeld->model); } } + #endif /*LV_USE_GLTF*/ diff --git a/src/libs/gltf/gltf_view/lv_gltf_view_internal.h b/src/libs/gltf/gltf_view/lv_gltf_view_internal.h index 559a4a7bd7..7a9ccc560f 100644 --- a/src/libs/gltf/gltf_view/lv_gltf_view_internal.h +++ b/src/libs/gltf/gltf_view/lv_gltf_view_internal.h @@ -95,7 +95,6 @@ typedef struct { float focal_y; float focal_z; bool frame_was_antialiased; - int32_t animation_speed_ratio; lv_gltf_aa_mode_t aa_mode; lv_gltf_bg_mode_t bg_mode; float blur_bg; /** How much to blur the environment background, between 0.0 and 1.0 */ @@ -156,6 +155,17 @@ typedef struct { #include #include +typedef struct { + lv_gltf_model_t * model; + lv_array_t skin_textures; + uint32_t last_camera_index; + bool owned; + bool last_frame_was_antialiased; + bool last_frame_no_motion; + bool _last_frame_no_motion; +} lv_gltf_model_data_t; + + struct _lv_gltf_t { lv_3dtexture_t texture; lv_array_t models; @@ -163,13 +173,13 @@ struct _lv_gltf_t { lv_gltf_view_desc_t desc; lv_gltf_view_desc_t last_desc; lv_opengl_shader_manager_t shader_manager; + lv_gltf_model_loader_t * model_loader; lv_gltf_environment_t * environment; fastgltf::math::fmat4x4 view_matrix; fastgltf::math::fmat4x4 projection_matrix; fastgltf::math::fmat4x4 view_projection_matrix; fastgltf::math::fvec3 camera_pos; - std::map> ibm_by_skin_then_node; bool owns_environment; }; diff --git a/src/libs/gltf/gltf_view/lv_gltf_view_render.cpp b/src/libs/gltf/gltf_view/lv_gltf_view_render.cpp index 28cc95c188..a67a764e4b 100644 --- a/src/libs/gltf/gltf_view/lv_gltf_view_render.cpp +++ b/src/libs/gltf/gltf_view/lv_gltf_view_render.cpp @@ -72,18 +72,19 @@ struct lv_gltf_matrices_saver_t { * STATIC PROTOTYPES **********************/ -static GLuint lv_gltf_view_render_model(lv_gltf_t * viewer, lv_gltf_model_t * model, bool prepare_bg, bool dirty); +static GLuint lv_gltf_view_render_model(lv_gltf_t * viewer, lv_gltf_model_data_t * modeld, bool is_first_model, + bool dirty); static void lv_gltf_view_push_opengl_state(lv_opengl_state_t * state); static void lv_gltf_view_pop_opengl_state(const lv_opengl_state_t * state); static void setup_finish_frame(void); -static void render_materials(lv_gltf_t * viewer, lv_gltf_model_t * gltf_data, const MaterialIndexMap & map, +static void render_materials(lv_gltf_t * viewer, lv_gltf_model_data_t * modeld, const MaterialIndexMap & map, bool is_transmission_pass); -static void render_skins(lv_gltf_t * viewer, lv_gltf_model_t * gltf_data); +static void render_skins(lv_gltf_t * viewer, lv_gltf_model_data_t * modeld); static lv_result_t render_primary_output(lv_gltf_t * viewer, const lv_gltf_renwin_state_t * state, int32_t texture_w, int32_t texture_h, bool prepare_bg); -static void lv_gltf_view_recache_all_transforms(lv_gltf_model_t * gltf_data); +static void lv_gltf_view_recache_all_transforms(lv_gltf_model_data_t * gltf_data); static fastgltf::math::fmat3x3 create_texture_transform_matrix(std::unique_ptr & transform); static fastgltf::math::nvec4 color_convert_to_srgb(fastgltf::math::nvec4 color); static void render_uniform_color_alpha(GLint uniform_loc, fastgltf::math::nvec4 color); @@ -91,20 +92,20 @@ static void render_uniform_color(GLint uniform_loc, fastgltf::math::nvec3 color) static uint32_t render_texture(uint32_t tex_unit, uint32_t tex_name, int32_t tex_coord_index, std::unique_ptr & tex_transform, GLint sampler, GLint uv_set, GLint uv_transform); -static void draw_primitive(int32_t prim_num, lv_gltf_t * viewer, lv_gltf_model_t * gltf_data, fastgltf::Node & node, +static void draw_primitive(int32_t prim_num, lv_gltf_t * viewer, lv_gltf_model_data_t * modeld, fastgltf::Node & node, std::size_t mesh_index, const fastgltf::math::fmat4x4 & matrix, const lv_gltf_environment_t * env_tex, bool is_transmission_pass); -static bool setup_primitive(int32_t prim_num, lv_gltf_t * viewer, lv_gltf_model_t * gltf_data, - fastgltf::Node & node, +static bool setup_primitive(int32_t prim_num, lv_gltf_t * viewer, lv_gltf_model_data_t * modeld, fastgltf::Node & node, std::size_t mesh_index, const fastgltf::math::fmat4x4 & matrix, const lv_gltf_environment_t * env_tex, bool is_transmission_pass); -static bool draw_material(lv_gltf_t * viewer, const lv_gltf_uniform_locations_t * uniforms, lv_gltf_model_t * model, +static bool draw_material(lv_gltf_t * viewer, const lv_gltf_uniform_locations_t * uniforms, + lv_gltf_model_data_t * modeld, lv_gltf_primitive_t * _prim_data, size_t materialIndex, bool is_transmission_pass, GLuint program, uint32_t * tex_num); -static void draw_lights(lv_gltf_model_t * model, GLuint program); +static void draw_lights(lv_gltf_model_data_t * modeld, GLuint program); static lv_gltf_renwin_state_t setup_opaque_output(uint32_t texture_width, uint32_t texture_height); static void setup_cleanup_opengl_output(lv_gltf_renwin_state_t * state); @@ -141,20 +142,20 @@ GLuint lv_gltf_view_render(lv_gltf_t * viewer) if(n == 0) { return GL_NONE; } - lv_gltf_model_t * model = *(lv_gltf_model_t **)lv_array_at(&viewer->models, 0); + lv_gltf_model_data_t * model_data = (lv_gltf_model_data_t *)lv_array_at(&viewer->models, 0); bool dirty = lv_memcmp(&viewer->last_desc, &viewer->desc, sizeof(viewer->desc)) != 0; for(size_t i = 0; i < n; ++i) { - lv_gltf_model_t * model = *(lv_gltf_model_t **)lv_array_at(&viewer->models, i); - dirty |= model->is_animation_enabled || model->write_ops_pending; + lv_gltf_model_data_t * model_data = (lv_gltf_model_data_t *)lv_array_at(&viewer->models, i); + dirty |= model_data->model->is_animation_enabled || model_data->model->write_ops_pending; } GLuint texture_id = GL_NONE; - texture_id = lv_gltf_view_render_model(viewer, model, true, dirty); + texture_id = lv_gltf_view_render_model(viewer, model_data, true, dirty); for(size_t i = 1; i < n; ++i) { - lv_gltf_model_t * model = *(lv_gltf_model_t **)lv_array_at(&viewer->models, i); - lv_gltf_view_render_model(viewer, model, false, dirty); + lv_gltf_model_data_t * model_data = (lv_gltf_model_data_t *)lv_array_at(&viewer->models, i); + lv_gltf_view_render_model(viewer, model_data, false, dirty); } lv_memcpy(&(viewer->last_desc), &viewer->desc, sizeof(viewer->desc)); @@ -285,13 +286,14 @@ static void lv_gltf_view_pop_opengl_state(const lv_opengl_state_t * state) GL_CALL(glClearDepthf(state->clear_depth)); } -static GLuint lv_gltf_view_render_model(lv_gltf_t * viewer, lv_gltf_model_t * model, bool is_first_model, bool dirty) +static GLuint lv_gltf_view_render_model(lv_gltf_t * viewer, lv_gltf_model_data_t * modeld, bool is_first_model, + bool dirty) { lv_gltf_view_state_t * vstate = &viewer->state; lv_gltf_view_desc_t * view_desc = &viewer->desc; bool opt_draw_bg = is_first_model && (view_desc->bg_mode == LV_GLTF_BG_MODE_ENVIRONMENT); bool opt_aa_this_frame = (view_desc->aa_mode == LV_GLTF_AA_MODE_ON) || - (view_desc->aa_mode == LV_GLTF_AA_MODE_DYNAMIC && model->last_frame_no_motion == true); + (view_desc->aa_mode == LV_GLTF_AA_MODE_DYNAMIC && modeld->last_frame_no_motion == true); if(!is_first_model) { /* If this data object is a secondary render pass, inherit the anti-alias setting for this frame from the first gltf_data drawn*/ opt_aa_this_frame = view_desc->frame_was_antialiased; @@ -307,7 +309,7 @@ static GLuint lv_gltf_view_render_model(lv_gltf_t * viewer, lv_gltf_model_t * mo bool new_size = last_render_h != view_desc->render_height || last_render_w != view_desc->render_width; - if(opt_aa_this_frame != model->last_frame_was_antialiased) { + if(opt_aa_this_frame != modeld->last_frame_was_antialiased) { /* Antialiasing state has changed since the last render */ if(is_first_model) { if(vstate->render_state_ready) { @@ -317,7 +319,7 @@ static GLuint lv_gltf_view_render_model(lv_gltf_t * viewer, lv_gltf_model_t * mo opt_aa_this_frame); } } - model->last_frame_was_antialiased = opt_aa_this_frame; + modeld->last_frame_was_antialiased = opt_aa_this_frame; } view_desc->frame_was_antialiased = opt_aa_this_frame; @@ -334,33 +336,34 @@ static GLuint lv_gltf_view_render_model(lv_gltf_t * viewer, lv_gltf_model_t * mo setup_finish_frame(); } - bool last_frame_no_motion = model->_last_frame_no_motion; - model->_last_frame_no_motion = model->last_frame_no_motion; - model->last_frame_no_motion = true; + bool last_frame_no_motion = modeld->_last_frame_no_motion; + modeld->_last_frame_no_motion = modeld->last_frame_no_motion; + modeld->last_frame_no_motion = true; - if(dirty || lv_gltf_data_transform_cache_is_empty(model) || (model->camera != model->last_camera_index)) { - model->last_frame_no_motion = false; - lv_gltf_view_recache_all_transforms(model); + if(dirty || lv_gltf_model_needs_transforms(modeld->model) || + (modeld->model->camera != modeld->last_camera_index)) { + modeld->last_frame_no_motion = false; + lv_gltf_view_recache_all_transforms(modeld); } - else if(model->last_frame_no_motion && model->_last_frame_no_motion && last_frame_no_motion) { + else if(modeld->last_frame_no_motion && modeld->_last_frame_no_motion && last_frame_no_motion) { /* Nothing changed at all, return the previous output frame */ setup_finish_frame(); lv_gltf_view_pop_opengl_state(&opengl_state); return vstate->render_state.texture; } - render_skins(viewer, model); + render_skins(viewer, modeld); NodeDistanceVector distance_sort_nodes; - for(const auto & kv : model->blended_nodes_by_material_index) { + for(const auto & kv : modeld->model->blended_nodes_by_material_index) { for(const auto & pair : kv.second) { auto node = pair.first; auto new_node = NodeIndexDistancePair( fastgltf::math::length( - model->view_pos - - lv_gltf_data_get_centerpoint(model, lv_gltf_data_get_cached_transform(model, node), + modeld->model->view_pos - + lv_gltf_data_get_centerpoint(modeld->model, lv_gltf_data_get_node_transform(modeld->model, node), node->meshIndex.value(), pair.second)), NodeIndexPair(node, pair.second)); distance_sort_nodes.push_back(new_node); @@ -372,7 +375,7 @@ static GLuint lv_gltf_view_render_model(lv_gltf_t * viewer, lv_gltf_model_t * mo }); /* Reset the last material index to an unused value once per frame at the start*/ - model->last_material_index = 99999; + modeld->model->last_material_index = 99999; if(vstate->render_opaque_buffer) { std::optional saver; @@ -381,11 +384,11 @@ static GLuint lv_gltf_view_render_model(lv_gltf_t * viewer, lv_gltf_model_t * mo saver.emplace(viewer); } - if(model->camera > 0) { - setup_view_proj_matrix_from_camera(viewer, model->camera - 1, view_desc, model, true); + if(modeld->model->camera > 0) { + setup_view_proj_matrix_from_camera(viewer, modeld->model->camera - 1, view_desc, modeld->model, true); } else { - setup_view_proj_matrix(viewer, view_desc, model, true); + setup_view_proj_matrix(viewer, view_desc, modeld->model, true); } lv_result_t result = setup_restore_opaque_output(viewer, &vstate->opaque_render_state, vstate->opaque_frame_buffer_width, @@ -400,13 +403,13 @@ static GLuint lv_gltf_view_render_model(lv_gltf_t * viewer, lv_gltf_model_t * mo setup_draw_environment_background(&viewer->shader_manager, viewer, view_desc->blur_bg); } - render_materials(viewer, model, model->opaque_nodes_by_material_index, true); + render_materials(viewer, modeld, modeld->model->opaque_nodes_by_material_index, true); for(const auto & node_distance_pair : distance_sort_nodes) { const auto & node_element = node_distance_pair.second; const auto & node = node_element.first; - draw_primitive(node_element.second, viewer, model, *node, node->meshIndex.value(), - lv_gltf_data_get_cached_transform(model, node), viewer->environment, true); + draw_primitive(node_element.second, viewer, modeld, *node, node->meshIndex.value(), + lv_gltf_data_get_node_transform(modeld->model, node), viewer->environment, true); } GL_CALL(glBindTexture(GL_TEXTURE_2D, vstate->opaque_render_state.texture)); @@ -416,11 +419,11 @@ static GLuint lv_gltf_view_render_model(lv_gltf_t * viewer, lv_gltf_model_t * mo } if(is_first_model) { - if(model->camera > 0) { - setup_view_proj_matrix_from_camera(viewer, model->camera - 1, view_desc, model, false); + if(modeld->model->camera > 0) { + setup_view_proj_matrix_from_camera(viewer, modeld->model->camera - 1, view_desc, modeld->model, false); } else { - setup_view_proj_matrix(viewer, view_desc, model, false); + setup_view_proj_matrix(viewer, view_desc, modeld->model, false); } } @@ -436,13 +439,13 @@ static GLuint lv_gltf_view_render_model(lv_gltf_t * viewer, lv_gltf_model_t * mo setup_draw_environment_background(&viewer->shader_manager, viewer, view_desc->blur_bg); } - render_materials(viewer, model, model->opaque_nodes_by_material_index, false); + render_materials(viewer, modeld, modeld->model->opaque_nodes_by_material_index, false); for(const auto & node_distance_pair : distance_sort_nodes) { const auto & node_element = node_distance_pair.second; const auto & node = node_element.first; - draw_primitive(node_element.second, viewer, model, *node, node->meshIndex.value(), - lv_gltf_data_get_cached_transform(model, node), viewer->environment, false); + draw_primitive(node_element.second, viewer, modeld, *node, node->meshIndex.value(), + lv_gltf_data_get_node_transform(modeld->model, node), viewer->environment, false); } if(opt_aa_this_frame) { GL_CALL(glBindTexture(GL_TEXTURE_2D, vstate->render_state.texture)); @@ -462,35 +465,44 @@ static void setup_finish_frame(void) GL_CALL(glUseProgram(0)); } -static void render_materials(lv_gltf_t * viewer, lv_gltf_model_t * gltf_data, const MaterialIndexMap & map, +static void render_materials(lv_gltf_t * viewer, lv_gltf_model_data_t * modeld, const MaterialIndexMap & map, bool is_transmission_pass) { for(const auto & kv : map) { for(const auto & pair : kv.second) { auto node = pair.first; - draw_primitive(pair.second, viewer, gltf_data, *node, node->meshIndex.value(), - lv_gltf_data_get_cached_transform(gltf_data, node), + draw_primitive(pair.second, viewer, modeld, *node, node->meshIndex.value(), + lv_gltf_data_get_node_transform(modeld->model, node), viewer->environment, is_transmission_pass); } } } -static void render_skins(lv_gltf_t * viewer, lv_gltf_model_t * model) +static void render_skins(lv_gltf_t * viewer, lv_gltf_model_data_t * modeld) { - uint32_t skin_count = lv_gltf_data_get_skins_size(model); + size_t skin_count = lv_gltf_data_get_skins_size(modeld->model); if(skin_count == 0) { return; } - lv_gltf_data_delete_textures(model); + const size_t skin_texture_count = lv_array_size(&modeld->skin_textures); + for(size_t i = 0; i < skin_texture_count; ++i) { + GLuint * texture_id = (GLuint *)lv_array_at(&modeld->skin_textures, i); + GL_CALL(glDeleteTextures(1, texture_id)); + } + lv_array_clear(&modeld->skin_textures); + for(size_t i = 0; i < skin_count; ++i) { - const auto & skin_index = lv_gltf_data_get_skin(model, i); - const auto & skin = model->asset.skins[skin_index]; - auto & ibm = viewer->ibm_by_skin_then_node[skin_index]; + const auto & skin_index = lv_gltf_data_get_skin(modeld->model, i); + const auto & skin = modeld->model->asset.skins[skin_index]; + auto & ibm = modeld->model->ibm_by_skin_then_node[skin_index]; size_t num_joints = skin.joints.size(); size_t tex_width = std::ceil(std::sqrt((float)num_joints * 8.0f)); - GLuint rtex = lv_gltf_data_create_texture(model); + GLuint rtex; + GL_CALL(glGenTextures(1, &rtex)); + lv_array_push_back(&modeld->skin_textures, &rtex); + GL_CALL(glBindTexture(GL_TEXTURE_2D, rtex)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); @@ -501,9 +513,9 @@ static void render_skins(lv_gltf_t * viewer, lv_gltf_model_t * model) size_t texture_data_index = 0; for(uint64_t j = 0; j < num_joints; j++) { - auto & joint_node = model->asset.nodes[skin.joints[j]]; + auto & joint_node = modeld->model->asset.nodes[skin.joints[j]]; fastgltf::math::fmat4x4 final_joint_matrix = - lv_gltf_data_get_cached_transform(model, &joint_node) * ibm[&joint_node]; + lv_gltf_data_get_node_transform(modeld->model, &joint_node) * ibm[&joint_node]; lv_memcpy(&texture_data[texture_data_index], final_joint_matrix.data(), sizeof(float) * 16); lv_memcpy(&texture_data[texture_data_index + 16], @@ -518,17 +530,17 @@ static void render_skins(lv_gltf_t * viewer, lv_gltf_model_t * model) lv_free(texture_data); } } -static void draw_primitive(int32_t prim_num, lv_gltf_t * viewer, lv_gltf_model_t * gltf_data, fastgltf::Node & node, +static void draw_primitive(int32_t prim_num, lv_gltf_t * viewer, lv_gltf_model_data_t * modeld, fastgltf::Node & node, std::size_t mesh_index, const fastgltf::math::fmat4x4 & matrix, const lv_gltf_environment_t * env_tex, bool is_transmission_pass) { - lv_gltf_mesh_data_t * mesh = lv_gltf_data_get_mesh(gltf_data, mesh_index); - const auto & asset = lv_gltf_data_get_asset(gltf_data); + lv_gltf_mesh_data_t * mesh = lv_gltf_data_get_mesh(modeld->model, mesh_index); + const auto & asset = lv_gltf_data_get_asset(modeld->model); const auto & _prim_data = lv_gltf_data_get_primitive_from_mesh(mesh, prim_num); std::size_t index_count = 0; auto & indexAccessor = asset->accessors[asset->meshes[mesh_index].primitives[prim_num].indicesAccessor.value()]; - if(!setup_primitive(prim_num, viewer, gltf_data, node, mesh_index, matrix, env_tex, is_transmission_pass)) { + if(!setup_primitive(prim_num, viewer, modeld, node, mesh_index, matrix, env_tex, is_transmission_pass)) { return; } @@ -539,10 +551,11 @@ static void draw_primitive(int32_t prim_num, lv_gltf_t * viewer, lv_gltf_model_t GL_CALL(glDrawElements(_prim_data->primitiveType, index_count, _prim_data->indexType, 0)); } } -static bool setup_primitive(int32_t prim_num, lv_gltf_t * viewer, lv_gltf_model_t * model, fastgltf::Node & node, +static bool setup_primitive(int32_t prim_num, lv_gltf_t * viewer, lv_gltf_model_data_t * modeld, fastgltf::Node & node, std::size_t mesh_index, const fastgltf::math::fmat4x4 & matrix, const lv_gltf_environment_t * env_tex, bool is_transmission_pass) { + lv_gltf_model_t * model = modeld->model; lv_gltf_view_desc_t * view_desc = &viewer->desc; lv_gltf_mesh_data_t * mesh = lv_gltf_data_get_mesh(model, mesh_index); const auto & _prim_data = lv_gltf_data_get_primitive_from_mesh(mesh, prim_num); @@ -589,7 +602,7 @@ static bool setup_primitive(int32_t prim_num, lv_gltf_t * viewer, lv_gltf_model_ GL_CALL(glCullFace(GL_BACK)); uint32_t tex_num = 0; - if(!draw_material(viewer, uniforms, model, _prim_data, materialIndex, is_transmission_pass, program, &tex_num)) { + if(!draw_material(viewer, uniforms, modeld, _prim_data, materialIndex, is_transmission_pass, program, &tex_num)) { return false; } @@ -604,8 +617,9 @@ static bool setup_primitive(int32_t prim_num, lv_gltf_t * viewer, lv_gltf_model_ } if(node.skinIndex.has_value()) { + GLuint skin_texture = *(GLuint *)lv_array_at(&modeld->skin_textures, node.skinIndex.value()); GL_CALL(glActiveTexture(GL_TEXTURE0 + tex_num)); - GL_CALL(glBindTexture(GL_TEXTURE_2D, lv_gltf_data_get_skin_texture_at(model, node.skinIndex.value()))); + GL_CALL(glBindTexture(GL_TEXTURE_2D, skin_texture)); GL_CALL(glUniform1i(uniforms->joints_sampler, tex_num)); tex_num++; } @@ -637,10 +651,12 @@ static bool setup_primitive(int32_t prim_num, lv_gltf_t * viewer, lv_gltf_model_ return true; } -static bool draw_material(lv_gltf_t * viewer, const lv_gltf_uniform_locations_t * uniforms, lv_gltf_model_t * model, +static bool draw_material(lv_gltf_t * viewer, const lv_gltf_uniform_locations_t * uniforms, + lv_gltf_model_data_t * modeld, lv_gltf_primitive_t * _prim_data, size_t materialIndex, bool is_transmission_pass, GLuint program, uint32_t * tex_num) { + lv_gltf_model_t * model = modeld->model; const auto & asset = lv_gltf_data_get_asset(model); bool has_material = asset->materials.size() > (materialIndex - 1); @@ -679,7 +695,7 @@ static bool draw_material(lv_gltf_t * viewer, const lv_gltf_uniform_locations_t } } - draw_lights(model, program); + draw_lights(modeld, program); #if LV_GLTF_CONVERT_BASE_COLOR_TO_SRGB render_uniform_color_alpha(uniforms->base_color_factor, color_convert_to_srgb(gltfMaterial.pbrData.baseColorFactor)); #else @@ -835,8 +851,11 @@ static bool draw_material(lv_gltf_t * viewer, const lv_gltf_uniform_locations_t } return true; } -static void draw_lights(lv_gltf_model_t * model, GLuint program) +static void draw_lights(lv_gltf_model_data_t * modeld, GLuint program) { + + lv_gltf_model_t * model = modeld->model; + if(model->node_by_light_index.empty()) { return; } @@ -853,7 +872,7 @@ static void draw_lights(lv_gltf_model_t * model, GLuint program) // Update each field of the light struct lv_snprintf(prefix, sizeof(prefix), "u_Lights[%zu]", i + 1); auto & lightNode = model->node_by_light_index[i]; - const fastgltf::math::fmat4x4 & light_matrix = lv_gltf_data_get_cached_transform(model, lightNode); + const fastgltf::math::fmat4x4 & light_matrix = lv_gltf_data_get_node_transform(modeld->model, lightNode); lv_snprintf(tag, sizeof(tag), "%s.position", prefix); glUniform3fv(glGetUniformLocation(program, tag), 1, &light_matrix[3][0]); @@ -1179,8 +1198,8 @@ static void setup_view_proj_matrix(lv_gltf_t * viewer, lv_gltf_view_desc_t * vie bool transmission_pass) { LV_UNUSED(model); - const lv_gltf_model_t * main_model = *(lv_gltf_model_t **)lv_array_at(&viewer->models, 0); - auto b_radius = lv_gltf_data_get_radius(main_model); + const lv_gltf_model_data_t * main_model = (lv_gltf_model_data_t *)lv_array_at(&viewer->models, 0); + auto b_radius = lv_gltf_data_get_radius(main_model->model); float radius = b_radius * LV_GLTF_DISTANCE_SCALE_FACTOR; radius *= view_desc->distance; @@ -1313,27 +1332,28 @@ static void setup_draw_solid_background(lv_gltf_t * viewer, lv_color_t bg_color, } -static void lv_gltf_view_recache_all_transforms(lv_gltf_model_t * model) +static void lv_gltf_view_recache_all_transforms(lv_gltf_model_data_t * modeld) { - int32_t anim_num = model->current_animation; + int32_t anim_num = modeld->model->current_animation; uint32_t scene_index = 0; - model->last_camera_index = model->camera; - model->write_ops_flushed = true; - model->write_ops_pending = false; + modeld->last_camera_index = modeld->model->camera; + modeld->model->write_ops_flushed = true; + modeld->model->write_ops_pending = false; size_t current_camera_count = 0; - lv_gltf_data_clear_transform_cache(model); + lv_gltf_model_clear_transforms(modeld->model); auto tmat = fastgltf::math::fmat4x4{}; fastgltf::custom_iterate_scene_nodes( - model, scene_index, &tmat, + modeld->model, scene_index, &tmat, [&](lv_gltf_model_node_t * node, fastgltf::math::fmat4x4 & parentworldmatrix, fastgltf::math::fmat4x4 & localmatrix) { bool made_changes = false; bool made_rotation_changes = false; - if(lv_gltf_data_animation_get_channel_set(anim_num, model, node->fastgltf_node)->size() > 0) { - lv_gltf_data_animation_matrix_apply(model->local_timestamp / 1000., anim_num, model, node->fastgltf_node, + if(lv_gltf_data_animation_get_channel_set(anim_num, modeld->model, node->fastgltf_node)->size() > 0) { + lv_gltf_data_animation_matrix_apply(modeld->model->local_timestamp / 1000., anim_num, modeld->model, + node->fastgltf_node, localmatrix); made_changes = true; } @@ -1411,18 +1431,18 @@ static void lv_gltf_view_recache_all_transforms(lv_gltf_model_t * model) } } - if(made_changes || !lv_gltf_data_has_cached_transform(model, node->fastgltf_node)) { + if(made_changes || !lv_gltf_model_has_node_transform(modeld->model, node->fastgltf_node)) { auto world_matrix = worldmatrix_was_inlined ? inlined_worldmatrix : (parentworldmatrix * localmatrix); - lv_gltf_data_set_cached_transform(model, node->fastgltf_node, world_matrix); + lv_gltf_model_set_transforms(modeld->model, node->fastgltf_node, world_matrix); } if(node->fastgltf_node->cameraIndex.has_value()) { current_camera_count++; - if(current_camera_count == model->camera) { + if(current_camera_count == modeld->model->camera) { fastgltf::math::fmat4x4 cammat = worldmatrix_was_inlined ? inlined_worldmatrix : (parentworldmatrix * localmatrix); fastgltf::removeScale(cammat); - model->view_pos = cammat.col(3); /* Implicit conversion from 4 element column to 3 element vector */ - model->view_mat = fastgltf::math::inverse(cammat); + modeld->model->view_pos = cammat.col(3); /* Implicit conversion from 4 element column to 3 element vector */ + modeld->model->view_mat = fastgltf::math::inverse(cammat); } } });