From d7116f22884e776aad97902180d4a1c3c06b9e04 Mon Sep 17 00:00:00 2001 From: luboslenco Date: Wed, 13 May 2026 17:11:52 +0200 Subject: [PATCH] paint: path and curve layers --- paint/sources/base.c | 11 +- paint/sources/brush_nodes/brush_output_node.c | 21 +- paint/sources/export_arm.c | 17 +- paint/sources/functions.h | 9 + paint/sources/gizmo.c | 4 +- paint/sources/globals.h | 2 +- paint/sources/history.c | 4 +- paint/sources/import_arm.c | 11 +- paint/sources/import_legacy.c | 25 +- paint/sources/main.c | 3 +- paint/sources/make_brush.c | 2 +- paint/sources/make_paint.c | 10 +- paint/sources/make_sculpt.c | 2 +- paint/sources/make_texcoord.c | 2 +- paint/sources/render_path_paint.c | 4 +- paint/sources/slot_layer.c | 64 ++- paint/sources/tab_layers.c | 56 ++- paint/sources/tab_materials.c | 4 +- paint/sources/types.h | 18 +- paint/sources/ui_nodes.c | 23 +- paint/sources/{uniforms_ext.c => uniforms.c} | 4 +- paint/sources/util_encode.c | 20 +- paint/sources/util_layer.c | 415 ++++++++++++++++++ paint/sources/util_render.c | 12 +- 24 files changed, 657 insertions(+), 86 deletions(-) rename paint/sources/{uniforms_ext.c => uniforms.c} (99%) create mode 100644 paint/sources/util_layer.c diff --git a/paint/sources/base.c b/paint/sources/base.c index 4867dc82..ffcc66df 100644 --- a/paint/sources/base.c +++ b/paint/sources/base.c @@ -365,19 +365,19 @@ gpu_texture_t *base_get_drag_image() { base_drag_tint = base_darker(ui->ops->theme->LABEL_COL, 0x00202020); return icons; } - if (base_drag_layer != NULL && slot_layer_is_mask(base_drag_layer) && base_drag_layer->fill_layer == NULL) { + if (base_drag_layer != NULL && slot_layer_is_mask(base_drag_layer) && base_drag_layer->fill_material == NULL) { tab_layers_make_mask_preview_rgba32(base_drag_layer); return g_context->mask_preview_rgba32; } if (base_drag_layer != NULL) { - return base_drag_layer->fill_layer != NULL ? base_drag_layer->fill_layer->image_icon : base_drag_layer->texpaint_preview; + return base_drag_layer->fill_material != NULL ? base_drag_layer->fill_material->image_icon : base_drag_layer->texpaint_preview; } return NULL; } rect_t *base_get_drag_background() { gpu_texture_t *icons = resource_get("icons.k"); - if (base_drag_layer != NULL && !slot_layer_is_group(base_drag_layer) && base_drag_layer->fill_layer == NULL) { + if (base_drag_layer != NULL && !slot_layer_is_group(base_drag_layer) && base_drag_layer->fill_material == NULL) { return resource_tile50(icons, ICON_CHECKER); } return NULL; @@ -766,6 +766,7 @@ void ui_base_update_ui() { } gizmo_update(); + util_layer_update_path(); // Same mapping for paint and rotate (predefined in touch keymap) if (context_in_3d_view()) { @@ -932,7 +933,7 @@ void ui_base_update_ui() { g_context->layer_preview_dirty = true; // Update layer preview // New color id picked, update fill layer - if (g_context->tool == TOOL_TYPE_COLORID && g_context->layer->fill_layer != NULL) { + if (g_context->tool == TOOL_TYPE_COLORID && g_context->layer->fill_material != NULL) { sys_notify_on_next_frame(&ui_base_update_ui_on_next_frame, NULL); } } @@ -2066,7 +2067,7 @@ void base_toggle_fullscreen() { bool base_is_decal_layer() { bool is_painting = g_context->tool != TOOL_TYPE_MATERIAL && g_context->tool != TOOL_TYPE_BAKE; - return is_painting && g_context->layer->fill_layer != NULL && g_context->layer->uv_type == UV_TYPE_PROJECT; + return is_painting && g_context->layer->fill_material != NULL && g_context->layer->uv_type == UV_TYPE_PROJECT; } void base_redraw_status() { diff --git a/paint/sources/brush_nodes/brush_output_node.c b/paint/sources/brush_nodes/brush_output_node.c index 2601fd18..a134c0d1 100644 --- a/paint/sources/brush_nodes/brush_output_node.c +++ b/paint/sources/brush_nodes/brush_output_node.c @@ -64,7 +64,8 @@ void brush_output_node_parse_inputs(brush_output_node_t *self) { } void brush_output_paint(brush_output_node_t *self) { - bool down = mouse_down("left") || pen_down("tip"); + bool down = mouse_down("left") || pen_down("tip"); + bool started = mouse_started("left") || pen_started("tip"); // Set color pick if (down && g_context->tool == TOOL_TYPE_COLORID && project_assets->length > 0) { @@ -72,6 +73,16 @@ void brush_output_paint(brush_output_node_t *self) { ui_toolbar_handle->redraws = 1; } + // Path layer - add path points only + if (slot_layer_is_path(g_context->layer) && !started) { + return; + } + + // Path layer - dragging existing path point + if (slot_layer_is_path(g_context->layer) && util_layer_is_path_point_dragging()) { + return; + } + // Prevent painting the same spot bool same_spot = g_context->paint_vec.x == g_context->last_paint_x && g_context->paint_vec.y == g_context->last_paint_y; bool lazy = g_context->tool == TOOL_TYPE_BRUSH && g_context->brush_lazy_radius > 0; @@ -92,6 +103,12 @@ void brush_output_paint(brush_output_node_t *self) { brush_output_node_parse_inputs(self); } + // Path layer - add point and repaint + if (slot_layer_is_path(g_context->layer)) { + util_layer_add_path_point(g_context->layer, g_context->paint_vec.x, g_context->paint_vec.y); + return; + } + if (g_context->painted <= 1) { g_context->pdirty = 1; g_context->rdirty = 2; @@ -135,7 +152,7 @@ void brush_output_node_run(brush_output_node_t *self, i32 from) { } // Do not paint over fill layer - if (g_context->layer->fill_layer != NULL && g_context->tool != TOOL_TYPE_PICKER && g_context->tool != TOOL_TYPE_MATERIAL && + if (g_context->layer->fill_material != NULL && g_context->tool != TOOL_TYPE_PICKER && g_context->tool != TOOL_TYPE_MATERIAL && g_context->tool != TOOL_TYPE_COLORID) { return; } diff --git a/paint/sources/export_arm.c b/paint/sources/export_arm.c index bdc07387..74b95d58 100644 --- a/paint/sources/export_arm.c +++ b/paint/sources/export_arm.c @@ -113,7 +113,7 @@ buffer_t *export_arm_rgba64_to_rgba32(buffer_t *buffer) { void export_arm_run_project() { workflow_t _workflow = g_config->workflow; - g_config->workflow = WORKFLOW_PBR; + g_config->workflow = WORKFLOW_PBR; base_update_workflow(); ui_node_canvas_t_array_t *mnodes = any_array_create_from_raw((void *[]){}, 0); @@ -182,7 +182,7 @@ void export_arm_run_project() { .uv_map = l->uv_map, .decal_mat = l->uv_type == UV_TYPE_PROJECT ? mat4_to_f32_array(l->decal_mat) : NULL, .opacity_mask = l->mask_opacity, - .fill_layer = l->fill_layer != NULL ? array_index_of(project_materials, l->fill_layer) : -1, + .fill_material = l->fill_material != NULL ? array_index_of(project_materials, l->fill_material) : -1, .object_mask = l->object_mask, .blending = l->blending, .parent = l->parent != NULL ? array_index_of(project_layers, l->parent) : -1, @@ -199,7 +199,14 @@ void export_arm_run_project() { .paint_height = l->paint_height, .paint_height_blend = l->paint_height_blend, .paint_emis = l->paint_emis, - .paint_subs = l->paint_subs}); + .paint_subs = l->paint_subs, + .path_points = l->path_points, + .path_points_world = l->path_points_world, + .path_points_camera = l->path_points_camera, + .path_points_parent = l->path_points_parent, + .path_tool = l->path_tool, + .path_curved = l->path_curved, + .path_material = l->path_material != NULL ? array_index_of(project_materials, l->path_material) : -1}); any_array_push(ld, d); } @@ -236,8 +243,8 @@ void export_arm_run_project() { f32_array_t_array_t *mesh_transforms = any_array_create_from_raw((void *[]){}, 0); for (i32 i = 0; i < project_paint_objects->length; ++i) { - mesh_object_t *p = project_paint_objects->buffer[i]; - f32_array_t *ar = f32_array_create_from_raw(p->base->transform->local.m, 16); + mesh_object_t *p = project_paint_objects->buffer[i]; + f32_array_t *ar = f32_array_create_from_raw(p->base->transform->local.m, 16); any_array_push(mesh_transforms, ar); } g_project->mesh_transforms = mesh_transforms; diff --git a/paint/sources/functions.h b/paint/sources/functions.h index 3f9353c8..99ec8d1a 100644 --- a/paint/sources/functions.h +++ b/paint/sources/functions.h @@ -38,6 +38,7 @@ slot_layer_t_array_t *slot_layer_get_filters(slot_layer_t *raw, bool include_gro f32 slot_layer_get_opacity(slot_layer_t *raw); i32 slot_layer_get_object_mask(slot_layer_t *raw); bool slot_layer_is_layer(slot_layer_t *raw); +bool slot_layer_is_path(slot_layer_t *raw); bool slot_layer_is_group(slot_layer_t *raw); slot_layer_t *slot_layer_get_containing_group(slot_layer_t *raw); bool slot_layer_is_filter(slot_layer_t *raw); @@ -57,12 +58,15 @@ void layers_commands_merge_pack(gpu_pipeline_t *pipe, gpu_texture_t *i0, gpu_tex i32 i1blending); bool layers_is_fill_material(); void layers_update_fill_layers(); +bool layers_is_path_material(); +void layers_update_path_layers(); void layers_update_fill_layer(bool parse_paint); void layers_update_linked_layers(); void layers_set_object_mask(); slot_layer_t *layers_new_layer(bool clear, i32 position, slot_layer_t *parent); slot_layer_t *layers_new_mask(bool clear, slot_layer_t *parent, i32 position); slot_layer_t *layers_new_group(); +slot_layer_t *layers_new_path_layer(bool curved); void layers_create_fill_layer(uv_type_t uv_type, mat4_t decal_mat, i32 position); void layers_create_image_mask(asset_t *asset); void layers_create_color_layer(i32 base_color, f32 occlusion, f32 roughness, f32 metallic, i32 position); @@ -172,6 +176,11 @@ void ui_base_toggle_browser(); void ui_base_set_icon_scale(); void ui_base_set_viewport_col(i32 col); void gizmo_update(); +void util_layer_update_path(); +bool util_layer_is_path_point_dragging(); +void util_layer_add_path_point(slot_layer_t *l, f32 screen_x, f32 screen_y); +void util_layer_repaint_path(slot_layer_t *l); +void util_layer_clear_path_points(slot_layer_t *l); void make_texcoord_run(node_shader_t *kong); void context_init(); void context_select_material(i32 i); diff --git a/paint/sources/gizmo.c b/paint/sources/gizmo.c index df53cf08..5010cdbb 100644 --- a/paint/sources/gizmo.c +++ b/paint/sources/gizmo.c @@ -165,8 +165,8 @@ void gizmo_update() { } g_context->gizmo_drag_last = g_context->gizmo_drag; - if (g_context->material != g_context->layer->fill_layer) { - context_set_material(g_context->layer->fill_layer); + if (g_context->material != g_context->layer->fill_material) { + context_set_material(g_context->layer->fill_material); } layers_update_fill_layer(g_context->gizmo_started); } diff --git a/paint/sources/globals.h b/paint/sources/globals.h index e4ddbf8b..d919cb2e 100644 --- a/paint/sources/globals.h +++ b/paint/sources/globals.h @@ -7,7 +7,7 @@ char *manifest_title = "ArmorPaint"; char *manifest_version = "1.0 alpha"; -char *manifest_version_project = "6"; +char *manifest_version_project = "7"; char *manifest_version_config = "1"; char *manifest_url = "https://armorpaint.org"; char *manifest_url_android = "https://play.google.com/store/apps/details?id=org.armorpaint"; diff --git a/paint/sources/history.c b/paint/sources/history.c index 883d2173..99dcbc72 100644 --- a/paint/sources/history.c +++ b/paint/sources/history.c @@ -264,7 +264,7 @@ void history_undo() { history_undo_i = history_undo_i - 1 < 0 ? g_config->undo_steps - 1 : history_undo_i - 1; slot_layer_t *lay = history_undo_layers->buffer[history_undo_i]; slot_layer_swap(g_context->layer, lay); - g_context->layer->fill_layer = project_materials->buffer[step->material]; + g_context->layer->fill_material = project_materials->buffer[step->material]; } else if (step->action == HISTORY_ACTION_LAYER_OPACITY) { context_set_layer(project_layers->buffer[step->layer]); @@ -536,7 +536,7 @@ void history_redo() { else if (step->action == HISTORY_ACTION_TO_FILL_LAYER || step->action == HISTORY_ACTION_TO_FILL_MASK) { slot_layer_t *lay = history_undo_layers->buffer[history_undo_i]; slot_layer_swap(g_context->layer, lay); - g_context->layer->fill_layer = project_materials->buffer[step->material]; + g_context->layer->fill_material = project_materials->buffer[step->material]; history_undo_i = (history_undo_i + 1) % g_config->undo_steps; } else if (step->action == HISTORY_ACTION_TO_PAINT_LAYER || step->action == HISTORY_ACTION_TO_PAINT_MASK) { diff --git a/paint/sources/import_arm.c b/paint/sources/import_arm.c index ddf58112..97b3090f 100644 --- a/paint/sources/import_arm.c +++ b/paint/sources/import_arm.c @@ -526,6 +526,12 @@ void import_arm_run_project(char *path) { l->paint_height_blend = ld->paint_height_blend; l->paint_emis = ld->paint_emis; l->paint_subs = ld->paint_subs; + l->path_points = ld->path_points; + l->path_points_world = ld->path_points_world; + l->path_points_camera = ld->path_points_camera; + l->path_points_parent = ld->path_points_parent; + l->path_tool = ld->path_tool; + l->path_curved = ld->path_curved; gpu_delete_texture(_texpaint); if (_texpaint_nor != NULL) { @@ -594,13 +600,14 @@ void import_arm_run_project(char *path) { util_render_make_brush_preview(); } - // Fill layers + // Fill layers and path layers materials for (i32 i = 0; i < project->layer_datas->length; ++i) { layer_data_t *ld = project->layer_datas->buffer[i]; slot_layer_t *l = project_layers->buffer[i]; bool is_group = ld->texpaint == NULL; if (!is_group) { - l->fill_layer = ld->fill_layer > -1 ? project_materials->buffer[ld->fill_layer] : NULL; + l->fill_material = ld->fill_material > -1 ? project_materials->buffer[ld->fill_material] : NULL; + l->path_material = ld->path_material > -1 ? project_materials->buffer[ld->path_material] : NULL; } } diff --git a/paint/sources/import_legacy.c b/paint/sources/import_legacy.c index cd1fa109..e749fbc7 100644 --- a/paint/sources/import_legacy.c +++ b/paint/sources/import_legacy.c @@ -159,7 +159,7 @@ project_t *import_arm_from_map_to_arm(any_map_t *old) { ld->uv_type = armpack_map_get_i32(old, "uv_type"); ld->decal_mat = any_map_get(old, "decal_mat"); ld->opacity_mask = armpack_map_get_f32(old, "opacity_mask"); - ld->fill_layer = armpack_map_get_i32(old, "fill_layer"); + ld->fill_material = armpack_map_get_i32(old, "fill_material"); ld->object_mask = armpack_map_get_i32(old, "object_mask"); ld->blending = armpack_map_get_i32(old, "blending"); ld->parent = armpack_map_get_i32(old, "parent"); @@ -215,6 +215,24 @@ project_t *import_arm_from_map_to_arm(any_map_t *old) { return project; } +project_t *import_arm_from_version_6(any_map_t *old) { + any_array_t *lds = any_map_get(old, "layer_datas"); + if (lds != NULL) { + for (i32 i = 0; i < lds->length; ++i) { + any_map_t *ld = lds->buffer[i]; + armpack_map_set_i32(ld, "fill_material", armpack_map_get_i32(ld, "fill_layer")); + any_map_set(ld, "path_points", NULL); + any_map_set(ld, "path_points_world", NULL); + any_map_set(ld, "path_points_camera", NULL); + any_map_set(ld, "path_points_parent", NULL); + armpack_map_set_i32(ld, "path_tool", 0); + armpack_map_set_i32(ld, "path_curved", 0); + armpack_map_set_i32(ld, "path_material", -1); + } + } + return import_arm_from_map_to_arm(old); +} + project_t *import_arm_from_version_5(any_map_t *old) { any_array_t *mds = any_map_get(old, "mesh_datas"); any_array_t *mts = any_array_create_from_raw((void *[]){}, 0); @@ -222,7 +240,7 @@ project_t *import_arm_from_version_5(any_map_t *old) { any_array_push(mts, mat4_to_f32_array(mat4_identity())); } any_map_set(old, "mesh_transforms", mts); - return import_arm_from_map_to_arm(old); + return import_arm_from_version_6(old); } project_t *import_arm_from_version_4(any_map_t *old) { @@ -255,6 +273,9 @@ project_t *import_arm_from_version_2(any_map_t *old) { project_t *import_arm_from_old(buffer_t *b) { any_map_t *old = armpack_decode_to_map(b); + if (import_arm_is_version(b, '6')) { + return import_arm_from_version_6(old); + } if (import_arm_is_version(b, '5')) { return import_arm_from_version_5(old); } diff --git a/paint/sources/main.c b/paint/sources/main.c index 50061ae6..12c9f590 100644 --- a/paint/sources/main.c +++ b/paint/sources/main.c @@ -193,9 +193,10 @@ #include "ui_statusbar.c" #include "ui_toolbar.c" #include "ui_view2d.c" -#include "uniforms_ext.c" +#include "uniforms.c" #include "util_clone.c" #include "util_encode.c" +#include "util_layer.c" #include "util_mesh.c" #include "util_particle.c" #include "util_render.c" diff --git a/paint/sources/make_brush.c b/paint/sources/make_brush.c index 481dcb4e..4d0d6aaa 100644 --- a/paint/sources/make_brush.c +++ b/paint/sources/make_brush.c @@ -9,7 +9,7 @@ void make_brush_run(node_shader_t *kong) { return; } - bool fill_layer = g_context->layer->fill_layer != NULL; + bool fill_layer = g_context->layer->fill_material != NULL; bool decal = context_is_decal(); if (decal && !fill_layer) { node_shader_write_frag(kong, "if (constants.decal_mask.z > 0.0) {"); diff --git a/paint/sources/make_paint.c b/paint/sources/make_paint.c index cd8c3e8d..ad8eb1a0 100644 --- a/paint/sources/make_paint.c +++ b/paint/sources/make_paint.c @@ -190,7 +190,7 @@ node_shader_context_t *make_paint_run(material_t *data, material_context_t *matc node_shader_write_vert(kong, "output.pos = float4(tpos, 0.0, 1.0);"); - bool decal_layer = g_context->layer->fill_layer != NULL && g_context->layer->uv_type == UV_TYPE_PROJECT; + bool decal_layer = g_context->layer->fill_material != NULL && g_context->layer->uv_type == UV_TYPE_PROJECT; if (decal_layer) { node_shader_add_constant(kong, "WVP: float4x4", "_decal_layer_matrix"); } @@ -207,7 +207,7 @@ node_shader_context_t *make_paint_run(material_t *data, material_context_t *matc node_shader_write_attrib_frag(kong, "sp.y = 1.0 - sp.y;"); node_shader_write_attrib_frag(kong, "sp.z -= 0.0001;"); // small bias - uv_type_t uv_type = g_context->layer->fill_layer != NULL ? g_context->layer->uv_type : g_context->brush_paint; + uv_type_t uv_type = g_context->layer->fill_material != NULL ? g_context->layer->uv_type : g_context->brush_paint; if (uv_type == UV_TYPE_PROJECT) { kong->frag_ndcpos = true; } @@ -311,7 +311,7 @@ node_shader_context_t *make_paint_run(material_t *data, material_context_t *matc parser_material_parse_subsurface = g_context->material->paint_subs; parser_material_parse_height = g_context->material->paint_height; parser_material_parse_height_as_channel = true; - uv_type_t uv_type = g_context->layer->fill_layer != NULL ? g_context->layer->uv_type : g_context->brush_paint; + uv_type_t uv_type = g_context->layer->fill_material != NULL ? g_context->layer->uv_type : g_context->brush_paint; parser_material_triplanar = uv_type == UV_TYPE_TRIPLANAR && !decal; parser_material_sample_keep_aspect = decal; gc_unroot(parser_material_sample_uv_scale); @@ -346,7 +346,7 @@ node_shader_context_t *make_paint_run(material_t *data, material_context_t *matc node_shader_write_frag(kong, "var opacity: float = mat_opacity;"); } - if (g_context->layer->fill_layer == NULL) { + if (g_context->layer->fill_material == NULL) { node_shader_write_frag(kong, "opacity *= constants.brush_opacity;"); } @@ -512,7 +512,7 @@ node_shader_context_t *make_paint_run(material_t *data, material_context_t *matc node_shader_write_frag(kong, "output[0] = float4((basecol * t_blur + sample_undo.rgb * sample_undo.a * (1.0 - t_blur)) / max(out_a, 0.0000001), out_a);"); } - else if (g_context->material->paint_opac_mode == OPACITY_MODE_TRANSLUC || g_context->layer->fill_layer != NULL) { + else if (g_context->material->paint_opac_mode == OPACITY_MODE_TRANSLUC || g_context->layer->fill_material != NULL) { node_shader_write_frag(kong, string("output[0] = float4(%s, %s);", make_material_blend_mode(kong, g_context->brush_blending, "sample_undo.rgb", "basecol", "str"), "mat_opacity")); } diff --git a/paint/sources/make_sculpt.c b/paint/sources/make_sculpt.c index f38fcbf7..a47eec82 100644 --- a/paint/sources/make_sculpt.c +++ b/paint/sources/make_sculpt.c @@ -183,7 +183,7 @@ node_shader_context_t *sculpt_make_sculpt_run(material_t *data, material_context 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))];"); - if (g_context->layer->fill_layer != NULL) { + if (g_context->layer->fill_material != NULL) { node_shader_add_function(kong, str_octahedron_wrap); node_shader_write_frag(kong, "var nor_v: float = floor(raw_undo.a) / 255.0;"); node_shader_write_frag(kong, "var nor_oct: float2 = float2(raw_undo.a - floor(raw_undo.a), nor_v) * 2.0 - 1.0;"); diff --git a/paint/sources/make_texcoord.c b/paint/sources/make_texcoord.c index 778ccaf7..223fb64e 100644 --- a/paint/sources/make_texcoord.c +++ b/paint/sources/make_texcoord.c @@ -3,7 +3,7 @@ void make_texcoord_run(node_shader_t *kong) { - bool fill_layer = g_context->layer->fill_layer != NULL; + bool fill_layer = g_context->layer->fill_material != NULL; uv_type_t uv_type = fill_layer ? g_context->layer->uv_type : g_context->brush_paint; bool decal = context_is_decal(); f32 angle = g_context->brush_angle + g_context->brush_nodes_angle; diff --git a/paint/sources/render_path_paint.c b/paint/sources/render_path_paint.c index 5100dcaa..b68c4d97 100644 --- a/paint/sources/render_path_paint.c +++ b/paint/sources/render_path_paint.c @@ -662,7 +662,7 @@ void render_path_paint_commands_cursor() { return; } - bool fill_layer = g_context->layer->fill_layer != NULL; + bool fill_layer = g_context->layer->fill_material != NULL; bool group_layer = slot_layer_is_group(g_context->layer); if (!base_ui_enabled || base_is_dragging || fill_layer || group_layer) { return; @@ -676,7 +676,7 @@ void render_path_paint_commands_cursor() { } bool render_path_paint_paint_enabled() { - bool fill_layer = g_context->layer->fill_layer != NULL && g_context->tool != TOOL_TYPE_PICKER && g_context->tool != TOOL_TYPE_MATERIAL && + bool fill_layer = g_context->layer->fill_material != NULL && g_context->tool != TOOL_TYPE_PICKER && g_context->tool != TOOL_TYPE_MATERIAL && g_context->tool != TOOL_TYPE_COLORID; bool group_layer = slot_layer_is_group(g_context->layer); diff --git a/paint/sources/slot_layer.c b/paint/sources/slot_layer.c index 439a73ab..92b24809 100644 --- a/paint/sources/slot_layer.c +++ b/paint/sources/slot_layer.c @@ -280,7 +280,7 @@ void layers_apply_mask(slot_layer_t *l, slot_layer_t *m) { } void slot_layer_apply_mask(slot_layer_t *raw) { - if (raw->parent->fill_layer != NULL) { + if (raw->parent->fill_material != NULL) { slot_layer_to_paint_layer(raw->parent); } if (slot_layer_is_group(raw->parent)) { @@ -346,7 +346,7 @@ slot_layer_t *slot_layer_duplicate(slot_layer_t *raw) { l->visible = raw->visible; l->mask_opacity = raw->mask_opacity; - l->fill_layer = raw->fill_layer; + l->fill_material = raw->fill_material; l->object_mask = raw->object_mask; l->blending = raw->blending; l->uv_type = raw->uv_type; @@ -453,14 +453,14 @@ void slot_layer_to_fill_layer_on_next_frame(void *_) { void slot_layer_to_fill_layer(slot_layer_t *raw) { context_set_layer(raw); - raw->fill_layer = g_context->material; + raw->fill_material = g_context->material; layers_update_fill_layer(true); sys_notify_on_next_frame(&slot_layer_to_fill_layer_on_next_frame, NULL); } void slot_layer_to_paint_layer(slot_layer_t *raw) { context_set_layer(raw); - raw->fill_layer = NULL; + raw->fill_material = NULL; make_material_parse_paint_material(true); g_context->layer_preview_dirty = true; ui_base_hwnds->buffer[TAB_AREA_SIDEBAR0]->redraws = 2; @@ -607,6 +607,10 @@ bool slot_layer_is_layer(slot_layer_t *raw) { return raw->texpaint != NULL && raw->texpaint_nor != NULL; } +bool slot_layer_is_path(slot_layer_t *raw) { + return raw->path_points != NULL; +} + bool slot_layer_is_group(slot_layer_t *raw) { return raw->texpaint == NULL; } @@ -1054,7 +1058,7 @@ bool layers_is_fill_material() { slot_material_t *m = g_context->material; for (i32 i = 0; i < project_layers->length; ++i) { slot_layer_t *l = project_layers->buffer[i]; - if (l->fill_layer == m) { + if (l->fill_material == m) { return true; } } @@ -1105,13 +1109,13 @@ void layers_update_fill_layers() { bool has_fill_mask = false; for (i32 i = 0; i < project_layers->length; ++i) { slot_layer_t *l = project_layers->buffer[i]; - if (slot_layer_is_layer(l) && l->fill_layer == g_context->material) { + if (slot_layer_is_layer(l) && l->fill_material == g_context->material) { has_fill_layer = true; } } for (i32 i = 0; i < project_layers->length; ++i) { slot_layer_t *l = project_layers->buffer[i]; - if (slot_layer_is_mask(l) && l->fill_layer == g_context->material) { + if (slot_layer_is_mask(l) && l->fill_material == g_context->material) { has_fill_mask = true; } } @@ -1129,7 +1133,7 @@ void layers_update_fill_layers() { bool first = true; for (i32 i = 0; i < project_layers->length; ++i) { slot_layer_t *l = project_layers->buffer[i]; - if (slot_layer_is_layer(l) && l->fill_layer == g_context->material) { + if (slot_layer_is_layer(l) && l->fill_material == g_context->material) { g_context->layer = l; if (first) { first = false; @@ -1157,7 +1161,7 @@ void layers_update_fill_layers() { bool first = true; for (i32 i = 0; i < project_layers->length; ++i) { slot_layer_t *l = project_layers->buffer[i]; - if (slot_layer_is_mask(l) && l->fill_layer == g_context->material) { + if (slot_layer_is_mask(l) && l->fill_material == g_context->material) { g_context->layer = l; if (first) { first = false; @@ -1184,6 +1188,26 @@ void layers_update_fill_layers() { } } +bool layers_is_path_material() { + slot_material_t *m = g_context->material; + for (i32 i = 0; i < project_layers->length; ++i) { + slot_layer_t *l = project_layers->buffer[i]; + if (slot_layer_is_path(l) && l->path_material == m) { + return true; + } + } + return false; +} + +void layers_update_path_layers() { + for (i32 i = 0; i < project_layers->length; ++i) { + slot_layer_t *l = project_layers->buffer[i]; + if (slot_layer_is_path(l) && l->path_material == g_context->material) { + util_layer_repaint_path(l); + } + } +} + void layers_update_fill_layer(bool parse_paint) { gpu_texture_t *current = _draw_current; bool in_use = gpu_in_use; @@ -1366,6 +1390,22 @@ slot_layer_t *layers_new_group() { return l; } +slot_layer_t *layers_new_path_layer(bool curved) { + slot_layer_t *l = layers_new_layer(true, -1, NULL); + if (l == NULL) { + return NULL; + } + l->path_points = f32_array_create(0); + l->path_points_world = f32_array_create(0); + l->path_points_camera = f32_array_create(0); + l->path_points_parent = i32_array_create(0); + l->path_tool = -1; + l->path_curved = curved; + l->path_material = g_context->material; + l->name = string(curved ? "Curve %d" : "Path %d", l->id + 1); + return l; +} + void layers_create_fill_layer_on_next_frame(void *_) { slot_layer_t *l = layers_new_layer(false, _layers_position, NULL); history_new_layer(); @@ -1608,7 +1648,7 @@ slot_layer_t *layers_merge_group(slot_layer_t *l) { children->buffer[0]->parent = NULL; children->buffer[0]->name = l->name; - if (children->buffer[0]->fill_layer != NULL) { + if (children->buffer[0]->fill_material != NULL) { slot_layer_to_paint_layer(children->buffer[0]); } slot_layer_delete(l); @@ -1883,9 +1923,9 @@ void layers_on_resized_on_next_frame(void *_) { slot_material_t *_material = g_context->material; for (i32 i = 0; i < project_layers->length; ++i) { slot_layer_t *l = project_layers->buffer[i]; - if (l->fill_layer != NULL) { + if (l->fill_material != NULL) { g_context->layer = l; - g_context->material = l->fill_layer; + g_context->material = l->fill_material; layers_update_fill_layer(true); } } diff --git a/paint/sources/tab_layers.c b/paint/sources/tab_layers.c index 21dd50b5..76c1aa79 100644 --- a/paint/sources/tab_layers.c +++ b/paint/sources/tab_layers.c @@ -63,8 +63,11 @@ void tab_layers_handle_layer_icon_state(slot_layer_t *l, i32 i, ui_state_t state if (sys_time() - g_context->select_time > 0.2) { g_context->select_time = sys_time(); } - if (l->fill_layer != NULL) { - context_set_material(l->fill_layer); + if (l->fill_material != NULL) { + context_set_material(l->fill_material); + } + else if (l->path_material != NULL) { + context_set_material(l->path_material); } } } @@ -90,17 +93,17 @@ ui_state_t tab_layers_draw_layer_icon(slot_layer_t *l, i32 i, f32 uix, f32 uiy, gpu_texture_t *texpaint_preview = l->texpaint_preview; gpu_texture_t *icon; - if (l->fill_layer == NULL) { - icon = texpaint_preview; + if (l->fill_material != NULL) { + icon = g_config->window_scale > 1 ? l->fill_material->image : l->fill_material->image_icon; } - else if (g_config->window_scale > 1) { - icon = l->fill_layer->image; + else if (l->path_material != NULL) { + icon = g_config->window_scale > 1 ? l->path_material->image : l->path_material->image_icon; } else { - icon = l->fill_layer->image_icon; + icon = texpaint_preview; } - if (l->fill_layer == NULL) { + if (l->fill_material == NULL && l->path_material == NULL) { // Checker rect_t *r = resource_tile50(icons, ICON_CHECKER); f32 _x = ui->_x; @@ -111,14 +114,14 @@ ui_state_t tab_layers_draw_layer_icon(slot_layer_t *l, i32 i, f32 uix, f32 uiy, ui->_y = _y; ui->_w = _w; } - if (l->fill_layer == NULL && slot_layer_is_mask(l)) { + if (l->fill_material == NULL && slot_layer_is_mask(l)) { draw_set_pipeline(ui_view2d_pipe); gpu_set_int(ui_view2d_channel_loc, 1); } ui_state_t state = ui_image(icon, 0xffffffff, icon_h); - if (l->fill_layer == NULL && slot_layer_is_mask(l)) { + if (l->fill_material == NULL && slot_layer_is_mask(l)) { draw_set_pipeline(NULL); } @@ -231,7 +234,7 @@ void tab_layers_draw_layer_slot_full_delete_layer(void *_) { } void tab_layers_combo_object_layer_clear(slot_layer_t *l) { - g_context->material = l->fill_layer; + g_context->material = l->fill_material; slot_layer_clear(l, 0x00000000, NULL, 1.0, layers_default_rough, 0.0); layers_update_fill_layers(); } @@ -259,7 +262,7 @@ ui_handle_t *tab_layers_combo_object(slot_layer_t *l, bool label) { if (object_handle->changed) { context_set_layer(l); make_material_parse_mesh_material(); - if (l->fill_layer != NULL) { + if (l->fill_material != NULL) { sys_notify_on_next_frame(&tab_layers_combo_object_layer_clear, l); } else { @@ -574,7 +577,7 @@ void tab_layers_draw_layer_context_menu_merge_down(void *_) { context_set_layer(l); history_merge_layers(); layers_merge_down(); - if (g_context->layer->fill_layer != NULL) + if (g_context->layer->fill_material != NULL) slot_layer_to_paint_layer(g_context->layer); } @@ -611,6 +614,7 @@ void tab_layers_draw_layer_context_menu_clear(void *_) { if (!slot_layer_is_group(l)) { history_clear_layer(); slot_layer_clear(l, 0x00000000, NULL, 1.0, layers_default_rough, 0.0); + util_layer_clear_path_points(l); } else { for (i32 i = 0; i < slot_layer_get_children(l)->length; ++i) { @@ -663,15 +667,15 @@ void tab_layers_draw_layer_context_menu_draw() { char *to_fill_string = slot_layer_is_layer(l) ? tr("To Fill Layer") : tr("To Fill Mask"); char *to_paint_string = slot_layer_is_layer(l) ? tr("To Paint Layer") : tr("To Paint Mask"); - if (l->fill_layer == NULL && ui_menu_button(to_fill_string, "", ICON_SPHERE)) { + if (l->fill_material == NULL && ui_menu_button(to_fill_string, "", ICON_SPHERE)) { sys_notify_on_next_frame(&tab_layers_draw_layer_context_menu_to_fill_layer, NULL); } - if (l->fill_layer != NULL && ui_menu_button(to_paint_string, "", ICON_PAINT)) { + if (l->fill_material != NULL && ui_menu_button(to_paint_string, "", ICON_PAINT)) { sys_notify_on_next_frame(&tab_layers_draw_layer_context_menu_to_paint_layer, NULL); } } - if (slot_layer_is_layer(l) && l->fill_layer != NULL && l->texpaint_sculpt != NULL && ui_menu_button(tr("Apply"), "", ICON_CHECK)) { + if (slot_layer_is_layer(l) && l->fill_material != NULL && l->texpaint_sculpt != NULL && ui_menu_button(tr("Apply"), "", ICON_CHECK)) { sys_notify_on_next_frame(&tab_layers_draw_layer_context_menu_apply_sculpt, NULL); } @@ -681,11 +685,11 @@ void tab_layers_draw_layer_context_menu_draw() { } ui->enabled = true; - if (l->fill_layer == NULL && ui_menu_button(tr("Clear"), "", ICON_ERASE)) { + if (l->fill_material == NULL && ui_menu_button(tr("Clear"), "", ICON_ERASE)) { context_set_layer(l); sys_notify_on_next_frame(&tab_layers_draw_layer_context_menu_clear, NULL); } - if (slot_layer_is_mask(l) && l->fill_layer == NULL && ui_menu_button(tr("Invert"), "", ICON_INVERT)) { + if (slot_layer_is_mask(l) && l->fill_material == NULL && ui_menu_button(tr("Invert"), "", ICON_INVERT)) { sys_notify_on_next_frame(&tab_layers_draw_layer_context_menu_invert, NULL); } if (slot_layer_is_mask(l) && ui_menu_button(tr("Apply"), "", ICON_CHECK)) { @@ -798,13 +802,13 @@ void tab_layers_draw_layer_context_menu_draw() { ui_menu_keep_open = true; } } - if (l->fill_layer != NULL) { + if (l->fill_material != NULL) { ui_menu_align(); ui_handle_t *scale_handle = ui_nest(ui_handle(__ID__), l->id); scale_handle->f = l->scale; l->scale = ui_slider(scale_handle, tr("UV Scale"), 0.0, 5.0, true, 100.0, true, UI_ALIGN_RIGHT, true); if (scale_handle->changed) { - context_set_material(l->fill_layer); + context_set_material(l->fill_material); context_set_layer(l); sys_notify_on_next_frame(&tab_layers_draw_layer_context_menu_update_fill_layers, NULL); ui_menu_keep_open = true; @@ -815,7 +819,7 @@ void tab_layers_draw_layer_context_menu_draw() { angle_handle->f = l->angle; l->angle = ui_slider(angle_handle, tr("Angle"), 0.0, 360, true, 1, true, UI_ALIGN_RIGHT, true); if (angle_handle->changed) { - context_set_material(l->fill_layer); + context_set_material(l->fill_material); context_set_layer(l); make_material_parse_paint_material(true); sys_notify_on_next_frame(&tab_layers_draw_layer_context_menu_update_fill_layers, NULL); @@ -834,7 +838,7 @@ void tab_layers_draw_layer_context_menu_draw() { 3); l->uv_type = ui_inline_radio(uv_type_handle, uv_type_items, UI_ALIGN_LEFT); if (uv_type_handle->changed) { - context_set_material(l->fill_layer); + context_set_material(l->fill_material); context_set_layer(l); make_material_parse_paint_material(true); sys_notify_on_next_frame(&tab_layers_draw_layer_context_menu_update_fill_layers, NULL); @@ -1029,7 +1033,13 @@ void tab_layers_button_new_menu() { if (ui_menu_button(tr("Decal"), "", ICON_DECAL)) { layers_create_fill_layer(UV_TYPE_PROJECT, mat4_nan(), -1); } - if (ui_menu_button(tr("Path"), "", ICON_RULER)) { + if (ui_menu_button(tr("Path"), "", ICON_PATH)) { + layers_new_path_layer(false); + history_new_layer(); + } + if (ui_menu_button(tr("Curve"), "", ICON_CURVE)) { + layers_new_path_layer(true); + history_new_layer(); } if (ui_menu_button(tr("Black Mask"), "", ICON_MASK)) { if (slot_layer_is_mask(l) || slot_layer_is_filter(l)) { diff --git a/paint/sources/tab_materials.c b/paint/sources/tab_materials.c index 4bd92cf7..980fb7c3 100644 --- a/paint/sources/tab_materials.c +++ b/paint/sources/tab_materials.c @@ -60,8 +60,8 @@ void tab_materials_delete_material(slot_material_t *m) { i32 i = array_index_of(project_materials, m); for (i32 i = 0; i < project_layers->length; ++i) { slot_layer_t *l = project_layers->buffer[i]; - if (l->fill_layer == m) { - l->fill_layer = NULL; + if (l->fill_material == m) { + l->fill_material = NULL; } } history_delete_material(); diff --git a/paint/sources/types.h b/paint/sources/types.h index f67135a4..3e34cd2d 100644 --- a/paint/sources/types.h +++ b/paint/sources/types.h @@ -24,7 +24,7 @@ typedef struct slot_layer { struct gpu_texture *texpaint_pack; struct gpu_texture *texpaint_preview; // Layer preview f32 mask_opacity; // Opacity mask - struct slot_material *fill_layer; + struct slot_material *fill_material; bool show_panel; blend_type_t blending; i32 object_mask; @@ -45,6 +45,13 @@ typedef struct slot_layer { bool paint_subs; mat4_t decal_mat; // Decal layer struct gpu_texture *texpaint_sculpt; + f32_array_t *path_points; // uv-space [x, y, ..] + f32_array_t *path_points_world; // world-space [x, y, z, ..] + f32_array_t *path_points_camera; // [loc.xyzw, rot.xyzw, aspect, ..] at time of point placement + i32_array_t *path_points_parent; + i32 path_tool; + bool path_curved; + struct slot_material *path_material; } slot_layer_t; typedef struct slot_font { @@ -572,7 +579,7 @@ typedef struct layer_data { i32 uv_type; struct f32_array *decal_mat; f32 opacity_mask; - i32 fill_layer; + i32 fill_material; i32 object_mask; i32 blending; i32 parent; @@ -591,6 +598,13 @@ typedef struct layer_data { bool paint_emis; bool paint_subs; i32 uv_map; + f32_array_t *path_points; + f32_array_t *path_points_world; + f32_array_t *path_points_camera; + i32_array_t *path_points_parent; + i32 path_tool; + bool path_curved; + i32 path_material; } layer_data_t; typedef struct shader_out { diff --git a/paint/sources/ui_nodes.c b/paint/sources/ui_nodes.c index 6ccb3546..692b90d9 100644 --- a/paint/sources/ui_nodes.c +++ b/paint/sources/ui_nodes.c @@ -787,7 +787,16 @@ void ui_nodes_recompile() { if (ui_nodes_is_tab_selected()) { g_context->material = ui_nodes_tabs->buffer[ui_nodes_tab_index()]; } - layers_is_fill_material() ? layers_update_fill_layers() : util_render_make_material_preview(); + if (layers_is_fill_material()) { + layers_update_fill_layers(); + } + else if (layers_is_path_material()) { + layers_update_path_layers(); + } + else { + util_render_make_material_preview(); + } + g_context->material = _material; if (ui_view2d_show && ui_view2d_type == VIEW_2D_TYPE_NODE) { @@ -805,9 +814,15 @@ void ui_nodes_recompile() { else if (ui_nodes_recompile_mat_final) { make_material_parse_paint_material(true); - if (ui_nodes_canvas_type == CANVAS_TYPE_MATERIAL && layers_is_fill_material()) { - layers_update_fill_layers(); - util_render_make_material_preview(); + if (ui_nodes_canvas_type == CANVAS_TYPE_MATERIAL) { + if (layers_is_fill_material()) { + layers_update_fill_layers(); + util_render_make_material_preview(); + } + if (layers_is_path_material()) { + layers_update_path_layers(); + util_render_make_material_preview(); + } } bool decal = context_is_decal(); diff --git a/paint/sources/uniforms_ext.c b/paint/sources/uniforms.c similarity index 99% rename from paint/sources/uniforms_ext.c rename to paint/sources/uniforms.c index 87022937..83b63c38 100644 --- a/paint/sources/uniforms_ext.c +++ b/paint/sources/uniforms.c @@ -79,7 +79,7 @@ f32 uniforms_ext_f32_link(object_t *object, material_data_t *mat, char *link) { // i32 atlas_stride = atlas_w / (float)item_w; // return atlas_stride; // } - bool fill = g_context->layer->fill_layer != NULL; + bool fill = g_context->layer->fill_material != NULL; f32 val = (fill ? g_context->layer->scale : g_context->brush_scale) * g_context->brush_nodes_scale; return val; } @@ -153,7 +153,7 @@ vec2_t uniforms_ext_vec2_link(object_t *object, material_data_t *mat, char *link } else if (string_equals(link, "_brush_angle")) { f32 brush_angle = g_context->brush_angle + g_context->brush_nodes_angle; - f32 angle = g_context->layer->fill_layer != NULL ? g_context->layer->angle : brush_angle; + f32 angle = g_context->layer->fill_material != NULL ? g_context->layer->angle : brush_angle; angle *= (math_pi() / 180.0); if (g_config->pressure_angle && pen_down("tip")) { angle *= pen_pressure * g_config->pressure_sensitivity; diff --git a/paint/sources/util_encode.c b/paint/sources/util_encode.c index 7dee6a48..15863857 100644 --- a/paint/sources/util_encode.c +++ b/paint/sources/util_encode.c @@ -275,7 +275,7 @@ buffer_t *util_encode_project(project_t *raw) { if (raw->layer_datas != NULL) { armpack_encode_array(raw->layer_datas->length); for (i32 i = 0; i < raw->layer_datas->length; ++i) { - armpack_encode_map(28); + armpack_encode_map(35); armpack_encode_string("name"); armpack_encode_string(raw->layer_datas->buffer[i]->name); armpack_encode_string("res"); @@ -294,8 +294,8 @@ buffer_t *util_encode_project(project_t *raw) { armpack_encode_array_f32(raw->layer_datas->buffer[i]->decal_mat); armpack_encode_string("opacity_mask"); armpack_encode_f32(raw->layer_datas->buffer[i]->opacity_mask); - armpack_encode_string("fill_layer"); - armpack_encode_i32(raw->layer_datas->buffer[i]->fill_layer); + armpack_encode_string("fill_material"); + armpack_encode_i32(raw->layer_datas->buffer[i]->fill_material); armpack_encode_string("object_mask"); armpack_encode_i32(raw->layer_datas->buffer[i]->object_mask); armpack_encode_string("blending"); @@ -332,6 +332,20 @@ buffer_t *util_encode_project(project_t *raw) { armpack_encode_bool(raw->layer_datas->buffer[i]->paint_subs); armpack_encode_string("uv_map"); armpack_encode_i32(raw->layer_datas->buffer[i]->uv_map); + armpack_encode_string("path_points"); + armpack_encode_array_f32(raw->layer_datas->buffer[i]->path_points); + armpack_encode_string("path_points_world"); + armpack_encode_array_f32(raw->layer_datas->buffer[i]->path_points_world); + armpack_encode_string("path_points_camera"); + armpack_encode_array_f32(raw->layer_datas->buffer[i]->path_points_camera); + armpack_encode_string("path_points_parent"); + armpack_encode_array_i32(raw->layer_datas->buffer[i]->path_points_parent); + armpack_encode_string("path_tool"); + armpack_encode_i32(raw->layer_datas->buffer[i]->path_tool); + armpack_encode_string("path_curved"); + armpack_encode_bool(raw->layer_datas->buffer[i]->path_curved); + armpack_encode_string("path_material"); + armpack_encode_i32(raw->layer_datas->buffer[i]->path_material); } } else { diff --git a/paint/sources/util_layer.c b/paint/sources/util_layer.c new file mode 100644 index 00000000..f5dbb2a7 --- /dev/null +++ b/paint/sources/util_layer.c @@ -0,0 +1,415 @@ + +#include "global.h" + +static slot_layer_t *path_layer_current = NULL; +static object_t **path_point_spheres = NULL; +static i32 path_point_sphere_count = 0; +static i32 path_point_dragging = -1; +static i32 path_layer_last_active = -1; + +bool util_layer_is_path_point_dragging() { + return path_point_dragging >= 0; +} + +static void path_layer_destroy_spheres() { + for (i32 i = 0; i < path_point_sphere_count; i++) { + mesh_object_remove((mesh_object_t *)path_point_spheres[i]->ext); + } + gc_free(path_point_spheres); + path_point_spheres = NULL; + path_point_sphere_count = 0; +} + +static void path_layer_push_camera(f32_array_t *ar) { + vec4_t loc = scene_camera->base->transform->loc; + quat_t rot = scene_camera->base->transform->rot; + f32_array_push(ar, loc.x); + f32_array_push(ar, loc.y); + f32_array_push(ar, loc.z); + f32_array_push(ar, loc.w); + f32_array_push(ar, rot.x); + f32_array_push(ar, rot.y); + f32_array_push(ar, rot.z); + f32_array_push(ar, rot.w); + f32_array_push(ar, sys_w() / (f32)sys_h()); +} + +static void path_set_camera(f32_array_t *points_camera, i32 num_camera, i32 ci) { + if (ci < num_camera) { + scene_camera->base->transform->loc = (vec4_t){points_camera->buffer[ci * 9 + 0], points_camera->buffer[ci * 9 + 1], points_camera->buffer[ci * 9 + 2], + points_camera->buffer[ci * 9 + 3]}; + scene_camera->base->transform->rot = (quat_t){points_camera->buffer[ci * 9 + 4], points_camera->buffer[ci * 9 + 5], points_camera->buffer[ci * 9 + 6], + points_camera->buffer[ci * 9 + 7]}; + camera_object_build_proj(scene_camera, points_camera->buffer[ci * 9 + 8]); + camera_object_build_mat(scene_camera); + render_path_base_draw_gbuffer(); + } +} + +static void path_paint(f32 px, f32 py, bool is_decal, f32 *prev_px, f32 *prev_py) { + if (is_decal) { + g_context->decal_x = px; + g_context->decal_y = py; + } + g_context->paint_vec.x = px; + g_context->paint_vec.y = py; + g_context->last_paint_vec_x = *prev_px; + g_context->last_paint_vec_y = *prev_py; + g_context->pdirty = 1; + render_path_paint_commands_paint(false); + *prev_px = px; + *prev_py = py; +} + +static void layers_repaint_path_layer(slot_layer_t *l) { + if (l->path_material == NULL) { + return; + } + f32_array_t *points_world = l->path_points_world; + if (points_world == NULL || points_world->length == 0) { + return; + } + + slot_layer_t *_layer = g_context->layer; + slot_material_t *_material = g_context->material; + tool_type_t _tool = g_context->tool; + f32 _last_x = g_context->last_paint_vec_x; + f32 _last_y = g_context->last_paint_vec_y; + vec4_t _paint_vec = g_context->paint_vec; + g_context->layer = l; + g_context->material = l->path_material; + g_context->tool = l->path_tool; + + bool _depth_reject = g_config->brush_depth_reject; + bool _angle_reject = g_config->brush_angle_reject; + g_config->brush_depth_reject = false; + g_config->brush_angle_reject = false; + + make_material_parse_paint_material(false); + layers_set_object_mask(); + + slot_layer_clear(l, 0x00000000, NULL, 1.0, layers_default_rough, 0.0); + + vec4_t _camera_loc = scene_camera->base->transform->loc; + quat_t _camera_rot = scene_camera->base->transform->rot; + + f32_array_t *points = l->path_points; + f32_array_t *points_camera = l->path_points_camera; + i32_array_t *points_parent = l->path_points_parent; + i32 num_world = points_world->length / 3; + i32 num_camera = points_camera->length / 9; + i32 num_parent = points_parent->length; + bool is_decal = context_is_decal(); + + if (l->path_curved && num_world >= 1) { + f32 prev_px = 0.5f; + f32 prev_py = 0.5f; + + // Paint the first anchor + f32 pt0x = points->length >= 2 ? points->buffer[0] : 0.5f; + f32 pt0y = points->length >= 2 ? points->buffer[1] : 0.5f; + path_set_camera(points_camera, num_camera, 0); + prev_px = pt0x; + prev_py = pt0y; + path_paint(pt0x, pt0y, is_decal, &prev_px, &prev_py); + + // Paint curve - anchor[k], control[k+1], anchor[k+2] + for (i32 k = 0; k + 2 < num_world; k += 2) { + f32 ax0 = k * 2 + 1 < (i32)points->length ? points->buffer[k * 2] : 0.5f; + f32 ay0 = k * 2 + 1 < (i32)points->length ? points->buffer[k * 2 + 1] : 0.5f; + f32 cx = (k + 1) * 2 + 1 < (i32)points->length ? points->buffer[(k + 1) * 2] : 0.5f; + f32 cy = (k + 1) * 2 + 1 < (i32)points->length ? points->buffer[(k + 1) * 2 + 1] : 0.5f; + f32 ax1 = (k + 2) * 2 + 1 < (i32)points->length ? points->buffer[(k + 2) * 2] : 0.5f; + f32 ay1 = (k + 2) * 2 + 1 < (i32)points->length ? points->buffer[(k + 2) * 2 + 1] : 0.5f; + + // 16 intermediate points + final anchor + bool have_k2 = k + 2 < num_camera; + for (i32 s = 1; s <= 17; s++) { + f32 t = s / 17.0f; + f32 t1 = 1.0f - t; + f32 bx = t1 * t1 * t1 * ax0 + 3.0f * t * t1 * cx + t * t * t * ax1; + f32 by = t1 * t1 * t1 * ay0 + 3.0f * t * t1 * cy + t * t * t * ay1; + // f32 bx = t1 * t1 * ax0 + 2.0f * t * t1 * cx + t * t * ax1; + // f32 by = t1 * t1 * ay0 + 2.0f * t * t1 * cy + t * t * ay1; + if (s == 17 && have_k2) { + path_set_camera(points_camera, num_camera, k + 2); + } + path_paint(bx, by, is_decal, &prev_px, &prev_py); + } + } + + render_path_paint_dilate(true, true); + } + else { + for (i32 i = 0; i < num_world; i++) { + path_set_camera(points_camera, num_camera, i); + + f32 cur_px = points->buffer[i * 2]; + f32 cur_py = points->buffer[i * 2 + 1]; + + // Project parent world point for stroke connection + f32 prev_px = cur_px; + f32 prev_py = cur_py; + if (!is_decal) { + i32 parent = points_parent->buffer[i]; + if (parent >= 0) { + f32 pwx = points_world->buffer[parent * 3]; + f32 pwy = points_world->buffer[parent * 3 + 1]; + f32 pwz = points_world->buffer[parent * 3 + 2]; + vec4_t clip = vec4_apply_mat4((vec4_t){pwx, pwy, pwz, 1.0f}, scene_camera->vp); + if (clip.w > 0.0f) { + prev_px = (clip.x / clip.w + 1.0f) * 0.5f; + prev_py = (-clip.y / clip.w + 1.0f) * 0.5f; + } + } + } + + path_paint(cur_px, cur_py, is_decal, &prev_px, &prev_py); + } + + render_path_paint_dilate(true, true); + } + + g_config->brush_depth_reject = _depth_reject; + g_config->brush_angle_reject = _angle_reject; + g_context->tool = _tool; + + scene_camera->base->transform->loc = _camera_loc; + scene_camera->base->transform->rot = _camera_rot; + camera_object_build_proj(scene_camera, -1.0); + camera_object_build_mat(scene_camera); + render_path_base_draw_gbuffer(); + + g_context->pdirty = 0; + g_context->rdirty = 2; + g_context->layer = _layer; + g_context->material = _material; + make_material_parse_paint_material(false); + g_context->last_paint_vec_x = _last_x; + g_context->last_paint_vec_y = _last_y; + g_context->paint_vec = _paint_vec; +} + +void util_layer_clear_path_points(slot_layer_t *l) { + if (l->path_points == NULL) { + return; + } + l->path_points->length = 0; + l->path_points_world->length = 0; + l->path_points_camera->length = 0; + l->path_points_parent->length = 0; + l->path_tool = -1; + path_layer_last_active = -1; +} + +void util_layer_add_path_point(slot_layer_t *l, f32 screen_x, f32 screen_y) { + if (l->path_tool == -1) { + l->path_tool = g_context->tool; + } + if ((i32)g_context->tool != l->path_tool) { + return; + } + + for (i32 j = 0; j < path_point_sphere_count; j++) { + path_point_spheres[j]->visible = false; + } + + // Re-render gbuffer without spheres so gbufferD is clean + render_path_base_draw_gbuffer(); + + f32_array_t *points = l->path_points; + f32_array_t *points_world = l->path_points_world; + f32_array_t *points_camera = l->path_points_camera; + i32_array_t *points_parent = l->path_points_parent; + + f32_array_push(points, screen_x); + f32_array_push(points, screen_y); + f32 saved_pvx = g_context->paint_vec.x; + f32 saved_pvy = g_context->paint_vec.y; + g_context->paint_vec.x = screen_x; + g_context->paint_vec.y = screen_y; + util_render_pick_pos_nor_tex(); + g_context->paint_vec.x = saved_pvx; + g_context->paint_vec.y = saved_pvy; + f32_array_push(points_world, g_context->posx_picked); + f32_array_push(points_world, g_context->posy_picked); + f32_array_push(points_world, g_context->posz_picked); + path_layer_push_camera(points_camera); + i32_array_push(points_parent, path_layer_last_active); + + // New point becomes the parent for the next stroke + i32 new_index = points->length / 2 - 1; + path_layer_last_active = new_index; + + layers_repaint_path_layer(l); + + for (i32 j = 0; j < path_point_sphere_count; j++) { + path_point_spheres[j]->visible = true; + } +} + +void util_layer_repaint_path(slot_layer_t *l) { + for (i32 j = 0; j < path_point_sphere_count; j++) { + path_point_spheres[j]->visible = false; + } + layers_repaint_path_layer(l); + for (i32 j = 0; j < path_point_sphere_count; j++) { + path_point_spheres[j]->visible = true; + } +} + +void util_layer_update_path() { + if (g_config->workspace == WORKSPACE_PLAYER || g_context->paint2d) { + return; + } + + slot_layer_t *l = g_context->layer; + bool is_path = slot_layer_is_path(l); + + // Clear spheres when switching away from path layer + if (path_layer_current != l) { + path_layer_destroy_spheres(); + path_point_dragging = -1; + path_layer_current = is_path ? l : NULL; + path_layer_last_active = -1; + if (is_path) { + // Resume from the last point when switching to a path layer + i32 n = l->path_points->length / 2; + path_layer_last_active = n > 0 ? n - 1 : -1; + } + } + + if (!is_path) { + return; + } + + f32_array_t *points = l->path_points; + f32_array_t *points_world = l->path_points_world; + i32 num_points = points->length / 2; + i32 num_world = points_world->length / 3; + i32 vis_points = num_points; + + // Sync sphere count with number of visible path points + if (vis_points < path_point_sphere_count) { + while (path_point_sphere_count > vis_points) { + path_point_sphere_count--; + mesh_object_remove((mesh_object_t *)path_point_spheres[path_point_sphere_count]->ext); + path_point_spheres[path_point_sphere_count] = NULL; + } + } + else if (vis_points > path_point_sphere_count) { + path_point_spheres = (object_t **)gc_realloc(path_point_spheres, vis_points * sizeof(object_t *)); + gc_root(path_point_spheres); + for (i32 i = path_point_sphere_count; i < vis_points; i++) { + object_t *o = scene_spawn_object(".Sphere", NULL, true); + o->visible = true; + path_point_spheres[i] = o; + } + path_point_sphere_count = vis_points; + } + + // Update sphere transforms + for (i32 i = 0; i < vis_points; i++) { + if (path_point_spheres[i] == NULL) { + continue; + } + f32 wx = points_world->buffer[i * 3]; + f32 wy = points_world->buffer[i * 3 + 1]; + f32 wz = points_world->buffer[i * 3 + 2]; + f32 dist = vec4_dist(scene_camera->base->transform->loc, (vec4_t){wx, wy, wz, 1.0}); + f32 fov = scene_camera->data->fov; + f32 scale = dist / 8.0f * fov * 0.1f * (i == path_layer_last_active ? 1.5f : 1.0f); + + path_point_spheres[i]->transform->loc = (vec4_t){wx, wy, wz, 1.0}; + path_point_spheres[i]->transform->scale = (vec4_t){scale, scale, scale, 1.0}; + transform_build_matrix(path_point_spheres[i]->transform); + } + + // Release drag - the dragged point becomes the parent for the next new point + if (mouse_released("left") && path_point_dragging >= 0) { + for (i32 j = 0; j < path_point_sphere_count; j++) { + path_point_spheres[j]->visible = true; + } + path_layer_last_active = path_point_dragging; + path_point_dragging = -1; + g_context->layer_preview_dirty = true; + g_context->layers_preview_dirty = true; + } + + // Drag point + if (mouse_down("left") && !mouse_started("left") && path_point_dragging >= 0) { + f32 new_x = g_context->paint_vec.x; + f32 new_y = g_context->paint_vec.y; + f32 old_x = points->buffer[path_point_dragging * 2]; + f32 old_y = points->buffer[path_point_dragging * 2 + 1]; + + if (math_abs(new_x - old_x) > 0.0005f || math_abs(new_y - old_y) > 0.0005f) { + points->buffer[path_point_dragging * 2] = new_x; + points->buffer[path_point_dragging * 2 + 1] = new_y; + + // Pick world position at new screen location + f32 saved_pvx = g_context->paint_vec.x; + f32 saved_pvy = g_context->paint_vec.y; + g_context->paint_vec.x = new_x; + g_context->paint_vec.y = new_y; + util_render_pick_pos_nor_tex(); + g_context->paint_vec.x = saved_pvx; + g_context->paint_vec.y = saved_pvy; + + if (math_abs(g_context->posx_picked) < 50.0f) { + points_world->buffer[path_point_dragging * 3] = g_context->posx_picked; + points_world->buffer[path_point_dragging * 3 + 1] = g_context->posy_picked; + points_world->buffer[path_point_dragging * 3 + 2] = g_context->posz_picked; + f32_array_t *points_camera = l->path_points_camera; + if (points_camera->length >= (path_point_dragging + 1) * 9) { + vec4_t loc = scene_camera->base->transform->loc; + quat_t rot = scene_camera->base->transform->rot; + points_camera->buffer[path_point_dragging * 9 + 0] = loc.x; + points_camera->buffer[path_point_dragging * 9 + 1] = loc.y; + points_camera->buffer[path_point_dragging * 9 + 2] = loc.z; + points_camera->buffer[path_point_dragging * 9 + 3] = loc.w; + points_camera->buffer[path_point_dragging * 9 + 4] = rot.x; + points_camera->buffer[path_point_dragging * 9 + 5] = rot.y; + points_camera->buffer[path_point_dragging * 9 + 6] = rot.z; + points_camera->buffer[path_point_dragging * 9 + 7] = rot.w; + points_camera->buffer[path_point_dragging * 9 + 8] = sys_w() / (f32)sys_h(); + } + } + + util_layer_repaint_path(l); + for (i32 j = 0; j < path_point_sphere_count; j++) { + path_point_spheres[j]->visible = false; + } + } + return; + } + + // Check if mouse ray hits any point sphere + if (mouse_started("left") && !ui->is_hovered && !base_is_dragging && vis_points > 0) { + f32 min_dist = 1e10f; + i32 closest = -1; + + for (i32 i = 0; i < vis_points; i++) { + f32 wx = points_world->buffer[i * 3]; + f32 wy = points_world->buffer[i * 3 + 1]; + f32 wz = points_world->buffer[i * 3 + 2]; + f32 dist = vec4_dist(scene_camera->base->transform->loc, (vec4_t){wx, wy, wz, 1.0}); + f32 fov = scene_camera->data->fov; + f32 hit_radius = dist / 8.0f * fov * 0.25f; + ray_t *ray = raycast_get_ray(mouse_view_x(), mouse_view_y(), scene_camera); + if (ray_intersects_sphere(ray, (vec4_t){wx, wy, wz, 1.0}, hit_radius) && dist < min_dist) { + min_dist = dist; + closest = i; + } + } + + if (closest >= 0) { + path_point_dragging = closest; + for (i32 j = 0; j < path_point_sphere_count; j++) { + if (path_point_spheres[j] != NULL) { + path_point_spheres[j]->visible = false; + } + } + } + } +} diff --git a/paint/sources/util_render.c b/paint/sources/util_render.c index 250c93f9..ebebd19c 100644 --- a/paint/sources/util_render.c +++ b/paint/sources/util_render.c @@ -264,8 +264,8 @@ void util_render_make_brush_preview() { g_context->layer = g_context->layer->parent; } - slot_material_t *_fill_layer = g_context->layer->fill_layer; - g_context->layer->fill_layer = NULL; + slot_material_t *_fill_material = g_context->layer->fill_material; + g_context->layer->fill_material = NULL; render_path_paint_use_live_layer(true); make_material_parse_paint_material(false); @@ -375,10 +375,10 @@ void util_render_make_brush_preview() { g_context->prev_paint_vec_y = -1; g_context->pdirty = _pdirty; render_path_paint_use_live_layer(false); - g_context->layer->fill_layer = _fill_layer; - g_context->layer = _layer; - g_context->material = _material; - g_context->tool = _tool; + g_context->layer->fill_material = _fill_material; + g_context->layer = _layer; + g_context->material = _material; + g_context->tool = _tool; sys_notify_on_next_frame(&util_render_make_brush_preview_parse_paint_material, NULL); // Restore paint mesh