From: hgn Date: Thu, 20 Mar 2025 03:14:31 +0000 (+0000) Subject: board maker continues X-Git-Url: https://harrygodden.com/git/?a=commitdiff_plain;h=29fe25f7c8befff52af7678ed9eb78a05cc2625b;p=carveJwlIkooP6JGAAIwe30JlM.git board maker continues --- diff --git a/content_skaterift/models/workshop/regular.mdl b/content_skaterift/models/workshop/regular.mdl index dc4952f..c4d1e99 100644 Binary files a/content_skaterift/models/workshop/regular.mdl and b/content_skaterift/models/workshop/regular.mdl differ diff --git a/shaders/workshop_compositor.fs b/shaders/workshop_compositor.fs index 7011c33..fd5ddf8 100644 --- a/shaders/workshop_compositor.fs +++ b/shaders/workshop_compositor.fs @@ -1,11 +1,28 @@ out vec4 FragColor; uniform sampler2D uTexMain; +uniform sampler2D uTexDecal; uniform vec4 uColour; +uniform int uDecal; -in vec2 aUv; +in vec4 aUv; void main() { - FragColor = texture( uTexMain, aUv ) * uColour; + vec4 c = texture( uTexMain, aUv.xy ); + + if( uDecal == 1 ) + { + vec4 d = texture( uTexDecal, aUv.zw ); + vec2 mask = smoothstep( 0.5, 0.495, abs(aUv.zw - vec2(0.5,0.5)) ); + c.rgb = mix( c.rgb*uColour.rgb, c.rgb*d.rgb, d.a * min(mask.x,mask.y) ); + } + + if( uDecal == 2 ) + { + vec4 d = texture( uTexDecal, aUv.zw ); + c.rgb = mix( c.rgb*uColour.rgb, c.rgb*d.rgb, d.a ); + } + + FragColor = c; } diff --git a/shaders/workshop_compositor.vs b/shaders/workshop_compositor.vs index 2593725..7eafaa7 100644 --- a/shaders/workshop_compositor.vs +++ b/shaders/workshop_compositor.vs @@ -1,8 +1,19 @@ layout (location=0) in vec2 a_co; -out vec2 aUv; + +out vec4 aUv; + +uniform vec2 uOffset; +uniform vec2 uScale; +uniform float uRotation; void main() { - gl_Position = vec4(a_co*2.0-1.0,0.0,1.0); - aUv = a_co; + vec2 co = a_co*2.0-1.0; + gl_Position = vec4(co,0.0,1.0); + + mat3 R = mat3(cos(uRotation)*uScale.x, -sin(uRotation)*uScale.x, 0, + sin(uRotation)*uScale.y, cos(uRotation)*uScale.y, 0, + uOffset.x, uOffset.y, 1 ); + + aUv = vec4( a_co, (inverse(R)*vec3(co,1.0)).xy*0.5+0.5 ); } diff --git a/src/board_maker.c b/src/board_maker.c index e8b8a1f..45a4fe4 100644 --- a/src/board_maker.c +++ b/src/board_maker.c @@ -1,5 +1,6 @@ #include "board_maker.h" #include "shaders/workshop_compositor.h" +#include "vg/vg_ui/filebrowser.h" struct _board_maker _board_maker = { @@ -24,6 +25,52 @@ struct _board_maker _board_maker = } }; +/* image loader thread + * ---------------------------------------- */ + +struct async_board_maker_image_load_thread_data +{ + void *data; + i32 w, h; +}; + +static void _async_board_maker_image_finish( void *payload, u32 size ) +{ + struct async_board_maker_image_load_thread_data *inf = payload; + + struct board_maker_decal *decal = &_board_maker.decals[ _board_maker.ui_target_part ]; + + if( decal->loaded ) + glDeleteTextures( 1, &decal->texture ); + + decal->loaded = 1; + + glGenTextures( 1, &decal->texture ); + glBindTexture( GL_TEXTURE_2D, decal->texture ); + glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, inf->w, inf->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, inf->data ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); + glGenerateMipmap( GL_TEXTURE_2D ); + + stbi_image_free( inf->data ); + + _board_maker.state = k_board_maker_state_none; + _board_maker.compositor_state = k_board_maker_compositor_dirty; +} + +static void _board_maker_image_thread( void *_ ) +{ + vg_async_item *call = vg_async_alloc( sizeof(struct async_board_maker_image_load_thread_data) ); + struct async_board_maker_image_load_thread_data *inf = call->payload; + + i32 nc; + stbi_set_flip_vertically_on_load(1); + inf->data = stbi_load( _board_maker.browser->current_path, &inf->w, &inf->h, &nc, 4 ); + vg_async_dispatch( call, _async_board_maker_image_finish ); +} + /* model loader thread * ----------------------------------------- */ @@ -103,8 +150,8 @@ void _board_maker_pre_update(void) shader_workshop_compositor_use(); shader_workshop_compositor_uTexMain( 0 ); + shader_workshop_compositor_uTexDecal( 1 ); - glActiveTexture( GL_TEXTURE0 ); if( _board_maker.base_shader ) { @@ -115,8 +162,29 @@ void _board_maker_pre_update(void) if( tex_id ) { mdl_texture *tex = &_board_maker.template_mdl.textures[ tex_id-1 ]; - shader_workshop_compositor_uColour( _board_maker.colours[i] ); + + glActiveTexture( GL_TEXTURE0 ); glBindTexture( GL_TEXTURE_2D, tex->glname ); + shader_workshop_compositor_uColour( _board_maker.colours[i] ); + + struct board_maker_decal *decal = &_board_maker.decals[ i ]; + if( decal->loaded ) + { + glActiveTexture( GL_TEXTURE1 ); + glBindTexture( GL_TEXTURE_2D, decal->texture ); + shader_workshop_compositor_uDecal( 1 + decal->wrap ); + shader_workshop_compositor_uOffset( decal->offset ); + shader_workshop_compositor_uScale( decal->scale ); + shader_workshop_compositor_uRotation( decal->rotation * (VG_PIf/180.0f) ); + } + else + { + shader_workshop_compositor_uDecal( 0 ); + shader_workshop_compositor_uOffset( (v2f){0,0} ); + shader_workshop_compositor_uScale( (v2f){1,1} ); + shader_workshop_compositor_uRotation( 0.0f ); + } + vg_render_fullscreen_quad(); } } @@ -145,7 +213,14 @@ void _board_maker_pre_update(void) { _board_maker.state = k_board_maker_state_initializing; vg_loader_start( _board_maker_init_thread, NULL ); - return; + } + } + else if( _board_maker.state == k_board_maker_state_load_image ) + { + if( vg_loader_availible() ) + { + vg_loader_start( _board_maker_image_thread, NULL ); + _board_maker.state = k_board_maker_state_loading_image; } } } @@ -209,9 +284,11 @@ void _board_maker_render( world_instance *world, vg_camera *cam ) } } -static void _board_maker_colour_button( ui_context *ctx, ui_rect rect, f32 *colour_pointer ) +static void _board_maker_colour_button( ui_context *ctx, ui_rect rect, f32 *colour_pointer, bool clickable ) { - enum ui_button_state state = ui_button_base( ctx, rect ); + enum ui_button_state state = k_ui_button_none; + if( clickable ) + state = ui_button_base( ctx, rect ); u32 colour = v4f_u32_colour( colour_pointer ); ui_fill( ctx, rect, colour ); @@ -223,7 +300,7 @@ static void _board_maker_colour_button( ui_context *ctx, ui_rect rect, f32 *colo { _board_maker.ui_target_colour = colour_pointer; _board_maker.ui_state = k_board_maker_ui_state_pick_colour; - _board_maker.picker_panel[1] = rect[1]; + _board_maker.pop_panel[1] = rect[1]; } if( state == k_ui_button_holding_outside ) @@ -244,6 +321,76 @@ static void _board_maker_colour_button( ui_context *ctx, ui_rect rect, f32 *colo } } +/* Only allowed in decal editor panel. */ +static void _board_maker_image_button( ui_context *ctx, ui_rect rect, enum workshop_shader_part part, bool clickable ) +{ + enum ui_button_state state = k_ui_button_none; + if( clickable ) + state = ui_button_text( ctx, rect, "Open\xb2", 1 ); + else + { + // TODO: standardize me + ui_fill( ctx, rect, ui_colour( ctx, k_ui_bg ) ); + ui_text( ctx, rect, "Open image\xb2", 1, k_ui_align_middle_center, ui_colour( ctx, k_ui_bg+3 ) ); + } + + if( state == k_ui_button_click ) + { + vg_filebrowser_populate( _board_maker.browser ); + _board_maker.ui_target_part = part; + _board_maker.ui_image_picker_return_state = _board_maker.ui_state; + _board_maker.ui_state = k_board_maker_ui_state_pick_image; + } +} + +static void _board_maker_decal_button( ui_context *ctx, ui_rect rect, enum workshop_shader_part part, bool clickable ) +{ + ctx->font = &vgf_default_large; + enum ui_button_state state = k_ui_button_none; + if( clickable ) + state = ui_button_text( ctx, rect, "\xb9", 1 ); + else + { + // TODO: standardize me + ui_fill( ctx, rect, ui_colour( ctx, k_ui_bg ) ); + ui_text( ctx, rect, "\xb9", 1, k_ui_align_middle_center, ui_colour( ctx, k_ui_bg+3 ) ); + } + ctx->font = &vgf_default_small; + + if( state == k_ui_button_click ) + { + _board_maker.pop_panel[1] = rect[1]; + _board_maker.ui_target_part = part; + _board_maker.ui_state = k_board_maker_ui_state_edit_decal; + } + + struct board_maker_decal *decal = &_board_maker.decals[ part ]; + if( decal->loaded ) + ui_outline( ctx, rect, -1, ui_colour( ctx, k_ui_orange ), 0 ); +} + +static void clear_decal( struct board_maker_decal *decal ) +{ + if( decal->texture ) + { + glDeleteTextures( 1, &decal->texture ); + decal->texture = 0; + } + + decal->wrap = 0; + decal->wrap_prev = 0; + decal->loaded = 0; + v2_zero( decal->offset ); + v2_fill( decal->scale, 1.0f ); + v4_fill( decal->colour, 1.0f ); +} + +struct ui_enum_opt _board_maker_wrap_opts[] = +{ + { 0, "Normal" }, + { 1, "Repeat" } +}; + void _board_maker_ui( ui_context *ctx ) { if( _world.event != k_world_event_board_maker ) @@ -251,42 +398,56 @@ void _board_maker_ui( ui_context *ctx ) ui_capture_mouse( ctx, 1 ); - ui_rect panel = { 8, 8, 200, 600 }; + ui_rect root = { 8, 8, 200, 600 }, panel; + ui_panel( ctx, root, panel ); if( !ui_click_up( ctx, UI_MOUSE_LEFT ) ) _board_maker.ui_dropper_colour = NULL; + bool clickable = 1; + if( _board_maker.state != k_board_maker_state_none ) + clickable = 0; + + bool main_clickable = clickable; + +#if 0 + if( _board_maker.ui_state != k_board_maker_ui_state_default ) + main_clickable = 0; +#endif + ui_info( ctx, panel, "Wheels" ); ui_rect w1, w2, w3, w4, wheel_row; ui_standard_widget( ctx, panel, wheel_row, 1 ); ui_split_ratio( wheel_row, k_ui_axis_v, 0.5f, 2, w1, w3 ); ui_split_ratio( w1, k_ui_axis_v, 0.5f, 2, w1, w2 ); ui_split_ratio( w3, k_ui_axis_v, 0.5f, 2, w3, w4 ); - _board_maker_colour_button( ctx, w1, _board_maker.colours[ k_workshop_shader_part_wheel1 ] ); - _board_maker_colour_button( ctx, w2, _board_maker.colours[ k_workshop_shader_part_wheel2 ] ); - _board_maker_colour_button( ctx, w3, _board_maker.colours[ k_workshop_shader_part_wheel3 ] ); - _board_maker_colour_button( ctx, w4, _board_maker.colours[ k_workshop_shader_part_wheel4 ] ); + _board_maker_colour_button( ctx, w1, _board_maker.colours[ k_workshop_shader_part_wheel1 ], main_clickable ); + _board_maker_colour_button( ctx, w2, _board_maker.colours[ k_workshop_shader_part_wheel2 ], main_clickable ); + _board_maker_colour_button( ctx, w3, _board_maker.colours[ k_workshop_shader_part_wheel3 ], main_clickable ); + _board_maker_colour_button( ctx, w4, _board_maker.colours[ k_workshop_shader_part_wheel4 ], main_clickable ); ui_info( ctx, panel, "Trucks" ); ui_rect truck_row, t1, t2; ui_standard_widget( ctx, panel, truck_row, 1 ); ui_split_ratio( truck_row, k_ui_axis_v, 0.5f, 2, t1, t2 ); - _board_maker_colour_button( ctx, t1, _board_maker.colours[ k_workshop_shader_part_truck1 ] ); - _board_maker_colour_button( ctx, t2, _board_maker.colours[ k_workshop_shader_part_truck2 ] ); + _board_maker_colour_button( ctx, t1, _board_maker.colours[ k_workshop_shader_part_truck1 ], main_clickable ); + _board_maker_colour_button( ctx, t2, _board_maker.colours[ k_workshop_shader_part_truck2 ], main_clickable ); ui_info( ctx, panel, "Deck" ); ui_rect deck_row, deckc, decki, decke; ui_standard_widget( ctx, panel, deck_row, 1 ); ui_split_ratio( deck_row, k_ui_axis_v, 0.66666f, 2, deckc, decke ); ui_split_ratio( deckc, k_ui_axis_v, 0.5f, 2, deckc, decki ); - _board_maker_colour_button( ctx, deckc, _board_maker.colours[ k_workshop_shader_part_deck ] ); - _board_maker_colour_button( ctx, decke, _board_maker.colours[ k_workshop_shader_part_edge ] ); + _board_maker_colour_button( ctx, deckc, _board_maker.colours[ k_workshop_shader_part_deck ], main_clickable ); + _board_maker_colour_button( ctx, decki, _board_maker.colours[ k_workshop_shader_part_edge ], main_clickable ); + _board_maker_decal_button( ctx, decke, k_workshop_shader_part_deck, main_clickable ); ui_info( ctx, panel, "Griptape" ); ui_rect griptape_row, gtc, gti; ui_standard_widget( ctx, panel, griptape_row, 1 ); ui_split_ratio( griptape_row, k_ui_axis_v, 0.5f, 2, gtc, gti ); - _board_maker_colour_button( ctx, gtc, _board_maker.colours[ k_workshop_shader_part_griptape ] ); + _board_maker_colour_button( ctx, gtc, _board_maker.colours[ k_workshop_shader_part_griptape ], main_clickable ); + _board_maker_decal_button( ctx, gti, k_workshop_shader_part_griptape, main_clickable ); if( _board_maker.ui_dropper_colour ) { @@ -297,18 +458,110 @@ void _board_maker_ui( ui_context *ctx ) if( _board_maker.ui_state == k_board_maker_ui_state_pick_colour ) { - _board_maker.picker_panel[0] = panel[0] + panel[2] + 8; - _board_maker.picker_panel[2] = 300; - _board_maker.picker_panel[3] = 160; + _board_maker.pop_panel[0] = root[0] + root[2] + 8; + _board_maker.pop_panel[2] = 300; + _board_maker.pop_panel[3] = 176; ui_rect picker_panel, ok_box; - rect_copy( _board_maker.picker_panel, picker_panel ); - ui_outline( ctx, picker_panel, 1, ui_colour( ctx, k_ui_fg ), 0 ); + ui_panel( ctx, _board_maker.pop_panel, picker_panel ); if( ui_colourpicker( ctx, picker_panel, NULL, _board_maker.ui_target_colour, k_ui_colour_type_rgb ) ) _board_maker.compositor_state = k_board_maker_compositor_dirty; ui_split( picker_panel, k_ui_axis_h, -24, 0, picker_panel, ok_box ); + ctx->font = &vgf_default_large; + if( ui_button_text( ctx, ok_box, "\xb3", 1 ) == k_ui_button_click ) + { + _board_maker.ui_state = k_board_maker_ui_state_default; + } + ctx->font = &vgf_default_small; + } + + if( _board_maker.ui_state == k_board_maker_ui_state_edit_decal ) + { + struct board_maker_decal *decal = &_board_maker.decals[ _board_maker.ui_target_part ]; + ui_rect decal_panel; + + _board_maker.pop_panel[0] = root[0] + root[2] + 8; + _board_maker.pop_panel[2] = 400; + _board_maker.pop_panel[3] = 127; + ui_panel( ctx, _board_maker.pop_panel, decal_panel ); + + /* top bar */ + ui_rect top_bar, l, r; + ui_standard_widget( ctx, decal_panel, top_bar, 1 ); + ui_split( top_bar, k_ui_axis_v, -130, 8, l, r ); + ui_fill( ctx, top_bar, ui_colour(ctx, k_ui_bg+2) ); + + ui_rect remove_button; + ui_split( l, k_ui_axis_v, -80, 8, l, remove_button ); + ui_text( ctx, l, decal->loaded? "Loaded": "Not Loaded", 1, k_ui_align_middle_center, 0 ); + + bool delete_me = 0; + if( decal->loaded ) + { + if( ui_button_text( ctx, remove_button, "Remove", 1 ) == k_ui_button_click ) + delete_me = 1; + } + else + _board_maker_image_button( ctx, remove_button, _board_maker.ui_target_part, clickable ); + + _board_maker_colour_button( ctx, deckc, _board_maker.colours[ k_workshop_shader_part_deck ], main_clickable ); + + /* options */ + ui_rect opt_row; + ui_standard_widget( ctx, decal_panel, opt_row, 2 ); + + ui_rect pos, scale, rot; + ui_split_ratio( opt_row, k_ui_axis_v, 0.6666f, 8, pos, rot ); + ui_split_ratio( pos, k_ui_axis_v, 0.5f, 8, pos, scale ); + + ui_rect posx, posy; + ui_split_ratio( pos, k_ui_axis_h, 0.5f, 0, posx, posy ); + + ui_rect scalex, scaley; + ui_split_ratio( scale, k_ui_axis_h, 0.5f, 0, scalex, scaley ); + + bool mod = 0; + + mod |= ui_slider_standard( ctx, posx, -1.0f, 1.0f, &decal->offset[0], "x %.2f" ); + mod |= ui_slider_standard( ctx, posy, -1.0f, 1.0f, &decal->offset[1], "y %.2f" ); + mod |= ui_slider_standard( ctx, scalex, -4.0f, 4.0f, &decal->scale[0], "w %.2f" ); + mod |= ui_slider_standard( ctx, scaley, -4.0f, 4.0f, &decal->scale[1], "h %.2f" ); + + if( ui_slider_standard( ctx, rot, -180.0f, 180.0f, &decal->rotation, "%.2f\xb0" ) ) + { + mod = 1; + if( fabsf(decal->rotation-0.0f) < 2.0f ) + decal->rotation = 0.0f; + + if( fabsf(decal->rotation-90.0f) < 2.0f ) + decal->rotation = 90.0f; + + if( fabsf(decal->rotation+90.0f) < 2.0f ) + decal->rotation = -90.0f; + + if( fabsf(decal->rotation-180.0f) < 2.0f ) + decal->rotation = 180.0f; + + if( fabsf(decal->rotation+180.0f) < 2.0f ) + decal->rotation = -180.0f; + } + + ui_enum( ctx, r, NULL, _board_maker_wrap_opts, 2, &decal->wrap ); + + // warning: next frame + if( decal->wrap != decal->wrap_prev ) + { + decal->wrap_prev = decal->wrap; + mod = 1; + } + + if( mod ) + _board_maker.compositor_state = k_board_maker_compositor_dirty; + + ui_rect ok_box; + ui_split( decal_panel, k_ui_axis_h, -28, 8, decal_panel, ok_box ); ctx->font = &vgf_default_large; if( ui_button_text( ctx, ok_box, "\xb3", 1 ) == k_ui_button_click ) @@ -316,7 +569,35 @@ void _board_maker_ui( ui_context *ctx ) _board_maker.ui_state = k_board_maker_ui_state_default; } ctx->font = &vgf_default_small; + + if( delete_me ) + { + clear_decal( decal ); + _board_maker.compositor_state = k_board_maker_compositor_dirty; + } + } + + if( _board_maker.ui_state == k_board_maker_ui_state_pick_image ) + { + ui_rect fb_rect = { root[0]+root[2]+8, root[1], 800, 600 }; + ui_panel( ctx, fb_rect, fb_rect ); + + enum filebrowser_action action = vg_filebrowser_ui( ctx, fb_rect, _board_maker.browser ); + + if( action != k_filebrowser_action_none ) + { + if( action == k_filebrowser_action_accept ) + { + _board_maker.state = k_board_maker_state_load_image; + } + + _board_maker.ui_state = _board_maker.ui_image_picker_return_state; + } } + + ui_rect box = { vg.window_x-(400+8),8, 400,400 }; + ui_image( ctx, box, &_board_maker.compositor_fb.attachments[0].id ); + ui_outline( ctx, box, 1, ui_colour( ctx, k_ui_fg ), 0 ); } void _board_maker_open(void) @@ -332,5 +613,15 @@ void _board_maker_open(void) for( u32 i=0; imode = k_filebrowser_mode_pick_file; + browser->filter = 0x1 << k_media_type_image; } diff --git a/src/board_maker.h b/src/board_maker.h index 4a6a754..0f9ad87 100644 --- a/src/board_maker.h +++ b/src/board_maker.h @@ -35,8 +35,9 @@ struct _board_maker k_board_maker_state_not_ready, k_board_maker_state_initializing, k_board_maker_state_none, - k_board_maker_state_load_template, k_board_maker_state_loading_template, + k_board_maker_state_load_image, + k_board_maker_state_loading_image } state; @@ -55,15 +56,36 @@ struct _board_maker v4f colours[ k_workshop_shader_part_max ]; struct shader_props_workshop *base_shader; - ui_rect picker_panel; + struct board_maker_decal + { + bool loaded; + GLuint texture; + + i32 wrap, wrap_prev; + + v4f colour; + v2f offset; + v2f scale; + f32 rotation; + } + decals[ k_workshop_shader_part_max ]; + + ui_rect pop_panel; f32 *ui_target_colour; f32 *ui_dropper_colour; + enum workshop_shader_part ui_target_part; + enum board_maker_ui_state { k_board_maker_ui_state_default, - k_board_maker_ui_state_pick_colour + k_board_maker_ui_state_pick_colour, + k_board_maker_ui_state_edit_decal, + k_board_maker_ui_state_pick_image } ui_state; + enum board_maker_ui_state ui_image_picker_return_state; + + struct vg_filebrowser *browser; } extern _board_maker; diff --git a/src/ent_skateshop.c b/src/ent_skateshop.c index 7ba0762..74308ce 100644 --- a/src/ent_skateshop.c +++ b/src/ent_skateshop.c @@ -85,8 +85,7 @@ static void skateshop_update_preview_image_thread(void *_args) } vg_strcat( &folder, "/preview.jpg" ); - vg_async_item *call = - vg_async_alloc( sizeof(struct async_preview_load_thread_data) ); + vg_async_item *call = vg_async_alloc( sizeof(struct async_preview_load_thread_data) ); struct async_preview_load_thread_data *inf = call->payload; inf->reg = reg_preview; @@ -101,8 +100,7 @@ static void skateshop_update_preview_image_thread(void *_args) { if( (x != WORKSHOP_PREVIEW_WIDTH) || (y != WORKSHOP_PREVIEW_HEIGHT) ) { - vg_error( "Resolution does not match framebuffer, so we can't" - " show it\n" ); + vg_error( "Resolution does not match framebuffer, so we can't show it\n" ); stbi_image_free( inf->data ); inf->data = NULL; } diff --git a/src/skaterift.c b/src/skaterift.c index 3aab6ac..9ca0420 100644 --- a/src/skaterift.c +++ b/src/skaterift.c @@ -556,6 +556,7 @@ void vg_framebuffer_resize( int w, int h ) { } +#include "vg/vg_ui/filebrowser.c" #include "addon.c" #include "addon_types.c" #include "audio.c"