From 1f825fdba4191236cd19532facae2dde87c85151 Mon Sep 17 00:00:00 2001 From: luboslenco Date: Sat, 13 Jun 2026 21:27:16 +0200 Subject: [PATCH] sculpt: export fixes --- paint/sources/functions.h | 2 +- paint/sources/io/export_mesh.c | 5 +- paint/sources/io/export_obj.c | 148 ++++++++++++++++++++++++++++----- paint/sources/ui/tab_layers.c | 2 +- 4 files changed, 131 insertions(+), 26 deletions(-) diff --git a/paint/sources/functions.h b/paint/sources/functions.h index 51752d82..4b78e6b1 100644 --- a/paint/sources/functions.h +++ b/paint/sources/functions.h @@ -609,7 +609,7 @@ void import_obj_run(char *path, bool replace_existing); void import_folder_run(char *path); void export_obj_run(char *path, mesh_object_t_array_t *paint_objects, bool apply_disp); void export_obj_run_fast(char *path, mesh_object_t_array_t *paint_objects); -void export_obj_sculpt_run(char *path, mesh_object_t_array_t *paint_objects); +void export_obj_run_sculpt(char *path, mesh_object_t_array_t *paint_objects); void make_blur_run(node_shader_t *kong); void import_font_run(char *path); buffer_t *export_exr_run(i32 width, i32 height, buffer_t *src, i32 bits, i32 type, i32 off); diff --git a/paint/sources/io/export_mesh.c b/paint/sources/io/export_mesh.c index c38d01b8..78dfda1c 100644 --- a/paint/sources/io/export_mesh.c +++ b/paint/sources/io/export_mesh.c @@ -10,7 +10,10 @@ void export_mesh_run(char *path, mesh_object_t_array_t *paint_objects, bool appl paint_objects = g_project->_->paint_objects; } if (g_context->export_mesh_format == MESH_FORMAT_OBJ) { - if (merge_vertices) { + if (g_config->workflow == WORKFLOW_SCULPT) { + export_obj_run_sculpt(path, paint_objects); + } + else if (merge_vertices) { export_obj_run(path, paint_objects, apply_disp); } else { diff --git a/paint/sources/io/export_obj.c b/paint/sources/io/export_obj.c index fa599665..08fa5ff6 100644 --- a/paint/sources/io/export_obj.c +++ b/paint/sources/io/export_obj.c @@ -275,15 +275,63 @@ void export_obj_run_fast(char *path, mesh_object_t_array_t *paint_objects) { iron_file_save_bytes(path, o, 0); } -void export_obj_run_sculpt(char *path, mesh_object_t_array_t *paint_objects) { - slot_layer_t *l = NULL; - for (i32 i = 0; i < g_project->_->layers->length; ++i) { - if (g_project->_->layers->buffer[i]->texpaint_sculpt != NULL) { - l = g_project->_->layers->buffer[i]; - break; +static bool export_obj_sculpt_layer_mask(slot_layer_t *l, f32_array_t *pmask, i32 len, i16_array_t *texa, u32_array_t *inda, f32 inv) { + slot_layer_t_array_t *masks = slot_layer_get_masks(l, true); + if (masks == NULL) { + return false; + } +#ifdef IRON_BGRA + i32 r_off = 2; +#else + i32 r_off = 0; +#endif + bool any = false; + for (i32 mi = 0; mi < masks->length; ++mi) { + slot_layer_t *m = masks->buffer[mi]; + if (!slot_layer_is_visible(m) || m->texpaint == NULL) { + continue; + } + if (!any) { + for (i32 i = 0; i < len; ++i) { + pmask->buffer[i] = 1.0; + } + any = true; + } + buffer_t *mp = gpu_get_texture_pixels(m->texpaint); + i32 mw = m->texpaint->width; + i32 mh = m->texpaint->height; + f32 opac = slot_layer_get_opacity(m); + for (i32 i = 0; i < len; ++i) { + i32 vid = inda->buffer[i]; + f32 u = texa->buffer[vid * 2] * inv; + f32 v = texa->buffer[vid * 2 + 1] * inv; + i32 x = math_floor(u * mw); + i32 y = math_floor(v * mh); + x = x < 0 ? 0 : (x >= mw ? mw - 1 : x); + y = y < 0 ? 0 : (y >= mh ? mh - 1 : y); + f32 r = buffer_get_u8(mp, (y * mw + x) * 4 + r_off) / 255.0; + pmask->buffer[i] *= (1.0 - opac) + r * opac; } } - if (l == NULL) { + if (any) { + for (i32 i = 0; i < len; ++i) { + f32 c = pmask->buffer[i]; + pmask->buffer[i] = c < 0.0 ? 0.0 : (c > 1.0 ? 1.0 : c); + } + } + return any; +} + +void export_obj_run_sculpt(char *path, mesh_object_t_array_t *paint_objects) { + slot_layer_t_array_t *sculpt_layers = any_array_create_from_raw((void *[]){}, 0); + for (i32 i = 0; i < g_project->_->layers->length; ++i) { + slot_layer_t *l = g_project->_->layers->buffer[i]; + if (l->texpaint_sculpt != NULL && slot_layer_is_visible(l)) { + any_array_push(sculpt_layers, l); + } + } + i32 count = sculpt_layers->length; + if (count == 0) { return; } @@ -294,18 +342,71 @@ void export_obj_run_sculpt(char *path, mesh_object_t_array_t *paint_objects) { mesh_data_t *mesh = p->data; f32 sc = mesh->scale_pos; i16_array_t *texa = mesh->vertex_arrays->buffer[2]->values; - i32 len = math_floor(mesh->index_array->length); + u32_array_t *inda = mesh->index_array; + i32 len = math_floor(inda->length); i32 tris = math_floor(len / 3.0); f32 inv = 1.0 / 32767.0; - buffer_t *pixels = gpu_get_texture_pixels(l->texpaint_sculpt); + // The base render target holds the rest-pose positions + buffer_t *base_pixels = NULL; + render_target_t *base_rt = any_map_get(render_path_render_targets, "texpaint_sculpt_base"); + if (base_rt != NULL && base_rt->_image != NULL) { + base_pixels = gpu_get_texture_pixels(base_rt->_image); + } + + // Accumulate the combined position of every vertex across all layers and masks + f32_array_t *cpos = f32_array_create(len * 3); + f32_array_t *pmask = f32_array_create(len); + + buffer_t *l0 = gpu_get_texture_pixels(sculpt_layers->buffer[0]->texpaint_sculpt); + for (i32 i = 0; i < len; ++i) { + cpos->buffer[i * 3] = buffer_get_f32(l0, i * 16); + cpos->buffer[i * 3 + 1] = buffer_get_f32(l0, i * 16 + 4); + cpos->buffer[i * 3 + 2] = buffer_get_f32(l0, i * 16 + 8); + } + // Blend the base layer back toward the rest pose where its mask is dark + if (export_obj_sculpt_layer_mask(sculpt_layers->buffer[0], pmask, len, texa, inda, inv) && base_pixels != NULL) { + for (i32 i = 0; i < len; ++i) { + f32 bx = buffer_get_f32(base_pixels, i * 16); + f32 by = buffer_get_f32(base_pixels, i * 16 + 4); + f32 bz = buffer_get_f32(base_pixels, i * 16 + 8); + f32 w = pmask->buffer[i]; + cpos->buffer[i * 3] = bx + (cpos->buffer[i * 3] - bx) * w; + cpos->buffer[i * 3 + 1] = by + (cpos->buffer[i * 3 + 1] - by) * w; + cpos->buffer[i * 3 + 2] = bz + (cpos->buffer[i * 3 + 2] - bz) * w; + } + } + // Add each additional layers displacement relative to the rest pose + for (i32 k = 1; k < count; ++k) { + buffer_t *lk = gpu_get_texture_pixels(sculpt_layers->buffer[k]->texpaint_sculpt); + bool masked = export_obj_sculpt_layer_mask(sculpt_layers->buffer[k], pmask, len, texa, inda, inv); + for (i32 i = 0; i < len; ++i) { + f32 dx = buffer_get_f32(lk, i * 16); + f32 dy = buffer_get_f32(lk, i * 16 + 4); + f32 dz = buffer_get_f32(lk, i * 16 + 8); + if (base_pixels != NULL) { + dx -= buffer_get_f32(base_pixels, i * 16); + dy -= buffer_get_f32(base_pixels, i * 16 + 4); + dz -= buffer_get_f32(base_pixels, i * 16 + 8); + } + if (masked) { + f32 w = pmask->buffer[i]; + dx *= w; + dy *= w; + dz *= w; + } + cpos->buffer[i * 3] += dx; + cpos->buffer[i * 3 + 1] += dy; + cpos->buffer[i * 3 + 2] += dz; + } + } export_obj_write_string(o, string("o %s\n", p->base->name)); for (i32 i = 0; i < len; ++i) { - f32 x = buffer_get_f32(pixels, i * 16) * sc; - f32 y = buffer_get_f32(pixels, i * 16 + 4) * sc; - f32 z = buffer_get_f32(pixels, i * 16 + 8) * sc; + f32 x = cpos->buffer[i * 3] * sc; + f32 y = cpos->buffer[i * 3 + 1] * sc; + f32 z = cpos->buffer[i * 3 + 2] * sc; export_obj_write_string(o, "v "); export_obj_write_string(o, f32_to_string(x)); export_obj_write_string(o, " "); @@ -317,15 +418,15 @@ void export_obj_run_sculpt(char *path, mesh_object_t_array_t *paint_objects) { for (i32 t = 0; t < tris; ++t) { i32 i0 = t * 3, i1 = t * 3 + 1, i2 = t * 3 + 2; - f32 x0 = buffer_get_f32(pixels, i0 * 16) * sc; - f32 y0 = buffer_get_f32(pixels, i0 * 16 + 4) * sc; - f32 z0 = buffer_get_f32(pixels, i0 * 16 + 8) * sc; - f32 x1 = buffer_get_f32(pixels, i1 * 16) * sc; - f32 y1 = buffer_get_f32(pixels, i1 * 16 + 4) * sc; - f32 z1 = buffer_get_f32(pixels, i1 * 16 + 8) * sc; - f32 x2 = buffer_get_f32(pixels, i2 * 16) * sc; - f32 y2 = buffer_get_f32(pixels, i2 * 16 + 4) * sc; - f32 z2 = buffer_get_f32(pixels, i2 * 16 + 8) * sc; + f32 x0 = cpos->buffer[i0 * 3] * sc; + f32 y0 = cpos->buffer[i0 * 3 + 1] * sc; + f32 z0 = cpos->buffer[i0 * 3 + 2] * sc; + f32 x1 = cpos->buffer[i1 * 3] * sc; + f32 y1 = cpos->buffer[i1 * 3 + 1] * sc; + f32 z1 = cpos->buffer[i1 * 3 + 2] * sc; + f32 x2 = cpos->buffer[i2 * 3] * sc; + f32 y2 = cpos->buffer[i2 * 3 + 1] * sc; + f32 z2 = cpos->buffer[i2 * 3 + 2] * sc; f32 e1x = x1 - x0, e1y = y1 - y0, e1z = z1 - z0; f32 e2x = x2 - x0, e2y = y2 - y0, e2z = z2 - z0; f32 nx = e1y * e2z - e1z * e2y; @@ -347,8 +448,9 @@ void export_obj_run_sculpt(char *path, mesh_object_t_array_t *paint_objects) { } for (i32 i = 0; i < len; ++i) { - f32 u = texa->buffer[i * 2] * inv; - f32 v = 1.0 - texa->buffer[i * 2 + 1] * inv; + i32 vid = inda->buffer[i]; + f32 u = texa->buffer[vid * 2] * inv; + f32 v = 1.0 - texa->buffer[vid * 2 + 1] * inv; export_obj_write_string(o, "vt "); export_obj_write_string(o, f32_to_string(u)); export_obj_write_string(o, " "); diff --git a/paint/sources/ui/tab_layers.c b/paint/sources/ui/tab_layers.c index 1172fc93..ce5fbc92 100644 --- a/paint/sources/ui/tab_layers.c +++ b/paint/sources/ui/tab_layers.c @@ -667,7 +667,7 @@ void tab_layers_draw_layer_context_menu_draw() { } } - if (slot_layer_is_layer(l) && l->fill_material != NULL && l->texpaint_sculpt != NULL && ui_menu_button(tr("Apply"), "", ICON_CHECK)) { + if (slot_layer_is_layer(l) && l->texpaint_sculpt != NULL && ui_menu_button(tr("Apply Sculpt"), "", ICON_CHECK)) { sys_notify_on_next_frame(&tab_layers_draw_layer_context_menu_apply_sculpt, NULL); }