#include "../global.h" #define TEXTURE_MESH_NODE_NUM_VIEWS 6 #define TEXTURE_MESH_NODE_GRID_COLS 3 #define TEXTURE_MESH_NODE_GRID_ROWS 2 #define TEXTURE_MESH_NODE_CELL 512 #define TEXTURE_MESH_NODE_GRID_W (TEXTURE_MESH_NODE_CELL * TEXTURE_MESH_NODE_GRID_COLS) #define TEXTURE_MESH_NODE_GRID_H (TEXTURE_MESH_NODE_CELL * TEXTURE_MESH_NODE_GRID_ROWS) static ui_node_t *texture_mesh_node_current = NULL; static i32 texture_mesh_node_step = 0; static i32 texture_mesh_node_model = 0; static void texture_mesh_node_run_sd(ui_node_t *node); static void texture_mesh_node_project(ui_node_t *node); static void texture_mesh_node_set_view(i32 view) { f32 h = math_pi() / 2.0; f32 p = math_pi(); switch (view) { case 0: viewport_set_view(0, 0, -1, p, 0, p); break; // bottom case 1: viewport_set_view(0, 0, 1, 0, 0, 0); break; // top case 2: viewport_set_view(0, 1, 0, h, 0, p); break; // back case 3: viewport_set_view(-1, 0, 0, h, 0, -h); break; // left case 4: viewport_set_view(1, 0, 0, h, 0, h); break; // right case 5: viewport_set_view(0, -1, 0, h, 0, 0); break; // front } } static void texture_mesh_node_restore_camera() { camera_reset(g_context->view_index_last); } static void texture_mesh_node_grab_view(i32 view_idx) { char *dir = neural_node_dir(); render_target_t *rt = any_map_get(render_path_render_targets, "last"); gpu_texture_t *tex = rt->_image; iron_write_png(string("%s%sinput_%d.png", dir, PATH_SEP, view_idx), buffer_half_to_u8(gpu_get_texture_pixels(tex)), tex->width, tex->height, 0); } static void texture_mesh_node_capture(void *_) { if (texture_mesh_node_step == 0) { texture_mesh_node_set_view(0); texture_mesh_node_step = 1; sys_notify_on_next_frame(texture_mesh_node_capture, NULL); return; } i32 view = texture_mesh_node_step - 1; texture_mesh_node_grab_view(view); if (texture_mesh_node_step < TEXTURE_MESH_NODE_NUM_VIEWS) { texture_mesh_node_set_view(texture_mesh_node_step); texture_mesh_node_step++; sys_notify_on_next_frame(texture_mesh_node_capture, NULL); } else { texture_mesh_node_restore_camera(); texture_mesh_node_run_sd(texture_mesh_node_current); } } static void texture_mesh_node_build_grid(char *dir) { gpu_texture_t *grid = gpu_create_render_target(TEXTURE_MESH_NODE_GRID_W, TEXTURE_MESH_NODE_GRID_H, GPU_TEXTURE_FORMAT_RGBA32); draw_begin(grid, true, 0xff000000); for (i32 v = 0; v < TEXTURE_MESH_NODE_NUM_VIEWS; v++) { char *input = string("%s%sinput_%d.png", dir, PATH_SEP, v); gpu_texture_t *tex = iron_load_texture(input); f32 dx = (v % TEXTURE_MESH_NODE_GRID_COLS) * TEXTURE_MESH_NODE_CELL; f32 dy = (v / TEXTURE_MESH_NODE_GRID_COLS) * TEXTURE_MESH_NODE_CELL; draw_scaled_image(tex, dx, dy, TEXTURE_MESH_NODE_CELL, TEXTURE_MESH_NODE_CELL); } draw_end(); iron_write_png(string("%s%sinput_grid.png", dir, PATH_SEP), gpu_get_texture_pixels(grid), grid->width, grid->height, 0); } static void texture_mesh_node_split_grid(char *dir) { char *grid_path = string("%s%soutput_grid.png", dir, PATH_SEP); gpu_texture_t *grid = iron_load_texture(grid_path); f32 cw = grid->width / (f32)TEXTURE_MESH_NODE_GRID_COLS; f32 ch = grid->height / (f32)TEXTURE_MESH_NODE_GRID_ROWS; for (i32 v = 0; v < TEXTURE_MESH_NODE_NUM_VIEWS; v++) { f32 sx = (v % TEXTURE_MESH_NODE_GRID_COLS) * cw; f32 sy = (v / TEXTURE_MESH_NODE_GRID_COLS) * ch; gpu_texture_t *cell = gpu_create_render_target((i32)cw, (i32)ch, GPU_TEXTURE_FORMAT_RGBA32); draw_begin(cell, false, 0); draw_scaled_sub_image(grid, sx, sy, cw, ch, 0, 0, cw, ch); draw_end(); iron_write_png(string("%s%soutput_%d.png", dir, PATH_SEP, v), gpu_get_texture_pixels(cell), cell->width, cell->height, 0); } } static char *texture_mesh_node_grid_prompt(char *prompt) { return string("%s Keep the grid layout unchanged.", prompt, TEXTURE_MESH_NODE_GRID_COLS, TEXTURE_MESH_NODE_GRID_ROWS, TEXTURE_MESH_NODE_NUM_VIEWS); } static string_array_t *texture_mesh_node_flux_klein_args(char *dir, char *prompt) { any_array_t *argv = any_array_create(0); any_array_push(argv, string("%s/%s", dir, neural_node_iris_bin())); any_array_push(argv, "--diffusion-model"); any_array_push(argv, string("%s/flux-2-klein-4b-Q8_0.gguf", dir)); any_array_push(argv, "--vae"); any_array_push(argv, string("%s/flux_ae.safetensors", dir)); any_array_push(argv, "--llm"); any_array_push(argv, string("%s/Qwen3-4B-Q8_0.gguf", dir)); any_array_push(argv, "--cfg-scale"); any_array_push(argv, "1.0"); any_array_push(argv, "--sampling-method"); any_array_push(argv, "euler"); any_array_push(argv, "--diffusion-fa"); any_array_push(argv, "--offload-to-cpu"); any_array_push(argv, "--steps"); any_array_push(argv, "4"); any_array_push(argv, "-s"); any_array_push(argv, "-1"); any_array_push(argv, "-W"); any_array_push(argv, string("%d", TEXTURE_MESH_NODE_GRID_W)); any_array_push(argv, "-H"); any_array_push(argv, string("%d", TEXTURE_MESH_NODE_GRID_H)); any_array_push(argv, "-r"); any_array_push(argv, string("%s%sinput_grid.png", dir, PATH_SEP)); any_array_push(argv, "-p"); any_array_push(argv, texture_mesh_node_grid_prompt(prompt)); any_array_push(argv, "-o"); any_array_push(argv, string("%s%soutput_grid.png", dir, PATH_SEP)); any_array_push(argv, NULL); return argv; } static string_array_t *texture_mesh_node_qwen_args(char *dir, char *prompt) { any_array_t *argv = any_array_create(0); any_array_push(argv, string("%s/%s", dir, neural_node_iris_bin())); any_array_push(argv, "--diffusion-model"); any_array_push(argv, string("%s/qwen-image-edit-2511-Q4_K_S.gguf", dir)); any_array_push(argv, "--vae"); any_array_push(argv, string("%s/Qwen_Image-VAE.safetensors", dir)); any_array_push(argv, "--llm"); any_array_push(argv, string("%s/Qwen2.5-VL-7B-Instruct-Q4_K_S.gguf", dir)); any_array_push(argv, "--llm_vision"); any_array_push(argv, string("%s/mmproj-F16.gguf", dir)); any_array_push(argv, "--offload-to-cpu"); any_array_push(argv, "--diffusion-fa"); any_array_push(argv, "--steps"); any_array_push(argv, "30"); any_array_push(argv, "-s"); any_array_push(argv, "-1"); any_array_push(argv, "--cfg-scale"); any_array_push(argv, "2.5"); any_array_push(argv, "--flow-shift"); any_array_push(argv, "3"); any_array_push(argv, "--qwen-image-zero-cond-t"); any_array_push(argv, "-W"); any_array_push(argv, string("%d", TEXTURE_MESH_NODE_GRID_W / 2)); any_array_push(argv, "-H"); any_array_push(argv, string("%d", TEXTURE_MESH_NODE_GRID_H / 2)); any_array_push(argv, "-p"); any_array_push(argv, texture_mesh_node_grid_prompt(prompt)); any_array_push(argv, "-r"); any_array_push(argv, string("%s%sinput_grid.png", dir, PATH_SEP)); any_array_push(argv, "-o"); any_array_push(argv, string("%s%soutput_grid.png", dir, PATH_SEP)); any_array_push(argv, NULL); return argv; } static void texture_mesh_node_run_upscale(char *dir) { char *grid = string("%s%soutput_grid.png", dir, PATH_SEP); string_array_t *argv = any_array_create_from_raw( (void *[]){ string("%s/%s", dir, neural_node_iris_bin()), "-M", "upscale", "--upscale-model", string("%s/RealESRGAN_x4plus.pth", dir), "-i", grid, "-o", grid, NULL, }, 10); iron_exec_async(argv->buffer[0], argv->buffer); } static void texture_mesh_node_check_upscale(ui_node_t *node) { gc_unroot(neural_node_current); neural_node_current = node; gc_root(neural_node_current); iron_delay_idle_sleep(); if (iron_exec_async_done == 1) { sys_remove_update(texture_mesh_node_check_upscale); texture_mesh_node_split_grid(neural_node_dir()); texture_mesh_node_project(node); } } static void texture_mesh_node_check_sd(ui_node_t *node) { gc_unroot(neural_node_current); neural_node_current = node; gc_root(neural_node_current); iron_delay_idle_sleep(); if (iron_exec_async_done == 1) { sys_remove_update(texture_mesh_node_check_sd); texture_mesh_node_run_upscale(neural_node_dir()); sys_notify_on_update(texture_mesh_node_check_upscale, node); } } static void texture_mesh_node_run_sd(ui_node_t *node) { char *node_name = parser_material_node_name(node, NULL); ui_handle_t *h = ui_handle(node_name); char *prompt = ui_nest(h, 1)->text; char *dir = neural_node_dir(); if (prompt == NULL || string_equals(prompt, "")) { prompt = "."; } texture_mesh_node_build_grid(dir); string_array_t *argv = texture_mesh_node_model == 0 ? texture_mesh_node_qwen_args(dir, prompt) : texture_mesh_node_flux_klein_args(dir, prompt); iron_exec_async(argv->buffer[0], argv->buffer); sys_notify_on_update(texture_mesh_node_check_sd, node); } static void texture_mesh_node_project(ui_node_t *node) { char *dir = neural_node_dir(); char *node_name = parser_material_node_name(node, NULL); // Save state tool_type_t _tool = g_context->tool; f32 _brush_radius = g_context->brush_radius; f32 _brush_opacity = g_context->brush_opacity; f32 _brush_angle = g_context->brush_angle; f32 _brush_scale = g_context->brush_scale; f32 _brush_scale_x = g_context->brush_scale_x; // Project a camera-aligned decal g_context->tool = TOOL_TYPE_DECAL; g_context->brush_camera_align = true; g_context->brush_opacity = 1.0; g_context->brush_angle = 0.0; g_context->brush_scale = 1.0; g_context->brush_scale_x = (f32)sys_w() / (f32)sys_h(); g_context->brush_directional = false; g_context->decal_x = 0.5; g_context->decal_y = 0.5; // viewport_update_camera_type(CAMERA_TYPE_ORTHOGRAPHIC); for (i32 v = 0; v < TEXTURE_MESH_NODE_NUM_VIEWS; v++) { char *output = string("%s%soutput_%d.png", dir, PATH_SEP, v); if (!iron_file_exists(output)) { continue; } gpu_texture_t *sd_tex = iron_load_texture(output); any_imap_set(neural_node_results, node->outputs->buffer[0]->id, sd_tex); any_map_set(data_cached_textures, node_name, sd_tex); texture_mesh_node_set_view(v); camera_object_build_mat(scene_camera); render_path_base_draw_gbuffer(); mat4_t vp = scene_camera->vp; vec4_t clip_c = vec4_apply_mat4((vec4_t){0.0, 0.0, 0.0, 1.0}, vp); vec4_t cam_up = camera_object_up_world(scene_camera); vec4_t up_clip = vec4_apply_mat4((vec4_t){cam_up.x, cam_up.y, cam_up.z, 0.0}, vp); f32 m = math_abs(up_clip.y) > 0.00001 ? clip_c.w / up_clip.y : 1.0; f32 scale2d = (900.0 / (f32)base_h()) * g_config->window_scale; g_context->brush_radius = (math_abs(m) * 15.0) / (scale2d * 2.0); make_material_parse_paint_material(false); g_context->paint_vec.x = 0.5; g_context->paint_vec.y = 0.5; g_context->last_paint_vec_x = 0.5; g_context->last_paint_vec_y = 0.5; g_context->pdirty = 1; render_path_paint_commands_paint(false); } render_path_paint_dilate(true, true); // Restore texture_mesh_node_restore_camera(); camera_object_build_mat(scene_camera); g_context->pdirty = 0; g_context->tool = _tool; g_context->brush_camera_align = false; g_context->brush_radius = _brush_radius; g_context->brush_opacity = _brush_opacity; g_context->brush_angle = _brush_angle; g_context->brush_scale = _brush_scale; g_context->brush_scale_x = _brush_scale_x; make_material_parse_paint_material(false); render_path_base_draw_gbuffer(); g_context->rtdirty = 1; g_context->rdirty = 2; ui_nodes_hwnd->redraws = 2; ui_view2d_hwnd->redraws = 2; texture_mesh_node_step = 0; gc_unroot(texture_mesh_node_current); texture_mesh_node_current = NULL; } void texture_mesh_node_button(i32 node_id) { ui_node_t *node = ui_get_node(ui_nodes_get_canvas(true)->nodes, node_id); char *node_name = parser_material_node_name(node, NULL); ui_handle_t *h = ui_handle(node_name); string_array_t *models = any_array_create_from_raw( (void *[]){ "Qwen Image Edit", "FLUX 2 klein", }, 2); i32 model = ui_combo(ui_nest(h, 0), models, tr("Model"), false, UI_ALIGN_LEFT, true); char *prompt = ui_text_area(ui_nest(h, 1), UI_ALIGN_LEFT, true, tr("prompt"), true); node->buttons->buffer[0]->height = string_split(prompt, "\n")->length + 2; if (neural_node_button(node, models->buffer[model])) { texture_mesh_node_model = model; g_context->capturing_screenshot = true; texture_mesh_node_step = 0; gc_unroot(texture_mesh_node_current); texture_mesh_node_current = node; gc_root(texture_mesh_node_current); sys_notify_on_next_frame(texture_mesh_node_capture, NULL); } } void texture_mesh_node_init() { ui_node_t *texture_mesh_node_def = GC_ALLOC_INIT(ui_node_t, {.id = 0, .name = _tr("Texture Mesh"), .type = "NEURAL_TEXTURE_MESH", .x = 0, .y = 0, .color = 0xff4982a0, .inputs = any_array_create_from_raw( (void *[]){ GC_ALLOC_INIT(ui_node_socket_t, {.id = 0, .node_id = 0, .name = _tr("In"), .type = "BOOL", .color = 0xff6363c7, .default_value = f32_array_create_xyz(0.0, 0.0, 0.0), .min = 0.0, .max = 1.0, .precision = 100, .display = 0}), }, 1), .outputs = any_array_create_from_raw( (void *[]){ GC_ALLOC_INIT(ui_node_socket_t, {.id = 0, .node_id = 0, .name = _tr("Color"), .type = "RGBA", .color = 0xffc7c729, .default_value = f32_array_create_xyzw(0.0, 0.0, 0.0, 1.0), .min = 0.0, .max = 1.0, .precision = 100, .display = 0}), }, 1), .buttons = any_array_create_from_raw( (void *[]){ GC_ALLOC_INIT(ui_node_button_t, {.name = "texture_mesh_node_button", .type = "CUSTOM", .output = -1, .default_value = f32_array_create_x(0), .data = NULL, .min = 0.0, .max = 1.0, .precision = 100, .height = 1}), }, 1), .width = 0, .flags = 0}); any_array_push(nodes_material_neural, texture_mesh_node_def); any_map_set(parser_material_node_vectors, "NEURAL_TEXTURE_MESH", neural_node_vector); any_map_set(ui_nodes_custom_buttons, "texture_mesh_node_button", texture_mesh_node_button); }