diff --git a/paint/sources/enums.h b/paint/sources/enums.h index 61317c2f..df97cf34 100644 --- a/paint/sources/enums.h +++ b/paint/sources/enums.h @@ -125,6 +125,11 @@ typedef enum { UV_TYPE_PROJECT = 2, } uv_type_t; +typedef enum { + SCULPT_TYPE_DRAW = 0, + SCULPT_TYPE_GRAB = 1, +} sculpt_type_t; + typedef enum { PICKER_MASK_NONE = 0, PICKER_MASK_MATERIAL = 1, diff --git a/paint/sources/render/make_sculpt.c b/paint/sources/render/make_sculpt.c index 755afcf8..5e6a88e8 100644 --- a/paint/sources/render/make_sculpt.c +++ b/paint/sources/render/make_sculpt.c @@ -113,16 +113,16 @@ node_shader_context_t *sculpt_make_sculpt_run(material_t *data, material_context .compare_mode = "always", .cull_mode = "none", .vertex_elements = any_array_create_from_raw( - (void *[]){ - GC_ALLOC_INIT(vertex_element_t, {.name = "pos", .data = "float2"}), - }, - 1), + (void *[]){ + GC_ALLOC_INIT(vertex_element_t, {.name = "pos", .data = "float2"}), + }, + 1), .color_attachments = any_array_create_from_raw( - (void *[]){ - "RGBA128", - "R8", - }, - 2)}); + (void *[]){ + "RGBA128", + "R8", + }, + 2)}); node_shader_context_t *con_paint = node_shader_context_create(data, props); con_paint->data->color_writes_red = u8_array_create_from_raw( (u8[]){ @@ -160,8 +160,10 @@ node_shader_context_t *sculpt_make_sculpt_run(material_t *data, material_context node_shader_t *kong = node_shader_context_make_kong(con_paint); bool decal = context_is_decal(); bool particle = g_context->tool == TOOL_TYPE_PARTICLE; + // Grab drags the vertices along with the cursor instead of displacing along the normal + bool grab = g_context->tool == TOOL_TYPE_BRUSH && g_context->brush_sculpt == SCULPT_TYPE_GRAB; // 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; + bool decal_layer = g_context->layer->fill_material != NULL && g_context->layer->uv_type == UV_TYPE_PROJECT && g_context->tool == TOOL_TYPE_FILL; 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; @@ -197,20 +199,51 @@ node_shader_context_t *sculpt_make_sculpt_run(material_t *data, material_context 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 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);"); - node_shader_write_frag(kong, "var winp: float4 = float4(float2(constants.inp.x, 1.0 - constants.inp.y) * 2.0 - 1.0, depth, 1.0);"); - node_shader_write_frag(kong, "winp = constants.invVP * winp;"); - node_shader_write_frag(kong, "winp.xyz = winp.xyz / winp.w;"); + if (grab) { + // Anchor a camera-facing plane at the grab_start surface point + node_shader_add_constant(kong, "grab_start: float2", "_grab_start"); + node_shader_write_frag(kong, "var grab_ndc: float2 = float2(constants.grab_start.x, 1.0 - constants.grab_start.y) * 2.0 - 1.0;"); + node_shader_write_frag(kong, "var grab_adepth: float = sample_lod(gbufferD, sampler_linear, constants.grab_start, 0.0).r;"); + node_shader_write_frag(kong, "var grab_anchor4: float4 = constants.invVP * float4(grab_ndc, grab_adepth, 1.0);"); + node_shader_write_frag(kong, "var grab_anchor: float3 = grab_anchor4.xyz / grab_anchor4.w;"); + node_shader_write_frag(kong, "var grab_near4: float4 = constants.invVP * float4(grab_ndc, 0.0, 1.0);"); + node_shader_write_frag(kong, "var grab_far4: float4 = constants.invVP * float4(grab_ndc, 1.0, 1.0);"); + node_shader_write_frag(kong, "var grab_plane_n: float3 = normalize(grab_far4.xyz / grab_far4.w - grab_near4.xyz / grab_near4.w);"); + // Intersect the cursor ray with the grab plane + node_shader_write_frag(kong, "var winp_ndc: float2 = float2(constants.inp.x, 1.0 - constants.inp.y) * 2.0 - 1.0;"); + node_shader_write_frag(kong, "var winp_o4: float4 = constants.invVP * float4(winp_ndc, 0.0, 1.0);"); + node_shader_write_frag(kong, "var winp_f4: float4 = constants.invVP * float4(winp_ndc, 1.0, 1.0);"); + node_shader_write_frag(kong, "var winp_o: float3 = winp_o4.xyz / winp_o4.w;"); + node_shader_write_frag(kong, "var winp_d: float3 = normalize(winp_f4.xyz / winp_f4.w - winp_o);"); + node_shader_write_frag(kong, + "var winp: float4 = float4(winp_o + winp_d * (dot(grab_anchor - winp_o, grab_plane_n) / dot(winp_d, grab_plane_n)), 1.0);"); + } + else { + node_shader_write_frag(kong, "var depth: float = sample_lod(gbufferD, sampler_linear, constants.inp.xy, 0.0).r;"); + node_shader_write_frag(kong, "var winp: float4 = float4(float2(constants.inp.x, 1.0 - constants.inp.y) * 2.0 - 1.0, depth, 1.0);"); + 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_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 (grab) { + node_shader_write_frag(kong, "var winpl_ndc: float2 = float2(constants.inplast.x, 1.0 - constants.inplast.y) * 2.0 - 1.0;"); + node_shader_write_frag(kong, "var winpl_o4: float4 = constants.invVP * float4(winpl_ndc, 0.0, 1.0);"); + node_shader_write_frag(kong, "var winpl_f4: float4 = constants.invVP * float4(winpl_ndc, 1.0, 1.0);"); + node_shader_write_frag(kong, "var winpl_o: float3 = winpl_o4.xyz / winpl_o4.w;"); + node_shader_write_frag(kong, "var winpl_d: float3 = normalize(winpl_f4.xyz / winpl_f4.w - winpl_o);"); + node_shader_write_frag( + kong, "var winplast: float4 = float4(winpl_o + winpl_d * (dot(grab_anchor - winpl_o, grab_plane_n) / dot(winpl_d, grab_plane_n)), 1.0);"); + } + else { + 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 (particle) { node_shader_add_constant(kong, "particle_hit: float3", "_particle_hit"); @@ -616,6 +649,10 @@ node_shader_context_t *sculpt_make_sculpt_run(material_t *data, material_context 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 if (grab) { + node_shader_write_frag(kong, "var grab_delta: float3 = (winp.xyz - winplast.xyz) * falloff * opacity;"); + node_shader_write_frag(kong, "output[0] = float4(sample_undo.rgb + grab_delta, 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);"); @@ -1043,7 +1080,6 @@ void render_path_sculpt_commands() { } 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"); @@ -1060,6 +1096,8 @@ void render_path_sculpt_begin() { if (history_push_undo && history_undo_layers != NULL) { history_paint(); render_path_sculpt_snapshot_gbuffer(); + g_context->grab_start_x = g_context->paint_vec.x; + g_context->grab_start_y = g_context->paint_vec.y; } sculpt_push_undo = false; } diff --git a/paint/sources/types.h b/paint/sources/types.h index 9a48b84a..ee195db4 100644 --- a/paint/sources/types.h +++ b/paint/sources/types.h @@ -239,6 +239,8 @@ typedef struct context { f32 clone_start_y; f32 clone_delta_x; f32 clone_delta_y; + f32 grab_start_x; + f32 grab_start_y; bool show_compass; i32 project_type; f32 last_paint_vec_x; @@ -362,6 +364,7 @@ typedef struct context { f32 brush_lazy_x; f32 brush_lazy_y; uv_type_t brush_paint; + sculpt_type_t brush_sculpt; f32 brush_angle_reject_dot; bake_type_t bake_type; bake_axis_t bake_axis; diff --git a/paint/sources/ui/ui_header.c b/paint/sources/ui/ui_header.c index 9150b8e4..d40fde08 100644 --- a/paint/sources/ui/ui_header.c +++ b/paint/sources/ui/ui_header.c @@ -384,7 +384,7 @@ void ui_header_draw_tool_properties() { g_context->brush_hardness = ui_slider(h, tr("Hardness"), 0.0, 1.0, true, 100.0, true, UI_ALIGN_RIGHT, true); } - if (g_context->tool != TOOL_TYPE_ERASER) { + if (g_context->tool != TOOL_TYPE_ERASER && g_config->workflow != WORKFLOW_SCULPT) { ui_handle_t *brush_blending_handle = ui_handle(__ID__); brush_blending_handle->i = g_context->brush_blending; string_array_t *brush_blending_combo = any_array_create_from_raw( @@ -415,7 +415,7 @@ void ui_header_draw_tool_properties() { } } - if (g_context->tool == TOOL_TYPE_BRUSH || g_context->tool == TOOL_TYPE_FILL) { + if ((g_context->tool == TOOL_TYPE_BRUSH || g_context->tool == TOOL_TYPE_FILL) && g_config->workflow != WORKFLOW_SCULPT) { ui_handle_t *paint_handle = ui_handle(__ID__); string_array_t *texcoord_combo = any_array_create_from_raw( (void *[]){ @@ -430,6 +430,20 @@ void ui_header_draw_tool_properties() { } } + if (g_context->tool == TOOL_TYPE_BRUSH && g_config->workflow == WORKFLOW_SCULPT) { + ui_handle_t *sculpt_handle = ui_handle(__ID__); + string_array_t *mode_combo = any_array_create_from_raw( + (void *[]){ + tr("Draw"), + tr("Grab"), + }, + 2); + g_context->brush_sculpt = ui_combo(sculpt_handle, mode_combo, tr("Mode"), false, UI_ALIGN_LEFT, true); + if (sculpt_handle->changed) { + make_material_parse_paint_material(true); + } + } + if (g_context->tool == TOOL_TYPE_TEXT) { ui_handle_t *h = ui_handle(__ID__); h->text = string_copy(g_context->text_tool_text); diff --git a/paint/sources/uniforms.c b/paint/sources/uniforms.c index 5fff32c9..080142ab 100644 --- a/paint/sources/uniforms.c +++ b/paint/sources/uniforms.c @@ -173,6 +173,9 @@ vec2_t uniforms_ext_vec2_link(object_t *object, material_data_t *mat, char *link else if (string_equals(link, "_clone_delta")) { return (vec2_t){g_context->clone_delta_x, g_context->clone_delta_y}; } + else if (string_equals(link, "_grab_start")) { + return (vec2_t){g_context->grab_start_x, g_context->grab_start_y}; + } else if (string_equals(link, "_texpaint_size")) { return (vec2_t){config_get_texture_res_x(), config_get_texture_res_y()}; } @@ -202,10 +205,10 @@ vec4_t uniforms_ext_vec3_link(object_t *object, material_data_t *mat, char *link // Discard first paint for directional brush (no prev position yet) bool allow_paint = g_context->prev_paint_vec_x > 0 && g_context->prev_paint_vec_y > 0 && (g_context->prev_paint_vec_x != g_context->paint_vec.x || g_context->prev_paint_vec_y != g_context->paint_vec.y); - f32 x = g_context->paint_vec.x; - f32 y = g_context->paint_vec.y; - f32 lastx = g_context->prev_paint_vec_x; - f32 lasty = g_context->prev_paint_vec_y; + f32 x = g_context->paint_vec.x; + f32 y = g_context->paint_vec.y; + f32 lastx = g_context->prev_paint_vec_x; + f32 lasty = g_context->prev_paint_vec_y; if (g_context->paint2d) { x = uniforms_ext_vec2d(x); lastx = uniforms_ext_vec2d(lastx); @@ -311,10 +314,10 @@ mat4_t uniforms_ext_mat4_link(object_t *object, material_data_t *mat, char *link transform_t *t = object->transform; mat4_t W = t->world; vec4_t axes[3] = { - vec4_norm((vec4_t){W.m00, W.m10, W.m20, 0.0}), - vec4_norm((vec4_t){W.m01, W.m11, W.m21, 0.0}), - vec4_norm((vec4_t){W.m02, W.m12, W.m22, 0.0}), - }; + vec4_norm((vec4_t){W.m00, W.m10, W.m20, 0.0}), + vec4_norm((vec4_t){W.m01, W.m11, W.m21, 0.0}), + vec4_norm((vec4_t){W.m02, W.m12, W.m22, 0.0}), + }; f32 scale[3] = {t->scale.x, t->scale.y, t->scale.z}; mat4_t F = mat4_identity(); for (i32 i = 0; i < 3; ++i) {