From 94e0df832c83bc7b9ead0824cfbc41f166869c68 Mon Sep 17 00:00:00 2001 From: kasull Date: Thu, 26 Feb 2026 04:31:08 -0500 Subject: add wall shape dropdown and drag-based polygon creation with height controls fix wall Z bounds calculation and key index masking in input handling preserve 2D view/cursor stability when map bounds update --- src/editor/view2d.cpp | 469 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 395 insertions(+), 74 deletions(-) (limited to 'src/editor/view2d.cpp') diff --git a/src/editor/view2d.cpp b/src/editor/view2d.cpp index 4498248..73b4062 100644 --- a/src/editor/view2d.cpp +++ b/src/editor/view2d.cpp @@ -9,6 +9,91 @@ const I32 EDITORVIEW_GUTTERS_OFFSETX = 22; const I32 EDITORVIEW_GUTTERS_OFFSETY = 16; const I32 EDITORVIEW_TOOLBAR_OFFSET = 20; +VEC2 gui_editor_2dview_screen_to_world( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ); + +F32 gui_editor_2dview_ground_z() { + WORLD_MAP* map = editor->map; + if( !map ) + return 0.f; + + F32 wantedz = map->startpos.z; + F32 bestz = 0.f; + F32 bestdist = FLT_MAX; + U8 found_floor = 0; + map->polygons.each( fn( MAP_POLYGON* p ) { + if( p->type != MPT_FLOOR || !p->vertices.size ) + return; + + F32 floorz = p->vertices[0].pos.z; + F32 dist = fabsf( floorz - wantedz ); + if( !found_floor || dist < bestdist ) { + bestz = floorz; + bestdist = dist; + found_floor = 1; + } + } ); + + if( found_floor ) + return bestz; + + return isfinite( map->mins.z ) ? map->mins.z : 0.f; +} + +F32 gui_editor_2dview_wall_height() { + F32 wallheight = editor->tool.wallheight; + if( !isfinite( wallheight ) || wallheight < 1.f ) + wallheight = EDITOR_DEFAULT_WALL_HEIGHT; + return wallheight; +} + +F32 gui_editor_2dview_placement_z() { + F32 placeheight = editor->tool.placementheight; + if( !isfinite( placeheight ) ) + placeheight = EDITOR_DEFAULT_PLACEMENT_HEIGHT; + + return gui_editor_2dview_ground_z() + placeheight; +} + +F32 gui_editor_2dview_fit_scale_for_bounds( GUI_EDITOR_2DVIEW* view, VEC3 mins, VEC3 maxs ) { + I32 w = view->w - EDITORVIEW_GUTTERS_OFFSETX - 1; + I32 h = view->h - EDITORVIEW_GUTTERS_OFFSETY - 1; + if( w <= 0 || h <= 0 ) + return 1.f; + + F32 mapw = maxs.x - mins.x; + F32 maph = maxs.y - mins.y; + if( !isfinite( mapw ) || !isfinite( maph ) || mapw <= 0.0001f || maph <= 0.0001f ) + return 1.f; + + return min( w / mapw, h / maph ); +} + +void gui_editor_2dview_check_bounds_preserve_view( GUI_EDITOR_2DVIEW* view ) { + if( !view || !editor || !editor->map ) + return; + + I32 mx, my; + gui_cursor_pos( &mx, &my ); + VEC2 world_before = gui_editor_2dview_screen_to_world( view, mx, my ); + + VEC3 oldmins = editor->map->mins; + VEC3 oldmaxs = editor->map->maxs; + F32 oldfit = gui_editor_2dview_fit_scale_for_bounds( view, oldmins, oldmaxs ); + + map_check_bounds( editor->map ); + + VEC3 newmins = editor->map->mins; + VEC3 newmaxs = editor->map->maxs; + F32 newfit = gui_editor_2dview_fit_scale_for_bounds( view, newmins, newmaxs ); + if( newfit > 0.000001f ) { + view->scale *= oldfit / newfit; + } + + VEC2 world_after = gui_editor_2dview_screen_to_world( view, mx, my ); + view->posx += world_before.x - world_after.x; + view->posy += world_before.y - world_after.y; +} + F32 gui_editor_2dview_calc_scale( GUI_EDITOR_2DVIEW* view ) { WORLD_MAP* m = editor->map; @@ -24,31 +109,20 @@ F32 gui_editor_2dview_calc_scale( GUI_EDITOR_2DVIEW* view ) { VEC2 gui_editor_2dview_screen_to_world( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { WORLD_MAP* m = editor->map; + F32 scale = gui_editor_2dview_calc_scale( view ); + if( !isfinite( scale ) || fabsf( scale ) < 0.000001f ) { + return { 0.f, 0.f }; + } - I32 _x = gui_relx( view ) + EDITORVIEW_GUTTERS_OFFSETX; - I32 _y = gui_rely( view ) + EDITORVIEW_TITLE_OFFSET + EDITORVIEW_GUTTERS_OFFSETY; - x -= _x; y -= _y; - - F32 w = view->w - EDITORVIEW_GUTTERS_OFFSETX; - F32 h = view->h - EDITORVIEW_GUTTERS_OFFSETY; - - F32 tx = x / w; - F32 ty = y / h; - - F32 scaledx = m->mins.x + (m->maxs.x - m->mins.x) * tx; - F32 scaledy = m->mins.y + (m->maxs.y - m->mins.y) * ty; - - if( w > h ) - scaledx *= ( w / h ); - if( w < h ) - scaledy *= h / w; + F32 xoff = m->mins.x + view->posx; + F32 yoff = m->mins.y + view->posy; - scaledx /= view->scale; - scaledx += view->posx; - scaledy /= view->scale; - scaledy += view->posy; + F32 vx = (F32)gui_relx( view ) + EDITORVIEW_GUTTERS_OFFSETX; + F32 vy = (F32)gui_rely( view ) + EDITORVIEW_TITLE_OFFSET + EDITORVIEW_GUTTERS_OFFSETY; - return { scaledx, scaledy }; + F32 worldx = ((F32)x - vx) / scale + xoff; + F32 worldy = ((F32)y - vy) / scale + yoff; + return { worldx, worldy }; } VEC2 gui_editor_2dview_world_to_screen( GUI_EDITOR_2DVIEW* view, VEC2 world ) { @@ -102,6 +176,244 @@ void gui_editor_2dview_draw_gizmo( I32 x, I32 y, CLR clr, U8 selected = 0 ) { gui_draw_frect( x - ihalf, y - ihalf, GIZMO_SIZE, GIZMO_SIZE, clr ); } +I32 gui_editor_2dview_poly_sides() { + I32 sides = (I32)floorf( editor->tool.polysides + 0.5f ); + if( sides < EDITOR_POLY_SIDES_MIN ) sides = EDITOR_POLY_SIDES_MIN; + if( sides > EDITOR_POLY_SIDES_MAX ) sides = EDITOR_POLY_SIDES_MAX; + return sides; +} + +U8 gui_editor_2dview_poly_keep_regular( I32 sides ) { + if( sides == 4 ) + return 0; + + return kb_down( SDLK_LSHIFT ) || kb_down( SDLK_RSHIFT ); +} + +U8 gui_editor_2dview_is_poly_drag_tool() { + if( editor->tool.type == EDITOR_TOOL_POLY ) + return 1; + + return editor->tool.type == EDITOR_TOOL_WALL + && editor->tool.wallshape == EDITOR_WALLSHAPE_POLYGON; +} + +I32 gui_editor_2dview_poly_points( VEC2 start, VEC2 end, I32 sides, U8 regular, VEC2* out_points ) { + if( !out_points || sides < EDITOR_POLY_SIDES_MIN || sides > EDITOR_POLY_SIDES_MAX ) + return 0; + + F32 minx = start.x < end.x ? start.x : end.x; + F32 maxx = start.x > end.x ? start.x : end.x; + F32 miny = start.y < end.y ? start.y : end.y; + F32 maxy = start.y > end.y ? start.y : end.y; + + if( sides == 4 ) { + out_points[0] = { maxx, maxy }; + out_points[1] = { minx, maxy }; + out_points[2] = { minx, miny }; + out_points[3] = { maxx, miny }; + return 4; + } + + F32 cx = ( minx + maxx ) * 0.5f; + F32 cy = ( miny + maxy ) * 0.5f; + F32 sx = ( maxx - minx ) * 0.5f; + F32 sy = ( maxy - miny ) * 0.5f; + + if( regular ) { + F32 uniform = sx < sy ? sx : sy; + sx = sy = uniform; + } + + F32 step = ( 2.f * PI ) / sides; + F32 rot = PI * 0.5f; + for( I32 i = 0; i < sides; ++i ) { + F32 ang = rot + step * i; + out_points[i] = { + cx + cosf( ang ) * sx, + cy + sinf( ang ) * sy + }; + } + + return sides; +} + +struct EDITORVIEW_DRAG_RECT { + F32 minx, maxx; + F32 miny, maxy; + F32 w, h; +}; + +struct EDITORVIEW_DRAG_SHAPE { + EDITORVIEW_DRAG_RECT rect; + VEC2 points[EDITOR_POLY_SIDES_MAX]; + I32 pointc; +}; + +U8 gui_editor_2dview_build_drag_shape( GUI_EDITOR_2DVIEW* view, EDITORVIEW_DRAG_SHAPE* out_shape ) { + if( !out_shape ) + return 0; + + EDITORVIEW_DRAG_RECT& rect = out_shape->rect; + rect.minx = view->poly_start.x < view->poly_end.x ? view->poly_start.x : view->poly_end.x; + rect.maxx = view->poly_start.x > view->poly_end.x ? view->poly_start.x : view->poly_end.x; + rect.miny = view->poly_start.y < view->poly_end.y ? view->poly_start.y : view->poly_end.y; + rect.maxy = view->poly_start.y > view->poly_end.y ? view->poly_start.y : view->poly_end.y; + rect.w = rect.maxx - rect.minx; + rect.h = rect.maxy - rect.miny; + + if( rect.w <= 0.0001f || rect.h <= 0.0001f ) + return 0; + + I32 sides = gui_editor_2dview_poly_sides(); + U8 regular = gui_editor_2dview_poly_keep_regular( sides ); + out_shape->pointc = gui_editor_2dview_poly_points( + view->poly_start, + view->poly_end, + sides, + regular, + out_shape->points + ); + + return out_shape->pointc >= 3; +} + +U8 gui_editor_2dview_is_color( CLR c, F32 r, F32 g, F32 b ) { + return fabsf( c.r - r ) < 0.01f + && fabsf( c.g - g ) < 0.01f + && fabsf( c.b - b ) < 0.01f; +} + +I32 gui_editor_2dview_find_or_create_wallpoly_propid() { + I32 propid = editor->map->props.idx_where( fn( SURF_PROPS* p ) { + return !p->tex + && ( gui_editor_2dview_is_color( p->clr, 0.f, 1.f, 1.f ) + || gui_editor_2dview_is_color( p->clr, 0.f, 0.f, 1.f ) ); + } ); + + if( propid != -1 ) + return propid; + + editor->map->props.push( { .tex = 0, .clr = CLR::CYAN() } ); + return editor->map->props.size - 1; +} + +void gui_editor_2dview_draw_dashed_hline( I32 x0, I32 x1, I32 y, CLR col ) { + if( x0 > x1 ) { + I32 tmp = x0; + x0 = x1; + x1 = tmp; + } + + const I32 dash = 6; + const I32 gap = 4; + for( I32 x = x0; x <= x1; x += dash + gap ) { + I32 ex = x + dash; + if( ex > x1 ) ex = x1; + gui_draw_line( x, y, ex, y, col ); + } +} + +void gui_editor_2dview_draw_dashed_vline( I32 x, I32 y0, I32 y1, CLR col ) { + if( y0 > y1 ) { + I32 tmp = y0; + y0 = y1; + y1 = tmp; + } + + const I32 dash = 6; + const I32 gap = 4; + for( I32 y = y0; y <= y1; y += dash + gap ) { + I32 ey = y + dash; + if( ey > y1 ) ey = y1; + gui_draw_line( x, y, x, ey, col ); + } +} + +void gui_editor_2dview_draw_poly_preview( GUI_EDITOR_2DVIEW* view ) { + if( !gui_editor_2dview_is_poly_drag_tool() || !view->poly_drag ) + return; + + VEC2 start = gui_editor_2dview_world_to_screen( view, view->poly_start ); + VEC2 end = gui_editor_2dview_world_to_screen( view, view->poly_end ); + + I32 minx = (I32)( start.x < end.x ? start.x : end.x ); + I32 maxx = (I32)( start.x > end.x ? start.x : end.x ); + I32 miny = (I32)( start.y < end.y ? start.y : end.y ); + I32 maxy = (I32)( start.y > end.y ? start.y : end.y ); + + CLR dash_clr = CLR::WHITE( 0.7f ); + gui_editor_2dview_draw_dashed_hline( minx, maxx, miny, dash_clr ); + gui_editor_2dview_draw_dashed_hline( minx, maxx, maxy, dash_clr ); + gui_editor_2dview_draw_dashed_vline( minx, miny, maxy, dash_clr ); + gui_editor_2dview_draw_dashed_vline( maxx, miny, maxy, dash_clr ); + + EDITORVIEW_DRAG_SHAPE shape{}; + if( !gui_editor_2dview_build_drag_shape( view, &shape ) ) + return; + + CLR poly_clr = CLR::CYAN( 0.9f ); + for( I32 i = 0; i < shape.pointc; ++i ) { + I32 next = ( i + 1 ) % shape.pointc; + VEC2 p0 = gui_editor_2dview_world_to_screen( view, shape.points[i] ); + VEC2 p1 = gui_editor_2dview_world_to_screen( view, shape.points[next] ); + gui_draw_line( (I32)p0.x, (I32)p0.y, (I32)p1.x, (I32)p1.y, poly_clr ); + } +} + +void gui_editor_2dview_create_poly_from_drag( GUI_EDITOR_2DVIEW* view ) { + EDITORVIEW_DRAG_SHAPE shape{}; + if( !gui_editor_2dview_build_drag_shape( view, &shape ) ) + return; + + MAP_POLYGON newp{}; + newp.propid = 0; + newp.type = MPT_FLOOR; + F32 placez = gui_editor_2dview_placement_z(); + + F32 invw = 1.f / shape.rect.w; + F32 invh = 1.f / shape.rect.h; + for( I32 i = 0; i < shape.pointc; ++i ) { + VEC2 p = shape.points[i]; + + MAP_VERTEX v{}; + v.pos = { p.x, p.y, placez }; + v.uv = { + ( p.x - shape.rect.minx ) * invw, + ( shape.rect.maxy - p.y ) * invh + }; + v.clr = CLR::WHITE(); + newp.vertices.push( v ); + } + + map_polygon_calc_bounds( &newp ); + editor->map->polygons.push( newp ); + gui_editor_2dview_check_bounds_preserve_view( view ); +} + +void gui_editor_2dview_create_wallpoly_from_drag( GUI_EDITOR_2DVIEW* view ) { + EDITORVIEW_DRAG_SHAPE shape{}; + if( !gui_editor_2dview_build_drag_shape( view, &shape ) ) + return; + + I32 propid = gui_editor_2dview_find_or_create_wallpoly_propid(); + + F32 basez = gui_editor_2dview_placement_z(); + F32 wallheight = gui_editor_2dview_wall_height(); + for( I32 i = 0; i < shape.pointc; ++i ) { + VEC2 p0 = shape.points[i]; + VEC2 p1 = shape.points[(i + 1) % shape.pointc]; + + MAP_WALL wall{}; + wall.start = { p0.x, p0.y, basez }; + wall.end = { p1.x, p1.y, wallheight }; + wall.propid = propid; + editor->map->walls.push( wall ); + } + + gui_editor_2dview_check_bounds_preserve_view( view ); +} + void gui_editor_2dview_draw_gutters( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { WORLD_MAP* m = editor->map; F32 w = view->w; @@ -344,6 +656,7 @@ void gui_editor_2dview_draw_fn( void* ptr ) { gui_editor_2dview_draw_sprites( view, x + offx, y + offy ); gui_editor_2dview_draw_player( view, x + offx, y + offy ); gui_editor_2dview_draw_origin( view, x + offx, y + offy ); + gui_editor_2dview_draw_poly_preview( view ); gui_draw_pop_clip(); gui_draw_push_clip( x, y + 16, view->w, view->h ); @@ -752,9 +1065,40 @@ void gui_editor_2dview_input_scroll( GUI_EDITOR_2DVIEW* view ) { } void gui_editor_2dview_input_tool_wall( GUI_EDITOR_2DVIEW* view ) { + if( editor->tool.wallshape == EDITOR_WALLSHAPE_POLYGON ) { + U8 m1 = gui_mbutton_down( 0 ); + I32 mx, my; + gui_cursor_pos( &mx, &my ); + VEC2 world = gui_editor_2dview_screen_to_world( view, mx, my ); + + if( !m1 ) { + if( view->poly_drag ) { + view->poly_end = world; + gui_editor_2dview_create_wallpoly_from_drag( view ); + } + + view->poly_drag = 0; + return; + } + + if( !view->poly_drag ) { + view->poly_drag = 1; + view->poly_start = world; + view->poly_end = world; + return; + } + + view->poly_end = world; + return; + } + U8 m1 = gui_mbutton_down( 0 ); if( !m1 ) { + if( view->held ) { + gui_editor_2dview_check_bounds_preserve_view( view ); + } view->held = 0; + view->curdrag = 0; return; } @@ -763,13 +1107,14 @@ void gui_editor_2dview_input_tool_wall( GUI_EDITOR_2DVIEW* view ) { VEC2 world = gui_editor_2dview_screen_to_world( view, mx, my ); if( !view->held ) { + F32 basez = gui_editor_2dview_placement_z(); MAP_WALL neww; neww.start = neww.end = { m_snap_to_grid( world.x, editor->grid ), m_snap_to_grid( world.y, editor->grid ), - 0 + basez }; - neww.end.z = 80.f; + neww.end.z = gui_editor_2dview_wall_height(); neww.propid = 0; I32 idx = editor->map->walls.size; @@ -784,68 +1129,37 @@ void gui_editor_2dview_input_tool_wall( GUI_EDITOR_2DVIEW* view ) { return; } - gui_editor_2dview_input_select_drag_vertex( view ); + VEC3* end = (VEC3*)view->curdrag; + if( end ) { + end->x = m_snap_to_grid( world.x, editor->grid ); + end->y = m_snap_to_grid( world.y, editor->grid ); + } } void gui_editor_2dview_input_tool_poly( GUI_EDITOR_2DVIEW* view ) { U8 m1 = gui_mbutton_down( 0 ); - if( !m1 ) { - view->held = 0; - return; - } - I32 mx, my; gui_cursor_pos( &mx, &my ); - VEC2 world = gui_editor_2dview_screen_to_world( view, mx, my ); - if( !view->held ) { - MAP_POLYGON newp; - newp.propid = 0; - newp.vertices.push( { .pos = { world.x, world.y, 0.f }, .uv = { 1, 0 } } ); - newp.vertices.push( { .pos = { world.x, world.y, 0.f }, .uv = { 0, 0 } } ); - newp.vertices.push( { .pos = { world.x, world.y, 0.f }, .uv = { 0, 1 } } ); - newp.vertices.push( { .pos = { world.x, world.y, 0.f }, .uv = { 1, 1 } } ); - - map_polygon_calc_bounds( &newp ); - - I32 idx = editor->map->polygons.size; - editor->map->polygons.push( newp ); - view->curdrag = &editor->map->polygons[idx]; - view->held = 1; - view->oldmx = mx; - view->oldmy = my; - view->mremainx = 0.0; - view->mremainy = 0.0; + if( !m1 ) { + if( view->poly_drag ) { + view->poly_end = world; + gui_editor_2dview_create_poly_from_drag( view ); + } + + view->poly_drag = 0; return; } - MAP_POLYGON* p = (MAP_POLYGON*)view->curdrag; - MAP_VERTEX - *tl = &p->vertices[1], - *tr = &p->vertices[0], - *bl = &p->vertices[2], - *br = &p->vertices[3]; - - VEC3 start = tl->pos; - VEC3 end = br->pos; - - for( U32 i = 0; i < 3; ++i ) { - start[i] = m_snap_to_grid( start[i], editor->grid ); - end[i] = m_snap_to_grid( end[i], editor->grid ); + if( !view->poly_drag ) { + view->poly_drag = 1; + view->poly_start = world; + view->poly_end = world; + return; } - VEC2 move = gui_editor_2dview_input_get_drag_vec( view ); - if( !is_zero( move ) ) { - end += move; - tl->pos = { start.x, start.y, tl->pos.z }; - tr->pos = { end.x, start.y, tr->pos.z }; - bl->pos = { start.x, end.y, bl->pos.z }; - br->pos = { end.x, end.y, br->pos.z }; - - map_polygon_calc_bounds( p ); - gui_editor_2dview_input_select_onmove( view ); - } + view->poly_end = world; } void gui_editor_2dview_input_tool_ent( GUI_EDITOR_2DVIEW* view ) { @@ -1013,6 +1327,10 @@ void gui_editor_2dview_input_fn( void* ptr ) { if( my >= y + h - 18 ) return; + if( !gui_editor_2dview_is_poly_drag_tool() ) { + view->poly_drag = 0; + } + switch( editor->tool.type ) { case EDITOR_TOOL_SELECT: return gui_editor_2dview_input_tool_select( view ); case EDITOR_TOOL_WALL: @@ -1066,7 +1384,7 @@ void gui_editor_2dview_create_toolbar( GUI_EDITOR_2DVIEW* view ) { } GUI_EDITOR_2DVIEW* gui_editor_2dview( I32 x, I32 y, I32 w, I32 h ) { - GUI_EDITOR_2DVIEW* view = new GUI_EDITOR_2DVIEW; + GUI_EDITOR_2DVIEW* view = new GUI_EDITOR_2DVIEW(); view->x = x; view->y = y; view->xbound = view->w = w; @@ -1078,6 +1396,9 @@ GUI_EDITOR_2DVIEW* gui_editor_2dview( I32 x, I32 y, I32 w, I32 h ) { strcpy( view->name, "EDITOR_2D_VIEW" ); view->scale = 1.f; + view->poly_drag = 0; + view->poly_start = { 0.f, 0.f }; + view->poly_end = { 0.f, 0.f }; GUI_BASE* parent = gui_get_view(); if( !parent ) -- cgit v1.2.3 From 17780f161914e30aaeb1321872730f7fb2cd26c3 Mon Sep 17 00:00:00 2001 From: kasull Date: Thu, 26 Feb 2026 04:42:05 -0500 Subject: color-code previews: polygon tool pink, wall-polygon preview cyan --- src/editor/view2d.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/editor/view2d.cpp') diff --git a/src/editor/view2d.cpp b/src/editor/view2d.cpp index 73b4062..92988df 100644 --- a/src/editor/view2d.cpp +++ b/src/editor/view2d.cpp @@ -352,7 +352,9 @@ void gui_editor_2dview_draw_poly_preview( GUI_EDITOR_2DVIEW* view ) { if( !gui_editor_2dview_build_drag_shape( view, &shape ) ) return; - CLR poly_clr = CLR::CYAN( 0.9f ); + CLR poly_clr = editor->tool.type == EDITOR_TOOL_POLY + ? CLR::MAGENTA( 0.9f ) + : CLR::CYAN( 0.9f ); for( I32 i = 0; i < shape.pointc; ++i ) { I32 next = ( i + 1 ) % shape.pointc; VEC2 p0 = gui_editor_2dview_world_to_screen( view, shape.points[i] ); -- cgit v1.2.3