summaryrefslogtreecommitdiff
path: root/src/editor
diff options
context:
space:
mode:
authornavewindre <boneyaard@gmail.com>2025-09-03 20:10:09 +0200
committernavewindre <boneyaard@gmail.com>2025-09-03 20:10:09 +0200
commitf8b92ce3aa08b1445c9f956d8166830946562d12 (patch)
tree94e63a5aec9f8f52b577f56799e0c9201fd976a5 /src/editor
a
Diffstat (limited to 'src/editor')
-rw-r--r--src/editor/editor.cpp138
-rw-r--r--src/editor/editor.h135
-rw-r--r--src/editor/gui.cpp255
-rw-r--r--src/editor/properties.cpp348
-rw-r--r--src/editor/texturepicker.cpp356
-rw-r--r--src/editor/view2d.cpp938
-rw-r--r--src/editor/view3d.cpp88
7 files changed, 2258 insertions, 0 deletions
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<GUI_LIST_ENTRY>* editor_get_map_list( GAME_EDITOR* ge ) {
+ const char* mapsdir = "../assets/maps";
+ const char* ext[] = { "hmap", 0 };
+ LIST<FILE_ENTRY> files = assets_get_files_by_ext_dir( ext, mapsdir );
+ LIST<GUI_LIST_ENTRY> 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<GUI_LIST_ENTRY> 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<GUI_LIST_ENTRY>* 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<FILE_ENTRY> files;
+ LIST<struct GL_TEX2D*> 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<GUI_LIST_ENTRY>* 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 <math.h>
+#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<VERTEX> 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<VEC2> 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;
+}