From f8b92ce3aa08b1445c9f956d8166830946562d12 Mon Sep 17 00:00:00 2001 From: navewindre Date: Wed, 3 Sep 2025 20:10:09 +0200 Subject: a --- src/editor/editor.cpp | 138 +++++++ src/editor/editor.h | 135 +++++++ src/editor/gui.cpp | 255 ++++++++++++ src/editor/properties.cpp | 348 ++++++++++++++++ src/editor/texturepicker.cpp | 356 ++++++++++++++++ src/editor/view2d.cpp | 938 +++++++++++++++++++++++++++++++++++++++++++ src/editor/view3d.cpp | 88 ++++ 7 files changed, 2258 insertions(+) create mode 100644 src/editor/editor.cpp create mode 100644 src/editor/editor.h create mode 100644 src/editor/gui.cpp create mode 100644 src/editor/properties.cpp create mode 100644 src/editor/texturepicker.cpp create mode 100644 src/editor/view2d.cpp create mode 100644 src/editor/view3d.cpp (limited to 'src/editor') diff --git a/src/editor/editor.cpp b/src/editor/editor.cpp new file mode 100644 index 0000000..d97164e --- /dev/null +++ b/src/editor/editor.cpp @@ -0,0 +1,138 @@ +#include "editor.h" + +#include "../game.h" + +GAME_EDITOR* editor = 0; + +GAME_EDITOR* editor_create( GAME_DATA* game ) { + if( editor ) { + dlog( "editor_create() : attempted to create editor when one already exists\n" ); + return 0; + } + + GAME_EDITOR* e = new GAME_EDITOR(); + editor = e; + + e->grid = 1.f; + e->spritesize = EDITOR_DEFAULT_SPRITE_SIZE; + + e->game = game; + gui_init( game ); + + e->wnd = gui_editorwindow( 800, 600 ); + + gui_end(); + return e; +} + +STAT editor_close( GAME_EDITOR* e ) { + game_unload_map( e->game ); + e->map = 0; + + gui_push_callback( e, pfn( void* data ) { + GAME_EDITOR* e = (GAME_EDITOR*)data; + I32 w = e->wnd->w, h = e->wnd->h; + + gui_free( e->wnd ); + e->wnd = gui_editorwindow( w, h ); + } ); + + return STAT_OK; +} + +STAT editor_load_map( GAME_EDITOR* e, const char* mapname ) { + if( e->map ) { + dlog( "editor_load() : map already loaded\n" ); + return STAT_ERR; + } + + dlog( "editor_load() : loading map %s\n", mapname ); + + WORLD_MAP* m = game_load_map( e->game, mapname ); + if( !m ) + return STAT_ERR; + + e->game->state.map = e->map = m; + gui_push_callback( e, pfn( void* d ) { + editor_create_map_view( (GAME_EDITOR*)d ); + } ); + return STAT_OK; +} + +STAT editor_new_map( GAME_EDITOR* e, const char* mapname ) { + WORLD_MAP* m = new WORLD_MAP; + defer( map_free( e->game, m ) ); + + strcpy( m->name, mapname ); + strcat( m->name, ".hmap" ); + + m->startpos = { 10.f, 10.f, 0.f }; + m->props.push( { .clr = { 1.f, 1.f, 1.f, 1.f } } ); + m->walls.push( { + .start = { 0 , 0, -40.f }, + .end = { 10.f, 0, 80.f }, + .propid = 0 + } ); + + CFG_SECTION* map_cfg = map_serialize( m ); + defer( cfg_free( map_cfg ) ); + + char full_path[256]; + sprintf( full_path, "../assets/maps/%s.hmap", mapname ); + if( !OK( cfg_save( map_cfg, full_path ) ) ) { + dlog( "editor_new_map() : error saving file %s\n", full_path ); + return STAT_ERR; + } + + GUI_LIST_ENTRY ne; + ne.val = e->map_list.size; + strcpy( ne.title, mapname ); + strcat( ne.title, ".hmap" ); + e->map_list.push( ne ); + + return STAT_OK; +} + +STAT editor_save_map( GAME_EDITOR* e ) { + if( !e->map ) return STAT_ERR; + + CFG_SECTION* serialized = map_serialize( e->map ); + if( !serialized ) return STAT_ERR; + defer( cfg_free( serialized ) ); + + char full_path[256]; + sprintf( full_path, "../assets/maps/%s", e->map->name ); + + if( !OK( cfg_save( serialized, full_path ) ) ) { + dlog( "failed to save map %s", full_path ); + return STAT_ERR; + } + + return STAT_OK; +} + +LIST* editor_get_map_list( GAME_EDITOR* ge ) { + const char* mapsdir = "../assets/maps"; + const char* ext[] = { "hmap", 0 }; + LIST files = assets_get_files_by_ext_dir( ext, mapsdir ); + LIST list; + + I32 i = 0; + files.each( fn( FILE_ENTRY* e ) { + if( e->dir ) return; + + GUI_LIST_ENTRY ne; + ne.val = i++; + strcpy( ne.title, file_path_last_of( e->name ) ); + + list.push( ne ); + } ); + + ge->map_list = list; + return &ge->map_list; +} + +void editor_destroy( GAME_EDITOR* e ) { + delete e; + editor = 0; +} diff --git a/src/editor/editor.h b/src/editor/editor.h new file mode 100644 index 0000000..0bf2ab4 --- /dev/null +++ b/src/editor/editor.h @@ -0,0 +1,135 @@ +#pragma once +#include "../gui/base.h" +#include "../util/file.h" +#include "../game/world/map.h" + +const F32 EDITOR_DEFAULT_SPRITE_SIZE = 32.f; + +enum EditorTools_t { + EDITOR_TOOL_NONE, + EDITOR_TOOL_SELECT, + EDITOR_TOOL_WALL, + EDITOR_TOOL_POLY, + EDITOR_TOOL_SPRITE, + EDITOR_TOOL_ENT +}; + +enum EditorSelectType_t { + EDITOR_SELECT_NONE, + EDITOR_SELECT_WALL, + EDITOR_SELECT_POLY, + EDITOR_SELECT_WVERTEX, // wall vertex + EDITOR_SELECT_PVERTEX, // polygon vertex + EDITOR_SELECT_SPRITE, + EDITOR_SELECT_ENT, + EDITOR_SELECT_ORIGIN, + EDITOR_SELECT_SURFPROPS +}; + +struct GAME_EDITOR { + GAME_DATA* game; + + struct WORLD_MAP* map; + struct GUI_EDITORWINDOW* wnd; + + struct EDITOR_GUI { + struct GUI_WINDOW* new_map_popup; + + struct GUI_EDITOR_2DVIEW* v2d; + struct GUI_EDITOR_3DVIEW* v3d; + + struct GUI_LABEL* toollabel; + + struct GUI_LABEL* gridlabel; + + struct GUI_EDITOR_PROPVIEW* props; + I32 map_select{}; + } gui; + + + U8 wireframe{}; + U8 tool{}; + F32 grid{}; + U8 propgrid{}; + F32 spritesize{}; + U8 drawbsp{}; + + LIST map_list{}; +}; + +extern GAME_EDITOR* editor_create( struct GAME_DATA* game ); +extern void editor_destroy( GAME_EDITOR* editor ); +extern STAT editor_load_map( GAME_EDITOR* e, const char* mapname ); +extern STAT editor_save_map( GAME_EDITOR* e ); +extern STAT editor_new_map( GAME_EDITOR* e, const char* mapname ); +extern STAT editor_close( GAME_EDITOR* e ); + +extern LIST* editor_get_map_list( GAME_EDITOR* e ); + +extern void editor_load_map_cb( void* ); +extern void editor_new_map_cb( void* ); + +extern void editor_create_map_view( GAME_EDITOR* e ); + +struct GUI_EDITORWINDOW : GUI_WINDOW {}; +struct GUI_EDITOR_3DVIEW : GUI_VIEW {}; +struct GUI_EDITOR_2DVIEW : GUI_VIEW { + F32 scale; + F32 posx, posy; + + I32 moffx, moffy; + F32 voffx, voffy; + U8 dragheld; + U8 held; + U8 heldoutbounds; + + I32 oldmx, oldmy; + F32 mremainx, mremainy; + + void* curselect; + U8 seltype; + + void* curdrag; + U8 dragtype; + U8 dragmoved; +}; + +struct GUI_EDITOR_PROPVIEW : GUI_VIEW { + void* curselect; + U8 seltype; + + GUI_VIEW* itemview; +}; + +struct GUI_EDITOR_TEXTUREPICKER : GUI_WINDOW { + struct GL_TEX2D** target; + struct GL_TEX2D* curselect; + + I32 scrolloff; + + LIST files; + LIST textures; + I32 scrollheight; + + U32 rowcount; + char search[256]; + + U32 curload; + U8 loaded; + + GL_TEX2D* heldselect; + GUI_LABEL* densitylabel; + + GUI_CALLBACK cb; +}; + +extern GUI_EDITORWINDOW* gui_editorwindow( I32 w, I32 h ); +extern GUI_EDITOR_2DVIEW* gui_editor_2dview( I32 x, I32 y, I32 w, I32 h ); +extern GUI_EDITOR_3DVIEW* gui_editor_3dview( I32 x, I32 y, I32 w, I32 h ); +extern GUI_EDITOR_PROPVIEW* gui_editor_propview( I32 x, I32 y, I32 w, I32 h ); +extern GUI_EDITOR_TEXTUREPICKER* gui_editor_texturepicker( I32 x, I32 y, I32 w, I32 h, GL_TEX2D** target ); + +extern void gui_editor_propview_select( GUI_EDITOR_PROPVIEW* e, void* what, U8 seltype ); +extern void gui_editor_propview_update( GUI_EDITOR_PROPVIEW* e ); + +extern GAME_EDITOR* editor; diff --git a/src/editor/gui.cpp b/src/editor/gui.cpp new file mode 100644 index 0000000..71ce113 --- /dev/null +++ b/src/editor/gui.cpp @@ -0,0 +1,255 @@ +#include "editor.h" +#include "../game/world/bsp.h" + +void gui_editorwindow_draw_fn( void* ptr ) { + GUI_EDITORWINDOW* wnd = (GUI_EDITORWINDOW*)ptr; + + CLR clr = gui_is_fg_window( wnd )? ui_clr.border : ui_clr.border_inactive; + gui_draw_frect( wnd->x, wnd->y, wnd->w, wnd->h, clr ); + gui_draw_frect( wnd->x + 1, wnd->y + 1, wnd->w - 2, wnd->h - 2, ui_clr.bg ); + + wnd->children.each( fn( GUI_BASE** ptr ) { + GUI_BASE* it = *ptr; + if( !it->enabled ) return; + + if( it->draw_fn ) it->draw_fn( it ); + else dlog( "gui_editorwindow_draw_fn(): child %p has no draw_fn", it ); + } ); +} + +GUI_EDITORWINDOW* gui_editorwindow_create( I32 w, I32 h ) { + GUI_EDITORWINDOW* wnd = new GUI_EDITORWINDOW; + wnd->x = 0; + wnd->y = 0; + wnd->w = w; + wnd->h = h; + wnd->locked = 1; + wnd->draw_fn = gui_editorwindow_draw_fn; + strcpy( wnd->name, "EDITORWINDOW" ); + + _gui.windows.push( wnd ); + gui_set_window( wnd ); + + gui_set_view( 0 ); + gui_view( 1, 1, w - 2, h - 2 ); + return wnd; +} + +GUI_EDITORWINDOW* gui_editorwindow( I32 w, I32 h ) { + GUI_EDITORWINDOW* wnd = gui_editorwindow_create( w, h ); + + gui_title( "ray2Dscape (level editor)" ); + + GAME_EDITOR* e = editor; + LIST* map_list = editor_get_map_list( e ); + + gui_list( wnd->w / 2 - 100, 40, 200, 200, "map list", map_list, &editor->gui.map_select ); + gui_button( wnd->w / 2 - 100, 260, 95, 25, "new map", editor_new_map_cb ); + gui_button( wnd->w / 2 + 5, 260, 95, 25, "load map", editor_load_map_cb ); + return wnd; +} + +void editor_update_active_tool_label( GAME_EDITOR* e ) { + char lstr[256]; + switch( e->tool ) { + case EDITOR_TOOL_SELECT: + sprintf( lstr, "current tool: select" ); break; + case EDITOR_TOOL_WALL: + sprintf( lstr, "current tool: wall" ); break; + case EDITOR_TOOL_POLY: + sprintf( lstr, "current tool: polygon" ); break; + case EDITOR_TOOL_SPRITE: + sprintf( lstr, "current tool: sprite" ); break; + case EDITOR_TOOL_ENT: + sprintf( lstr, "current tool: entity" ); break; + default: + sprintf( lstr, "current tool: none" ); break; + } + + strcpy( e->gui.toollabel->name, lstr ); +} + +void editor_update_properties_column( GAME_EDITOR* e ) { + GAME_EDITOR::EDITOR_GUI* egui = &e->gui; + gui_editor_propview_update( egui->props ); +} + +void editor_create_properties_column( GAME_EDITOR* e ) { + GAME_EDITOR::EDITOR_GUI* egui = &e->gui; + gui_button( 10, 10, 145, 20, "back", pfn( void* ) { editor_close( editor ); } ); + gui_button( 165, 10, 145, 20, "save", pfn( void* ) { editor_save_map( editor ); } ); + + egui->props = gui_editor_propview( 10, 38, 300, 460 ); + gui_editor_propview_select( egui->props, e->map, EDITOR_SELECT_ORIGIN ); +} + +void editor_create_game_view_column( GAME_EDITOR* e ) { + GAME_EDITOR::EDITOR_GUI* egui = &e->gui; + I32 x = 320, y = 10; + + gui_button( x, y, 229, 20, "2d view", pfn( void* ptr ) { + editor->gui.v3d->enabled = 0; + editor->gui.v2d->enabled = 1; + } ); + + gui_button( x + 239, y, 229, 20, "3d view", pfn( void* ptr ) { + editor->gui.v3d->enabled = 1; + editor->gui.v2d->enabled = 0; + } ); + + y += 28; + egui->v2d = gui_editor_2dview( x, y, 468, 370 ); + egui->v3d = gui_editor_3dview( x, y, 468, 370 ); + egui->v2d->enabled = 0; + egui->v3d->enabled = 1; +} + +void settool( U8 t ) { editor->tool = t; editor_update_active_tool_label( editor ); } +void editor_create_tools_row( GAME_EDITOR* e ) { + I32 x = 10, y = 520; + + e->gui.toollabel = gui_label( x, y, "current tool: none" ); + y += 16; + gui_button( x, y, 80, 20, "none", pfn( void* ) { settool( EDITOR_TOOL_NONE ); } ); x += 90; + gui_button( x, y, 80, 20, "select", pfn( void* ) { settool( EDITOR_TOOL_SELECT ); } ); x += 90; + gui_button( x, y, 80, 20, "wall", pfn( void* ) { settool( EDITOR_TOOL_WALL ); } ); x += 90; + + x = 10; y += 28; + gui_button( x, y, 80, 20, "polygon", pfn( void* ) { settool( EDITOR_TOOL_POLY ); } ); x += 90; + gui_button( x, y, 80, 20, "sprite", pfn( void* ) { settool( EDITOR_TOOL_SPRITE ); } ); x += 90; + gui_button( x, y, 80, 20, "entity", pfn( void* ) { settool( EDITOR_TOOL_ENT ); } ); x += 90; + editor_update_active_tool_label( e ); +} + +void editor_update_view_settings( GAME_EDITOR* e ) { + GAME_EDITOR::EDITOR_GUI* egui = &e->gui; + + sprintf( egui->gridlabel->name, "grid size: %1.2f", e->grid ); +} + +void editor_grid_increment_cb( void* ) { + if( editor->grid < 16.f ) editor->grid *= 2.f; + editor_update_view_settings( editor ); + + if( editor->propgrid ) + editor_update_properties_column( editor ); +} + +void editor_grid_decrement_cb( void* ) { + if( editor->grid > 0.25f ) editor->grid *= 0.5f; + editor_update_view_settings( editor ); + + if( editor->propgrid ) + editor_update_properties_column( editor ); +} + +void editor_grid_propgrid_cb( void* ) { + editor_update_properties_column( editor ); +} + +void editor_create_view_settings_row( GAME_EDITOR* e ) { + I32 x = 320, y = 426; + + gui_label( x, y, "view settings:" ); x += 95; + editor->gui.gridlabel = gui_label( x, y, "grid size: %1.2f", e->grid ); + x += 95; + gui_button( x, y, 20, 20, "+", editor_grid_increment_cb ); + gui_button( x + 25, y, 20, 20, "-", editor_grid_decrement_cb ); + x += 55; + GUI_CHECKBOX* check = gui_checkbox( x, y, "properties grid", &e->propgrid ); + check->cb = editor_grid_propgrid_cb; + x += 120; + gui_checkbox( x, y, "wireframe", &e->wireframe ); + + x = 320; + y = 440; + gui_button( x, y, 100, 20, "compile bsp", pfn( void* b ) { + if( editor->map->bsp ) + bsp_free( editor->map->bsp ); + editor->map->bsp = bsp_build_map( editor->map ); + } ); x += 110; + + gui_checkbox( x, y, "draw bsp", &e->drawbsp ); +} + +void editor_create_map_view( GAME_EDITOR* e ) { + if( !e->map ) { + dlog( "editor_create_map_views() : no map loaded\n" ); + return; + } + + GUI_EDITORWINDOW* w = e->wnd; + w->children.each( fn( GUI_BASE** ptr ) { + gui_free( *ptr ); + } ); + w->children.clear(); + + gui_set_window( w ); + gui_set_view( 0 ); + + gui_view( 1, 1, w->w - 2, w->h - 2 ); + + editor_create_properties_column( e ); + editor_create_game_view_column( e ); + editor_create_tools_row( e ); + editor_create_view_settings_row( e ); +} + +void close_new_map_popup( void* ) { + gui_push_callback( pfn( void* ) { + GUI_WINDOW* popup = editor->gui.new_map_popup; + if( !popup ) + return; + + gui_free( popup ); + editor->gui.new_map_popup = 0; + } ); +} + +void editor_new_map_cb( void* ptr ) { + if( editor->gui.new_map_popup ) return; + + GUI_WINDOW* cwnd = gui_get_window(); + GUI_VIEW* view = gui_get_view(); + + I32 wx = 250; + I32 wy = 140; + I32 ww = 300; + I32 wh = 200; + + editor->gui.new_map_popup = gui_window( wx, wy, ww, wh ); + gui_title( "new map" ); + GUI_TEXTBOX* tb = gui_textbox( 10, 20, ww - 20, 20, "name", 32 ); + tb->active = 1; + + gui_button( ww - 50 - 70, wh - 20 - 10, 50, 20, "ok", pfn( void* ptr ) { + GUI_BUTTON* btn = (GUI_BUTTON*)ptr; + GUI_TEXTBOX* name = (GUI_TEXTBOX*)gui_find_node( btn->parent, "name" ); + if( !name ) + return; + + editor_new_map( editor, name->value ); + close_new_map_popup( 0 ); + } ); + + gui_button( ww - 50 - 10, wh - 20 - 10, 50, 20, "cancel", close_new_map_popup ); + + gui_set_window( cwnd ); + gui_set_view( view ); +} + +void editor_load_map_cb( void* ptr ) { + GUI_LIST* list = (GUI_LIST*)gui_find_node( editor->wnd, "map list" ); + + GUI_LIST_ENTRY* e = gui_list_get_selected( list ); + if( !e ) + return; + + char full_path[256]; + sprintf( full_path, "../assets/maps/%s", e->title ); + + if( editor->map ) + editor_close( editor ); + + editor_load_map( editor, full_path ); +} diff --git a/src/editor/properties.cpp b/src/editor/properties.cpp new file mode 100644 index 0000000..ebae177 --- /dev/null +++ b/src/editor/properties.cpp @@ -0,0 +1,348 @@ +#include "editor.h" +#include "../render/gl.h" +#include "../game/assets.h" + +const I32 PROPVIEW_TITLE_OFFSET = 15; + +void gui_editor_propview_select( GUI_EDITOR_PROPVIEW* view, void* what, U8 seltype ) { + if( !editor->map ) + return; + + if( seltype == EDITOR_SELECT_WVERTEX ) { + MAP_WALL* s = editor->map->walls.where( fn( MAP_WALL* s ) { + return ( &s->start == what || &s->end == what ); + } ); + + if( !s ) + return gui_editor_propview_select( view, editor->map, EDITOR_SELECT_ORIGIN ); + + view->curselect = s; + view->seltype = EDITOR_SELECT_WALL; + return gui_editor_propview_update( view ); + } + + view->curselect = what; + view->seltype = seltype; + gui_editor_propview_update( view ); +} + +// returns the subentry height +I32 gui_editor_propview_surfprops_subentry( I32 x, I32 y, I32* propid, SURF_PROPS* props ) { + I32 oldy = y; + I32 space = 20; + gui_label( x, y, "prop id: %d", *propid ); + GUI_BUTTON* newprop = gui_button( x + 235, y, 20, 20, "+", pfn( void* ptr ) { + GUI_BUTTON* btn = (GUI_BUTTON*)ptr; + SURF_PROPS props; + props.tex = 0; + props.clr = { 1.f, 1.f, 1.f, 1.f }; + + editor->map->props.push( props ); + *(U32*)(btn->extra) = editor->map->props.size - 1; + gui_editor_propview_update( editor->gui.props ); + } ); + newprop->extra = propid; + GUI_BUTTON* goprop = gui_button( x + 260, y, 20, 20, "\x1A", pfn( void* ptr ) { + GUI_BUTTON* btn = (GUI_BUTTON*)ptr; + gui_editor_propview_select( editor->gui.props, btn->extra, EDITOR_SELECT_SURFPROPS ); + } ); + goprop->extra = props; + + y += space; + + if( props->tex ) + gui_label( x + 10, y, "texture: %s", props->tex->name ); + else + gui_label( x + 10, y, "texture: none" ); + + GUI_BUTTON* btn = gui_button( x + 260, y, 20, 20, "\x1A", pfn( void* ptr ) { + GUI_BUTTON* btn = (GUI_BUTTON*)ptr; + GL_TEX2D** ptex = (GL_TEX2D**)btn->extra; + GUI_EDITOR_TEXTUREPICKER* picker = gui_editor_texturepicker( 200, 100, 400, 400, ptex ); + picker->cb = pfn( void* ) { gui_editor_propview_update( editor->gui.props ); }; + } ); + + btn->extra = &props->tex; + y += space; + gui_colorinput( x + 10, y, 270, "color", &props->clr ); y += (space+18); + + return y - oldy; +} + +void gui_editor_propview_create_wallprops( GUI_EDITOR_PROPVIEW* view ) { + MAP_WALL* s = (MAP_WALL*)view->curselect; + WORLD_MAP* m = editor->map; + SURF_PROPS* props = wall_get_props( m, s ); + + I32 x = 10, y = 10; + I32 space = 20; + F32 step = editor->propgrid? editor->grid : 0.25f; + + I32 wall_idx = m->walls.idx_where( fn( MAP_WALL* ms ) { + return s == ms; + } ); + + gui_label( x, y, "idx: %d", wall_idx ); y += space; + y += gui_editor_propview_surfprops_subentry( x, y, &s->propid, props ); + + GUI_VECTORINPUT* posinput; + posinput = gui_vectorinput( x, y, 280, "start", (F32*)&s->start, 3, -INFINITY, INFINITY, step ); y += (space+18); + posinput->cb = pfn( void* ) { map_check_bounds( editor->map ); }; + posinput = gui_vectorinput( x, y, 280, "end", (F32*)&s->end, 3, -INFINITY, INFINITY, step ); y += (space+18); + posinput->cb = pfn( void* ) { map_check_bounds( editor->map ); }; +} + +void gui_editor_propview_create_polyprops( GUI_EDITOR_PROPVIEW* view ) { + MAP_POLYGON* p = (MAP_POLYGON*)view->curselect; + WORLD_MAP* m = editor->map; + SURF_PROPS* props = polygon_get_props( m, p ); + + I32 x = 10, y = 10; + I32 space = 20; + + I32 poly_idx = m->polygons.idx_where( fn( MAP_POLYGON* mp ) { + return p == mp; + } ); + + gui_label( x, y, "idx: %d", poly_idx ); y += space; + y += gui_editor_propview_surfprops_subentry( x, y, &p->propid, props ); + + gui_label( x, y, "vertices: %d", p->vertices.size ); y += space; + I32 idx = 0; + p->vertices.each( fn( MAP_VERTEX* v ) { + gui_label( x + 10, y, "[%d] -> { %.02f, %.02f, %.02f }", idx, v->pos.x, v->pos.y, v->pos.z ); + GUI_BUTTON* btn = gui_button( x + 260, y - 2, 20, 20, "\x1A", pfn( void* ptr ) { + GUI_EDITOR_PROPVIEW* view = editor->gui.props; + GUI_BUTTON* btn = (GUI_BUTTON*)ptr; + MAP_POLYGON* p = (MAP_POLYGON*)view->curselect; + I64 idx = (I64)btn->extra; + if( p ) // p100 + gui_editor_propview_select( view, &p->vertices[idx], EDITOR_SELECT_PVERTEX ); + } ); + + y += space; + btn->extra = (void*)( (I64)idx++ ); + } ); + gui_label( x, y, "mins: { %1.02f, %1.02f, %1.02f }", p->mins.x, p->mins.y, p->mins.z ); y += space; + gui_label( x, y, "maxs: { %1.02f, %1.02f, %1.02f }", p->maxs.x, p->maxs.y, p->maxs.z ); y += space; +} + +void gui_editor_propview_create_mapprops( GUI_EDITOR_PROPVIEW* view ) { + WORLD_MAP* m = (WORLD_MAP*)view->curselect; + + I32 x = 10, y = 10; + I32 space = 20; + + F32 step = editor->propgrid? editor->grid : 0.25f; + + gui_label( x, y, "name: %s", m->name ); y += space; + gui_label( x, y, "walls: %d", m->walls.size ); y += space; + gui_label( x, y, "polygons: %d", m->polygons.size ); y += space; + gui_label( x, y, "props: %d", m->props.size ); y += space; + I32 i = 0; + m->props.each( fn( SURF_PROPS* p ) { + if( p->tex ) + gui_label( x + 10, y, "[%d] -> %s", i++, assets_abspath( p->tex->name ) ); + else + gui_label( x + 10, y, "[%d] -> { %.02f, %.02f, %.02f, %.02f }", i++, p->clr.r, p->clr.g, p->clr.b, p->clr.a ); + + GUI_BUTTON* btn = gui_button( x + 260, y, 20, 20, "\x1A", pfn( void* ptr ) { + GUI_BUTTON* btn = (GUI_BUTTON*)ptr; + gui_editor_propview_select( editor->gui.props, btn->extra, EDITOR_SELECT_SURFPROPS ); + } ); + btn->extra = p; + y += space; + } ); + gui_label( x, y, "sprites: %d", m->sprites.size ); y += space; + gui_label( x, y, "loaded textures: %d", m->textures.size ); y += space; + gui_vectorinput( x, y, 280, "spawn position", (F32*)&m->startpos, 3, -INFINITY, INFINITY, step ); y += (space+18); + GUI_FLOATINPUT* ang = gui_floatinput( x, y, 280, "spawn angle", &m->startang, -180.f, 180.f, 1.f ); y += (space+18); + ang->wraparound = 1; +} + +void gui_editor_propview_create_pvertexprops( GUI_EDITOR_PROPVIEW* view ) { + MAP_VERTEX* v = (MAP_VERTEX*)view->curselect; + WORLD_MAP* m = editor->map; + + I32 x = 10, y = 10; + I32 space = 20; + + I32 vert_idx = -1; + I32 poly_idx = m->polygons.idx_where( fn( MAP_POLYGON* p ) { + vert_idx = p->vertices.idx_where( fn( MAP_VERTEX* pv ) { return (pv == v); } ); + return vert_idx != -1; + } ); + + F32 step = editor->propgrid? editor->grid : 0.25f; + + gui_label( x, y, "idx: %d", vert_idx ); y += space; + gui_label( x, y, "polygon idx: %d", poly_idx ); + + gui_button( x + 260, y - 2, 20, 20, "\x1A", pfn( void* ) { + GUI_EDITOR_PROPVIEW* view = editor->gui.props; + MAP_VERTEX* v = (MAP_VERTEX*)view->curselect; + WORLD_MAP* m = editor->map; + MAP_POLYGON* polygon = m->polygons.where( fn( MAP_POLYGON* p ) { + I32 vert_idx = p->vertices.idx_where( fn( MAP_VERTEX* pv ) { return (pv == v); } ); + return vert_idx != -1; + } ); + + if( polygon ) + gui_editor_propview_select( view, polygon, EDITOR_SELECT_POLY ); + } ); + y += space; + + GUI_VECTORINPUT* posinput = gui_vectorinput( x, y, 280, "position", (F32*)&v->pos, 3, -INFINITY, INFINITY, step ); y += (space+18); + posinput->cb = pfn( void* ptr ) { map_check_bounds( editor->map ); }; + gui_vectorinput( x, y, 280, "texture coordinates", (F32*)&v->uv, 2, 0.f, 1.f, 0.005f, "xy", "%.03f" ); y += (space+18); + gui_colorinput( x, y, 280, "color", &v->clr ); y += (space+18); +} + +void gui_editor_propview_create_spriteprops( GUI_EDITOR_PROPVIEW* view ) { + MAP_SPRITE* s = (MAP_SPRITE*)view->curselect; + WORLD_MAP* m = editor->map; + + I32 x = 10, y = 10; + I32 space = 20; + + I32 sprite_idx = m->sprites.idx_where( fn( MAP_SPRITE* ms ) { + return ms == s; + } ); + + F32 step = editor->propgrid? editor->grid : 0.25f; + + gui_label( x, y, "idx: %d", sprite_idx ); y += space; + gui_vectorinput( x, y, 280, "position", (F32*)&s->pos, 3, -INFINITY, INFINITY, step ); y += (space+18); + gui_vectorinput( x, y, 280, "size", (F32*)&s->size, 2, 1.f, INFINITY, 1.f, "wh" ); y += (space+18); + gui_colorinput( x, y, 280, "color", &s->clr ); y += (space+18); + if( s->tex ) + gui_label( x, y, "texture: %s", s->tex->name ); + else + gui_label( x, y, "texture: none" ); + + GUI_BUTTON* btn = gui_button( x + 260, y, 20, 20, "\x1A", pfn( void* ptr ) { + GUI_BUTTON* btn = (GUI_BUTTON*)ptr; + GL_TEX2D** ptex = (GL_TEX2D**)btn->extra; + GUI_EDITOR_TEXTUREPICKER* picker = gui_editor_texturepicker( 200, 100, 400, 400, ptex ); + picker->cb = pfn( void* ) { gui_editor_propview_update( editor->gui.props ); }; + } ); + btn->extra = &s->tex; + y += space; +} + +void gui_editor_propview_create_surfprops( GUI_EDITOR_PROPVIEW* view ) { + SURF_PROPS* p = (SURF_PROPS*)view->curselect; + WORLD_MAP* m = editor->map; + + I32 x = 10, y = 10; + I32 space = 20; + + I32 i = m->props.idx_where( fn( SURF_PROPS* mp ) { return mp == p; } ); + if( i == -1 ) + return; + + gui_label( x, y, "prop id: %d", i ); y += space; + if( p->tex ) + gui_label( x, y, "texture: %s", assets_abspath( p->tex->name ) ); + else + gui_label( x, y, "texture: none" ); + GUI_BUTTON* btn = gui_button( x + 260, y, 20, 20, "\x1A", pfn( void* ptr ) { + GUI_BUTTON* btn = (GUI_BUTTON*)ptr; + GL_TEX2D** ptex = (GL_TEX2D**)btn->extra; + GUI_EDITOR_TEXTUREPICKER* picker = gui_editor_texturepicker( 200, 100, 400, 400, ptex ); + picker->cb = pfn( void* ) { gui_editor_propview_update( editor->gui.props ); }; + } ); + btn->extra = &p->tex; + y += space; + gui_colorinput( x, y, 280, "color", &p->clr ); y += (space+18); +} + +void gui_editor_propview_create_entprops( GUI_EDITOR_PROPVIEW* view ) { + +} + +void gui_editor_propview_update( GUI_EDITOR_PROPVIEW* view ) { + if( !editor->map ) return; + + GUI_VIEW* target = gui_get_view(); + defer({ if( target ) gui_set_view( target ); }); + + gui_empty_children( view->itemview ); + gui_set_view( view->itemview ); + view->itemview->initheld = 1; + + switch( view->seltype ) { + case EDITOR_SELECT_WALL: gui_editor_propview_create_wallprops( view ); break; + case EDITOR_SELECT_POLY: gui_editor_propview_create_polyprops( view ); break; + case EDITOR_SELECT_ORIGIN: gui_editor_propview_create_mapprops( view ); break; + case EDITOR_SELECT_ENT: gui_editor_propview_create_entprops( view ); break; + case EDITOR_SELECT_PVERTEX: gui_editor_propview_create_pvertexprops( view ); break; + case EDITOR_SELECT_SPRITE: gui_editor_propview_create_spriteprops( view ); break; + case EDITOR_SELECT_SURFPROPS: gui_editor_propview_create_surfprops( view ); break; + default: break; + } +} + +void gui_editor_propview_get_title( GUI_EDITOR_PROPVIEW* view, char* buf ) { + switch( view->seltype ) { + case EDITOR_SELECT_NONE: sprintf( buf, "properties: " ); break; + case EDITOR_SELECT_POLY: sprintf( buf, "polygon properties: " ); break; + case EDITOR_SELECT_ORIGIN: sprintf( buf, "map [%s] properties: ", editor->map->name ); break; + case EDITOR_SELECT_ENT: sprintf( buf, "entity properties: " ); break; + case EDITOR_SELECT_SPRITE: sprintf( buf, "sprite properties: " ); break; + case EDITOR_SELECT_PVERTEX: sprintf( buf, "vertex properties: " ); break; + case EDITOR_SELECT_SURFPROPS: sprintf( buf, "surface properties: " ); break; + case EDITOR_SELECT_WVERTEX: + case EDITOR_SELECT_WALL: sprintf( buf, "wall properties: " ); break; + } +} + +void gui_editor_propview_draw_fn( void* ptr ) { + if( !editor->map ) return; + + GUI_EDITOR_PROPVIEW* view = (GUI_EDITOR_PROPVIEW*)ptr; + + I32 x = gui_relx( view ); + I32 y = gui_rely( view ); + I32 w = view->w; + I32 h = view->h; + + char title[64]; + gui_editor_propview_get_title( view, title ); + gui_draw_str( x, y, ALIGN_L, FNT_JPN12, ui_clr.txt, title ); + y += PROPVIEW_TITLE_OFFSET; + + CLR col = gui_is_fg_window( view )? ui_clr.border : ui_clr.border_inactive; + gui_draw_frect( x, y, w, h, col ); + gui_draw_frect( x+1, y+1, w-2, h-2, ui_clr.bg_sec ); + + view->itemview->draw_fn( view->itemview ); +} + +GUI_EDITOR_PROPVIEW* gui_editor_propview( I32 x, I32 y, I32 w, I32 h ) { + if( !gui_check_target() ) return 0; + + GUI_EDITOR_PROPVIEW* view = new GUI_EDITOR_PROPVIEW; + view->x = x; + view->y = y; + view->w = w; + view->h = h; + view->xbound = view->w; + view->ybound = view->h + PROPVIEW_TITLE_OFFSET; + strcpy( view->name, "EDITOR_PROP_VIEW" ); + view->draw_fn = gui_editor_propview_draw_fn; + view->input_fn = gui_base_input_fn; + + view->curselect = 0; + view->seltype = 0; + + GUI_VIEW* parent = gui_get_view(); + parent->children.push( view ); + view->parent = parent; + + gui_set_view( view ); + view->itemview = gui_view( 0, PROPVIEW_TITLE_OFFSET, w, h ); + + gui_set_view( parent ); + return view; +} diff --git a/src/editor/texturepicker.cpp b/src/editor/texturepicker.cpp new file mode 100644 index 0000000..a24e955 --- /dev/null +++ b/src/editor/texturepicker.cpp @@ -0,0 +1,356 @@ +#include "editor.h" +#include "../game/assets.h" +#include "../render/gl_2d.h" + +I32 TEXTUREVIEW_TOP_OFFSET = 20; +I32 TEXTUREVIEW_BOTTOM_OFFSET = 28; +const char* TEXTURE_EXTENSIONS[] = { + "png", + 0 +}; + +I32 gui_editor_texturepicker_get_scrollheight( GUI_EDITOR_TEXTUREPICKER* wnd ) { + return 0; +} + +void gui_editor_texturepicker_handle_scroll( GUI_EDITOR_TEXTUREPICKER* wnd ) { + I32 x = wnd->x; + I32 y = wnd->y + TEXTUREVIEW_TOP_OFFSET; + I32 w = wnd->w; + I32 h = wnd->h - TEXTUREVIEW_TOP_OFFSET - TEXTUREVIEW_BOTTOM_OFFSET; + + I32 mx, my; + gui_cursor_pos( &mx, &my ); + + U8 inbounds = mx >= x && mx <= x + w && my >= y && my <= y + h; + if( !inbounds ) + return; + + U8 scroll = gui_mbutton_down( GUI_MBTNSCROLL ); + gui_capture_scroll(); + + if( scroll == 1 ) + wnd->scrolloff -= 20; +} + +void gui_editor_texturepicker_trim_texname( GUI_EDITOR_TEXTUREPICKER* wnd, char* trimname ) { + I32 size = (wnd->w - 20) / wnd->rowcount - 10; + I32 tw; + while( 1 ) { + gui_draw_get_str_bounds( &tw, 0, FNT_JPN12, trimname ); + if( tw >= size ) { + if( trimname[0] != '.' && trimname[1] != '.' && trimname[2] != '.' ) { + sprintf( trimname, "...%s", trimname ); + } + + for( U32 i = 3; !!trimname[i]; ++i ) + trimname[i] = trimname[i + 1]; + } else break; + } +} + +void gui_editor_texturepicker_draw_file_str( I32 x, I32 y, const char* str, U8 issel ) { + if( !issel ) { + return gui_draw_str( + x, y, + ALIGN_C, + FNT_JPN12, + ui_clr.txt, + str + ); + } + + I32 w, h; + gui_draw_get_str_bounds( &w, &h, FNT_JPN12, str ); + w += 2; + + gui_draw_frect( x - w / 2, y + 1, w, h, ui_clr.border ); + gui_draw_str( x, y, ALIGN_C, FNT_JPN12, ui_clr.bg_alt, str ); +} + +void gui_editor_texturepicker_draw_files( GUI_EDITOR_TEXTUREPICKER* wnd ) { + I32 x = wnd->x + 11; + I32 y = wnd->y + TEXTUREVIEW_TOP_OFFSET; + I32 w = wnd->w - 22; + I32 size = (w + 10) / wnd->rowcount - 10; + + I32 colcnt = (I32)floorf( (F32)w / size ); + CLR clr = gui_is_fg_window( wnd )? ui_clr.border : ui_clr.border_inactive; + + U32 i = 0; + wnd->textures.each( fn( GL_TEX2D** ptr ) { + GL_TEX2D* tex = *ptr; + + if( strlen( wnd->search ) > 0 && !strstr( tex->name, wnd->search ) ) + return; + + I32 tx = x + (i % colcnt) * (size + 10); + I32 ty = y + (i / colcnt) * (size + 18); + + gui_draw_frect( tx - 1, ty - 1, size + 2, size + 2, clr ); + gui_draw_frect( tx, ty, size, size, clr ); + gl_2d_textured_frect( + _gui.gl2d_font, + { (F32)tx, (F32)ty }, + { (F32)size, (F32)size }, + tex, + CLR::WHITE() + ); + + char trimname[256]; + strcpy( trimname, assets_abspath( tex->name ) ); + gui_editor_texturepicker_trim_texname( wnd, trimname ); + + gui_editor_texturepicker_draw_file_str( + tx + size / 2, + ty + size + 1, + trimname, + wnd->curselect == tex + ); + + ++i; + } ); +} + +void gui_editor_texturepicker_draw_fn( void* ptr ) { + GUI_EDITOR_TEXTUREPICKER* wnd = (GUI_EDITOR_TEXTUREPICKER*)ptr; + + CLR clr = gui_is_fg_window( wnd )? ui_clr.border : ui_clr.border_inactive; + gui_draw_frect( wnd->x, wnd->y, wnd->w, wnd->h, clr ); + gui_draw_frect( wnd->x + 1, wnd->y + 1, wnd->w - 2, wnd->h - 2, ui_clr.bg ); + + //gui_draw_str( wnd->x + 2, wnd->y + 2, ALIGN_LEFT, FNT_JPN12, ui_clr.txt, "texture list" ); + gui_editor_texturepicker_draw_files( wnd ); + + if( !wnd->loaded ) { + gui_draw_str( + wnd->x + wnd->w - 2, + wnd->y, + ALIGN_R, + FNT_JPN12, + ui_clr.txt, + "loading... [%d/%d]", wnd->curload + 1, wnd->files.size + ); + } + + wnd->children.each( fn( GUI_BASE** childptr ) { + GUI_BASE* child = *childptr; + if( !child->enabled ) return; + if( child->draw_fn ) + child->draw_fn( child ); + } ); +} + +void gui_editor_textureview_delete_textures( GUI_EDITOR_TEXTUREPICKER* wnd ) { + wnd->textures.each( fn( GL_TEX2D** ptr ) { + GL_TEX2D* tex = *ptr; + gl_texture_destroy( _gui.gl2d->gl, tex ); + } ); + + wnd->textures.clear(); +} + +void gui_editor_texturepicker_load_texture( void* ptr ) { + GUI_EDITOR_TEXTUREPICKER* wnd = (GUI_EDITOR_TEXTUREPICKER*)ptr; + + FILE_ENTRY* f = &wnd->files[wnd->curload]; + const char* name = assets_abspath( f->name ); + GL_TEX2D* tex = gl_texture_from_file( _gui.gl2d->gl, name ); + if( !tex ) { + dlog( "gui_editor_textureview_create_textures() : error creating texture %s", name ); + return; + } + wnd->textures.push( tex ); + + if( ++wnd->curload < wnd->files.size ) + gui_push_callback( wnd, gui_editor_texturepicker_load_texture ); + else + wnd->loaded = 1; +} + +void gui_editor_textureview_create_textures( GUI_EDITOR_TEXTUREPICKER* wnd ) { + wnd->curload = 0; + GL_TEX2D* emptytex = gl_texture_create( _gui.gl2d->gl, "" ); + strcpy( emptytex->name, "none" ); + emptytex->width = emptytex->height = 1; + + wnd->textures.push( emptytex ); + + gui_push_callback( wnd, gui_editor_texturepicker_load_texture ); +} + +void gui_editor_textureview_accept( GUI_EDITOR_TEXTUREPICKER* wnd ) { + if( !wnd->curselect ) + return; + + U8 isnone = !strcmp( wnd->curselect->name, "none" ); + wnd->textures.each( fn( GL_TEX2D** ptr ) { + GL_TEX2D* tex = *ptr; + if( !isnone && tex == wnd->curselect ) + return; + + gl_texture_destroy( _gui.gl2d->gl, tex ); + } ); + + if( !isnone ) { + if( map_add_texture_ref( editor->map, wnd->curselect ) == STAT_ALREADYEXISTS ) { + const char* name = assets_abspath( wnd->curselect->name ); + GL_TEX2D* tex = map_find_texture( editor->map, name ); + *wnd->target = tex; + + gl_texture_destroy( _gui.gl2d->gl, wnd->curselect ); + } else { + *wnd->target = wnd->curselect; + } + } else { + *wnd->target = 0; + } + + if( wnd->cb ) + wnd->cb( wnd ); + + wnd->textures.clear(); + gui_push_callback( wnd, pfn( void* p ) { gui_free( (GUI_BASE*)p ); } ); +} + +U8 gui_editor_texturepicker_input_files( GUI_EDITOR_TEXTUREPICKER* wnd ) { + I32 x = wnd->x + 11; + I32 y = wnd->y + TEXTUREVIEW_TOP_OFFSET; + I32 w = wnd->w - 22; + I32 size = (w + 10) / wnd->rowcount - 10; + + I32 colcnt = (I32)floorf( (F32)w / size ); + I32 i = 0; + + U8 m1 = gui_mbutton_down( 0 ); + I32 mx, my; + gui_cursor_pos( &mx, &my ); + + wnd->textures.each( fn( GL_TEX2D** ptr ) { + GL_TEX2D* tex = *ptr; + + // yea technically this is slow as fuck but whatever + if( strlen( wnd->search ) > 0 && !strstr( tex->name, wnd->search ) ) + return; + + I32 tx = x + (i % colcnt) * (size + 10); + I32 ty = y + (i / colcnt) * (size + 18); + + U8 inbounds = ( (mx >= tx) && (mx < tx + size) && (my >= ty) && (my < ty + size + 18) ); + if( inbounds && m1 && !wnd->heldselect ) { + wnd->heldselect = tex; + } + if( !m1 && wnd->heldselect == tex ) { + wnd->curselect = tex; + } + + ++i; + } ); + + if( !m1 ) { + wnd->heldselect = 0; + } + + return 0; +} + +void gui_editor_texturepicker_input_fn( void* ptr ) { + GUI_EDITOR_TEXTUREPICKER* wnd = (GUI_EDITOR_TEXTUREPICKER*)ptr; + + if( !gui_editor_texturepicker_input_files( wnd ) ) + gui_base_input_fn( ptr ); +} + +void gui_editor_texturepicker_update_density( GUI_EDITOR_TEXTUREPICKER* wnd ) { + wnd->scrollheight = gui_editor_texturepicker_get_scrollheight( wnd ); + sprintf( wnd->densitylabel->name, "density: %d", wnd->rowcount ); +} + +void gui_editor_textureview_create_bottom_row( GUI_EDITOR_TEXTUREPICKER* wnd ) { + I32 x = 10; + + gui_label( x, 2, "filter:" ); + GUI_TEXTBOX* tb = gui_textbox( x + 50, -15, 92, 20, "", 256 ); + tb->cb = pfn( void* ptr ) { + GUI_TEXTBOX* tb = (GUI_TEXTBOX*)ptr; + GUI_EDITOR_TEXTUREPICKER* wnd = (GUI_EDITOR_TEXTUREPICKER*)tb->parent->parent; + strcpy( wnd->search, tb->value ); + }; + x += 150; + + wnd->densitylabel = gui_label( x, 2, "density: %d", wnd->rowcount ); + gui_button( x + 66, 0, 20, 20, "+", pfn( void* ptr ) { + GUI_BUTTON* btn = (GUI_BUTTON*)ptr; + GUI_EDITOR_TEXTUREPICKER* wnd = (GUI_EDITOR_TEXTUREPICKER*)btn->parent->parent; + if( wnd->rowcount < 9 ) wnd->rowcount++; + gui_editor_texturepicker_update_density( wnd ); + } ); + + gui_button( x + 91, 0, 20, 20, "-", pfn( void* ptr ) { + GUI_BUTTON* btn = (GUI_BUTTON*)ptr; + GUI_EDITOR_TEXTUREPICKER* wnd = (GUI_EDITOR_TEXTUREPICKER*)btn->parent->parent; + if( wnd->rowcount > 3 ) wnd->rowcount--; + gui_editor_texturepicker_update_density( wnd ); + } ); + + gui_button( wnd->w - 120, 0, 50, 20, "cancel", pfn( void* ptr ) { + GUI_BUTTON* btn = (GUI_BUTTON*)ptr; + GUI_EDITOR_TEXTUREPICKER* wnd = (GUI_EDITOR_TEXTUREPICKER*)btn->parent->parent; + gui_editor_textureview_delete_textures( wnd ); + gui_push_callback( wnd, pfn( void* p ) { gui_free( (GUI_BASE*)p ); } ); + } ); + + gui_button( wnd->w - 60, 0, 50, 20, "ok", pfn( void* ptr ) { + GUI_BUTTON* btn = (GUI_BUTTON*)ptr; + GUI_EDITOR_TEXTUREPICKER* wnd = (GUI_EDITOR_TEXTUREPICKER*)btn->parent->parent; + gui_editor_textureview_accept( wnd ); + } ); +} + +void gui_editor_texturepicker_init( void* ptr ) { + GUI_EDITOR_TEXTUREPICKER* wnd = (GUI_EDITOR_TEXTUREPICKER*)ptr; + + wnd->files = assets_get_files_by_ext( TEXTURE_EXTENSIONS ); + gui_editor_textureview_create_textures( wnd ); +} + +GUI_EDITOR_TEXTUREPICKER* gui_editor_texturepicker( I32 x, I32 y, I32 w, I32 h, GL_TEX2D **target ) { + GUI_EDITOR_TEXTUREPICKER* wnd = new GUI_EDITOR_TEXTUREPICKER; + wnd->x = x; + wnd->y = y; + wnd->w = w; + wnd->h = h; + wnd->ontop = 1; + wnd->locked = 0; + wnd->draw_fn = gui_editor_texturepicker_draw_fn; + wnd->input_fn = gui_editor_texturepicker_input_fn; + strcpy( wnd->name, "TEXTURE_PICKER_WINDOW" ); + wnd->cb = 0; + + _gui.windows.push( wnd ); + gui_set_window( wnd ); + + wnd->target = target; + wnd->rowcount = 3; + wnd->curselect = 0; + wnd->scrolloff = 0; + wnd->scrollheight = gui_editor_texturepicker_get_scrollheight( wnd ); + wnd->search[0] = 0; + + wnd->loaded = 0; + gui_push_callback( wnd, gui_editor_texturepicker_init ); + + GUI_VIEW* view = gui_get_view(); + + gui_set_view( 0 ); + gui_view( 0, 0, w, 20 ); + gui_title( "texture picker" ); + gui_set_view( 0 ); + + gui_view( 0, h - TEXTUREVIEW_BOTTOM_OFFSET - 1, w, TEXTUREVIEW_BOTTOM_OFFSET ); + gui_editor_textureview_create_bottom_row( wnd ); + + gui_set_view( view ); + + return wnd; +} diff --git a/src/editor/view2d.cpp b/src/editor/view2d.cpp new file mode 100644 index 0000000..35fd208 --- /dev/null +++ b/src/editor/view2d.cpp @@ -0,0 +1,938 @@ +#include +#include "editor.h" +#include "../render/gl_2d.h" + +#include "../game/objlist.h" + +const I32 EDITORVIEW_TITLE_OFFSET = 15; +const I32 EDITORVIEW_GUTTERS_OFFSETX = 22; +const I32 EDITORVIEW_GUTTERS_OFFSETY = 16; + +F32 gui_editor_2dview_calc_scale( GUI_EDITOR_2DVIEW* view ) { + WORLD_MAP* m = editor->map; + + I32 w = view->w - EDITORVIEW_GUTTERS_OFFSETX - 1; + I32 h = view->h - EDITORVIEW_GUTTERS_OFFSETY - 1; + + F32 mapw = m->maxs.x - m->mins.x; + F32 maph = m->maxs.y - m->mins.y; + + F32 scale = min( w / mapw, h / maph ); + return scale * view->scale; +} + +VEC2 gui_editor_2dview_screen_to_world( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { + WORLD_MAP* m = editor->map; + + 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; + + scaledx /= view->scale; + scaledx += view->posx; + scaledy /= view->scale; + scaledy += view->posy; + + return { scaledx, scaledy }; +} + +VEC2 gui_editor_2dview_world_to_screen( GUI_EDITOR_2DVIEW* view, VEC2 world ) { + WORLD_MAP* m = editor->map; + F32 scale = gui_editor_2dview_calc_scale( view ); + + F32 xoff = m->mins.x + view->posx; + F32 yoff = m->mins.y + view->posy; + + F32 vx = (F32)gui_relx( view ); + F32 vy = (F32)gui_rely( view ); + vy += EDITORVIEW_TITLE_OFFSET + EDITORVIEW_GUTTERS_OFFSETY; + vx += EDITORVIEW_GUTTERS_OFFSETX; + + F32 x = vx + (world.x - xoff) * scale; + F32 y = vy + (world.y - yoff) * scale; + + return { x, y }; +} + +void gui_editor_2dview_select( GUI_EDITOR_2DVIEW* view, void* what, U8 seltype ) { + view->curselect = what; + view->seltype = seltype; +} + +U8 gui_editor_2dview_is_gizmo_active( GUI_EDITOR_2DVIEW* view, void* what, U8 seltype ) { + GUI_EDITOR_PROPVIEW* props = editor->gui.props; + if( props->seltype == EDITOR_SELECT_WALL && (seltype == EDITOR_SELECT_WVERTEX) ) { + MAP_WALL* s = (MAP_WALL*)props->curselect; + if( what == &s->start || what == &s->end ) + return 1; + } + else if( props->seltype == seltype && props->curselect == what ) { + return 1; + } + + if( view->curdrag ) { + return what == view->curdrag && seltype == view->dragtype; + } + else { + return what == view->curselect && seltype == view->seltype; + } +} + +void gui_editor_2dview_draw_gizmo( I32 x, I32 y, CLR clr, U8 selected = 0 ) { + const I32 GIZMO_SIZE = selected ? 10 : 6; + const I32 ihalf = GIZMO_SIZE / 2; + + gui_draw_frect( x - ihalf - 1, y - ihalf - 1, GIZMO_SIZE, GIZMO_SIZE, { .7f, .7f, .7f, 1.f } ); + gui_draw_frect( x - ihalf + 1, y - ihalf + 1, GIZMO_SIZE, GIZMO_SIZE, { 0.f, 0.f, 0.f, 1.f } ); + gui_draw_frect( x - ihalf, y - ihalf, GIZMO_SIZE, GIZMO_SIZE, clr ); +} + +void gui_editor_2dview_draw_gutters( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { + WORLD_MAP* m = editor->map; + F32 w = view->w; + F32 h = view->h; + + gui_draw_str( x + 2, y, ALIGN_L, FNT_JPN12, ui_clr.txt, "%1.f,%1.f", view->posx, view->posy ); + for( U32 i = 1; i <= 5; ++i ) { + F32 tx = x + i * (w / 5); + F32 ty = y; + + F32 tp = (tx - x) / w; + + F32 scaledx = m->mins.x + (m->maxs.x - m->mins.x) * tp; + if( w > h ) + scaledx *= ( w / h ); + scaledx /= view->scale; + scaledx += view->posx; + gui_draw_str( tx - 2, ty, ALIGN_R, FNT_JPN12, ui_clr.txt, "%1.f", scaledx ); + gui_draw_line( tx, ty, tx + 1, ty + 16, ui_clr.txt ); + } + + for( U32 i = 1; i <= 5; ++i ) { + F32 tx = x + 2; + F32 ty = y + i * (h / 5); + + F32 tp = (ty - y) / h; + + F32 scaledy = m->mins.y + (m->maxs.y - m->mins.y) * tp; + if( w < h ) + scaledy *= h / w; + scaledy /= view->scale; + scaledy += view->posy; + gui_draw_str( tx, ty - 16, ALIGN_L, FNT_JPN12, ui_clr.txt, "%1.f", scaledy ); + gui_draw_line( tx - 2, ty, tx + 20, ty, ui_clr.txt ); + } +} + +void gui_editor_2dview_draw_polygons( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { + WORLD_MAP* m = editor->map; + + F32 scale = gui_editor_2dview_calc_scale( view ); + F32 xoff = m->mins.x + view->posx; + F32 yoff = m->mins.y + view->posy; + + m->polygons.each( fn( MAP_POLYGON* p ) { + SURF_PROPS* props = polygon_get_props( m, p ); + if( !editor->wireframe ) { + LIST vertices; + p->vertices.each( fn( MAP_VERTEX* v ) { + VERTEX v2; + v2.uv = v->uv; + v2.pos.x = x + (v->pos.x - xoff) * scale; + v2.pos.y = y + (v->pos.y - yoff) * scale; + v2.clr = props->clr; + vertices.push( v2 ); + } ); + + if( props->tex ) + gl_textured_polygon( _gui.gl2d_font, vertices.data, vertices.size, props->tex ); + else + gl_polygon( _gui.gl2d, vertices.data, vertices.size ); + } + else { + for( U32 i = 0; i < p->vertices.size; ++i ) { + MAP_VERTEX* v1 = &p->vertices[i]; + MAP_VERTEX* v2 = &p->vertices[(i+1) % p->vertices.size]; + + I32 x0 = x + (I32)( (v1->pos.x - xoff) * scale ); + I32 y0 = y + (I32)( (v1->pos.y - yoff) * scale ); + I32 x1 = x + (I32)( (v2->pos.x - xoff) * scale ); + I32 y1 = y + (I32)( (v2->pos.y - yoff) * scale ); + + gui_draw_line( x0, y0, x1, y1, props->clr ); + } + } + + if( editor->tool != EDITOR_TOOL_POLY && editor->tool != EDITOR_TOOL_SELECT ) + return; + // draw gizmos + p->vertices.each( fn( MAP_VERTEX* v ) { + I32 vx = (I32)( x + (v->pos.x - xoff) * scale ); + I32 vy = (I32)( y + (v->pos.y - yoff) * scale ); + U8 selected = gui_editor_2dview_is_gizmo_active( view, v, EDITOR_SELECT_PVERTEX ) + || gui_editor_2dview_is_gizmo_active( view, p, EDITOR_SELECT_POLY ); + + gui_editor_2dview_draw_gizmo( vx, vy, CLR::CYAN(), selected ); + } ); + } ); +} + +void gui_editor_2dview_draw_walls( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { + WORLD_MAP* m = editor->map; + + F32 scale = gui_editor_2dview_calc_scale( view ); + F32 xoff = m->mins.x + view->posx; + F32 yoff = m->mins.y + view->posy; + + m->walls.each( fn( MAP_WALL* s ) { + SURF_PROPS* props = wall_get_props( m, s ); + + I32 x0 = (I32)( x + (s->start.x - xoff) * scale ); + I32 y0 = (I32)( y + (s->start.y - yoff) * scale ); + I32 x1 = (I32)( x + (s->end.x - xoff) * scale ); + I32 y1 = (I32)( y + (s->end.y - yoff) * scale ); + + gui_draw_line( x0, y0, x1, y1, props->clr ); + if( gui_editor_2dview_is_gizmo_active( view, s, EDITOR_SELECT_WALL ) ) { + gui_draw_line( x0, y0 - 1, x1, y1 - 1, CLR::WHITE( .5f ) ); + gui_draw_line( x0, y0 + 1, x1, y1 + 1, CLR::WHITE( .5f ) ); + gui_draw_line( x0 - 1, y0, x1 - 1, y1, CLR::WHITE( .5f ) ); + gui_draw_line( x0 + 1, y0, x1 + 1, y1, CLR::WHITE( .5f ) ); + } + } ); + + if( editor->tool != EDITOR_TOOL_WALL && editor->tool != EDITOR_TOOL_SELECT ) + return; + + // gizmos + m->walls.each( fn( MAP_WALL* s ) { + I32 x0 = (I32)( x + (s->start.x - xoff) * scale ); + I32 y0 = (I32)( y + (s->start.y - yoff) * scale ); + I32 x1 = (I32)( x + (s->end.x - xoff) * scale ); + I32 y1 = (I32)( y + (s->end.y - yoff) * scale ); + + U8 sel = gui_editor_2dview_is_gizmo_active( view, s, EDITOR_SELECT_WALL ); + U8 sel1 = gui_editor_2dview_is_gizmo_active( view, &s->start, EDITOR_SELECT_WVERTEX ); + U8 sel2 = gui_editor_2dview_is_gizmo_active( view, &s->end, EDITOR_SELECT_WVERTEX ); + + gui_editor_2dview_draw_gizmo( x0, y0, CLR::GREEN(), sel || sel1 ); + gui_editor_2dview_draw_gizmo( x1, y1, CLR::GREEN(), sel || sel2 ); + } ); +} + +void gui_editor_2dview_draw_sprites( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { + WORLD_MAP* m = editor->map; + + F32 scale = gui_editor_2dview_calc_scale( view ); + F32 xoff = m->mins.x + view->posx; + F32 yoff = m->mins.y + view->posy; + + m->sprites.each( fn( MAP_SPRITE* s ) { + F32 wantedsize = editor->spritesize; + F32 aspect = s->size.x / s->size.y; + F32 w = (aspect > 1.0f)? wantedsize : wantedsize * aspect; + F32 h = (aspect < 1.0f)? wantedsize : wantedsize / aspect; + VEC2 pos = { + (F32)x + (s->pos.x - xoff) * scale, + (F32)y + (s->pos.y - yoff) * scale + }; + + U8 sel= gui_editor_2dview_is_gizmo_active( view, s, EDITOR_SELECT_SPRITE ); + if( sel ) { + gui_draw_rect( pos.x - w/2 - 1, pos.y - h/2 - 1, w + 2, h + 2, CLR::WHITE( 0.5f ) ); + } + + gl_2d_textured_frect( + _gui.gl2d_font, + { (F32)((I32)pos.x - w/2), (F32)((I32)pos.y - h/2) }, + { (F32)w, (F32)h }, + s->tex, + s->clr + ); + } ); +} + +void gui_editor_2dview_draw_player( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { + WORLD_MAP* m = editor->map; + + if( !objl->pl ) + return; + + F32 scale = gui_editor_2dview_calc_scale( view ); + F32 xoff = m->mins.x + view->posx; + F32 yoff = m->mins.y + view->posy; + + VEC3 pos = objl->pl->pos; + + VEC2 pos2d = { + (F32)x + (pos.x - xoff) * scale, + (F32)y + (pos.y - yoff) * scale + }; + + F32 yaw = objl->pl->rot.y; + VEC2 dir = m_radial_offset( yaw, 20.f ); + VEC2 ray = pos2d + dir; + + gui_draw_line( (I32)pos2d.x, (I32)pos2d.y, (I32)ray.x, (I32)ray.y, CLR::GREEN() ); + gui_draw_frect( (I32)pos2d.x-4, (I32)pos2d.y-4, 8, 8, CLR::WHITE() ); + gui_draw_frect( (I32)pos2d.x-3, (I32)pos2d.y-3, 6, 6, CLR::RED() ); +} + +void gui_editor_2dview_draw_origin( GUI_EDITOR_2DVIEW* view, I32 x, I32 y ) { + WORLD_MAP* m = editor->map; + + F32 scale = gui_editor_2dview_calc_scale( view ); + F32 xoff = m->mins.x + view->posx; + F32 yoff = m->mins.y + view->posy; + + VEC3 pos = m->startpos; + I32 screenx = (I32)( x + (pos.x - xoff) * scale ); + I32 screeny = (I32)( y + (pos.y - yoff) * scale ); + + if( gui_editor_2dview_is_gizmo_active( view, m, EDITOR_SELECT_ORIGIN ) ) { + I32 w, h; + gui_draw_get_str_bounds( &w, &h, FNT_JPN12, "(origin)" ); + gui_draw_frect( screenx - w / 2 - 1, screeny + h - 1, w, 1, CLR::BLACK() ); + gui_draw_frect( screenx - w / 2, screeny + h, w, 1, ui_clr.txt ); + gui_draw_str( screenx - 1, screeny - 1, ALIGN_C, FNT_JPN12, CLR::WHITE( .5f ), "(origin)" ); + } + + gui_draw_str( screenx + 1, screeny + 1, ALIGN_C, FNT_JPN12, CLR::BLACK(), "(origin)" ); + gui_draw_str( screenx, screeny, ALIGN_C, FNT_JPN12, ui_clr.txt, "(origin)" ); +} + +void gui_editor_2dview_draw_fn( void* ptr ) { + GUI_EDITOR_2DVIEW* view = (GUI_EDITOR_2DVIEW*)ptr; + if( !editor->map ) + return; + + I32 x = gui_relx( view ); + I32 y = gui_rely( view ); + I32 w = view->w; + I32 h = view->h; + + gui_draw_str( x, y, ALIGN_L, FNT_JPN12, ui_clr.txt, "2d view" ); + y += EDITORVIEW_TITLE_OFFSET; + + CLR col = gui_is_fg_window( view )? ui_clr.border : ui_clr.border_inactive; + gui_draw_frect( x-1, y-1, view->w+2, view->h+2, col ); + gui_draw_frect( x, y, view->w, view->h, CLR::BLACK() ); + + gui_editor_2dview_draw_gutters( view, x, y ); + + U32 offx = EDITORVIEW_GUTTERS_OFFSETX; + U32 offy = EDITORVIEW_GUTTERS_OFFSETY; + + gui_draw_push_clip( x + offx, y + offy, w - offx, h - offy ); + gui_editor_2dview_draw_polygons( view, x + offx, y + offy ); + gui_editor_2dview_draw_walls( view, x + offx, y + offy ); + 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_draw_pop_clip(); +} + +U8 gui_editor_2dview_input_drag( GUI_EDITOR_2DVIEW* view, U8 mouse ) { + if( !mouse ) { + view->dragheld = 0; + return 0; + } + + I32 mx, my; + gui_cursor_pos( &mx, &my ); + + I32 x = gui_relx( view ); + I32 y = gui_rely( view ) + EDITORVIEW_TITLE_OFFSET; + I32 w = view->w - EDITORVIEW_GUTTERS_OFFSETX; + I32 h = view->h - EDITORVIEW_GUTTERS_OFFSETY; + x += EDITORVIEW_GUTTERS_OFFSETX; + y += EDITORVIEW_GUTTERS_OFFSETY; + + U8 inbound = mx >= x && mx <= x + w && my >= y && my <= y + h; + if( !inbound && !view->dragheld ) + return 0; + + I32 moffx = mx - x; + I32 moffy = my - y; + + if( !view->dragheld ) { + view->moffx = moffx; + view->moffy = moffy; + view->voffx = view->posx; + view->voffy = view->posy; + view->dragheld = 1; + return 1; + } + + F32 iscale = 1.f / gui_editor_2dview_calc_scale( view ); + + view->posx = view->voffx - (moffx - view->moffx) * iscale; + view->posy = view->voffy - (moffy - view->moffy) * iscale; + return 1; +} + +void gui_editor_2dview_input_tool_none( GUI_EDITOR_2DVIEW* view ) { + U8 mouse = gui_mbutton_down( 0 ) || gui_mbutton_down( 1 ); + + gui_editor_2dview_input_drag( view, mouse ); +} + +VEC2 gui_editor_2dview_input_get_drag_vec( GUI_EDITOR_2DVIEW* view ) { + I32 mx, my; + gui_cursor_pos( &mx, &my ); + + F32 iscale = 1.f / gui_editor_2dview_calc_scale( view ); + F32 dx = (mx - view->oldmx) * iscale + view->mremainx; + F32 dy = (my - view->oldmy) * iscale + view->mremainy; + + F32 igridx = copysignf( floorf( fabsf( dx / editor->grid ) ), dx ); + F32 igridy = copysignf( floorf( fabsf( dy / editor->grid ) ), dy ); + + return { editor->grid * igridx, editor->grid * igridy }; +} + +void gui_editor_2dview_input_select_onmove( GUI_EDITOR_2DVIEW* view ) { + I32 mx, my; + gui_cursor_pos( &mx, &my ); + + F32 iscale = 1.f / gui_editor_2dview_calc_scale( view ); + F32 dx = (mx - view->oldmx) * iscale + view->mremainx; + F32 dy = (my - view->oldmy) * iscale + view->mremainy; + + F32 rmx = copysignf( fmodf( fabsf( dx ), editor->grid ), dx ); + F32 rmy = copysignf( fmodf( fabsf( dy ), editor->grid ), dy ); + + view->oldmx = mx; + view->oldmy = my; + view->mremainx = rmx; + view->mremainy = rmy; + view->dragmoved = 1; + + GUI_EDITOR_PROPVIEW* props = editor->gui.props; + // special case for dragging wall vertices, just always update + U8 iswallv = props->seltype == EDITOR_SELECT_WALL && view->dragtype == EDITOR_SELECT_WVERTEX; + if( props->curselect == view->curdrag || iswallv ) + gui_editor_propview_update( editor->gui.props ); +} + +void gui_editor_2dview_input_select_drag_wall( GUI_EDITOR_2DVIEW* view ) { + MAP_WALL* s = (MAP_WALL*)view->curdrag; + + s->start.x = m_snap_to_grid( s->start.x, editor->grid ); + s->start.y = m_snap_to_grid( s->start.y, editor->grid ); + s->end.x = m_snap_to_grid( s->end.x, editor->grid ); + s->end.y = m_snap_to_grid( s->end.y, editor->grid ); + + VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); + if( !is_zero( mv ) ) { + s->start += mv; + s->end += mv; + map_check_bounds( editor->map ); + + gui_editor_2dview_input_select_onmove( view ); + } +} + +void gui_editor_2dview_input_select_drag_vertex( GUI_EDITOR_2DVIEW* view ) { + VEC2* v = (VEC2*)view->curdrag; + v->x = m_snap_to_grid( v->x, editor->grid ); + v->y = m_snap_to_grid( v->y, editor->grid ); + + VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); + if( !is_zero( mv ) ) { + *v += mv; + map_check_bounds( editor->map ); + + gui_editor_2dview_input_select_onmove( view ); + } +} + +void gui_editor_2dview_input_select_drag_polygon( GUI_EDITOR_2DVIEW* view ) { + MAP_POLYGON* p = (MAP_POLYGON*)view->curdrag; + + VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); + if( !is_zero( mv ) ) { + p->vertices.each( fn( MAP_VERTEX* v ) { + v->pos.x = m_snap_to_grid( v->pos.x, editor->grid ); + v->pos.y = m_snap_to_grid( v->pos.y, editor->grid ); + + v->pos.x += mv.x; + v->pos.y += mv.y; + } ); + + map_polygon_calc_bounds( p ); + map_check_bounds( editor->map ); + gui_editor_2dview_input_select_onmove( view ); + } +} + +void gui_editor_2dview_input_select_drag_sprite( GUI_EDITOR_2DVIEW* view ) { + MAP_SPRITE* s = (MAP_SPRITE*)view->curdrag; + + s->pos.x = m_snap_to_grid( s->pos.x, editor->grid ); + s->pos.y = m_snap_to_grid( s->pos.y, editor->grid ); + + VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); + if( !is_zero( mv ) ) { + s->pos += mv; + gui_editor_2dview_input_select_onmove( view ); + } +} + +void gui_editor_2dview_input_select_drag_origin( GUI_EDITOR_2DVIEW* view ) { + WORLD_MAP* m = editor->map; + + m->startpos.x = m_snap_to_grid( m->startpos.x, editor->grid ); + m->startpos.y = m_snap_to_grid( m->startpos.y, editor->grid ); + + VEC2 mv = gui_editor_2dview_input_get_drag_vec( view ); + if( !is_zero( mv ) ) { + m->startpos += mv; + gui_editor_2dview_input_select_onmove( view ); + } +} + +void gui_editor_2dview_input_select_ondrop( GUI_EDITOR_2DVIEW* view ) { + if( !view->dragmoved ) + gui_editor_propview_select( editor->gui.props, view->curdrag, view->dragtype ); + + view->curdrag = 0; +} + +void gui_editor_2dview_input_select_drag( GUI_EDITOR_2DVIEW* view ) { + U8 m1 = gui_mbutton_down( 0 ); + I32 mx, my; + gui_cursor_pos( &mx, &my ); + + if( !m1 ) { + if( view->held ) { + gui_editor_2dview_input_select_ondrop( view ); + view->held = 0; + } + return; + } + + if( !view->held ) { + view->curdrag = view->curselect; + view->dragtype = view->seltype; + view->oldmx = mx; + view->oldmy = my; + view->mremainx = view->mremainy = 0; + view->dragmoved = 0; + view->held = 1; + return; + } + + switch( view->dragtype ) { + case EDITOR_SELECT_WALL: gui_editor_2dview_input_select_drag_wall( view ); break; + case EDITOR_SELECT_POLY: gui_editor_2dview_input_select_drag_polygon( view ); break; + case EDITOR_SELECT_SPRITE: gui_editor_2dview_input_select_drag_sprite( view ); break; + case EDITOR_SELECT_ORIGIN: gui_editor_2dview_input_select_drag_origin( view ); break; + case EDITOR_SELECT_WVERTEX: + case EDITOR_SELECT_PVERTEX: + gui_editor_2dview_input_select_drag_vertex( view ); break; + default: { + view->oldmx = mx; + view->oldmy = my; + } break; + } +} + +void gui_editor_2dview_input_select_walls( GUI_EDITOR_2DVIEW* view ) { + WORLD_MAP* m = editor->map; + + I32 mx, my; + gui_cursor_pos( &mx, &my ); + + VEC2 mpos = { (F32)mx, (F32)my }; + + F32 mindist = 5.0001f; + for( U32 i = 0; i < m->walls.size; ++i ) { + MAP_WALL* w = &m->walls[i]; + + VEC2 w1 = { w->start.x, w->start.y }; + VEC2 w2 = { w->end.x, w->end.y }; + w1 = gui_editor_2dview_world_to_screen( view, w1 ); + w2 = gui_editor_2dview_world_to_screen( view, w2 ); + + F32 d1 = vec_dist( mpos, w1 ); + F32 d2 = vec_dist( mpos, w2 ); + if( d1 < mindist && d1 < d2 ) { + gui_editor_2dview_select( view, &w->start, EDITOR_SELECT_WVERTEX ); + mindist = d1; + continue; + } + else if( d2 < mindist ) { + gui_editor_2dview_select( view, &w->end, EDITOR_SELECT_WVERTEX ); + mindist = d2; + continue; + } + // give priority to vertices + if( view->seltype == EDITOR_SELECT_WVERTEX ) + continue; + + F32 dist = m_dist_line_to_point( w1, w2, mpos ); + if( dist < 3.f && dist < mindist ) { + gui_editor_2dview_select( view, w, EDITOR_SELECT_WALL ); + mindist = dist; + } + } + + if( !view->curselect && !view->held ) + return; + + gui_editor_2dview_input_select_drag( view ); +} + +void gui_editor_2dview_input_select_polygons( GUI_EDITOR_2DVIEW* view ) { + WORLD_MAP* m = editor->map; + + I32 mx, my; + gui_cursor_pos( &mx, &my ); + + VEC2 mpos = { (F32)mx, (F32)my }; + + F32 mindist = 5.0001f; + for( U32 i = 0; i < m->polygons.size; ++i ) { + MAP_POLYGON* p = &m->polygons[i]; + LIST plist; + + p->vertices.each( fn( MAP_VERTEX* v ) { + VEC2 world = { v->pos.x, v->pos.y }; + VEC2 screen = gui_editor_2dview_world_to_screen( view, world ); + F32 d = vec_dist( mpos, screen ); + if( d < mindist ) { + gui_editor_2dview_select( view, v, EDITOR_SELECT_PVERTEX ); + mindist = d; + } + + plist.push( screen ); + } ); + + if( view->seltype == EDITOR_SELECT_PVERTEX ) + continue; + + if( m_point_in_polygon( mpos, plist.data, plist.size ) ) + gui_editor_2dview_select( view, p, EDITOR_SELECT_POLY ); + } + + if( !view->curselect && !view->held ) + return; + + gui_editor_2dview_input_select_drag( view ); +} + +void gui_editor_2dview_input_select_sprites( GUI_EDITOR_2DVIEW* view ) { + WORLD_MAP* m = editor->map; + + I32 mx, my; + gui_cursor_pos( &mx, &my ); + + VEC2 mpos = { (F32)mx, (F32)my }; + + F32 hsize = editor->spritesize * .5f; + F32 mindist = editor->spritesize; + for( U32 i = 0; i < m->sprites.size; ++i ) { + MAP_SPRITE* s = &m->sprites[i]; + + VEC2 world = { s->pos.x, s->pos.y }; + VEC2 screen = gui_editor_2dview_world_to_screen( view, world ); + + if( mpos.x >= screen.x - hsize && mpos.x <= screen.x + hsize + && mpos.y >= screen.y - hsize && mpos.y <= screen.y + hsize ) + { + F32 dist = vec_dist( mpos, screen ); + if( dist < mindist ) { + mindist = dist; + gui_editor_2dview_select( view, s, EDITOR_SELECT_SPRITE ); + } + } + } + + if( !view->curselect && !view->held ) + return; + + gui_editor_2dview_input_select_drag( view ); +} + +void gui_editor_2dview_input_select_origin( GUI_EDITOR_2DVIEW* view ) { + WORLD_MAP* m = editor->map; + + I32 mx, my; + gui_cursor_pos( &mx, &my ); + + VEC2 mpos = { (F32)mx, (F32)my }; + VEC2 world = { m->startpos.x, m->startpos.y }; + VEC2 screen = gui_editor_2dview_world_to_screen( view, world ); + + I32 w, h; + gui_draw_get_str_bounds( &w, &h, FNT_JPN12, "(origin)" ); + + if( fabsf( (F32)mx - screen.x ) < (w * .5f + 2.f) + && fabsf( (F32)my - h * .5f - screen.y ) < (h * .5f + 2.f) ) { + gui_editor_2dview_select( view, m, EDITOR_SELECT_ORIGIN ); + } + + if( !view->curselect && !view->held ) + return; + + gui_editor_2dview_input_select_drag( view ); +} + +void gui_editor_2dview_input_tool_select( GUI_EDITOR_2DVIEW* view ) { + U8 m1 = gui_mbutton_down( 0 ); + U8 m2 = !m1 && gui_mbutton_down( 1 ); + + if( gui_editor_2dview_input_drag( view, m2 ) ) { + return; + } + + view->curselect = 0; + view->seltype = EDITOR_SELECT_NONE; + gui_editor_2dview_input_select_origin( view ); + if( view->curselect ) return; + gui_editor_2dview_input_select_sprites( view ); + if( view->curselect ) return; + gui_editor_2dview_input_select_walls( view ); + if( view->curselect ) return; + gui_editor_2dview_input_select_polygons( view ); +} + +void gui_editor_2dview_input_scroll( GUI_EDITOR_2DVIEW* view ) { + U8 scroll = gui_mbutton_down( GUI_MBTNSCROLL ); + gui_capture_scroll(); + + if( scroll == (U8)-1 && view->scale > 0.5f ) { + view->scale *= 0.75f; + } + else if( scroll == 1 && view->scale < 16.f ) { + view->scale *= 1.25f; + } +} + +void gui_editor_2dview_input_tool_wall( 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_WALL neww; + neww.start = neww.end = { + m_snap_to_grid( world.x, editor->grid ), + m_snap_to_grid( world.y, editor->grid ), + 0 + }; + neww.end.z = 80.f; + neww.propid = 0; + + I32 idx = editor->map->walls.size; + editor->map->walls.push( neww ); + view->curdrag = &editor->map->walls[idx].end; + view->held = 1; + + view->oldmx = mx; + view->oldmy = my; + view->mremainx = 0.0; + view->mremainy = 0.0; + return; + } + + gui_editor_2dview_input_select_drag_vertex( view ); +} + +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; + 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 ); + } + + 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 ); + } +} + +void gui_editor_2dview_input_tool_ent( GUI_EDITOR_2DVIEW* view ) { + +} + +void gui_editor_2dview_input_tool_sprite( 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_SPRITE news; + news.pos = { world.x, world.y, 20.f }; + news.size = { 20.f, 20.f }; + news.clr = { 1.f, 1.f, 1.f, 1.f }; + news.tex = 0; + + I32 idx = editor->map->walls.size; + editor->map->sprites.push( news ); + view->curdrag = &editor->map->sprites[idx]; + view->held = 1; + + view->oldmx = mx; + view->oldmy = my; + view->mremainx = 0.0; + view->mremainy = 0.0; + return; + } + + gui_editor_2dview_input_select_drag_sprite( view ); +} + + +void gui_editor_2dview_input_tool_draw( GUI_EDITOR_2DVIEW* view ) { + U8 m1 = gui_mbutton_down( 0 ); + U8 m2 = !m1 && gui_mbutton_down( 1 ); + + if( gui_editor_2dview_input_drag( view, m2 ) ) { + return; + } + + switch( editor->tool ) { + case EDITOR_TOOL_WALL: return gui_editor_2dview_input_tool_wall( view ); + case EDITOR_TOOL_POLY: return gui_editor_2dview_input_tool_poly( view ); + case EDITOR_TOOL_ENT: return gui_editor_2dview_input_tool_ent( view ); + case EDITOR_TOOL_SPRITE: return gui_editor_2dview_input_tool_sprite( view ); + default: return; + } +} + +void gui_editor_2dview_input_fn( void* ptr ) { + GUI_EDITOR_2DVIEW* view = (GUI_EDITOR_2DVIEW*)ptr; + if( !editor->map ) + return; + + I32 x = gui_relx( view ); + I32 y = gui_rely( view ) + EDITORVIEW_TITLE_OFFSET; + I32 w = view->w; + I32 h = view->h; + + U8 mouse = gui_mbutton_down( 0 ) || gui_mbutton_down( 1 ); + I32 mx, my; + gui_cursor_pos( &mx, &my ); + + U8 inbounds = mx >= x && mx <= x + w && my >= y && my <= y + h; + if( inbounds ) + gui_editor_2dview_input_scroll( view ); + + if( !mouse ) + view->heldoutbounds = 0; + else if( mouse && !inbounds ) + view->heldoutbounds = 1; + + if( view->heldoutbounds ) + return; + + switch( editor->tool ) { + case EDITOR_TOOL_SELECT: return gui_editor_2dview_input_tool_select( view ); + case EDITOR_TOOL_WALL: + case EDITOR_TOOL_POLY: + case EDITOR_TOOL_SPRITE: + case EDITOR_TOOL_ENT: + return gui_editor_2dview_input_tool_draw( view ); + default: return gui_editor_2dview_input_tool_none( view ); + } +} + +GUI_EDITOR_2DVIEW* gui_editor_2dview( I32 x, I32 y, I32 w, I32 h ) { + GUI_EDITOR_2DVIEW* view = new GUI_EDITOR_2DVIEW; + view->x = x; + view->y = y; + view->xbound = view->w = w; + view->h = h; + view->ybound = h + EDITORVIEW_TITLE_OFFSET; + + view->draw_fn = gui_editor_2dview_draw_fn; + view->input_fn = gui_editor_2dview_input_fn; + strcpy( view->name, "EDITOR_2D_VIEW" ); + + view->scale = 1.f; + + GUI_BASE* parent = gui_get_view(); + if( !parent ) + parent = gui_get_window(); + + parent->children.push( view ); + view->parent = parent; + + return view; +} diff --git a/src/editor/view3d.cpp b/src/editor/view3d.cpp new file mode 100644 index 0000000..f9bd60e --- /dev/null +++ b/src/editor/view3d.cpp @@ -0,0 +1,88 @@ +#include "editor.h" + +#include "../game/objlist.h" +#include "../game/world/draw.h" +#include "../game/world/bsp_draw.h" +#include "../game.h" + +const I32 EDITORVIEW_TITLE_OFFSET = 15; + +void gui_editor_3dview_draw_showpos( GUI_EDITOR_3DVIEW* view ) { + I32 x = gui_relx( view ); + I32 y = gui_rely( view ) + EDITORVIEW_TITLE_OFFSET; + + VEC3 pos = objl->pl->pos; + + gui_draw_str( + x + view->w - 2, y + 2, + ALIGN_R, + FNT_JPN12, + ui_clr.txt, + "pos: %.02f %.02f %.02f", + pos.x, pos.y, pos.z + ); + + gui_draw_str( + x + view->w - 2, y + 18, + ALIGN_R, + FNT_JPN12, + ui_clr.txt, + "rot: %.02f %.02f", + objl->pl->rot.y, + objl->pl->rot.x + ); +} + +void gui_editor_3dview_draw_fn( void* ptr ) { + GUI_EDITOR_3DVIEW* view = (GUI_EDITOR_3DVIEW*)ptr; + + if( !objl->pl ) + return; + + I32 x = gui_relx( view ); + I32 y = gui_rely( view ); + + gui_draw_str( x, y, ALIGN_L, FNT_JPN12, ui_clr.txt, "3d view" ); + y += EDITORVIEW_TITLE_OFFSET; + + VEC2 wnd = { (F32)x, (F32)y }; + VEC2 winsize = { (F32)view->w, (F32)view->h }; + + CLR col = gui_is_fg_window( view )? ui_clr.border : ui_clr.border_inactive; + gui_draw_frect( x-1, y-1, view->w+2, view->h+2, col ); + gui_draw_frect( x, y, view->w, view->h, CLR::BLACK() ); + if( editor->drawbsp && editor->map->bsp ) + bsp_draw( editor->map->bsp, editor->game, wnd, winsize ); + else + world_draw( editor->game, objl->world, wnd, winsize ); + + gui_editor_3dview_draw_showpos( view ); +} + +void gui_editor_3dview_input_fn( void* ptr ) { + if( !objl->pl ) + return; + + game_on_tick( editor->game ); +} + +GUI_EDITOR_3DVIEW* gui_editor_3dview( I32 x, I32 y, I32 w, I32 h ) { + GUI_EDITOR_3DVIEW* view = new GUI_EDITOR_3DVIEW; + view->x = x; + view->y = y; + view->xbound = view->w = w; + view->h = h; + view->ybound = h + EDITORVIEW_TITLE_OFFSET; + + view->draw_fn = gui_editor_3dview_draw_fn; + view->input_fn = gui_editor_3dview_input_fn; + strcpy( view->name, "EDITOR_3D_VIEW" ); + + GUI_BASE* parent = gui_get_view(); + if( !parent ) + parent = gui_get_window(); + + parent->children.push( view ); + view->parent = parent; + return view; +} -- cgit v1.2.3