From 5c19a166404891d610d1bc8a8596b897465aacbc Mon Sep 17 00:00:00 2001 From: luboslenco Date: Thu, 11 Jun 2026 20:01:25 +0200 Subject: [PATCH] paint: merge sculpt --- paint/sources/functions.h | 12 +- paint/sources/render/make_paint.c | 43 +- paint/sources/render/make_particle.c | 79 +++ paint/sources/render/make_sculpt.c | 866 +++++++++++++++++++---- paint/sources/render/render_path_paint.c | 4 +- paint/sources/slot_layer.c | 23 + paint/sources/ui/tab_layers.c | 8 +- paint/sources/ui/ui_menubar.c | 7 +- paint/sources/ui/ui_view2d.c | 2 +- paint/sources/uniforms.c | 40 +- paint/sources/util/util_layer.c | 37 +- paint/sources/util/util_particle.c | 2 + paint/sources/util/util_path.c | 8 + paint/sources/util/util_render.c | 4 + 14 files changed, 923 insertions(+), 212 deletions(-) diff --git a/paint/sources/functions.h b/paint/sources/functions.h index 9fabade3..51752d82 100644 --- a/paint/sources/functions.h +++ b/paint/sources/functions.h @@ -286,6 +286,7 @@ void export_arm_pack_assets(project_t *raw, asset_t_array_t void export_arm_run_swatches(char *path); void import_asset_run(char *path, f32 drop_x, f32 drop_y, bool show_box, bool hdr_as_envmap, void (*done)(void)); void make_particle_mask(node_shader_t *kong); +material_data_t *make_particle_get_bullet_material(); void uniforms_ext_init(); void render_path_deferred_init(); void render_path_deferred_commands(); @@ -683,11 +684,18 @@ void render_path_raytrace_raytrace_init(char *shader_nam void render_path_raytrace_draw(bool use_live_layer); node_shader_context_t *sculpt_make_sculpt_run(material_t *data, material_context_t *matcon); void sculpt_make_mesh_run(node_shader_t *kong, slot_layer_t_array_t *sculpt_layers, i32_array_t *sculpt_indices); -void sculpt_import_mesh_pack_to_texture(mesh_data_t *mesh, gpu_texture_t *target); -void sculpt_init_sculpt_texture(slot_layer_t *l, mesh_data_t *md); +void sculpt_make_paint_run(node_shader_t *kong); +bool sculpt_layer_has_visible_masks(slot_layer_t *l); +bool sculpt_mask_value(node_shader_t *kong, slot_layer_t *l, char *out_var, bool attrib, slot_layer_t *skip); +i32 sculpt_object_vertex_offset(mesh_object_t *o); +void sculpt_import_mesh_pack_to_texture(gpu_texture_t *target); +void sculpt_init_sculpt_texture(slot_layer_t *l); +void sculpt_init_meshes(); void sculpt_init(); void sculpt_layers_create_sculpt_layer(); +void render_path_sculpt_displace_pass(char *texpaint_sculpt); void render_path_sculpt_commands(); +void render_path_sculpt_snapshot_gbuffer(); void render_path_sculpt_begin(); void ui_statusbar_init(); void ui_statusbar_render_ui(); diff --git a/paint/sources/render/make_paint.c b/paint/sources/render/make_paint.c index 0d85d90f..0628ead2 100644 --- a/paint/sources/render/make_paint.c +++ b/paint/sources/render/make_paint.c @@ -92,7 +92,9 @@ string_array_t *make_paint_color_attachments() { } node_shader_context_t *make_paint_run(material_t *data, material_context_t *matcon) { - if (g_context->layer->texpaint_sculpt != NULL) { + bool picking_tool = g_context->tool == TOOL_TYPE_COLORID || g_context->tool == TOOL_TYPE_PICKER || g_context->tool == TOOL_TYPE_MATERIAL || + g_context->tool == TOOL_TYPE_CURSOR; + if (g_context->layer->texpaint_sculpt != NULL && !picking_tool) { return sculpt_make_sculpt_run(data, matcon); } @@ -594,44 +596,7 @@ node_shader_context_t *make_paint_run(material_t *data, material_context_t *matc parser_material_triplanar = false; parser_material_sample_keep_aspect = false; - { - slot_layer_t_array_t *sculpt_layers = any_array_create_from_raw((void *[]){}, 0); - i32_array_t *sculpt_indices = i32_array_create(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_array_push(sculpt_indices, i); - } - } - i32 scount = sculpt_layers->length; - if (scount > 0) { - i32 idx0 = sculpt_indices->buffer[0]; - node_shader_add_constant(kong, string("texpaint_sculpt_size%d: float2", idx0), string("_size(_texpaint_sculpt%d)", idx0)); - for (i32 i = 0; i < scount; ++i) { - i32 idx = sculpt_indices->buffer[i]; - node_shader_add_texture(kong, string("texpaint_sculpt%d", idx), string("_texpaint_sculpt%d", idx)); - } - if (scount > 1) { - node_shader_add_texture(kong, "texpaint_sculpt_base", "_texpaint_sculpt_base"); - } - - node_shader_write_attrib_vert(kong, string("var sculpt_uv_paint: uint2 = uint2(uint(float(vertex_id()) %% constants.texpaint_sculpt_size%d.x), " - "uint(float(vertex_id()) / constants.texpaint_sculpt_size%d.y));", - idx0, idx0)); - - node_shader_write_attrib_vert(kong, string("var sculpt_pos_paint: float4 = texpaint_sculpt%d[sculpt_uv_paint];", idx0)); - for (i32 i = 1; i < scount; ++i) { - i32 idx = sculpt_indices->buffer[i]; - node_shader_write_attrib_vert( - kong, string("sculpt_pos_paint = sculpt_pos_paint + texpaint_sculpt%d[sculpt_uv_paint] - texpaint_sculpt_base[sculpt_uv_paint];", idx)); - } - node_shader_write_attrib_vert(kong, "output.ndc = constants.WVP * float4(sculpt_pos_paint.xyz, 1.0);"); - if (kong->frag_wposition) { - node_shader_write_attrib_vert(kong, "output.wposition = (constants.W * float4(sculpt_pos_paint.xyz, 1.0)).xyz;"); - } - } - } + sculpt_make_paint_run(kong); con_paint->data->shader_from_source = true; gpu_create_shaders_from_kong(node_shader_get(kong), &con_paint->data->vertex_shader, &con_paint->data->fragment_shader, diff --git a/paint/sources/render/make_particle.c b/paint/sources/render/make_particle.c index d32ed881..7abe91e1 100644 --- a/paint/sources/render/make_particle.c +++ b/paint/sources/render/make_particle.c @@ -1,6 +1,85 @@ #include "../global.h" +static material_data_t *particle_bullet_material = NULL; + +static node_shader_context_t *make_particle_bullet_run() { + material_t *mat = GC_ALLOC_INIT(material_t, {.name = "particle_bullet", .canvas = NULL}); + shader_context_t *props = GC_ALLOC_INIT(shader_context_t, { + .name = "mesh", + .depth_write = true, + .compare_mode = "less", + .cull_mode = "clockwise", + .vertex_elements = any_array_create_from_raw( + (void *[]){ + GC_ALLOC_INIT(vertex_element_t, {.name = "pos", .data = "short4norm"}), + GC_ALLOC_INIT(vertex_element_t, {.name = "nor", .data = "short2norm"}), + GC_ALLOC_INIT(vertex_element_t, {.name = "tex", .data = "short2norm"}), + }, + 3), + .color_attachments = any_array_create_from_raw((void *[]){"RGBA64", "RGBA64", "RGBA64"}, 3), + .depth_attachment = "D32", + }); + node_shader_context_t *con = node_shader_context_create(mat, props); + node_shader_t *kong = node_shader_context_make_kong(con); + + kong->frag_n = true; + kong->frag_out = "float4[3]"; + + node_shader_add_constant(kong, "WVP: float4x4", "_world_view_proj_matrix"); + node_shader_write_vert(kong, "output.pos = constants.WVP * float4(input.pos.xyz, 1.0);"); + + node_shader_add_out(kong, "tex_coord: float2"); + node_shader_write_vert(kong, "output.tex_coord = input.tex;"); + + node_shader_add_function(kong, str_octahedron_wrap); + node_shader_add_function(kong, str_pack_float_int16); + + node_shader_write_frag(kong, "var basecol: float3 = float3(0.8, 0.8, 0.8);"); + node_shader_write_frag(kong, "var roughness: float = 0.5;"); + node_shader_write_frag(kong, "var metallic: float = 0.0;"); + node_shader_write_frag(kong, "var occlusion: float = 1.0;"); + node_shader_write_frag(kong, "n = n / (abs(n.x) + abs(n.y) + abs(n.z));"); + node_shader_write_frag(kong, "if (n.z < 0.0) { n.xy = octahedron_wrap(n.xy); }"); + node_shader_write_frag(kong, "output[0] = float4(n.xy, roughness, pack_f32_i16(metallic, uint(0)));"); + node_shader_write_frag(kong, "output[1] = float4(basecol, occlusion);"); + node_shader_write_frag(kong, "output[2] = float4(0.0, 0.0, input.tex_coord.xy);"); + + parser_material_finalize(con); + con->data->shader_from_source = true; + gpu_create_shaders_from_kong(node_shader_get(kong), &con->data->vertex_shader, &con->data->fragment_shader, &con->data->_->vertex_shader_size, + &con->data->_->fragment_shader_size); + return con; +} + +material_data_t *make_particle_get_bullet_material() { + if (particle_bullet_material != NULL) { + return particle_bullet_material; + } + + node_shader_context_t *con = make_particle_bullet_run(); + shader_context_load(con->data); + + shader_data_t *sd = GC_ALLOC_INIT(shader_data_t, { + .name = "particle_bullet", + .contexts = any_array_create_from_raw((void *[]){con->data}, 1), + }); + material_context_t *mcon = GC_ALLOC_INIT(material_context_t, { + .name = "mesh", + .bind_constants = any_array_create_from_raw((void *[]){}, 0), + .bind_textures = any_array_create_from_raw((void *[]){}, 0), + }); + material_context_load(mcon); + particle_bullet_material = GC_ALLOC_INIT(material_data_t, { + .name = "particle_bullet", + .shader = "", + .contexts = any_array_create_from_raw((void *[]){mcon}, 1), + ._ = GC_ALLOC_INIT(material_data_runtime_t, {.uid = 0.0, .shader = sd}), + }); + gc_root(particle_bullet_material); + return particle_bullet_material; +} + void make_particle_mask(node_shader_t *kong) { node_shader_add_out(kong, "wpos: float4"); node_shader_add_constant(kong, "W: float4x4", "_world_matrix"); diff --git a/paint/sources/render/make_sculpt.c b/paint/sources/render/make_sculpt.c index 761a0a9b..9c4c8aff 100644 --- a/paint/sources/render/make_sculpt.c +++ b/paint/sources/render/make_sculpt.c @@ -1,26 +1,49 @@ #include "../global.h" -void sculpt_import_mesh_pack_to_texture(mesh_data_t *mesh, gpu_texture_t *target) { - buffer_t *b = buffer_create(config_get_texture_res_x() * config_get_texture_res_y() * 4 * 4); - for (i32 i = 0; i < math_floor(mesh->index_array->length); ++i) { - i32 index = mesh->index_array->buffer[i]; - buffer_set_f32(b, 4 * i * 4, mesh->vertex_arrays->buffer[0]->values->buffer[index * 4] / 32767.0); - buffer_set_f32(b, 4 * i * 4 + 1 * 4, mesh->vertex_arrays->buffer[0]->values->buffer[index * 4 + 1] / 32767.0); - buffer_set_f32(b, 4 * i * 4 + 2 * 4, mesh->vertex_arrays->buffer[0]->values->buffer[index * 4 + 2] / 32767.0); - f32 nor_x = mesh->vertex_arrays->buffer[1]->values->buffer[index * 2] / 32767.0f; - f32 nor_y = mesh->vertex_arrays->buffer[1]->values->buffer[index * 2 + 1] / 32767.0f; - f32 nor_z = mesh->vertex_arrays->buffer[0]->values->buffer[index * 4 + 3] / 32767.0f; - f32 l1 = math_abs(nor_x) + math_abs(nor_y) + math_abs(nor_z); - f32 oct_x = l1 > 0.0f ? nor_x / l1 : 0.0f; - f32 oct_y = l1 > 0.0f ? nor_y / l1 : 0.0f; - if (nor_z < 0.0f) { - f32 ox = oct_x; - f32 oy = oct_y; - oct_x = (1.0f - math_abs(oy)) * (ox >= 0.0f ? 1.0f : -1.0f); - oct_y = (1.0f - math_abs(ox)) * (oy >= 0.0f ? 1.0f : -1.0f); +i32 sculpt_object_vertex_offset(mesh_object_t *o) { + i32 offset = 0; + for (i32 i = 0; i < g_project->_->paint_objects->length; ++i) { + mesh_object_t *p = g_project->_->paint_objects->buffer[i]; + if (p == o) { + break; } - buffer_set_f32(b, 4 * i * 4 + 3 * 4, (oct_x + 1.0f) * 0.5f + math_floor((oct_y + 1.0f) * 0.5f * 255.0f + 0.5f)); + offset += p->data->index_array->length; + } + return offset; +} + +void sculpt_import_mesh_pack_to_texture(gpu_texture_t *target) { + // Pack positions and normals into texture + u32 capacity = config_get_texture_res_x() * config_get_texture_res_y(); + buffer_t *b = buffer_create(capacity * 4 * 4); + i32 vertex_offset = 0; + for (i32 o = 0; o < g_project->_->paint_objects->length; ++o) { + mesh_data_t *mesh = g_project->_->paint_objects->buffer[o]->data; + for (i32 i = 0; i < math_floor(mesh->index_array->length); ++i) { + i32 index = mesh->index_array->buffer[i]; + i32 t = i + vertex_offset; + if (t >= capacity) { + break; + } + buffer_set_f32(b, 4 * t * 4, mesh->vertex_arrays->buffer[0]->values->buffer[index * 4] / 32767.0); + buffer_set_f32(b, 4 * t * 4 + 1 * 4, mesh->vertex_arrays->buffer[0]->values->buffer[index * 4 + 1] / 32767.0); + buffer_set_f32(b, 4 * t * 4 + 2 * 4, mesh->vertex_arrays->buffer[0]->values->buffer[index * 4 + 2] / 32767.0); + f32 nor_x = mesh->vertex_arrays->buffer[1]->values->buffer[index * 2] / 32767.0f; + f32 nor_y = mesh->vertex_arrays->buffer[1]->values->buffer[index * 2 + 1] / 32767.0f; + f32 nor_z = mesh->vertex_arrays->buffer[0]->values->buffer[index * 4 + 3] / 32767.0f; + f32 l1 = math_abs(nor_x) + math_abs(nor_y) + math_abs(nor_z); + f32 oct_x = l1 > 0.0f ? nor_x / l1 : 0.0f; + f32 oct_y = l1 > 0.0f ? nor_y / l1 : 0.0f; + if (nor_z < 0.0f) { + f32 ox = oct_x; + f32 oy = oct_y; + oct_x = (1.0f - math_abs(oy)) * (ox >= 0.0f ? 1.0f : -1.0f); + oct_y = (1.0f - math_abs(ox)) * (oy >= 0.0f ? 1.0f : -1.0f); + } + buffer_set_f32(b, 4 * t * 4 + 3 * 4, (oct_x + 1.0f) * 0.5f + math_floor((oct_y + 1.0f) * 0.5f * 255.0f + 0.5f)); + } + vertex_offset += mesh->index_array->length; } gpu_texture_t *imgmesh = gpu_create_texture_from_bytes(b, config_get_texture_res_x(), config_get_texture_res_y(), GPU_TEXTURE_FORMAT_RGBA128); @@ -31,6 +54,57 @@ void sculpt_import_mesh_pack_to_texture(mesh_data_t *mesh, gpu_texture_t *target draw_end(); } +static char *sculpt_blend_mode(node_shader_t *kong, i32 blending, char *cola, char *colb, char *opac) { + // mix/add/screen/lighten/difference (raise), subtract/darken (carve) and linear light (bidirectional) + if (blending == BLEND_TYPE_DARKEN) { + return string("lerp(%s, min(%s, %s), %s)", cola, cola, colb, opac); + } + else if (blending == BLEND_TYPE_MULTIPLY) { + return string("lerp(%s, %s * %s, %s)", cola, cola, colb, opac); + } + else if (blending == BLEND_TYPE_BURN) { + return string("lerp(%s, 1.0 - (1.0 - %s) / %s, %s)", cola, cola, colb, opac); + } + else if (blending == BLEND_TYPE_LIGHTEN) { + return string("max(%s, %s * %s)", cola, colb, opac); + } + else if (blending == BLEND_TYPE_SCREEN) { + return string("(1.0 - (1.0 - %s * %s) * (1.0 - %s))", colb, opac, cola); + } + else if (blending == BLEND_TYPE_DODGE) { + return string("lerp(%s, %s / (1.0 - %s), %s)", cola, cola, colb, opac); + } + else if (blending == BLEND_TYPE_ADD) { + return string("lerp(%s, %s + %s, %s)", cola, cola, colb, opac); + } + else if (blending == BLEND_TYPE_OVERLAY) { + char *res = "sculpt_overlay_res"; + node_shader_write_frag(kong, string("var %s: float;", res)); + node_shader_write_frag(kong, string("if (%s < 0.5) { %s = 2.0 * %s * %s; } else { %s = 1.0 - 2.0 * (1.0 - %s) * (1.0 - %s); }", cola, res, cola, colb, + res, cola, colb)); + return string("lerp(%s, %s, %s)", cola, res, opac); + } + else if (blending == BLEND_TYPE_SOFT_LIGHT) { + return string("((1.0 - %s) * %s + %s * ((1.0 - %s) * %s * %s + %s * (1.0 - (1.0 - %s) * (1.0 - %s))))", opac, cola, opac, cola, colb, cola, cola, colb, + cola); + } + else if (blending == BLEND_TYPE_LINEAR_LIGHT) { + return string("(%s + %s * (2.0 * (%s - 0.5)))", cola, opac, colb); + } + else if (blending == BLEND_TYPE_DIFFERENCE) { + return string("lerp(%s, abs(%s - %s), %s)", cola, cola, colb, opac); + } + else if (blending == BLEND_TYPE_SUBTRACT) { + return string("lerp(%s, %s - %s, %s)", cola, cola, colb, opac); + } + else if (blending == BLEND_TYPE_DIVIDE) { + return string("((1.0 - %s) * %s + %s * %s / %s)", opac, cola, opac, cola, colb); + } + else { // BLEND_TYPE_MIX, hue / saturation / color / value + return string("lerp(%s, %s, %s)", cola, colb, opac); + } +} + node_shader_context_t *sculpt_make_sculpt_run(material_t *data, material_context_t *matcon) { char *context_id = "paint"; shader_context_t *props = GC_ALLOC_INIT(shader_context_t, {.name = context_id, @@ -82,8 +156,11 @@ node_shader_context_t *sculpt_make_sculpt_run(material_t *data, material_context }, 4); - node_shader_t *kong = node_shader_context_make_kong(con_paint); - bool decal = context_is_decal(); + node_shader_t *kong = node_shader_context_make_kong(con_paint); + bool decal = context_is_decal(); + bool particle = g_context->tool == TOOL_TYPE_PARTICLE; + // Decal fill layer: displacement must be confined to the decal box projection + bool decal_layer = g_context->layer->fill_material != NULL && g_context->layer->uv_type == UV_TYPE_PROJECT && g_context->tool == TOOL_TYPE_FILL; node_shader_add_out(kong, "tex_coord: float2"); node_shader_write_vert(kong, "var madd: float2 = float2(0.5, 0.5);"); node_shader_write_vert(kong, "output.tex_coord = input.pos.xy * madd + madd;"); @@ -92,6 +169,18 @@ node_shader_context_t *sculpt_make_sculpt_run(material_t *data, material_context node_shader_write_attrib_frag(kong, "var tex_coord: float2 = input.tex_coord;"); node_shader_write_attrib_frag(kong, "var sculpt_uv: float2 = tex_coord;"); + // Restrict displacement to the object selected in the layer's object combo + if (g_project->_->paint_objects->length > 1) { + node_shader_add_texture(kong, "texpaint_sculpt_undo", "_texpaint_sculpt_undo"); + node_shader_add_constant(kong, "texpaint_undo_size: float2", "_size(_texpaint_sculpt_undo)"); + node_shader_add_constant(kong, "sculpt_mask_offset: float", "_sculpt_mask_offset"); + node_shader_add_constant(kong, "sculpt_mask_count: float", "_sculpt_mask_count"); + node_shader_write_frag(kong, "var sculpt_mask_lin: float = floor(sculpt_uv.y * constants.texpaint_undo_size.y) * " + "constants.texpaint_undo_size.x + floor(sculpt_uv.x * constants.texpaint_undo_size.x);"); + node_shader_write_frag(kong, "if (sculpt_mask_lin < constants.sculpt_mask_offset || sculpt_mask_lin >= " + "constants.sculpt_mask_offset + constants.sculpt_mask_count) { discard; }"); + } + node_shader_add_constant(kong, "inp: float4", "_input_brush"); node_shader_add_constant(kong, "inplast: float4", "_input_brush_last"); node_shader_add_texture(kong, "gbufferD", NULL); @@ -99,10 +188,10 @@ node_shader_context_t *sculpt_make_sculpt_run(material_t *data, material_context node_shader_add_constant(kong, "brush_radius: float", "_brush_radius"); node_shader_add_constant(kong, "brush_opacity: float", "_brush_opacity"); node_shader_add_constant(kong, "brush_hardness: float", "_brush_hardness"); + node_shader_write_frag(kong, "var dist: float = 0.0;"); if (g_context->tool == TOOL_TYPE_BRUSH || g_context->tool == TOOL_TYPE_ERASER || g_context->tool == TOOL_TYPE_CLONE || g_context->tool == TOOL_TYPE_BLUR || g_context->tool == TOOL_TYPE_PARTICLE || decal) { - node_shader_write_frag(kong, "var dist: float = 0.0;"); node_shader_write_frag(kong, "var depth: float = sample_lod(gbufferD, sampler_linear, constants.inp.xy, 0.0).r;"); node_shader_add_constant(kong, "invVP: float4x4", "_inv_view_proj_matrix"); // node_shader_write_frag(kong, "var winp: float4 = float4(float2(constants.inp.x, 1.0 - constants.inp.y) * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);"); @@ -110,49 +199,175 @@ node_shader_context_t *sculpt_make_sculpt_run(material_t *data, material_context node_shader_write_frag(kong, "winp = constants.invVP * winp;"); node_shader_write_frag(kong, "winp.xyz = winp.xyz / winp.w;"); node_shader_add_constant(kong, "W: float4x4", "_world_matrix"); - // node_shader_add_constant(kong, "texpaint_undo_size: float2", "_size(texpaint_sculpt_undo)"); //// - // node_shader_write_attrib_frag(kong, "var wposition: float3 = (constants.W * texpaint_sculpt_undo[uint2(uint(tex_coord.x * - // constants.texpaint_undo_size.x), uint(tex_coord.y * constants.texpaint_undo_size.y))]).xyz;"); - node_shader_write_attrib_frag(kong, "var read_undo: float4 = texpaint_sculpt_undo[uint2(uint(tex_coord.x * 2048.0), uint(tex_coord.y * 2048.0))];"); + node_shader_write_attrib_frag(kong, "var read_undo: float4 = texpaint_sculpt_undo[uint2(uint(tex_coord.x * constants.texpaint_undo_size.x), " + "uint(tex_coord.y * constants.texpaint_undo_size.y))];"); node_shader_write_attrib_frag(kong, "var wposition: float3 = (constants.W * float4(read_undo.xyz, 1.0)).xyz;"); node_shader_write_frag(kong, "var depthlast: float = sample_lod(gbufferD, sampler_linear, constants.inplast.xy, 0.0).r;"); node_shader_write_frag(kong, "var winplast: float4 = float4(float2(constants.inplast.x, 1.0 - constants.inplast.y) * 2.0 - 1.0, depthlast, 1.0);"); node_shader_write_frag(kong, "winplast = constants.invVP * winplast;"); node_shader_write_frag(kong, "winplast.xyz = winplast.xyz / winplast.w;"); - if (!decal) { - node_shader_write_frag(kong, "dist = distance(wposition.xyz, winp.xyz);"); + if (particle) { + node_shader_add_constant(kong, "particle_hit: float3", "_particle_hit"); + node_shader_add_constant(kong, "particle_hit_last: float3", "_particle_hit_last"); + node_shader_add_constant(kong, "particle_radius: float", "_particle_radius"); + node_shader_write_frag(kong, "var ppa: float3 = wposition.xyz - constants.particle_hit;"); + node_shader_write_frag(kong, "var pba: float3 = constants.particle_hit_last - constants.particle_hit;"); + node_shader_write_frag(kong, "var pph: float = clamp(dot(ppa, pba) / max(dot(pba, pba), 0.00000001), 0.0, 1.0);"); + node_shader_write_frag(kong, "dist = length(ppa - pba * pph) * (5.0 / constants.particle_radius);"); + node_shader_write_frag(kong, "if (dist > 1.0) { discard; }"); + } + else if (!decal) { + if (g_context->brush_lazy_radius > 0 && g_context->brush_lazy_step > 0) { // Sphere + node_shader_write_frag(kong, "dist = distance(wposition.xyz, winp.xyz);"); + } + else { // Capsule + node_shader_write_frag(kong, "var pa: float3 = wposition.xyz - winp.xyz;"); + node_shader_write_frag(kong, "var ba: float3 = winplast.xyz - winp.xyz;"); + node_shader_write_frag(kong, "var h: float = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);"); + node_shader_write_frag(kong, "dist = length(pa - ba * h);"); + } node_shader_write_frag(kong, "if (dist > constants.brush_radius) { discard; }"); } + else { // decal + node_shader_add_constant(kong, "decal_mask: float4", "_decal_mask"); + node_shader_write_frag(kong, "if (constants.decal_mask.z > 0.0) {"); + if (g_context->brush_lazy_radius > 0 && g_context->brush_lazy_step > 0) { + node_shader_write_frag(kong, "dist = distance(wposition.xyz, winp.xyz);"); + } + else { + node_shader_write_frag(kong, "var pa: float3 = wposition.xyz - winp.xyz;"); + node_shader_write_frag(kong, "var ba: float3 = winplast.xyz - winp.xyz;"); + node_shader_write_frag(kong, "var h: float = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);"); + node_shader_write_frag(kong, "dist = length(pa - ba * h);"); + } + node_shader_write_frag(kong, "if (dist > constants.brush_radius) { discard; }"); + node_shader_write_frag(kong, "}"); + } } else if (g_context->tool == TOOL_TYPE_FILL) { - node_shader_write_frag(kong, "var dist: float = 0.0;"); - node_shader_add_constant(kong, "W: float4x4", "_world_matrix"); - node_shader_write_attrib_frag(kong, "var read_undo: float4 = texpaint_sculpt_undo[uint2(uint(tex_coord.x * 2048.0), uint(tex_coord.y * 2048.0))];"); + node_shader_write_attrib_frag(kong, "var read_undo: float4 = texpaint_sculpt_undo[uint2(uint(tex_coord.x * constants.texpaint_undo_size.x), " + "uint(tex_coord.y * constants.texpaint_undo_size.y))];"); node_shader_write_attrib_frag(kong, "var wposition: float3 = (constants.W * float4(read_undo.xyz, 1.0)).xyz;"); } + if (decal_layer) { + node_shader_add_function(kong, str_octahedron_wrap); + node_shader_add_constant(kong, "decal_layer_matrix: float4x4", "_decal_layer_matrix"); + node_shader_write_frag(kong, "var decal_proj: float4 = constants.decal_layer_matrix * float4(read_undo.xyz, 1.0);"); + node_shader_write_frag(kong, "var uvsp: float2 = decal_proj.xy / decal_proj.w;"); + node_shader_write_frag(kong, "uvsp.x = uvsp.x * 0.5 + 0.5;"); + node_shader_write_frag(kong, "uvsp.y = 1.0 - (uvsp.y * 0.5 + 0.5);"); + node_shader_write_frag(kong, "if (uvsp.x < 0.0 || uvsp.y < 0.0 || uvsp.x > 1.0 || uvsp.y > 1.0) { discard; }"); + + f32 uv_angle = g_context->layer->angle; + if (uv_angle != 0.0) { + node_shader_add_constant(kong, "brush_angle: float2", "_brush_angle"); + node_shader_write_frag(kong, "uvsp = float2(uvsp.x * constants.brush_angle.x - uvsp.y * constants.brush_angle.y, uvsp.x * " + "constants.brush_angle.y + uvsp.y * constants.brush_angle.x);"); + } + + // Reject surfaces not facing the decal direction + node_shader_write_frag(kong, "var dproj_undo: float4 = texpaint_sculpt_undo[uint2(uint(sculpt_uv.x * constants.texpaint_undo_size.x), uint(sculpt_uv.y " + "* constants.texpaint_undo_size.y))];"); + node_shader_write_frag(kong, "var dproj_nv: float = floor(dproj_undo.a) / 255.0;"); + node_shader_write_frag(kong, "var dproj_oct: float2 = float2(dproj_undo.a - floor(dproj_undo.a), dproj_nv) * 2.0 - 1.0;"); + node_shader_write_frag(kong, "var dproj_nz: float = 1.0 - abs(dproj_oct.x) - abs(dproj_oct.y);"); + node_shader_write_frag(kong, "var dproj_nor: float3 = float3(dproj_oct.xy, dproj_nz);"); + node_shader_write_frag(kong, "if (dproj_nz < 0.0) { dproj_nor.xy = octahedron_wrap(dproj_oct.xy); }"); + node_shader_write_frag(kong, "dproj_nor = normalize((constants.W * float4(normalize(dproj_nor), 0.0)).xyz);"); + node_shader_add_constant(kong, "decal_layer_nor: float3", "_decal_layer_nor"); + f32 dot_angle = g_context->brush_angle_reject_dot; + node_shader_write_frag(kong, string("if (abs(dot(dproj_nor, constants.decal_layer_nor) - 1.0) > %s) { discard; }", f32_to_string(dot_angle))); + + // Reject surfaces outside the decal box + node_shader_add_constant(kong, "decal_layer_loc: float3", "_decal_layer_loc"); + node_shader_add_constant(kong, "decal_layer_dim: float", "_decal_layer_dim"); + node_shader_write_frag(kong, + "if (abs(dot(constants.decal_layer_nor, constants.decal_layer_loc - wposition.xyz)) > constants.decal_layer_dim) { discard; }"); + + node_shader_add_constant(kong, "brush_scale: float", "_brush_scale"); + node_shader_write_frag(kong, "tex_coord = uvsp * constants.brush_scale;"); + } + if (decal) { - // Project wposition to screen-space and compute decal tex_coord - node_shader_add_constant(kong, "VP: float4x4", "_view_proj_matrix"); - node_shader_write_attrib_frag(kong, "var decal_ndc: float4 = constants.VP * float4(wposition.xyz, 1.0);"); - node_shader_write_attrib_frag(kong, "var sp: float3 = decal_ndc.xyz / decal_ndc.w;"); - node_shader_write_attrib_frag(kong, "sp.x = sp.x * 0.5 + 0.5;"); - node_shader_write_attrib_frag(kong, "sp.y = sp.y * 0.5 + 0.5;"); - node_shader_write_attrib_frag(kong, "sp.y = 1.0 - sp.y;"); - node_shader_add_constant(kong, "aspect_ratio: float", "_aspect_ratio_window"); + // Tangent-space decal projection + node_shader_add_function(kong, str_octahedron_wrap); + node_shader_add_texture(kong, "gbuffer0_undo", NULL); node_shader_add_constant(kong, "decal_mask: float4", "_decal_mask"); - node_shader_write_attrib_frag(kong, "var uvsp: float2 = sp.xy;"); - node_shader_write_attrib_frag(kong, "uvsp = uvsp - constants.decal_mask.xy;"); + node_shader_add_constant(kong, "VP: float4x4", "_view_proj_matrix"); + node_shader_add_constant(kong, "aspect_ratio: float", "_aspect_ratio_window"); + node_shader_add_constant(kong, "camera_right: float3", "_camera_right"); + node_shader_add_constant(kong, "camera_up: float3", "_camera_up"); + node_shader_add_constant(kong, "camera_align: float", "_decal_camera_align"); + + node_shader_write_attrib_frag(kong, "var uvsp: float2 = float2(0.0, 0.0);"); + + node_shader_write_attrib_frag(kong, "if (constants.camera_align > 0.0) {"); + // Project the surface point to screen-space for the planar, camera-aligned decal + node_shader_write_attrib_frag(kong, "var sp4: float4 = constants.VP * float4(wposition.xyz, 1.0);"); + node_shader_write_attrib_frag(kong, "var sp: float2 = sp4.xy / sp4.w;"); + node_shader_write_attrib_frag(kong, "sp.x = sp.x * 0.5 + 0.5;"); + node_shader_write_attrib_frag(kong, "sp.y = 1.0 - (sp.y * 0.5 + 0.5);"); + node_shader_write_attrib_frag(kong, "var ca_depth: float = sample_lod(gbufferD, sampler_linear, constants.decal_mask.xy, 0.0).r;"); + node_shader_write_attrib_frag(kong, "var ca_coord: float2 = float2(constants.decal_mask.x * 2.0 - 1.0, 1.0 - constants.decal_mask.y * 2.0);"); + node_shader_write_attrib_frag(kong, "var ca_homog: float4 = constants.invVP * float4(ca_coord.x, ca_coord.y, ca_depth, 1.0);"); + node_shader_write_attrib_frag(kong, "var ca_clip_w: float = 1.0 / ca_homog.w;"); + node_shader_write_attrib_frag(kong, "var vp_up_y: float = (constants.VP * float4(constants.camera_up, 0.0)).y;"); + node_shader_write_attrib_frag(kong, "uvsp = sp.xy - constants.decal_mask.xy;"); node_shader_write_attrib_frag(kong, "uvsp.x *= constants.aspect_ratio;"); - node_shader_write_attrib_frag(kong, "uvsp = uvsp * (0.21 / (constants.decal_mask.w * 0.9));"); + node_shader_write_attrib_frag(kong, "uvsp = uvsp * (ca_clip_w / (vp_up_y * constants.brush_radius));"); + node_shader_write_attrib_frag(kong, "}"); + + node_shader_write_attrib_frag(kong, "else {"); + // When mask is active, anchor the decal at the frozen position + node_shader_write_attrib_frag(kong, "var decal_xy: float2 = constants.inp.xy;"); + node_shader_write_attrib_frag(kong, "if (constants.decal_mask.z > 0.0) { decal_xy = constants.decal_mask.xy; }"); + + // Unproject the decal anchor point from the depth buffer + node_shader_write_attrib_frag(kong, "var decal_depth: float = sample_lod(gbufferD, sampler_linear, decal_xy, 0.0).r;"); + node_shader_write_attrib_frag(kong, "var decal_wpos4: float4 = float4(float2(decal_xy.x, 1.0 - decal_xy.y) * 2.0 - 1.0, decal_depth, 1.0);"); + node_shader_write_attrib_frag(kong, "decal_wpos4 = constants.invVP * decal_wpos4;"); + node_shader_write_attrib_frag(kong, "var decal_wpos: float3 = decal_wpos4.xyz / decal_wpos4.w;"); + + // Decode face normal at anchor point + node_shader_write_attrib_frag(kong, "var dg0: float2 = sample_lod(gbuffer0_undo, sampler_linear, decal_xy, 0.0).rg;"); + node_shader_write_attrib_frag(kong, "var dn: float3;"); + node_shader_write_attrib_frag(kong, "dn.z = 1.0 - abs(dg0.x) - abs(dg0.y);"); + node_shader_write_attrib_frag( + kong, "if (dn.z >= 0.0) { dn.x = dg0.x; dn.y = dg0.y; } else { var fw: float2 = octahedron_wrap(dg0.xy); dn.x = fw.x; dn.y = fw.y; }"); + node_shader_write_attrib_frag(kong, "dn = normalize(dn);"); + + // Build tangent basis + node_shader_write_attrib_frag(kong, "var d_right: float3 = constants.camera_right;"); + node_shader_write_attrib_frag(kong, "if (abs(dot(dn, d_right)) > 0.999) { d_right = float3(0.0, 0.0, 1.0); }"); + node_shader_write_attrib_frag(kong, "var d_tan: float3 = normalize(d_right - dn * dot(d_right, dn));"); + node_shader_write_attrib_frag(kong, "var d_bin: float3 = cross(d_tan, dn);"); + + node_shader_write_attrib_frag(kong, "var d_offset: float3 = wposition.xyz - decal_wpos;"); + node_shader_write_attrib_frag(kong, "var decal_radius: float = constants.brush_radius;"); + node_shader_write_attrib_frag(kong, "if (constants.decal_mask.z > 0.0) { decal_radius = constants.decal_mask.w; }"); + node_shader_write_attrib_frag(kong, "if (abs(dot(d_offset, dn)) > decal_radius) { discard; }"); + node_shader_write_attrib_frag(kong, "uvsp = float2(dot(d_offset, d_tan), dot(d_offset, d_bin));"); + node_shader_write_attrib_frag(kong, "uvsp = uvsp / decal_radius * 0.5;"); + + node_shader_write_attrib_frag(kong, "}"); + + if (g_context->brush_directional) { + node_shader_add_constant(kong, "brush_direction: float3", "_brush_direction"); + node_shader_write_attrib_frag(kong, "if (constants.brush_direction.z == 0.0) { discard; }"); + node_shader_write_attrib_frag(kong, "uvsp = float2(uvsp.x * constants.brush_direction.x - uvsp.y * constants.brush_direction.y, uvsp.x * " + "constants.brush_direction.y + uvsp.y * constants.brush_direction.x);"); + } + f32 angle = g_context->brush_angle + g_context->brush_nodes_angle; if (angle != 0.0) { node_shader_add_constant(kong, "brush_angle: float2", "_brush_angle"); node_shader_write_attrib_frag(kong, "uvsp = float2(uvsp.x * constants.brush_angle.x - uvsp.y * constants.brush_angle.y, uvsp.x * " "constants.brush_angle.y + uvsp.y * constants.brush_angle.x);"); } + node_shader_add_constant(kong, "brush_scale_x: float", "_brush_scale_x"); node_shader_write_attrib_frag(kong, "uvsp.x *= constants.brush_scale_x;"); node_shader_write_attrib_frag(kong, "uvsp += float2(0.5, 0.5);"); @@ -177,11 +392,131 @@ node_shader_context_t *sculpt_make_sculpt_run(material_t *data, material_context node_shader_write_frag(kong, "var basecol: float3 = float3(1.0, 1.0, 1.0);"); node_shader_write_frag(kong, string("var opacity: float = %s;", sout->out_opacity)); + if (g_context->layer->fill_material == NULL) { + node_shader_write_frag(kong, "opacity *= constants.brush_opacity;"); + } + else { + node_shader_write_frag(kong, "opacity *= 20.0;"); + } + + if (g_context->brush_mask_image != NULL && g_context->tool == TOOL_TYPE_DECAL) { + node_shader_add_texture(kong, "texbrushmask", "_texbrushmask"); + node_shader_write_frag(kong, "var mask_sample: float4 = sample_lod(texbrushmask, sampler_linear, uvsp, 0.0);"); + if (g_context->brush_mask_image_is_alpha) { + node_shader_write_frag(kong, "opacity *= mask_sample.a;"); + } + else { + node_shader_write_frag(kong, "opacity *= mask_sample.r * mask_sample.a;"); + } + } + else if (g_context->tool == TOOL_TYPE_TEXT) { + node_shader_add_texture(kong, "textexttool", "_textexttool"); + node_shader_write_frag(kong, "opacity *= sample_lod(textexttool, sampler_linear, uvsp, 0.0).r;"); + } + + if (g_context->brush_mask_image != NULL && (g_context->tool == TOOL_TYPE_BRUSH || g_context->tool == TOOL_TYPE_ERASER)) { + node_shader_add_texture(kong, "texbrushmask", "_texbrushmask"); + node_shader_add_function(kong, str_octahedron_wrap); + node_shader_add_texture(kong, "gbuffer0_undo", NULL); + node_shader_add_constant(kong, "camera_right: float3", "_camera_right"); + node_shader_write_frag(kong, "var mn0: float2 = sample_lod(gbuffer0_undo, sampler_linear, constants.inp.xy, 0.0).rg;"); + node_shader_write_frag(kong, "var mn: float3;"); + node_shader_write_frag(kong, "mn.z = 1.0 - abs(mn0.x) - abs(mn0.y);"); + node_shader_write_frag( + kong, "if (mn.z >= 0.0) { mn.x = mn0.x; mn.y = mn0.y; } else { var mfw: float2 = octahedron_wrap(mn0.xy); mn.x = mfw.x; mn.y = mfw.y; }"); + node_shader_write_frag(kong, "mn = normalize(mn);"); + node_shader_write_frag(kong, "var mr: float3 = constants.camera_right;"); + node_shader_write_frag(kong, "if (abs(dot(mn, mr)) > 0.999) { mr = float3(0.0, 0.0, 1.0); }"); + node_shader_write_frag(kong, "var mt: float3 = normalize(mr - mn * dot(mr, mn));"); + node_shader_write_frag(kong, "var mb: float3 = cross(mt, mn);"); + node_shader_write_frag(kong, "var pa_mask_3d: float3 = wposition.xyz - winp.xyz;"); + node_shader_write_frag(kong, "var pa_mask: float2 = float2(dot(pa_mask_3d, mt), dot(pa_mask_3d, mb));"); + if (g_context->brush_directional) { + node_shader_add_constant(kong, "brush_direction: float3", "_brush_direction"); + node_shader_write_frag(kong, "if (constants.brush_direction.z == 0.0) { discard; }"); + node_shader_write_frag(kong, "pa_mask = float2(pa_mask.x * constants.brush_direction.x - pa_mask.y * constants.brush_direction.y, pa_mask.x * " + "constants.brush_direction.y + pa_mask.y * constants.brush_direction.x);"); + } + f32 angle = g_context->brush_angle + g_context->brush_nodes_angle; + if (angle != 0.0) { + node_shader_add_constant(kong, "brush_angle: float2", "_brush_angle"); + node_shader_write_frag(kong, "pa_mask.xy = float2(pa_mask.x * constants.brush_angle.x - pa_mask.y * constants.brush_angle.y, pa_mask.x * " + "constants.brush_angle.y + pa_mask.y * constants.brush_angle.x);"); + } + node_shader_write_frag(kong, "pa_mask = pa_mask / constants.brush_radius * 0.5 + 0.5;"); + node_shader_write_frag(kong, "var mask_sample: float4 = sample_lod(texbrushmask, sampler_linear, pa_mask, 0.0);"); + if (g_context->brush_mask_image_is_alpha) { + node_shader_write_frag(kong, "opacity *= mask_sample.a;"); + } + else { + node_shader_write_frag(kong, "opacity *= mask_sample.r * mask_sample.a;"); + } + } + + bool has_wposition = g_context->tool == TOOL_TYPE_BRUSH || g_context->tool == TOOL_TYPE_ERASER || g_context->tool == TOOL_TYPE_CLONE || + g_context->tool == TOOL_TYPE_BLUR || g_context->tool == TOOL_TYPE_PARTICLE || g_context->tool == TOOL_TYPE_FILL || decal; + + if (g_context->select_active && has_wposition) { + node_shader_add_constant(kong, "VP: float4x4", "_view_proj_matrix"); + node_shader_add_constant(kong, "select_mask: float4", "_select_mask"); + node_shader_write_frag(kong, "var select_sp4: float4 = constants.VP * float4(wposition.xyz, 1.0);"); + node_shader_write_frag(kong, "var select_sp: float2 = select_sp4.xy / select_sp4.w;"); + node_shader_write_frag(kong, "select_sp.x = select_sp.x * 0.5 + 0.5;"); + node_shader_write_frag(kong, "select_sp.y = 1.0 - (select_sp.y * 0.5 + 0.5);"); + node_shader_write_frag(kong, "if (select_sp.x < constants.select_mask.x || select_sp.x > constants.select_mask.z || select_sp.y < " + "constants.select_mask.y || select_sp.y > constants.select_mask.w) { discard; }"); + } + + if (g_context->brush_stencil_image != NULL && has_wposition) { + node_shader_add_constant(kong, "VP: float4x4", "_view_proj_matrix"); + node_shader_add_constant(kong, "aspect_ratio: float", "_aspect_ratio_window"); + node_shader_add_texture(kong, "texbrushstencil", "_texbrushstencil"); + node_shader_add_constant(kong, "texbrushstencil_size: float2", "_size(_texbrushstencil)"); + node_shader_add_constant(kong, "stencil_transform: float4", "_stencil_transform"); + node_shader_write_frag(kong, "var stencil_sp4: float4 = constants.VP * float4(wposition.xyz, 1.0);"); + node_shader_write_frag(kong, "var stencil_sp: float2 = stencil_sp4.xy / stencil_sp4.w;"); + node_shader_write_frag(kong, "stencil_sp.x = stencil_sp.x * 0.5 + 0.5;"); + node_shader_write_frag(kong, "stencil_sp.y = 1.0 - (stencil_sp.y * 0.5 + 0.5);"); + node_shader_write_frag( + kong, + "var stencil_uv: float2 = (stencil_sp.xy - constants.stencil_transform.xy) / constants.stencil_transform.z * float2(constants.aspect_ratio, 1.0);"); + node_shader_write_frag(kong, "var stencil_size: float2 = constants.texbrushstencil_size;"); + node_shader_write_frag(kong, "var stencil_ratio: float = stencil_size.y / stencil_size.x;"); + node_shader_write_frag(kong, "stencil_uv -= float2(0.5 / stencil_ratio, 0.5);"); + node_shader_write_frag(kong, + "stencil_uv = float2(stencil_uv.x * cos(constants.stencil_transform.w) - stencil_uv.y * sin(constants.stencil_transform.w),\ + stencil_uv.x * sin(constants.stencil_transform.w) + stencil_uv.y * cos(constants.stencil_transform.w));"); + node_shader_write_frag(kong, "stencil_uv += float2(0.5 / stencil_ratio, 0.5);"); + node_shader_write_frag(kong, "stencil_uv.x *= stencil_ratio;"); + node_shader_write_frag(kong, "if (stencil_uv.x < 0.0 || stencil_uv.x > 1.0 || stencil_uv.y < 0.0 || stencil_uv.y > 1.0) { discard; }"); + node_shader_write_frag(kong, "var texbrushstencil_sample: float4 = sample_lod(texbrushstencil, sampler_linear, stencil_uv, 0.0);"); + if (g_context->brush_stencil_image_is_alpha) { + node_shader_write_frag(kong, "opacity *= texbrushstencil_sample.a;"); + } + else { + node_shader_write_frag(kong, "opacity *= texbrushstencil_sample.r * texbrushstencil_sample.a;"); + } + } + node_shader_write_frag(kong, "if (opacity == 0.0) { discard; }"); - node_shader_write_frag(kong, "var str: float = clamp((constants.brush_radius - dist) * constants.brush_hardness * 1.0, 0.0, 1.0) * opacity;"); + if (particle) { + node_shader_write_frag(kong, "var str: float = clamp(pow(dist * constants.brush_hardness * 0.2, 2.0) / 10.0, 0.0, 1.0) * opacity;"); + } + else { + node_shader_write_frag(kong, "var t: float = clamp(dist / constants.brush_radius, 0.0, 1.0);"); + node_shader_write_frag(kong, "var t2: float = clamp((t - constants.brush_hardness) / max(1.0 - constants.brush_hardness, 0.001), 0.0, 1.0);"); + node_shader_write_frag(kong, "var falloff: float = 1.0 - t2 * t2 * (3.0 - 2.0 * t2);"); + node_shader_write_frag(kong, "var str: float = falloff * constants.brush_radius * 0.05 * opacity;"); + } + if (decal) { + node_shader_write_frag(kong, "str = str * 0.2;"); + } + node_shader_add_texture(kong, "texpaint_sculpt_undo", "_texpaint_sculpt_undo"); + node_shader_add_constant(kong, "texpaint_undo_size: float2", "_size(_texpaint_sculpt_undo)"); node_shader_write_frag(kong, "var sample_undo: float4 = sample_lod(texpaint_sculpt_undo, sampler_linear, sculpt_uv, 0.0);"); - node_shader_write_frag(kong, "var raw_undo: float4 = texpaint_sculpt_undo[uint2(uint(tex_coord.x * 2048.0), uint(tex_coord.y * 2048.0))];"); + node_shader_write_frag(kong, "var raw_undo: float4 = texpaint_sculpt_undo[uint2(uint(sculpt_uv.x * constants.texpaint_undo_size.x), uint(sculpt_uv.y * " + "constants.texpaint_undo_size.y))];"); if (g_context->layer->fill_material != NULL) { node_shader_add_function(kong, str_octahedron_wrap); @@ -196,13 +531,35 @@ node_shader_context_t *sculpt_make_sculpt_run(material_t *data, material_context node_shader_write_frag(kong, "if (sample_undo.r == 0.0 && sample_undo.g == 0.0 && sample_undo.b == 0.0) { discard; }"); node_shader_add_function(kong, str_octahedron_wrap); node_shader_add_texture(kong, "gbuffer0_undo", NULL); - node_shader_write_frag(kong, "var g0_undo: float2 = sample_lod(gbuffer0_undo, sampler_linear, constants.inp.xy, 0.0).rg;"); + if (particle) { + node_shader_add_constant(kong, "VP: float4x4", "_view_proj_matrix"); + node_shader_add_constant(kong, "particle_hit: float3", "_particle_hit"); + node_shader_write_frag(kong, "var hit_ndc: float4 = constants.VP * float4(constants.particle_hit, 1.0);"); + node_shader_write_frag(kong, "var hit_uv: float2 = hit_ndc.xy / hit_ndc.w;"); + node_shader_write_frag(kong, "hit_uv.x = hit_uv.x * 0.5 + 0.5;"); + node_shader_write_frag(kong, "hit_uv.y = 1.0 - (hit_uv.y * 0.5 + 0.5);"); + node_shader_write_frag(kong, "var g0_undo: float2 = sample_lod(gbuffer0_undo, sampler_linear, hit_uv, 0.0).rg;"); + } + else { + node_shader_write_frag(kong, "var g0_undo: float2 = sample_lod(gbuffer0_undo, sampler_linear, constants.inp.xy, 0.0).rg;"); + } node_shader_write_frag(kong, "var wn: float3;"); node_shader_write_frag(kong, "wn.z = 1.0 - abs(g0_undo.x) - abs(g0_undo.y);"); node_shader_write_frag(kong, "if (wn.z >= 0.0) { wn.xy = g0_undo.xy; } else { wn.xy = octahedron_wrap(g0_undo.xy); }"); node_shader_write_frag(kong, "var n: float3 = normalize(wn);"); } - node_shader_write_frag(kong, "output[0] = float4(sample_undo.rgb + n * disp * str, raw_undo.a);"); + if (g_context->tool == TOOL_TYPE_BLUR) { + // Even out the surface by relaxing each vertex toward the cursors tangent plane + node_shader_write_frag(kong, "var plane_dist: float = dot(wposition.xyz - winp.xyz, n);"); + node_shader_write_frag(kong, "output[0] = float4(sample_undo.rgb - n * plane_dist * str, raw_undo.a);"); + } + else if (g_context->tool == TOOL_TYPE_ERASER) { + node_shader_write_frag(kong, "output[0] = float4(sample_undo.rgb - n * disp * str, raw_undo.a);"); + } + else { + node_shader_write_frag(kong, string("var sculpt_disp: float = %s;", sculpt_blend_mode(kong, g_context->brush_blending, "0.0", "disp.x", "str"))); + node_shader_write_frag(kong, "output[0] = float4(sample_undo.rgb + n * sculpt_disp, raw_undo.a);"); + } node_shader_write_frag(kong, "output[1] = float4(str, 0.0, 0.0, 1.0);"); parser_material_finalize(con_paint); con_paint->data->shader_from_source = true; @@ -211,6 +568,49 @@ node_shader_context_t *sculpt_make_sculpt_run(material_t *data, material_context return con_paint; } +static void sculpt_mesh_write(node_shader_t *kong, bool attrib, char *s) { + if (attrib) { + node_shader_write_attrib_vert(kong, s); + } + else { + node_shader_write_vert(kong, s); + } +} + +bool sculpt_layer_has_visible_masks(slot_layer_t *l) { + slot_layer_t_array_t *masks = slot_layer_get_masks(l, true); + if (masks == NULL) { + return false; + } + for (i32 i = 0; i < masks->length; ++i) { + if (slot_layer_is_visible(masks->buffer[i])) { + return true; + } + } + return false; +} + +bool sculpt_mask_value(node_shader_t *kong, slot_layer_t *l, char *out_var, bool attrib, slot_layer_t *skip) { + if (!sculpt_layer_has_visible_masks(l)) { + return false; + } + slot_layer_t_array_t *masks = slot_layer_get_masks(l, true); + sculpt_mesh_write(kong, attrib, string("var %s: float = 1.0;", out_var)); + for (i32 i = 0; i < masks->length; ++i) { + slot_layer_t *m = masks->buffer[i]; + if (!slot_layer_is_visible(m) || m == skip) { + continue; + } + node_shader_add_texture(kong, string("texpaint_vert%s", i32_to_string(m->id)), string("_texpaint_vert%s", i32_to_string(m->id))); + f32 opac = slot_layer_get_opacity(m); + sculpt_mesh_write(kong, attrib, + string("%s *= lerp(1.0, sample_lod(texpaint_vert%s, sampler_linear, input.tex, 0.0).r, float(%s));", out_var, i32_to_string(m->id), + f32_to_string(opac))); + } + sculpt_mesh_write(kong, attrib, string("%s = clamp(%s, 0.0, 1.0);", out_var, out_var)); + return true; +} + void sculpt_make_mesh_run(node_shader_t *kong, slot_layer_t_array_t *sculpt_layers, i32_array_t *sculpt_indices) { i32 count = sculpt_layers->length; if (count == 0) { @@ -221,6 +621,8 @@ void sculpt_make_mesh_run(node_shader_t *kong, slot_layer_t_array_t *sculpt_laye node_shader_add_constant(kong, "WVP: float4x4", "_world_view_proj_matrix"); node_shader_add_constant(kong, "N: float3x3", "_normal_matrix"); + // Per-object start index into the shared sculpt grid + node_shader_add_constant(kong, "sculpt_vertex_offset: int", "_sculpt_vertex_offset"); node_shader_add_out(kong, "wnormal: float3"); kong->frag_n = false; @@ -231,49 +633,78 @@ void sculpt_make_mesh_run(node_shader_t *kong, slot_layer_t_array_t *sculpt_laye node_shader_add_texture(kong, string("texpaint_sculpt%d", idx), string("_texpaint_sculpt%d", idx)); } - if (count > 1) { + // The base layer holds the absolute mesh position + bool base_masked = sculpt_layer_has_visible_masks(sculpt_layers->buffer[0]); + if (count > 1 || base_masked) { node_shader_add_texture(kong, "texpaint_sculpt_base", "_texpaint_sculpt_base"); } // Position - node_shader_write_vert(kong, string("var sculpt_uv: uint2 = uint2(uint(float(vertex_id()) %% constants.texpaint_sculpt_size%d.x), " - "uint(float(vertex_id()) / constants.texpaint_sculpt_size%d.y));", - idx0, idx0)); + node_shader_write_vert(kong, "var sculpt_vid: uint = uint(vertex_id()) + uint(constants.sculpt_vertex_offset);"); + node_shader_write_vert(kong, string("var sculpt_grid_w: uint = uint(constants.texpaint_sculpt_size%d.x);", idx0)); + node_shader_write_vert(kong, "var sculpt_uv: uint2 = uint2(sculpt_vid % sculpt_grid_w, sculpt_vid / sculpt_grid_w);"); node_shader_write_vert(kong, string("var texpaint_sculpt_sample: float4 = texpaint_sculpt%d[sculpt_uv];", idx0)); node_shader_write_vert(kong, "var sculpt_pos: float3 = texpaint_sculpt_sample.xyz;"); + if (sculpt_mask_value(kong, sculpt_layers->buffer[0], "sculpt_pmask0", false, NULL)) { + // Blend the base layers deformation back toward the rest pose where the mask is dark + node_shader_write_vert(kong, "var sculpt_pbase0: float4 = texpaint_sculpt_base[sculpt_uv];"); + node_shader_write_vert(kong, "sculpt_pos = sculpt_pbase0.xyz + (sculpt_pos - sculpt_pbase0.xyz) * sculpt_pmask0;"); + } for (i32 i = 1; i < count; ++i) { i32 idx = sculpt_indices->buffer[i]; node_shader_write_vert(kong, string("var texpaint_sculpt_sample_%d: float4 = texpaint_sculpt%d[sculpt_uv];", idx, idx)); node_shader_write_vert(kong, string("var texpaint_sculpt_base_sample_%d: float4 = texpaint_sculpt_base[sculpt_uv];", idx)); - node_shader_write_vert(kong, string("sculpt_pos = sculpt_pos + texpaint_sculpt_sample_%d.xyz - texpaint_sculpt_base_sample_%d.xyz;", idx, idx)); + if (sculpt_mask_value(kong, sculpt_layers->buffer[i], string("sculpt_pmask_%d", idx), false, NULL)) { + node_shader_write_vert(kong, + string("sculpt_pos = sculpt_pos + (texpaint_sculpt_sample_%d.xyz - texpaint_sculpt_base_sample_%d.xyz) * sculpt_pmask_%d;", + idx, idx, idx)); + } + else { + node_shader_write_vert(kong, string("sculpt_pos = sculpt_pos + texpaint_sculpt_sample_%d.xyz - texpaint_sculpt_base_sample_%d.xyz;", idx, idx)); + } } node_shader_write_vert(kong, "output.pos = constants.WVP * float4(sculpt_pos, 1.0);"); // Normal - node_shader_write_attrib_vert(kong, "var base_vertex0: float = float(vertex_id()) - (float(vertex_id()) % float(3));"); - node_shader_write_attrib_vert(kong, "var base_vertex1: float = base_vertex0 + 1.0;"); - node_shader_write_attrib_vert(kong, "var base_vertex2: float = base_vertex0 + 2.0;"); - node_shader_write_attrib_vert( - kong, - string("var uv0: uint2 = uint2(uint(base_vertex0 %% constants.texpaint_sculpt_size%d.x), uint(base_vertex0 / constants.texpaint_sculpt_size%d.y));", - idx0, idx0)); - node_shader_write_attrib_vert( - kong, - string("var uv1: uint2 = uint2(uint(base_vertex1 %% constants.texpaint_sculpt_size%d.x), uint(base_vertex1 / constants.texpaint_sculpt_size%d.y));", - idx0, idx0)); - node_shader_write_attrib_vert( - kong, - string("var uv2: uint2 = uint2(uint(base_vertex2 %% constants.texpaint_sculpt_size%d.x), uint(base_vertex2 / constants.texpaint_sculpt_size%d.y));", - idx0, idx0)); + node_shader_write_attrib_vert(kong, "var sculpt_nvid: uint = uint(vertex_id()) + uint(constants.sculpt_vertex_offset);"); + node_shader_write_attrib_vert(kong, "var base_vertex0: uint = sculpt_nvid - (sculpt_nvid % uint(3));"); + node_shader_write_attrib_vert(kong, "var base_vertex1: uint = base_vertex0 + uint(1);"); + node_shader_write_attrib_vert(kong, "var base_vertex2: uint = base_vertex0 + uint(2);"); + node_shader_write_attrib_vert(kong, string("var sculpt_ngrid_w: uint = uint(constants.texpaint_sculpt_size%d.x);", idx0)); + node_shader_write_attrib_vert(kong, "var uv0: uint2 = uint2(base_vertex0 % sculpt_ngrid_w, base_vertex0 / sculpt_ngrid_w);"); + node_shader_write_attrib_vert(kong, "var uv1: uint2 = uint2(base_vertex1 % sculpt_ngrid_w, base_vertex1 / sculpt_ngrid_w);"); + node_shader_write_attrib_vert(kong, "var uv2: uint2 = uint2(base_vertex2 % sculpt_ngrid_w, base_vertex2 / sculpt_ngrid_w);"); node_shader_write_attrib_vert(kong, string("var meshpos0: float4 = texpaint_sculpt%d[uv0];", idx0)); node_shader_write_attrib_vert(kong, string("var meshpos1: float4 = texpaint_sculpt%d[uv1];", idx0)); node_shader_write_attrib_vert(kong, string("var meshpos2: float4 = texpaint_sculpt%d[uv2];", idx0)); + // Mask the normal-reconstruction positions + if (sculpt_mask_value(kong, sculpt_layers->buffer[0], "sculpt_nmask0", true, NULL)) { + node_shader_write_attrib_vert(kong, "var nbase0: float4 = texpaint_sculpt_base[uv0];"); + node_shader_write_attrib_vert(kong, "var nbase1: float4 = texpaint_sculpt_base[uv1];"); + node_shader_write_attrib_vert(kong, "var nbase2: float4 = texpaint_sculpt_base[uv2];"); + node_shader_write_attrib_vert(kong, "meshpos0 = nbase0 + (meshpos0 - nbase0) * sculpt_nmask0;"); + node_shader_write_attrib_vert(kong, "meshpos1 = nbase1 + (meshpos1 - nbase1) * sculpt_nmask0;"); + node_shader_write_attrib_vert(kong, "meshpos2 = nbase2 + (meshpos2 - nbase2) * sculpt_nmask0;"); + } for (i32 i = 1; i < count; ++i) { i32 idx = sculpt_indices->buffer[i]; - node_shader_write_attrib_vert(kong, string("meshpos0 = meshpos0 + texpaint_sculpt%d[uv0] - texpaint_sculpt_base[uv0];", idx)); - node_shader_write_attrib_vert(kong, string("meshpos1 = meshpos1 + texpaint_sculpt%d[uv1] - texpaint_sculpt_base[uv1];", idx)); - node_shader_write_attrib_vert(kong, string("meshpos2 = meshpos2 + texpaint_sculpt%d[uv2] - texpaint_sculpt_base[uv2];", idx)); + if (sculpt_mask_value(kong, sculpt_layers->buffer[i], string("sculpt_nmask_%d", idx), true, NULL)) { + node_shader_write_attrib_vert(kong, string("var nsamp0_%d: float4 = texpaint_sculpt%d[uv0];", idx, idx)); + node_shader_write_attrib_vert(kong, string("var nsamp1_%d: float4 = texpaint_sculpt%d[uv1];", idx, idx)); + node_shader_write_attrib_vert(kong, string("var nsamp2_%d: float4 = texpaint_sculpt%d[uv2];", idx, idx)); + node_shader_write_attrib_vert(kong, string("var nbasel0_%d: float4 = texpaint_sculpt_base[uv0];", idx)); + node_shader_write_attrib_vert(kong, string("var nbasel1_%d: float4 = texpaint_sculpt_base[uv1];", idx)); + node_shader_write_attrib_vert(kong, string("var nbasel2_%d: float4 = texpaint_sculpt_base[uv2];", idx)); + node_shader_write_attrib_vert(kong, string("meshpos0 = meshpos0 + (nsamp0_%d - nbasel0_%d) * sculpt_nmask_%d;", idx, idx, idx)); + node_shader_write_attrib_vert(kong, string("meshpos1 = meshpos1 + (nsamp1_%d - nbasel1_%d) * sculpt_nmask_%d;", idx, idx, idx)); + node_shader_write_attrib_vert(kong, string("meshpos2 = meshpos2 + (nsamp2_%d - nbasel2_%d) * sculpt_nmask_%d;", idx, idx, idx)); + } + else { + node_shader_write_attrib_vert(kong, string("meshpos0 = meshpos0 + texpaint_sculpt%d[uv0] - texpaint_sculpt_base[uv0];", idx)); + node_shader_write_attrib_vert(kong, string("meshpos1 = meshpos1 + texpaint_sculpt%d[uv1] - texpaint_sculpt_base[uv1];", idx)); + node_shader_write_attrib_vert(kong, string("meshpos2 = meshpos2 + texpaint_sculpt%d[uv2] - texpaint_sculpt_base[uv2];", idx)); + } } node_shader_write_attrib_vert(kong, "var meshnor: float3 = normalize(cross(meshpos2.xyz - meshpos1.xyz, meshpos0.xyz - meshpos1.xyz));"); @@ -281,7 +712,58 @@ void sculpt_make_mesh_run(node_shader_t *kong, slot_layer_t_array_t *sculpt_laye node_shader_write_attrib_frag(kong, "var n: float3 = normalize(input.wnormal);"); } -void sculpt_init_sculpt_texture(slot_layer_t *l, mesh_data_t *md) { +void sculpt_make_paint_run(node_shader_t *kong) { + slot_layer_t_array_t *sculpt_layers = any_array_create_from_raw((void *[]){}, 0); + i32_array_t *sculpt_indices = i32_array_create(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_array_push(sculpt_indices, i); + } + } + i32 scount = sculpt_layers->length; + if (scount > 0) { + i32 idx0 = sculpt_indices->buffer[0]; + node_shader_add_constant(kong, string("texpaint_sculpt_size%d: float2", idx0), string("_size(_texpaint_sculpt%d)", idx0)); + for (i32 i = 0; i < scount; ++i) { + i32 idx = sculpt_indices->buffer[i]; + node_shader_add_texture(kong, string("texpaint_sculpt%d", idx), string("_texpaint_sculpt%d", idx)); + } + + bool base_masked = sculpt_layer_has_visible_masks(sculpt_layers->buffer[0]); + if (scount > 1 || base_masked) { + node_shader_add_texture(kong, "texpaint_sculpt_base", "_texpaint_sculpt_base"); + } + + node_shader_write_attrib_vert(kong, string("var sculpt_paint_w: uint = uint(constants.texpaint_sculpt_size%d.x);", idx0)); + node_shader_write_attrib_vert(kong, "var sculpt_uv_paint: uint2 = uint2(uint(vertex_id()) % sculpt_paint_w, uint(vertex_id()) / sculpt_paint_w);"); + + node_shader_write_attrib_vert(kong, string("var sculpt_pos_paint: float4 = texpaint_sculpt%d[sculpt_uv_paint];", idx0)); + if (sculpt_mask_value(kong, sculpt_layers->buffer[0], "sculpt_pmask0", true, g_context->layer)) { + node_shader_write_attrib_vert(kong, "var sculpt_pbase0: float4 = texpaint_sculpt_base[sculpt_uv_paint];"); + node_shader_write_attrib_vert(kong, "sculpt_pos_paint = sculpt_pbase0 + (sculpt_pos_paint - sculpt_pbase0) * sculpt_pmask0;"); + } + for (i32 i = 1; i < scount; ++i) { + i32 idx = sculpt_indices->buffer[i]; + if (sculpt_mask_value(kong, sculpt_layers->buffer[i], string("sculpt_pmask_%d", idx), true, g_context->layer)) { + node_shader_write_attrib_vert(kong, string("var psamp_%d: float4 = texpaint_sculpt%d[sculpt_uv_paint];", idx, idx)); + node_shader_write_attrib_vert(kong, string("var pbasel_%d: float4 = texpaint_sculpt_base[sculpt_uv_paint];", idx)); + node_shader_write_attrib_vert(kong, string("sculpt_pos_paint = sculpt_pos_paint + (psamp_%d - pbasel_%d) * sculpt_pmask_%d;", idx, idx, idx)); + } + else { + node_shader_write_attrib_vert( + kong, string("sculpt_pos_paint = sculpt_pos_paint + texpaint_sculpt%d[sculpt_uv_paint] - texpaint_sculpt_base[sculpt_uv_paint];", idx)); + } + } + node_shader_write_attrib_vert(kong, "output.ndc = constants.WVP * float4(sculpt_pos_paint.xyz, 1.0);"); + if (kong->frag_wposition) { + node_shader_write_attrib_vert(kong, "output.wposition = (constants.W * float4(sculpt_pos_paint.xyz, 1.0)).xyz;"); + } + } +} + +void sculpt_init_sculpt_texture(slot_layer_t *l) { i32 id = l->id; { render_target_t *t = render_target_create(); @@ -291,7 +773,7 @@ void sculpt_init_sculpt_texture(slot_layer_t *l, mesh_data_t *md) { t->format = "RGBA128"; l->texpaint_sculpt = render_path_create_render_target(t)->_image; } - sculpt_import_mesh_pack_to_texture(md, l->texpaint_sculpt); + sculpt_import_mesh_pack_to_texture(l->texpaint_sculpt); i32 sculpt_layer_count = 0; for (i32 i = 0; i < g_project->_->layers->length; ++i) { @@ -316,42 +798,73 @@ void sculpt_init_sculpt_texture(slot_layer_t *l, mesh_data_t *md) { } } +void sculpt_init_meshes() { + mesh_object_t_array_t *objects = g_project->_->paint_objects; + f32 max_scale = 0.0; + for (i32 o = 0; o < objects->length; ++o) { + if (objects->buffer[o]->data->scale_pos > max_scale) { + max_scale = objects->buffer[o]->data->scale_pos; + } + } + + for (i32 o = 0; o < objects->length; ++o) { + mesh_object_t *object = objects->buffer[o]; + mesh_data_t *md = object->data; + f32 ratio = objects->length > 1 ? md->scale_pos / max_scale : 1.0; + + i16_array_t *posa = i16_array_create(md->index_array->length * 4); + i16_array_t *nora = i16_array_create(md->index_array->length * 2); + i16_array_t *texa = i16_array_create(md->index_array->length * 2); + for (i32 i = 0; i < posa->length; ++i) { + posa->buffer[i] = 32767; + } + for (i32 i = 0; i < md->index_array->length; ++i) { + i32 index = md->index_array->buffer[i]; + posa->buffer[i * 4] = math_floor(md->vertex_arrays->buffer[0]->values->buffer[index * 4] * ratio); + posa->buffer[i * 4 + 1] = math_floor(md->vertex_arrays->buffer[0]->values->buffer[index * 4 + 1] * ratio); + posa->buffer[i * 4 + 2] = math_floor(md->vertex_arrays->buffer[0]->values->buffer[index * 4 + 2] * ratio); + posa->buffer[i * 4 + 3] = md->vertex_arrays->buffer[0]->values->buffer[index * 4 + 3]; + nora->buffer[i * 2] = md->vertex_arrays->buffer[1]->values->buffer[index * 2]; + nora->buffer[i * 2 + 1] = md->vertex_arrays->buffer[1]->values->buffer[index * 2 + 1]; + texa->buffer[i * 2] = md->vertex_arrays->buffer[2]->values->buffer[index * 2]; + texa->buffer[i * 2 + 1] = md->vertex_arrays->buffer[2]->values->buffer[index * 2 + 1]; + } + u32_array_t *inda = u32_array_create(md->index_array->length); + for (i32 i = 0; i < inda->length; ++i) { + inda->buffer[i] = i; + } + mesh_data_t *raw = GC_ALLOC_INIT(mesh_data_t, {.name = md->name, + .vertex_arrays = any_array_create_from_raw( + (void *[]){ + GC_ALLOC_INIT(vertex_array_t, {.values = posa, .attrib = "pos", .data = "short4norm"}), + GC_ALLOC_INIT(vertex_array_t, {.values = nora, .attrib = "nor", .data = "short2norm"}), + GC_ALLOC_INIT(vertex_array_t, {.values = texa, .attrib = "tex", .data = "short2norm"}), + }, + 3), + .index_array = inda, + .scale_pos = 1.0, + .scale_tex = 1.0}); + mesh_object_set_data(object, mesh_data_create(raw)); + } + + if (objects->length > 1) { + f32 ex = 0.0, ey = 0.0, ez = 0.0; + for (i32 o = 0; o < objects->length; ++o) { + vec4_t aabb = mesh_data_calculate_aabb(objects->buffer[o]->data); + ex = fmaxf(ex, aabb.x); + ey = fmaxf(ey, aabb.y); + ez = fmaxf(ez, aabb.z); + } + f32 r = math_sqrt(ex * ex + ey * ey + ez * ez); + mesh_object_t *main_object = context_main_object(); + main_object->base->transform->loc = (vec4_t){0, 0, 0, 1.0}; + main_object->base->transform->scale = (vec4_t){2.0 / r, 2.0 / r, 2.0 / r, 1.0}; + transform_build_matrix(main_object->base->transform); + } +} + void sculpt_init() { - mesh_data_t *md = g_context->paint_object->data; - i16_array_t *posa = i16_array_create(md->index_array->length * 4); - i16_array_t *nora = i16_array_create(md->index_array->length * 2); - i16_array_t *texa = i16_array_create(md->index_array->length * 2); - for (i32 i = 0; i < posa->length; ++i) { - posa->buffer[i] = 32767; - } - for (i32 i = 0; i < md->index_array->length; ++i) { - i32 index = md->index_array->buffer[i]; - posa->buffer[i * 4] = md->vertex_arrays->buffer[0]->values->buffer[index * 4]; - posa->buffer[i * 4 + 1] = md->vertex_arrays->buffer[0]->values->buffer[index * 4 + 1]; - posa->buffer[i * 4 + 2] = md->vertex_arrays->buffer[0]->values->buffer[index * 4 + 2]; - posa->buffer[i * 4 + 3] = md->vertex_arrays->buffer[0]->values->buffer[index * 4 + 3]; - nora->buffer[i * 2] = md->vertex_arrays->buffer[1]->values->buffer[index * 2]; - nora->buffer[i * 2 + 1] = md->vertex_arrays->buffer[1]->values->buffer[index * 2 + 1]; - texa->buffer[i * 2] = md->vertex_arrays->buffer[2]->values->buffer[index * 2]; - texa->buffer[i * 2 + 1] = md->vertex_arrays->buffer[2]->values->buffer[index * 2 + 1]; - } - u32_array_t *inda = u32_array_create(md->index_array->length); - for (i32 i = 0; i < inda->length; ++i) { - inda->buffer[i] = i; - } - mesh_data_t *raw = GC_ALLOC_INIT(mesh_data_t, {.name = md->name, - .vertex_arrays = any_array_create_from_raw( - (void *[]){ - GC_ALLOC_INIT(vertex_array_t, {.values = posa, .attrib = "pos", .data = "short4norm"}), - GC_ALLOC_INIT(vertex_array_t, {.values = nora, .attrib = "nor", .data = "short2norm"}), - GC_ALLOC_INIT(vertex_array_t, {.values = texa, .attrib = "tex", .data = "short2norm"}), - }, - 3), - .index_array = inda, - .scale_pos = 1.0, - .scale_tex = 1.0}); - md = mesh_data_create(raw); - mesh_object_set_data(g_context->paint_object, md); + sculpt_init_meshes(); if (g_context->merged_object != NULL) { util_mesh_merge(NULL); @@ -382,6 +895,15 @@ void sculpt_init() { t->scale = render_path_base_get_super_sampling(); render_path_create_render_target(t); } + { + // Holds the previous-frame sculpt state during a stroke + render_target_t *t = render_target_create(); + t->name = "texpaint_sculpt_ref"; + t->width = config_get_texture_res_x(); + t->height = config_get_texture_res_y(); + t->format = "RGBA128"; + render_path_create_render_target(t); + } render_path_load_shader("Scene/copy_pass/copyR32_pass"); @@ -400,18 +922,17 @@ void sculpt_init() { void sculpt_layers_create_sculpt_layer() { slot_layer_t *l = layers_new_layer(true, -1, NULL); l->name = string("Sculpt %d", l->id + 1); - mesh_data_t *md = g_context->paint_object->data; - sculpt_init_sculpt_texture(l, md); + sculpt_init_meshes(); + sculpt_init_sculpt_texture(l); sculpt_init(); } -void render_path_sculpt_commands() { - if (g_context->pdirty <= 0) { - return; - } +void render_path_sculpt_displace_pass(char *texpaint_sculpt) { + // Snapshot the current state so the displacement pass accumulates on top of the previous one + render_path_set_target("texpaint_sculpt_ref", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0); + render_path_bind_target(texpaint_sculpt, "tex"); + render_path_draw_shader("Scene/copy_pass/copyRGBA128_pass"); - i32 tid = g_context->layer->id; - char *texpaint_sculpt = string("texpaint_sculpt%s", i32_to_string(tid)); render_path_set_target("texpaint_blend1", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0); render_path_bind_target("texpaint_blend0", "tex"); render_path_draw_shader("Scene/copy_pass/copyR8_pass"); @@ -449,6 +970,45 @@ void render_path_sculpt_commands() { render_path_end(); } +void render_path_sculpt_commands() { + if (g_context->pdirty <= 0) { + return; + } + + i32 tid = g_context->layer->id; + char *texpaint_sculpt = string("texpaint_sculpt%s", i32_to_string(tid)); + + if (g_context->tool == TOOL_TYPE_PARTICLE) { + // Accumulate one displacement pass per active particle impact + for (i32 pi = 0; pi < 32; ++pi) { + if (g_context->particles[pi].timer == NULL || g_context->particles[pi].hit_x == 0) { + continue; + } + g_context->particle_index = pi; + g_context->particle_hit_x = g_context->particles[pi].hit_x; + g_context->particle_hit_y = g_context->particles[pi].hit_y; + g_context->particle_hit_z = g_context->particles[pi].hit_z; + g_context->last_particle_hit_x = g_context->particles[pi].hit_last_x; + g_context->last_particle_hit_y = g_context->particles[pi].hit_last_y; + g_context->last_particle_hit_z = g_context->particles[pi].hit_last_z; + render_path_sculpt_displace_pass(texpaint_sculpt); + } + return; + } + + render_path_sculpt_displace_pass(texpaint_sculpt); +} + +void render_path_sculpt_snapshot_gbuffer() { + // Freeze the current surface (depth + packed normal) as the reference the displacement pass reads + render_path_set_target("gbuffer0_undo", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0); + render_path_bind_target("gbuffer0", "tex"); + render_path_draw_shader("Scene/copy_pass/copyRGBA64_pass"); + render_path_set_target("gbufferD_undo", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0); + render_path_bind_target("main", "tex"); + render_path_draw_shader("Scene/copy_pass/copyR32_pass"); +} + void render_path_sculpt_begin() { if (!render_path_paint_paint_enabled()) { return; @@ -456,16 +1016,9 @@ void render_path_sculpt_begin() { render_path_paint_push_undo_last = history_push_undo; if (history_push_undo && history_undo_layers != NULL) { history_paint(); - render_path_set_target("gbuffer0_undo", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0); - render_path_bind_target("gbuffer0", "tex"); - render_path_draw_shader("Scene/copy_pass/copyRGBA64_pass"); - render_path_set_target("gbufferD_undo", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0); - render_path_bind_target("main", "tex"); - render_path_draw_shader("Scene/copy_pass/copyR32_pass"); - } - if (sculpt_push_undo && history_undo_layers != NULL) { - history_paint(); + render_path_sculpt_snapshot_gbuffer(); } + sculpt_push_undo = false; } void slot_layer_apply_sculpt(slot_layer_t *raw) { @@ -473,38 +1026,53 @@ void slot_layer_apply_sculpt(slot_layer_t *raw) { return; } - mesh_object_t *o = g_project->_->paint_objects->buffer[0]; - mesh_data_t *g = o->data; - i16_array_t *va0 = g->vertex_arrays->buffer[0]->values; - i32 num_verts = math_floor(va0->length / 4.0); - - buffer_t *pixels = gpu_get_texture_pixels(raw->texpaint_sculpt); + mesh_object_t_array_t *objects = g_project->_->paint_objects; + buffer_t *pixels = gpu_get_texture_pixels(raw->texpaint_sculpt); + // Each object occupies a contiguous slice of the shared sculpt grid f32 max_abs = 1.0; - for (i32 i = 0; i < num_verts; ++i) { - f32 x = math_abs(buffer_get_f32(pixels, i * 16)); - f32 y = math_abs(buffer_get_f32(pixels, i * 16 + 4)); - f32 z = math_abs(buffer_get_f32(pixels, i * 16 + 8)); - if (x > max_abs) - max_abs = x; - if (y > max_abs) - max_abs = y; - if (z > max_abs) - max_abs = z; + i32 offset = 0; + for (i32 o = 0; o < objects->length; ++o) { + mesh_data_t *g = objects->buffer[o]->data; + i32 nv = math_floor(g->vertex_arrays->buffer[0]->values->length / 4.0); + for (i32 i = 0; i < nv; ++i) { + i32 t = i + offset; + f32 x = math_abs(buffer_get_f32(pixels, t * 16)); + f32 y = math_abs(buffer_get_f32(pixels, t * 16 + 4)); + f32 z = math_abs(buffer_get_f32(pixels, t * 16 + 8)); + if (x > max_abs) + max_abs = x; + if (y > max_abs) + max_abs = y; + if (z > max_abs) + max_abs = z; + } + offset += g->index_array->length; } - if (max_abs > 1.0) { - o->base->transform->scale_world = g->scale_pos = g->scale_pos * max_abs; - transform_build_matrix(o->base->transform); + offset = 0; + for (i32 o = 0; o < objects->length; ++o) { + mesh_object_t *ob = objects->buffer[o]; + mesh_data_t *g = ob->data; + i16_array_t *va0 = g->vertex_arrays->buffer[0]->values; + i32 nv = math_floor(va0->length / 4.0); + + if (max_abs > 1.0) { + ob->base->transform->scale_world = g->scale_pos = g->scale_pos * max_abs; + transform_build_matrix(ob->base->transform); + } + + for (i32 i = 0; i < nv; ++i) { + i32 t = i + offset; + va0->buffer[i * 4] = math_floor(buffer_get_f32(pixels, t * 16) / max_abs * 32767.0); + va0->buffer[i * 4 + 1] = math_floor(buffer_get_f32(pixels, t * 16 + 4) / max_abs * 32767.0); + va0->buffer[i * 4 + 2] = math_floor(buffer_get_f32(pixels, t * 16 + 8) / max_abs * 32767.0); + } + + mesh_data_build_vertices(g->_->vertex_buffer, g->vertex_arrays); + offset += g->index_array->length; } - for (i32 i = 0; i < num_verts; ++i) { - va0->buffer[i * 4] = math_floor(buffer_get_f32(pixels, i * 16) / max_abs * 32767.0); - va0->buffer[i * 4 + 1] = math_floor(buffer_get_f32(pixels, i * 16 + 4) / max_abs * 32767.0); - va0->buffer[i * 4 + 2] = math_floor(buffer_get_f32(pixels, i * 16 + 8) / max_abs * 32767.0); - } - - mesh_data_build_vertices(g->_->vertex_buffer, o->data->vertex_arrays); util_mesh_calc_normals(true); render_path_raytrace_ready = false; diff --git a/paint/sources/render/render_path_paint.c b/paint/sources/render/render_path_paint.c index 5e6fad55..04474696 100644 --- a/paint/sources/render/render_path_paint.c +++ b/paint/sources/render/render_path_paint.c @@ -164,7 +164,9 @@ void render_path_paint_commands_particle(i32 tid, char *texpaint, bool is_mask) void render_path_paint_commands_paint(bool dilation) { i32 tid = g_context->layer->id; - if (g_context->layer->texpaint_sculpt != NULL) { + bool picking_tool = g_context->tool == TOOL_TYPE_COLORID || g_context->tool == TOOL_TYPE_PICKER || g_context->tool == TOOL_TYPE_MATERIAL || + g_context->tool == TOOL_TYPE_CURSOR; + if (g_context->layer->texpaint_sculpt != NULL && !picking_tool) { render_path_sculpt_commands(); return; } diff --git a/paint/sources/slot_layer.c b/paint/sources/slot_layer.c index dfa2b48e..60ba0d52 100644 --- a/paint/sources/slot_layer.c +++ b/paint/sources/slot_layer.c @@ -184,6 +184,16 @@ void slot_layer_swap(slot_layer_t *raw, slot_layer_t *other) { other->texpaint_preview = _texpaint_preview; } + if (raw->texpaint_sculpt != NULL && other->texpaint_sculpt != NULL) { + render_target_t *srt0 = any_map_get(render_path_render_targets, string("texpaint_sculpt%s", raw->ext)); + render_target_t *srt1 = any_map_get(render_path_render_targets, string("texpaint_sculpt%s", other->ext)); + srt0->_image = other->texpaint_sculpt; + srt1->_image = raw->texpaint_sculpt; + gpu_texture_t *_texpaint_sculpt = raw->texpaint_sculpt; + raw->texpaint_sculpt = other->texpaint_sculpt; + other->texpaint_sculpt = _texpaint_sculpt; + } + if (slot_layer_is_layer(raw) && slot_layer_is_layer(other)) { render_target_t *nor0 = any_map_get(render_path_render_targets, string("texpaint_nor%s", raw->ext)); nor0->_image = other->texpaint_nor; @@ -441,6 +451,19 @@ void slot_layer_resize_and_set_bits(slot_layer_t *raw) { render_target_t *rt = any_map_get(rts, string("texpaint%s", raw->ext)); rt->_image = raw->texpaint; } + + // Clear and repack the base mesh + if (raw->texpaint_sculpt != NULL) { + gpu_texture_t *_texpaint_sculpt = raw->texpaint_sculpt; + raw->texpaint_sculpt = gpu_create_render_target(res_x, res_y, GPU_TEXTURE_FORMAT_RGBA128); + sculpt_import_mesh_pack_to_texture(raw->texpaint_sculpt); + gpu_delete_texture(_texpaint_sculpt); + + render_target_t *srt = any_map_get(rts, string("texpaint_sculpt%s", raw->ext)); + srt->width = res_x; + srt->height = res_y; + srt->_image = raw->texpaint_sculpt; + } } void slot_layer_to_fill_layer_on_next_frame(void *_) { diff --git a/paint/sources/ui/tab_layers.c b/paint/sources/ui/tab_layers.c index b7e531d2..e7dda980 100644 --- a/paint/sources/ui/tab_layers.c +++ b/paint/sources/ui/tab_layers.c @@ -450,7 +450,7 @@ void tab_layers_draw_layer_slot_full(slot_layer_t *l, i32 i) { } // Blending combo - if (!slot_layer_is_group(l) && !slot_layer_is_mask(l) && !slot_layer_is_filter(l)) { + if (!slot_layer_is_group(l) && !slot_layer_is_mask(l) && !slot_layer_is_filter(l) && l->texpaint_sculpt == NULL) { g_ui->_x = uix + uiw * 0.60; g_ui->_y = uiy; g_ui->_w = uiw * 0.30; @@ -460,7 +460,7 @@ void tab_layers_draw_layer_slot_full(slot_layer_t *l, i32 i) { // Object combo if (!slot_layer_is_group(l) && !slot_layer_is_mask(l) && !slot_layer_is_filter(l)) { g_ui->_x = uix + uiw * 0.60; - g_ui->_y = uiy + center * 2; + g_ui->_y = l->texpaint_sculpt != NULL ? uiy + center : uiy + center * 2; g_ui->_w = uiw * 0.30; tab_layers_combo_object(l, false); } @@ -632,7 +632,7 @@ void tab_layers_draw_layer_context_menu_draw() { ui_menu_keep_open = true; } - if (!slot_layer_is_group(l) && !slot_layer_is_mask(l) && !slot_layer_is_filter(l)) { + if (!slot_layer_is_group(l) && !slot_layer_is_mask(l) && !slot_layer_is_filter(l) && l->texpaint_sculpt == NULL) { if (tab_layers_combo_blending(l, true)->changed) { ui_menu_keep_open = true; } @@ -915,7 +915,7 @@ void tab_layers_draw_layer_slot(slot_layer_t *l, i32 i, bool mini) { } bool is_sculpt_layer = l->texpaint_sculpt != NULL; - if (g_config->workflow == WORKFLOW_SCULPT && !is_sculpt_layer) { + if (g_config->workflow == WORKFLOW_SCULPT && !is_sculpt_layer && !slot_layer_is_group(l) && !slot_layer_is_mask(l)) { return; } if (g_config->workflow != WORKFLOW_SCULPT && is_sculpt_layer) { diff --git a/paint/sources/ui/ui_menubar.c b/paint/sources/ui/ui_menubar.c index bd8be93d..d9a6e9b7 100644 --- a/paint/sources/ui/ui_menubar.c +++ b/paint/sources/ui/ui_menubar.c @@ -812,12 +812,9 @@ void ui_menubar_draw_category_items() { (void *[]){ tr("PBR"), tr("Base"), + tr("Sculpt"), }, - 2); - - if (g_config->experimental) { - any_array_push(workflow_items, tr("Sculpt")); - } + 3); g_config->workflow = ui_inline_radio(workflow_handle, workflow_items, UI_ALIGN_LEFT); if (workflow_handle->changed) { diff --git a/paint/sources/ui/ui_view2d.c b/paint/sources/ui/ui_view2d.c index b3befb59..ff0fd898 100644 --- a/paint/sources/ui/ui_view2d.c +++ b/paint/sources/ui/ui_view2d.c @@ -85,7 +85,7 @@ void ui_view2d_draw_edit() { h_view2d_grid_snap->b = g_config->view2d_grid_snap; g_config->view2d_grid_snap = ui_check(h_view2d_grid_snap, tr("Grid Snap"), any_map_get(g_keymap, "grid_snap")); if (h_view2d_grid_snap->changed) { - ui_menu_keep_open = true; + ui_menu_keep_open = true; } ui_menu_separator(); diff --git a/paint/sources/uniforms.c b/paint/sources/uniforms.c index 7b04a002..1dc08fda 100644 --- a/paint/sources/uniforms.c +++ b/paint/sources/uniforms.c @@ -5,6 +5,9 @@ i32 uniforms_ext_i32_link(object_t *object, material_data_t *mat, char *link) { if (string_equals(link, "_bloom_current_mip")) { return render_path_base_bloom_current_mip; } + else if (string_equals(link, "_sculpt_vertex_offset")) { + return sculpt_object_vertex_offset(object->ext); + } return INT_MAX; } @@ -72,6 +75,9 @@ f32 uniforms_ext_f32_link(object_t *object, material_data_t *mat, char *link) { val *= pen_pressure * g_config->pressure_sensitivity; } val *= val; + if (g_config->workflow == WORKFLOW_SCULPT) { + val *= 0.8; + } return val; } else if (string_equals(link, "_brush_scale")) { @@ -88,6 +94,20 @@ f32 uniforms_ext_f32_link(object_t *object, material_data_t *mat, char *link) { else if (string_equals(link, "_object_id")) { return array_index_of(g_project->_->paint_objects, object->ext); } + else if (string_equals(link, "_sculpt_mask_offset")) { + i32 om = slot_layer_get_object_mask(g_context->layer); + if (om <= 0 || om > g_project->_->paint_objects->length) { + return 0; + } + return sculpt_object_vertex_offset(g_project->_->paint_objects->buffer[om - 1]); + } + else if (string_equals(link, "_sculpt_mask_count")) { + i32 om = slot_layer_get_object_mask(g_context->layer); + if (om <= 0 || om > g_project->_->paint_objects->length) { + return config_get_texture_res_x() * config_get_texture_res_y(); + } + return g_project->_->paint_objects->buffer[om - 1]->data->index_array->length; + } else if (string_equals(link, "_dilate_radius")) { return util_uv_dilatemap != NULL ? g_config->dilate_radius : 0.0; } @@ -289,9 +309,10 @@ vec4_t uniforms_ext_vec4_link(object_t *object, material_data_t *mat, char *link mat4_t uniforms_ext_mat4_link(object_t *object, material_data_t *mat, char *link) { if (string_equals(link, "_decal_layer_matrix")) { // Decal layer mat4_t m = mat4_inv(g_context->layer->decal_mat); - f32 f = object->parent->transform->scale.x * object->transform->scale_world; - m = mat4_scale(m, (vec4_t){f, f, f, 1.0}); - m = mat4_mult_mat(m, uniforms_ext_ortho_p); + f32 parent_scale = object->parent != NULL ? object->parent->transform->scale.x : 1.0; + f32 f = parent_scale * object->transform->scale_world; + m = mat4_scale(m, (vec4_t){f, f, f, 1.0}); + m = mat4_mult_mat(m, uniforms_ext_ortho_p); return m; } @@ -327,8 +348,8 @@ gpu_texture_t *uniforms_ext_tex_link(object_t *object, material_data_t *mat, cha return rt->_image; } else if (string_equals(link, "_texpaint_sculpt_undo")) { - i32 i = history_undo_i - 1 < 0 ? g_config->undo_steps - 1 : history_undo_i - 1; - render_target_t *rt = any_map_get(render_path_render_targets, string("texpaint_sculpt_undo%d", i)); + // Per-frame accumulation reference + render_target_t *rt = any_map_get(render_path_render_targets, "texpaint_sculpt_ref"); return rt->_image; } else if (string_equals(link, "_texcolorid")) { @@ -380,8 +401,13 @@ gpu_texture_t *uniforms_ext_tex_link(object_t *object, material_data_t *mat, cha return rt->_image; } if (starts_with(link, "_texpaint_vert")) { - i32 tid = parse_int(substring(link, string_length(link) - 1, string_length(link))); - return tid < g_project->_->layers->length ? g_project->_->layers->buffer[tid]->texpaint : NULL; + i32 tid = parse_int(substring(link, string_length("_texpaint_vert"), string_length(link))); + for (i32 i = 0; i < g_project->_->layers->length; ++i) { + if (g_project->_->layers->buffer[i]->id == tid) { + return g_project->_->layers->buffer[i]->texpaint; + } + } + return NULL; } if (starts_with(link, "_texpaint_nor")) { i32 tid = parse_int(substring(link, string_length(link) - 1, string_length(link))); diff --git a/paint/sources/util/util_layer.c b/paint/sources/util/util_layer.c index 13ca5d8a..649192db 100644 --- a/paint/sources/util/util_layer.c +++ b/paint/sources/util/util_layer.c @@ -115,6 +115,23 @@ void layers_resize() { if (render_path_paint_live_layer != NULL) { slot_layer_resize_and_set_bits(render_path_paint_live_layer); } + + render_target_t *sculpt_ref = any_map_get(rts, "texpaint_sculpt_ref"); + if (sculpt_ref != NULL) { + gpu_delete_texture(sculpt_ref->_image); + sculpt_ref->width = config_get_texture_res_x(); + sculpt_ref->height = config_get_texture_res_y(); + sculpt_ref->_image = gpu_create_render_target(config_get_texture_res_x(), config_get_texture_res_y(), GPU_TEXTURE_FORMAT_RGBA128); + } + + render_target_t *sculpt_base = any_map_get(rts, "texpaint_sculpt_base"); + if (sculpt_base != NULL) { + gpu_delete_texture(sculpt_base->_image); + sculpt_base->width = config_get_texture_res_x(); + sculpt_base->height = config_get_texture_res_y(); + sculpt_base->_image = gpu_create_render_target(config_get_texture_res_x(), config_get_texture_res_y(), GPU_TEXTURE_FORMAT_RGBA128); + } + render_path_raytrace_ready = false; // Rebuild baketex g_context->ddirty = 2; } @@ -344,7 +361,7 @@ void layers_update_fill_layers() { if (l->texpaint_sculpt != NULL) { i32 tid = l->id; i32 hid = history_undo_i - 1 < 0 ? g_config->undo_steps - 1 : history_undo_i - 1; - sculpt_import_mesh_pack_to_texture(g_context->paint_object->data, l->texpaint_sculpt); + sculpt_import_mesh_pack_to_texture(l->texpaint_sculpt); render_path_set_target(string("texpaint_sculpt_undo%d", hid), NULL, NULL, GPU_CLEAR_NONE, 0, 0.0); render_path_bind_target(string("texpaint_sculpt%d", tid), "tex"); render_path_draw_shader("Scene/copy_pass/copyRGBA128_pass"); @@ -424,7 +441,7 @@ void layers_update_fill_layer(bool parse_paint) { if (g_context->layer->texpaint_sculpt != NULL) { i32 tid = g_context->layer->id; i32 hid = history_undo_i - 1 < 0 ? g_config->undo_steps - 1 : history_undo_i - 1; - sculpt_import_mesh_pack_to_texture(g_context->paint_object->data, g_context->layer->texpaint_sculpt); + sculpt_import_mesh_pack_to_texture(g_context->layer->texpaint_sculpt); render_path_set_target(string("texpaint_sculpt_undo%d", hid), NULL, NULL, GPU_CLEAR_NONE, 0, 0.0); render_path_bind_target(string("texpaint_sculpt%d", tid), "tex"); render_path_draw_shader("Scene/copy_pass/copyRGBA128_pass"); @@ -637,6 +654,13 @@ slot_layer_t *layers_new_group() { return l; } +static slot_layer_t *_layers_path_sculpt_layer = NULL; + +void layers_new_path_layer_sculpt_init(void *_) { + sculpt_init(); + sculpt_init_sculpt_texture(_layers_path_sculpt_layer); +} + slot_layer_t *layers_new_path_layer(bool curved) { slot_layer_t *l = layers_new_layer(true, -1, NULL); if (l == NULL) { @@ -650,6 +674,12 @@ slot_layer_t *layers_new_path_layer(bool curved) { l->path_curved = curved; l->path_material = g_context->material; l->name = string(curved ? "Curve %d" : "Path %d", l->id + 1); + + if (g_config->workflow == WORKFLOW_SCULPT) { + _layers_path_sculpt_layer = l; + sys_notify_on_next_frame(&layers_new_path_layer_sculpt_init, NULL); + } + return l; } @@ -663,9 +693,8 @@ void layers_create_fill_layer_on_next_frame(void *_) { l->object_mask = g_context->layer_filter; if (g_config->workflow == WORKFLOW_SCULPT) { - mesh_data_t *md = g_context->paint_object->data; sculpt_init(); - sculpt_init_sculpt_texture(l, md); + sculpt_init_sculpt_texture(l); } history_to_fill_layer(); diff --git a/paint/sources/util/util_particle.c b/paint/sources/util/util_particle.c index 8e0237a6..0da9c2a7 100644 --- a/paint/sources/util/util_particle.c +++ b/paint/sources/util/util_particle.c @@ -27,6 +27,7 @@ void util_particle_update() { } util_particle_init_physics(); + make_particle_get_bullet_material(); physics_world_t *world = physics_world_active; physics_world_update(world); @@ -73,6 +74,7 @@ void util_particle_update() { mesh_object_t *mo = o->ext; mo->base->name = ".Bullet"; mo->base->visible = true; + mo->material = make_particle_get_bullet_material(); util_render_pick_pos_nor_tex(); f32 nx = g_context->norx_picked; diff --git a/paint/sources/util/util_path.c b/paint/sources/util/util_path.c index 296b453e..2253b370 100644 --- a/paint/sources/util/util_path.c +++ b/paint/sources/util/util_path.c @@ -77,6 +77,9 @@ static void path_set_camera(f32_array_t *points_camera, i32 num_camera, i32 ci) camera_object_build_proj(scene_camera, points_camera->buffer[ci * 9 + 8]); camera_object_build_mat(scene_camera); render_path_base_draw_gbuffer(); + if (g_context->layer->texpaint_sculpt != NULL) { + render_path_sculpt_snapshot_gbuffer(); + } } } @@ -291,6 +294,11 @@ static void path_repaint(slot_layer_t *l) { slot_layer_clear(l, 0x00000000, NULL, 1.0, layers_default_rough, 0.0); + if (l->texpaint_sculpt != NULL) { + // Restore the undeformed base mesh + sculpt_import_mesh_pack_to_texture(l->texpaint_sculpt); + } + vec4_t _camera_loc = scene_camera->base->transform->loc; quat_t _camera_rot = scene_camera->base->transform->rot; diff --git a/paint/sources/util/util_render.c b/paint/sources/util/util_render.c index 91ab0b9e..c8962ef2 100644 --- a/paint/sources/util/util_render.c +++ b/paint/sources/util/util_render.c @@ -226,6 +226,10 @@ void util_render_make_brush_preview() { return; } + if (g_config->workflow == WORKFLOW_SCULPT) { + return; + } + gpu_texture_t *current = _draw_current; bool in_use = gpu_in_use; if (in_use)