From a4a8fd0c82ab37c390a04c1b9b024868fcadec06 Mon Sep 17 00:00:00 2001 From: luboslenco Date: Thu, 16 Jul 2026 10:24:49 +0200 Subject: [PATCH] paint: add merge geometry button --- paint/sources/functions.h | 1 + paint/sources/ui/tab_meshes.c | 10 +++ paint/sources/util/util_mesh.c | 128 ++++++++++++++++++++++++++++----- 3 files changed, 122 insertions(+), 17 deletions(-) diff --git a/paint/sources/functions.h b/paint/sources/functions.h index 0ca6b926..c4eb3438 100644 --- a/paint/sources/functions.h +++ b/paint/sources/functions.h @@ -522,6 +522,7 @@ void tab_textures_accept_asset_drop(asset_t *asset); void tab_browser_show_directory(char *directory); void tab_browser_draw(ui_handle_t *htab); void util_mesh_merge(mesh_object_t_array_t *paint_objects); +void util_mesh_merge_geometry(); void util_mesh_swap_axis(i32 a, i32 b); void util_mesh_flip_normals(); void util_mesh_calc_normals(bool smooth); diff --git a/paint/sources/ui/tab_meshes.c b/paint/sources/ui/tab_meshes.c index 03cb33b7..9b39bd3c 100644 --- a/paint/sources/ui/tab_meshes.c +++ b/paint/sources/ui/tab_meshes.c @@ -149,6 +149,10 @@ void tab_meshes_duplicate_next_frame(void *_) { sim_duplicate(); } +void tab_meshes_merge_geometry_next_frame(void *_) { + util_mesh_merge_geometry(); +} + void tab_meshes_draw_transform_loc(mesh_object_t *o, char *ns) { transform_t *t = o->base->transform; bool changed = false; @@ -438,6 +442,12 @@ void tab_meshes_draw_edit() { g_context->ddirty = 2; } + g_ui->enabled = g_project->_->paint_objects->length > 1; + if (ui_menu_button(tr("Merge Geometry"), "", ICON_NONE)) { + sys_notify_on_next_frame(&tab_meshes_merge_geometry_next_frame, NULL); + } + g_ui->enabled = true; + if (ui_menu_button(tr("Apply Displacement"), "", ICON_NONE)) { util_mesh_apply_displacement(g_project->_->layers->buffer[0]->texpaint_pack, 0.1, 1.0); util_mesh_calc_normals(false); diff --git a/paint/sources/util/util_mesh.c b/paint/sources/util/util_mesh.c index a03cbb53..4636fc3d 100644 --- a/paint/sources/util/util_mesh.c +++ b/paint/sources/util/util_mesh.c @@ -34,22 +34,10 @@ mesh_object_t_array_t *util_mesh_get_unique() { return ar; } -void util_mesh_merge(mesh_object_t_array_t *paint_objects) { - if (paint_objects == NULL) { - // if (g_context->tool == TOOL_TYPE_CURSOR) { - // paint_objects = util_mesh_get_unique(); - // } - // else { - paint_objects = g_project->_->paint_objects; - // } - } - if (paint_objects->length == 0) { - return; - } - g_context->merged_object_is_atlas = paint_objects->length < g_project->_->paint_objects->length; - i32 vlen = 0; - i32 ilen = 0; - f32 max_scale = 0.0; +static mesh_data_t *util_mesh_build_merged_data(mesh_object_t_array_t *paint_objects, char *name) { + i32 vlen = 0; + i32 ilen = 0; + f32 max_scale = 0.0; for (i32 i = 0; i < paint_objects->length; ++i) { vlen += paint_objects->buffer[i]->data->vertex_arrays->buffer[0]->values->length; ilen += paint_objects->buffer[i]->data->index_array->length; @@ -121,7 +109,7 @@ void util_mesh_merge(mesh_object_t_array_t *paint_objects) { voff += math_floor(vas->buffer[0]->values->length / 4.0); ioff += math_floor(ias->length); } - mesh_data_t *raw = GC_ALLOC_INIT(mesh_data_t, {.name = g_context->paint_object->base->name, + mesh_data_t *raw = GC_ALLOC_INIT(mesh_data_t, {.name = name, .vertex_arrays = any_array_create_from_raw( (void *[]){ GC_ALLOC_INIT(vertex_array_t, {.values = va0, .attrib = "pos", .data = "short4norm"}), @@ -140,6 +128,24 @@ void util_mesh_merge(mesh_object_t_array_t *paint_objects) { vertex_array_t *va = GC_ALLOC_INIT(vertex_array_t, {.values = vacol, .attrib = "col", .data = "short4norm"}); any_array_push(raw->vertex_arrays, va); } + return raw; +} + +void util_mesh_merge(mesh_object_t_array_t *paint_objects) { + if (paint_objects == NULL) { + // if (g_context->tool == TOOL_TYPE_CURSOR) { + // paint_objects = util_mesh_get_unique(); + // } + // else { + paint_objects = g_project->_->paint_objects; + // } + } + if (paint_objects->length == 0) { + return; + } + g_context->merged_object_is_atlas = paint_objects->length < g_project->_->paint_objects->length; + + mesh_data_t *raw = util_mesh_build_merged_data(paint_objects, g_context->paint_object->base->name); util_mesh_remove_merged(); mesh_data_t *md = mesh_data_create(raw); material_data_t *paint_material = g_project->_->materials->buffer[0]->data; @@ -150,6 +156,94 @@ void util_mesh_merge(mesh_object_t_array_t *paint_objects) { render_path_raytrace_ready = false; } +static void util_mesh_bake_transform(mesh_object_t *o, mat4_t inv_world) { + mat4_t m = mat4_mult_mat(o->base->transform->world_unpack, inv_world); + mat4_t nm = mat4_transpose3(mat4_inv(m)); + i16_array_t *va0 = o->data->vertex_arrays->buffer[0]->values; + i16_array_t *va1 = o->data->vertex_arrays->buffer[1]->values; + i32 num_verts = math_floor(va0->length / 4.0); + f32_array_t *pos = f32_array_create(num_verts * 3); + f32 max_scale = 0.0; + + for (i32 i = 0; i < num_verts; ++i) { + vec4_t p = (vec4_t){va0->buffer[i * 4] / 32767.0, va0->buffer[i * 4 + 1] / 32767.0, va0->buffer[i * 4 + 2] / 32767.0, 1.0}; + p = vec4_apply_mat4(p, m); + + pos->buffer[i * 3] = p.x; + pos->buffer[i * 3 + 1] = p.y; + pos->buffer[i * 3 + 2] = p.z; + max_scale = math_max(max_scale, math_max(math_abs(p.x), math_max(math_abs(p.y), math_abs(p.z)))); + + vec4_t n = (vec4_t){va1->buffer[i * 2] / 32767.0, va1->buffer[i * 2 + 1] / 32767.0, va0->buffer[i * 4 + 3] / 32767.0, 0.0}; + n = vec4_norm(vec4_apply_mat4(n, nm)); + + va1->buffer[i * 2] = math_floor(n.x * 32767); + va1->buffer[i * 2 + 1] = math_floor(n.y * 32767); + va0->buffer[i * 4 + 3] = math_floor(n.z * 32767); + } + + if (max_scale <= 0.0) { + max_scale = 1.0; + } + for (i32 i = 0; i < num_verts; ++i) { + va0->buffer[i * 4] = math_floor(pos->buffer[i * 3] / max_scale * 32767); + va0->buffer[i * 4 + 1] = math_floor(pos->buffer[i * 3 + 1] / max_scale * 32767); + va0->buffer[i * 4 + 2] = math_floor(pos->buffer[i * 3 + 2] / max_scale * 32767); + } + o->data->scale_pos = max_scale; +} + +void util_mesh_merge_geometry() { + mesh_object_t_array_t *objects = g_project->_->paint_objects; + if (objects->length < 2) { + return; + } + + // Keep the first object and join the geometry of the rest into it + mesh_object_t *main_object = objects->buffer[0]; + mat4_t inv_world = mat4_inv(main_object->base->transform->world); + for (i32 i = 0; i < objects->length; ++i) { + util_mesh_bake_transform(objects->buffer[i], inv_world); + } + + mesh_data_t *raw = util_mesh_build_merged_data(objects, main_object->data->name); + util_mesh_remove_merged(); + + for (i32 i = 1; i < objects->length; ++i) { + mesh_object_t *o = objects->buffer[i]; + object_set_parent(o->base, NULL); + data_delete_mesh(o->data->_->handle); + mesh_object_remove(o); + } + + mesh_data_t *md = mesh_data_create(raw); + sys_notify_on_next_frame(&mesh_data_delete, main_object->data); + mesh_object_set_data(main_object, md); + md->_->handle = string_copy(raw->name); + any_map_set(data_cached_meshes, md->_->handle, md); + + g_project->_->paint_objects = any_array_create_from_raw( + (void *[]){ + main_object, + }, + 1); + context_select_paint_object(main_object); + + for (i32 i = 0; i < g_project->_->layers->length; ++i) { + g_project->_->layers->buffer[i]->object_mask = 0; + } + g_context->layer_filter = 0; + tab_stages_prune(); + tab_meshes_reset_preview_map(); + + util_mesh_merge(NULL); + util_uv_uvmap_cached = false; + util_uv_trianglemap_cached = false; + util_uv_dilatemap_cached = false; + g_context->ddirty = 2; + ui_base_hwnds->buffer[TAB_AREA_SIDEBAR0]->redraws = 2; +} + void util_mesh_swap_axis(i32 a, i32 b) { mesh_object_t_array_t *objects = g_project->_->paint_objects; for (i32 i = 0; i < objects->length; ++i) {