2 Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
5 This file is part of GtkRadiant.
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26 #include "debugging/debugging.h"
29 MapModules& ReferenceAPI_getMapModules();
30 #include "iselection.h"
34 #include "ireference.h"
35 #include "ifiletypes.h"
41 #include "ifilesystem.h"
42 #include "namespace.h"
43 #include "moduleobserver.h"
47 #include <gdk/gdkkeysyms.h>
48 #include "uilib/uilib.h"
51 #include "transformlib.h"
52 #include "selectionlib.h"
53 #include "instancelib.h"
54 #include "traverselib.h"
56 #include "eclasslib.h"
58 #include "stream/textfilestream.h"
60 #include "uniquenames.h"
61 #include "modulesystem/singletonmodule.h"
62 #include "modulesystem/moduleregistry.h"
63 #include "stream/stringstream.h"
64 #include "signal/signal.h"
66 #include "gtkutil/filechooser.h"
70 #include "filetypes.h"
72 #include "entityinspector.h"
75 #include "camwindow.h"
77 #include "mainframe.h"
78 #include "preferences.h"
79 #include "preferencesystem.h"
80 #include "referencecache.h"
84 #include "brushmodule.h"
94 //globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n";
95 m_names.insert( name_read( c_str() ) );
100 //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
101 m_names.erase( name_read( c_str() ) );
105 NameObserver& operator=( const NameObserver& other );
107 NameObserver( UniqueNames& names ) : m_names( names ){
110 NameObserver( const NameObserver& other ) : m_names( other.m_names ), m_name( other.m_name ){
117 return string_empty( c_str() );
119 const char* c_str() const {
120 return m_name.c_str();
122 void nameChanged( const char* name ){
127 typedef MemberCaller1<NameObserver, const char*, &NameObserver::nameChanged> NameChangedCaller;
130 class BasicNamespace : public Namespace
132 typedef std::map<NameCallback, NameObserver> Names;
134 UniqueNames m_uniqueNames;
137 ASSERT_MESSAGE( m_names.empty(), "namespace: names still registered at shutdown" );
139 void attach( const NameCallback& setName, const NameCallbackCallback& attachObserver ){
140 std::pair<Names::iterator, bool> result = m_names.insert( Names::value_type( setName, m_uniqueNames ) );
141 ASSERT_MESSAGE( result.second, "cannot attach name" );
142 attachObserver( NameObserver::NameChangedCaller( ( *result.first ).second ) );
143 //globalOutputStream() << "attach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
145 void detach( const NameCallback& setName, const NameCallbackCallback& detachObserver ){
146 Names::iterator i = m_names.find( setName );
147 ASSERT_MESSAGE( i != m_names.end(), "cannot detach name" );
148 //globalOutputStream() << "detach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
149 detachObserver( NameObserver::NameChangedCaller( ( *i ).second ) );
153 void makeUnique( const char* name, const NameCallback& setName ) const {
155 name_write( buffer, m_uniqueNames.make_unique( name_read( name ) ) );
159 void mergeNames( const BasicNamespace& other ) const {
160 typedef std::list<NameCallback> SetNameCallbacks;
161 typedef std::map<CopiedString, SetNameCallbacks> NameGroups;
164 UniqueNames uniqueNames( other.m_uniqueNames );
166 for ( Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i )
168 groups[( *i ).second.c_str()].push_back( ( *i ).first );
171 for ( NameGroups::iterator i = groups.begin(); i != groups.end(); ++i )
173 name_t uniqueName( uniqueNames.make_unique( name_read( ( *i ).first.c_str() ) ) );
174 uniqueNames.insert( uniqueName );
177 name_write( buffer, uniqueName );
179 //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n";
181 SetNameCallbacks& setNameCallbacks = ( *i ).second;
183 for ( SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j )
191 BasicNamespace g_defaultNamespace;
192 BasicNamespace g_cloneNamespace;
196 Namespace* m_namespace;
198 typedef Namespace Type;
199 STRING_CONSTANT( Name, "*" );
202 m_namespace = &g_defaultNamespace;
204 Namespace* getTable(){
209 typedef SingletonModule<NamespaceAPI> NamespaceModule;
210 typedef Static<NamespaceModule> StaticNamespaceModule;
211 StaticRegisterModule staticRegisterDefaultNamespace( StaticNamespaceModule::instance() );
214 std::list<Namespaced*> g_cloned;
216 inline Namespaced* Node_getNamespaced( scene::Node& node ){
217 return NodeTypeCast<Namespaced>::cast( node );
220 void Node_gatherNamespaced( scene::Node& node ){
221 Namespaced* namespaced = Node_getNamespaced( node );
222 if ( namespaced != 0 ) {
223 g_cloned.push_back( namespaced );
227 class GatherNamespaced : public scene::Traversable::Walker
230 bool pre( scene::Node& node ) const {
231 Node_gatherNamespaced( node );
236 void Map_gatherNamespaced( scene::Node& root ){
237 Node_traverseSubgraph( root, GatherNamespaced() );
240 void Map_mergeClonedNames(){
241 for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
243 ( *i )->setNamespace( g_cloneNamespace );
245 g_cloneNamespace.mergeNames( g_defaultNamespace );
246 for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
248 ( *i )->setNamespace( g_defaultNamespace );
261 void set( scene::Node* node ){
270 scene::Node* get() const {
276 void Map_SetValid( Map& map, bool valid );
277 void Map_UpdateTitle( const Map& map );
278 void Map_SetWorldspawn( Map& map, scene::Node* node );
281 class Map : public ModuleObserver
285 Resource* m_resource;
289 void ( *m_modified_changed )( const Map& );
291 Signal0 m_mapValidCallbacks;
293 WorldNode m_world_node; // "classname" "worldspawn" !
295 Map() : m_resource( 0 ), m_valid( false ), m_modified_changed( Map_UpdateTitle ){
299 if ( m_resource != 0 ) {
300 if ( Map_Unnamed( *this ) ) {
301 g_map.m_resource->setNode( NewMapRoot( "" ).get_pointer() );
302 MapFile* map = Node_getMapFile( *g_map.m_resource->getNode() );
312 GlobalSceneGraph().insert_root( *m_resource->getNode() );
316 Map_SetValid( g_map, true );
320 if ( m_resource != 0 ) {
321 Map_SetValid( g_map, false );
322 Map_SetWorldspawn( g_map, 0 );
325 GlobalUndoSystem().clear();
327 GlobalSceneGraph().erase_root();
333 Map* g_currentMap = 0;
335 void Map_addValidCallback( Map& map, const SignalHandler& handler ){
336 map.m_mapValidCallbacks.connectLast( handler );
339 bool Map_Valid( const Map& map ){
343 void Map_SetValid( Map& map, bool valid ){
345 map.m_mapValidCallbacks();
349 const char* Map_Name( const Map& map ){
350 return map.m_name.c_str();
353 bool Map_Unnamed( const Map& map ){
354 return string_equal( Map_Name( map ), "unnamed.map" );
357 inline const MapFormat& MapFormat_forFile( const char* filename ){
358 const char* moduleName = findModuleName( GetFileTypeRegistry(), MapFormat::Name(), path_get_extension( filename ) );
359 MapFormat* format = Radiant_getMapModules().findModule( moduleName );
360 ASSERT_MESSAGE( format != 0, "map format not found for file " << makeQuoted( filename ) );
364 const MapFormat& Map_getFormat( const Map& map ){
365 return MapFormat_forFile( Map_Name( map ) );
369 bool Map_Modified( const Map& map ){
370 return map.m_modified;
373 void Map_SetModified( Map& map, bool modified ){
374 if ( map.m_modified ^ modified ) {
375 map.m_modified = modified;
377 map.m_modified_changed( map );
381 void Map_UpdateTitle( const Map& map ){
382 Sys_SetTitle( map.m_name.c_str(), Map_Modified( map ) );
387 scene::Node* Map_GetWorldspawn( const Map& map ){
388 return map.m_world_node.get();
391 void Map_SetWorldspawn( Map& map, scene::Node* node ){
392 map.m_world_node.set( node );
397 // need that in a variable, will have to tweak depending on the game
398 float g_MaxWorldCoord = 64 * 1024;
399 float g_MinWorldCoord = -64 * 1024;
401 void AddRegionBrushes( void );
402 void RemoveRegionBrushes( void );
409 free all map elements, reinitialize the structures that depend on them
415 g_map.m_resource->detach( g_map );
416 GlobalReferenceCache().release( g_map.m_name.c_str() );
417 g_map.m_resource = 0;
422 Brush_unlatchPreferences();
425 class EntityFindByClassname : public scene::Graph::Walker
430 EntityFindByClassname( const char* name, Entity*& entity ) : m_name( name ), m_entity( entity ){
433 bool pre( const scene::Path& path, scene::Instance& instance ) const {
434 if ( m_entity == 0 ) {
435 Entity* entity = Node_getEntity( path.top() );
437 && string_equal( m_name, entity->getKeyValue( "classname" ) ) ) {
445 Entity* Scene_FindEntityByClass( const char* name ){
447 GlobalSceneGraph().traverse( EntityFindByClassname( name, entity ) );
451 Entity *Scene_FindPlayerStart(){
452 typedef const char* StaticString;
453 StaticString strings[] = {
455 "info_player_deathmatch",
456 "team_CTF_redplayer",
457 "team_CTF_blueplayer",
459 "team_CTF_bluespawn",
461 typedef const StaticString* StaticStringIterator;
462 for ( StaticStringIterator i = strings, end = strings + ( sizeof( strings ) / sizeof( StaticString ) ); i != end; ++i )
464 Entity* entity = Scene_FindEntityByClass( *i );
473 // move the view to a start position
477 void FocusViews( const Vector3& point, float angle ){
478 CamWnd& camwnd = *g_pParentWnd->GetCamWnd();
479 Camera_setOrigin( camwnd, point );
480 Vector3 angles( Camera_getAngles( camwnd ) );
481 angles[CAMERA_PITCH] = 0;
482 angles[CAMERA_YAW] = angle;
483 Camera_setAngles( camwnd, angles );
485 XYWnd* xywnd = g_pParentWnd->GetXYWnd();
486 xywnd->SetOrigin( point );
489 #include "stringio.h"
491 void Map_StartPosition(){
492 Entity* entity = Scene_FindPlayerStart();
496 string_parse_vector3( entity->getKeyValue( "origin" ), origin );
497 FocusViews( origin, string_read_float( entity->getKeyValue( "angle" ) ) );
501 FocusViews( g_vector3_identity, 0 );
506 inline bool node_is_worldspawn( scene::Node& node ){
507 Entity* entity = Node_getEntity( node );
508 return entity != 0 && string_equal( entity->getKeyValue( "classname" ), "worldspawn" );
512 // use first worldspawn
513 class entity_updateworldspawn : public scene::Traversable::Walker
516 bool pre( scene::Node& node ) const {
517 if ( node_is_worldspawn( node ) ) {
518 if ( Map_GetWorldspawn( g_map ) == 0 ) {
519 Map_SetWorldspawn( g_map, &node );
526 scene::Node* Map_FindWorldspawn( Map& map ){
527 Map_SetWorldspawn( map, 0 );
529 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
531 return Map_GetWorldspawn( map );
535 class CollectAllWalker : public scene::Traversable::Walker
538 UnsortedNodeSet& m_nodes;
540 CollectAllWalker( scene::Node& root, UnsortedNodeSet& nodes ) : m_root( root ), m_nodes( nodes ){
542 bool pre( scene::Node& node ) const {
543 m_nodes.insert( NodeSmartReference( node ) );
544 Node_getTraversable( m_root )->erase( node );
549 void Node_insertChildFirst( scene::Node& parent, scene::Node& child ){
550 UnsortedNodeSet nodes;
551 Node_getTraversable( parent )->traverse( CollectAllWalker( parent, nodes ) );
552 Node_getTraversable( parent )->insert( child );
554 for ( UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i )
556 Node_getTraversable( parent )->insert( ( *i ) );
560 scene::Node& createWorldspawn(){
561 NodeSmartReference worldspawn( GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "worldspawn", true ) ) );
562 Node_insertChildFirst( GlobalSceneGraph().root(), worldspawn );
566 void Map_UpdateWorldspawn( Map& map ){
567 if ( Map_FindWorldspawn( map ) == 0 ) {
568 Map_SetWorldspawn( map, &createWorldspawn() );
572 scene::Node& Map_FindOrInsertWorldspawn( Map& map ){
573 Map_UpdateWorldspawn( map );
574 return *Map_GetWorldspawn( map );
578 class MapMergeAll : public scene::Traversable::Walker
580 mutable scene::Path m_path;
582 MapMergeAll( const scene::Path& root )
585 bool pre( scene::Node& node ) const {
586 Node_getTraversable( m_path.top() )->insert( node );
587 m_path.push( makeReference( node ) );
588 selectPath( m_path, true );
591 void post( scene::Node& node ) const {
596 class MapMergeEntities : public scene::Traversable::Walker
598 mutable scene::Path m_path;
600 MapMergeEntities( const scene::Path& root )
603 bool pre( scene::Node& node ) const {
604 if ( node_is_worldspawn( node ) ) {
605 scene::Node* world_node = Map_FindWorldspawn( g_map );
606 if ( world_node == 0 ) {
607 Map_SetWorldspawn( g_map, &node );
608 Node_getTraversable( m_path.top().get() )->insert( node );
609 m_path.push( makeReference( node ) );
610 Node_getTraversable( node )->traverse( SelectChildren( m_path ) );
614 m_path.push( makeReference( *world_node ) );
615 Node_getTraversable( node )->traverse( MapMergeAll( m_path ) );
620 Node_getTraversable( m_path.top() )->insert( node );
621 m_path.push( makeReference( node ) );
622 if ( node_is_group( node ) ) {
623 Node_getTraversable( node )->traverse( SelectChildren( m_path ) );
627 selectPath( m_path, true );
632 void post( scene::Node& node ) const {
637 class BasicContainer : public scene::Node::Symbiot
641 NodeTypeCastTable m_casts;
644 NodeContainedCast<BasicContainer, scene::Traversable>::install( m_casts );
646 NodeTypeCastTable& get(){
652 TraversableNodeSet m_traverse;
655 typedef LazyStatic<TypeCasts> StaticTypeCasts;
657 scene::Traversable& get( NullType<scene::Traversable>){
661 BasicContainer() : m_node( this, this, StaticTypeCasts::instance().get() ){
671 /// Merges the map graph rooted at \p node into the global scene-graph.
672 void MergeMap( scene::Node& node ){
673 Node_getTraversable( node )->traverse( MapMergeEntities( scene::Path( makeReference( GlobalSceneGraph().root() ) ) ) );
675 void Map_ImportSelected( TextInputStream& in, const MapFormat& format ){
676 NodeSmartReference node( ( new BasicContainer )->node() );
677 format.readGraph( node, in, GlobalEntityCreator() );
678 Map_gatherNamespaced( node );
679 Map_mergeClonedNames();
683 inline scene::Cloneable* Node_getCloneable( scene::Node& node ){
684 return NodeTypeCast<scene::Cloneable>::cast( node );
687 inline scene::Node& node_clone( scene::Node& node ){
688 scene::Cloneable* cloneable = Node_getCloneable( node );
689 if ( cloneable != 0 ) {
690 return cloneable->clone();
693 return ( new scene::NullNode )->node();
696 class CloneAll : public scene::Traversable::Walker
698 mutable scene::Path m_path;
700 CloneAll( scene::Node& root )
701 : m_path( makeReference( root ) ){
703 bool pre( scene::Node& node ) const {
704 if ( node.isRoot() ) {
708 m_path.push( makeReference( node_clone( node ) ) );
709 m_path.top().get().IncRef();
713 void post( scene::Node& node ) const {
714 if ( node.isRoot() ) {
718 Node_getTraversable( m_path.parent() )->insert( m_path.top() );
720 m_path.top().get().DecRef();
725 scene::Node& Node_Clone( scene::Node& node ){
726 scene::Node& clone = node_clone( node );
727 scene::Traversable* traversable = Node_getTraversable( node );
728 if ( traversable != 0 ) {
729 traversable->traverse( CloneAll( clone ) );
735 typedef std::map<CopiedString, std::size_t> EntityBreakdown;
737 class EntityBreakdownWalker : public scene::Graph::Walker
739 EntityBreakdown& m_entitymap;
741 EntityBreakdownWalker( EntityBreakdown& entitymap )
742 : m_entitymap( entitymap ){
744 bool pre( const scene::Path& path, scene::Instance& instance ) const {
745 Entity* entity = Node_getEntity( path.top() );
747 const EntityClass& eclass = entity->getEntityClass();
748 if ( m_entitymap.find( eclass.name() ) == m_entitymap.end() ) {
749 m_entitymap[eclass.name()] = 1;
751 else{ ++m_entitymap[eclass.name()]; }
757 void Scene_EntityBreakdown( EntityBreakdown& entitymap ){
758 GlobalSceneGraph().traverse( EntityBreakdownWalker( entitymap ) );
762 WindowPosition g_posMapInfoWnd( c_default_window_pos );
766 GtkEntry* brushes_entry;
767 GtkEntry* entities_entry;
768 ui::ListStore EntityBreakdownWalker{ui::null};
770 ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback ), &dialog );
772 window_set_position( window, g_posMapInfoWnd );
775 auto vbox = create_dialog_vbox( 4, 4 );
779 GtkHBox* hbox = create_dialog_hbox( 4 );
780 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), FALSE, TRUE, 0 );
783 GtkTable* table = create_dialog_table( 2, 2, 4, 4 );
784 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
787 auto entry = ui::Entry();
789 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 0, 1,
790 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
791 (GtkAttachOptions) ( 0 ), 0, 0 );
792 gtk_editable_set_editable( GTK_EDITABLE(entry), FALSE );
794 brushes_entry = entry;
797 auto entry = ui::Entry();
799 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 1, 2,
800 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
801 (GtkAttachOptions) ( 0 ), 0, 0 );
802 gtk_editable_set_editable( GTK_EDITABLE(entry), FALSE );
804 entities_entry = entry;
807 ui::Widget label = ui::Label( "Total Brushes" );
809 gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 0, 1,
810 (GtkAttachOptions) ( GTK_FILL ),
811 (GtkAttachOptions) ( 0 ), 0, 0 );
812 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
815 ui::Widget label = ui::Label( "Total Entities" );
817 gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 1, 2,
818 (GtkAttachOptions) ( GTK_FILL ),
819 (GtkAttachOptions) ( 0 ), 0, 0 );
820 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
824 GtkVBox* vbox2 = create_dialog_vbox( 4 );
825 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox2 ), FALSE, FALSE, 0 );
828 GtkButton* button = create_dialog_button( "Close", G_CALLBACK( dialog_button_ok ), &dialog );
829 gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
834 ui::Widget label = ui::Label( "Entity breakdown" );
836 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, TRUE, 0 );
837 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
840 auto scr = create_scrolled_window( ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4 );
841 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( scr ), TRUE, TRUE, 0 );
844 ui::ListStore store = ui::ListStore(gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_STRING ));
846 ui::Widget view = ui::TreeView(ui::TreeModel( GTK_TREE_MODEL( store ) ));
847 gtk_tree_view_set_headers_clickable( GTK_TREE_VIEW( view ), TRUE );
850 auto renderer = ui::CellRendererText();
851 GtkTreeViewColumn* column = ui::TreeViewColumn( "Entity", renderer, {{"text", 0}} );
852 gtk_tree_view_append_column( GTK_TREE_VIEW( view ), column );
853 gtk_tree_view_column_set_sort_column_id( column, 0 );
857 auto renderer = ui::CellRendererText();
858 GtkTreeViewColumn* column = ui::TreeViewColumn( "Count", renderer, {{"text", 1}} );
859 gtk_tree_view_append_column( GTK_TREE_VIEW( view ), column );
860 gtk_tree_view_column_set_sort_column_id( column, 1 );
867 EntityBreakdownWalker = store;
875 EntityBreakdown entitymap;
876 Scene_EntityBreakdown( entitymap );
878 for ( EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i )
881 sprintf( tmp, "%u", Unsigned( ( *i ).second ) );
883 gtk_list_store_append( GTK_LIST_STORE( EntityBreakdownWalker ), &iter );
884 gtk_list_store_set( GTK_LIST_STORE( EntityBreakdownWalker ), &iter, 0, ( *i ).first.c_str(), 1, tmp, -1 );
888 EntityBreakdownWalker.unref();
891 sprintf( tmp, "%u", Unsigned( g_brushCount.get() ) );
892 gtk_entry_set_text( GTK_ENTRY( brushes_entry ), tmp );
893 sprintf( tmp, "%u", Unsigned( g_entityCount.get() ) );
894 gtk_entry_set_text( GTK_ENTRY( entities_entry ), tmp );
896 modal_dialog_show( window, dialog );
899 window_get_position( window, g_posMapInfoWnd );
901 gtk_widget_destroy( GTK_WIDGET( window ) );
909 const char* m_message;
911 ScopeTimer( const char* message )
912 : m_message( message ){
916 double elapsed_time = m_timer.elapsed_msec() / 1000.f;
917 globalOutputStream() << m_message << " timer: " << FloatFormat( elapsed_time, 5, 2 ) << " second(s) elapsed\n";
921 CopiedString g_strLastFolder = "";
929 void Map_LoadFile( const char *filename ){
930 globalOutputStream() << "Loading map from " << filename << "\n";
931 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
933 MRU_AddFile( filename );
934 g_strLastFolder = g_path_get_dirname( filename );
937 ScopeTimer timer( "map load" );
939 const MapFormat* format = NULL;
940 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
941 if ( string_not_empty( moduleName ) ) {
942 format = ReferenceAPI_getMapModules().findModule( moduleName );
945 for ( int i = 0; i < Brush_toggleFormatCount(); ++i )
950 Brush_toggleFormat( i );
951 g_map.m_name = filename;
952 Map_UpdateTitle( g_map );
953 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
955 format->wrongFormat = false;
957 g_map.m_resource->attach( g_map );
959 if ( !format->wrongFormat ) {
965 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
968 globalOutputStream() << "--- LoadMapFile ---\n";
969 globalOutputStream() << g_map.m_name.c_str() << "\n";
971 globalOutputStream() << Unsigned( g_brushCount.get() ) << " primitive\n";
972 globalOutputStream() << Unsigned( g_entityCount.get() ) << " entities\n";
974 //GlobalEntityCreator().printStatistics();
977 // move the view to a start position
981 g_currentMap = &g_map;
983 // restart VFS to apply new pak filtering based on mapname
984 // needed for daemon DPK VFS
991 virtual bool excluded( scene::Node& node ) const = 0;
994 class ExcludeWalker : public scene::Traversable::Walker
996 const scene::Traversable::Walker& m_walker;
997 const Excluder* m_exclude;
1000 ExcludeWalker( const scene::Traversable::Walker& walker, const Excluder& exclude )
1001 : m_walker( walker ), m_exclude( &exclude ), m_skip( false ){
1003 bool pre( scene::Node& node ) const {
1004 if ( m_exclude->excluded( node ) || node.isRoot() ) {
1010 m_walker.pre( node );
1014 void post( scene::Node& node ) const {
1020 m_walker.post( node );
1025 class AnyInstanceSelected : public scene::Instantiable::Visitor
1029 AnyInstanceSelected( bool& selected ) : m_selected( selected ){
1032 void visit( scene::Instance& instance ) const {
1033 Selectable* selectable = Instance_getSelectable( instance );
1034 if ( selectable != 0
1035 && selectable->isSelected() ) {
1041 bool Node_instanceSelected( scene::Node& node ){
1042 scene::Instantiable* instantiable = Node_getInstantiable( node );
1043 ASSERT_NOTNULL( instantiable );
1045 instantiable->forEachInstance( AnyInstanceSelected( selected ) );
1049 class SelectedDescendantWalker : public scene::Traversable::Walker
1053 SelectedDescendantWalker( bool& selected ) : m_selected( selected ){
1057 bool pre( scene::Node& node ) const {
1058 if ( node.isRoot() ) {
1062 if ( Node_instanceSelected( node ) ) {
1070 bool Node_selectedDescendant( scene::Node& node ){
1072 Node_traverseSubgraph( node, SelectedDescendantWalker( selected ) );
1076 class SelectionExcluder : public Excluder
1079 bool excluded( scene::Node& node ) const {
1080 return !Node_selectedDescendant( node );
1084 class IncludeSelectedWalker : public scene::Traversable::Walker
1086 const scene::Traversable::Walker& m_walker;
1087 mutable std::size_t m_selected;
1088 mutable bool m_skip;
1090 bool selectedParent() const {
1091 return m_selected != 0;
1094 IncludeSelectedWalker( const scene::Traversable::Walker& walker )
1095 : m_walker( walker ), m_selected( 0 ), m_skip( false ){
1097 bool pre( scene::Node& node ) const {
1099 // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1100 if ( !node.isRoot() && ( Node_selectedDescendant( node ) || selectedParent() ) ) {
1101 if ( Node_instanceSelected( node ) ) {
1104 m_walker.pre( node );
1113 void post( scene::Node& node ) const {
1119 if ( Node_instanceSelected( node ) ) {
1122 m_walker.post( node );
1127 void Map_Traverse_Selected( scene::Node& root, const scene::Traversable::Walker& walker ){
1128 scene::Traversable* traversable = Node_getTraversable( root );
1129 if ( traversable != 0 ) {
1131 traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1133 traversable->traverse( IncludeSelectedWalker( walker ) );
1138 void Map_ExportSelected( TextOutputStream& out, const MapFormat& format ){
1139 format.writeGraph( GlobalSceneGraph().root(), Map_Traverse_Selected, out );
1142 void Map_Traverse( scene::Node& root, const scene::Traversable::Walker& walker ){
1143 scene::Traversable* traversable = Node_getTraversable( root );
1144 if ( traversable != 0 ) {
1145 traversable->traverse( walker );
1149 class RegionExcluder : public Excluder
1152 bool excluded( scene::Node& node ) const {
1153 return node.excluded();
1157 void Map_Traverse_Region( scene::Node& root, const scene::Traversable::Walker& walker ){
1158 scene::Traversable* traversable = Node_getTraversable( root );
1159 if ( traversable != 0 ) {
1160 traversable->traverse( ExcludeWalker( walker, RegionExcluder() ) );
1164 bool Map_SaveRegion( const char *filename ){
1167 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Region, filename );
1169 RemoveRegionBrushes();
1175 void Map_RenameAbsolute( const char* absolute ){
1176 Resource* resource = GlobalReferenceCache().capture( absolute );
1177 NodeSmartReference clone( NewMapRoot( path_make_relative( absolute, GlobalFileSystem().findRoot( absolute ) ) ) );
1178 resource->setNode( clone.get_pointer() );
1181 //ScopeTimer timer("clone subgraph");
1182 Node_getTraversable( GlobalSceneGraph().root() )->traverse( CloneAll( clone ) );
1185 g_map.m_resource->detach( g_map );
1186 GlobalReferenceCache().release( g_map.m_name.c_str() );
1188 g_map.m_resource = resource;
1190 g_map.m_name = absolute;
1191 Map_UpdateTitle( g_map );
1193 g_map.m_resource->attach( g_map );
1194 // refresh VFS to apply new pak filtering based on mapname
1195 // needed for daemon DPK VFS
1199 void Map_Rename( const char* filename ){
1200 if ( !string_equal( g_map.m_name.c_str(), filename ) ) {
1201 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1203 Map_RenameAbsolute( filename );
1205 SceneChangeNotify();
1216 ScopeTimer timer( "map save" );
1218 return true; // assume success..
1228 //globalOutputStream() << "Map_New\n";
1230 g_map.m_name = "unnamed.map";
1231 Map_UpdateTitle( g_map );
1234 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
1235 // ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1236 g_map.m_resource->attach( g_map );
1238 SceneChangeNotify();
1241 FocusViews( g_vector3_identity, 0 );
1243 g_currentMap = &g_map;
1245 // restart VFS to apply new pak filtering based on mapname
1246 // needed for daemon DPK VFS
1250 extern void ConstructRegionBrushes( scene::Node * brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_maxs );
1252 void ConstructRegionStartpoint( scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs ){
1254 \todo we need to make sure that the player start IS inside the region and bail out if it's not
1255 the compiler will refuse to compile a map with a player_start somewhere in empty space..
1256 for now, let's just print an error
1259 Vector3 vOrig( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ) );
1261 for ( int i = 0 ; i < 3 ; i++ )
1263 if ( vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i] ) {
1264 globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1269 // write the info_playerstart
1271 sprintf( sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2] );
1272 Node_getEntity( *startpoint )->setKeyValue( "origin", sTmp );
1273 sprintf( sTmp, "%d", (int)Camera_getAngles( *g_pParentWnd->GetCamWnd() )[CAMERA_YAW] );
1274 Node_getEntity( *startpoint )->setKeyValue( "angle", sTmp );
1278 ===========================================================
1282 ===========================================================
1285 Vector3 region_mins( g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord );
1286 Vector3 region_maxs( g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord );
1288 scene::Node* region_sides[6];
1289 scene::Node* region_startpoint = 0;
1294 a regioned map will have temp walls put up at the region boundary
1295 \todo TODO TTimo old implementation of region brushes
1296 we still add them straight in the worldspawn and take them out after the map is saved
1297 with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1300 void AddRegionBrushes( void ){
1303 for ( i = 0; i < 6; i++ )
1305 region_sides[i] = &GlobalBrushCreator().createBrush();
1306 Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( NodeSmartReference( *region_sides[i] ) );
1309 region_startpoint = &GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "info_player_start", false ) );
1311 ConstructRegionBrushes( region_sides, region_mins, region_maxs );
1312 ConstructRegionStartpoint( region_startpoint, region_mins, region_maxs );
1314 Node_getTraversable( GlobalSceneGraph().root() )->insert( NodeSmartReference( *region_startpoint ) );
1317 void RemoveRegionBrushes( void ){
1318 for ( std::size_t i = 0; i < 6; i++ )
1320 Node_getTraversable( *Map_GetWorldspawn( g_map ) )->erase( *region_sides[i] );
1322 Node_getTraversable( GlobalSceneGraph().root() )->erase( *region_startpoint );
1325 inline void exclude_node( scene::Node& node, bool exclude ){
1327 ? node.enable( scene::Node::eExcluded )
1328 : node.disable( scene::Node::eExcluded );
1331 class ExcludeAllWalker : public scene::Graph::Walker
1335 ExcludeAllWalker( bool exclude )
1336 : m_exclude( exclude ){
1338 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1339 exclude_node( path.top(), m_exclude );
1345 void Scene_Exclude_All( bool exclude ){
1346 GlobalSceneGraph().traverse( ExcludeAllWalker( exclude ) );
1349 bool Instance_isSelected( const scene::Instance& instance ){
1350 const Selectable* selectable = Instance_getSelectable( instance );
1351 return selectable != 0 && selectable->isSelected();
1354 class ExcludeSelectedWalker : public scene::Graph::Walker
1358 ExcludeSelectedWalker( bool exclude )
1359 : m_exclude( exclude ){
1361 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1362 exclude_node( path.top(), ( instance.isSelected() || instance.childSelected() || instance.parentSelected() ) == m_exclude );
1367 void Scene_Exclude_Selected( bool exclude ){
1368 GlobalSceneGraph().traverse( ExcludeSelectedWalker( exclude ) );
1371 class ExcludeRegionedWalker : public scene::Graph::Walker
1375 ExcludeRegionedWalker( bool exclude )
1376 : m_exclude( exclude ){
1378 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1383 aabb_intersects_aabb(
1384 instance.worldAABB(),
1385 aabb_for_minmax( region_mins, region_maxs )
1395 void Scene_Exclude_Region( bool exclude ){
1396 GlobalSceneGraph().traverse( ExcludeRegionedWalker( exclude ) );
1403 Other filtering options may still be on
1406 void Map_RegionOff(){
1407 region_active = false;
1409 region_maxs[0] = g_MaxWorldCoord - 64;
1410 region_mins[0] = g_MinWorldCoord + 64;
1411 region_maxs[1] = g_MaxWorldCoord - 64;
1412 region_mins[1] = g_MinWorldCoord + 64;
1413 region_maxs[2] = g_MaxWorldCoord - 64;
1414 region_mins[2] = g_MinWorldCoord + 64;
1416 Scene_Exclude_All( false );
1419 void Map_ApplyRegion( void ){
1420 region_active = true;
1422 Scene_Exclude_Region( false );
1427 ========================
1428 Map_RegionSelectedBrushes
1429 ========================
1431 void Map_RegionSelectedBrushes( void ){
1434 if ( GlobalSelectionSystem().countSelected() != 0
1435 && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
1436 region_active = true;
1437 Select_GetBounds( region_mins, region_maxs );
1439 Scene_Exclude_Selected( false );
1441 GlobalSelectionSystem().setSelectedAll( false );
1451 void Map_RegionXY( float x_min, float y_min, float x_max, float y_max ){
1454 region_mins[0] = x_min;
1455 region_maxs[0] = x_max;
1456 region_mins[1] = y_min;
1457 region_maxs[1] = y_max;
1458 region_mins[2] = g_MinWorldCoord + 64;
1459 region_maxs[2] = g_MaxWorldCoord - 64;
1464 void Map_RegionBounds( const AABB& bounds ){
1467 region_mins = vector3_subtracted( bounds.origin, bounds.extents );
1468 region_maxs = vector3_added( bounds.origin, bounds.extents );
1480 void Map_RegionBrush( void ){
1481 if ( GlobalSelectionSystem().countSelected() != 0 ) {
1482 scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
1483 Map_RegionBounds( instance.worldAABB() );
1492 bool Map_ImportFile( const char* filename ){
1493 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
1495 g_strLastFolder = g_path_get_dirname( filename );
1497 bool success = false;
1499 if ( extension_equal( path_get_extension( filename ), "bsp" ) ) {
1504 const MapFormat* format = NULL;
1505 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
1506 if ( string_not_empty( moduleName ) ) {
1507 format = ReferenceAPI_getMapModules().findModule( moduleName );
1511 format->wrongFormat = false;
1513 Resource* resource = GlobalReferenceCache().capture( filename );
1514 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1515 if ( !resource->load() ) {
1516 GlobalReferenceCache().release( filename );
1520 if ( format->wrongFormat ) {
1521 GlobalReferenceCache().release( filename );
1525 NodeSmartReference clone( NewMapRoot( "" ) );
1526 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1527 Map_gatherNamespaced( clone );
1528 Map_mergeClonedNames();
1531 GlobalReferenceCache().release( filename );
1534 SceneChangeNotify();
1540 const char *type = GlobalRadiant().getRequiredGameDescriptionKeyValue( "q3map2_type" );
1541 int n = string_length( path_get_extension( filename ) );
1542 if ( n && ( extension_equal( path_get_extension( filename ), "bsp" ) || extension_equal( path_get_extension( filename ), "map" ) ) ) {
1543 StringBuffer output;
1544 output.push_string( AppPath_get() );
1545 output.push_string( "q3map2." );
1546 output.push_string( RADIANT_EXECUTABLE );
1547 output.push_string( " -v -game " );
1548 output.push_string( ( type && *type ) ? type : "quake3" );
1549 output.push_string( " -fs_basepath \"" );
1550 output.push_string( EnginePath_get() );
1551 output.push_string( "\" -fs_homepath \"" );
1552 output.push_string( g_qeglobals.m_userEnginePath.c_str() );
1553 output.push_string( "\" -fs_game " );
1554 output.push_string( gamename_get() );
1555 output.push_string( " -convert -format " );
1556 output.push_string( Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map" );
1557 if ( extension_equal( path_get_extension( filename ), "map" ) ) {
1558 output.push_string( " -readmap " );
1560 output.push_string( " \"" );
1561 output.push_string( filename );
1562 output.push_string( "\"" );
1565 Q_Exec( NULL, output.c_str(), NULL, false, true );
1567 // rebuild filename as "filenamewithoutext_converted.map"
1569 output.push_range( filename, filename + string_length( filename ) - ( n + 1 ) );
1570 output.push_string( "_converted.map" );
1571 filename = output.c_str();
1574 Resource* resource = GlobalReferenceCache().capture( filename );
1575 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1576 if ( !resource->load() ) {
1577 GlobalReferenceCache().release( filename );
1580 NodeSmartReference clone( NewMapRoot( "" ) );
1581 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1582 Map_gatherNamespaced( clone );
1583 Map_mergeClonedNames();
1586 GlobalReferenceCache().release( filename );
1589 SceneChangeNotify();
1598 bool Map_SaveFile( const char* filename ){
1599 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1600 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse, filename );
1602 // refresh VFS to apply new pak filtering based on mapname
1603 // needed for daemon DPK VFS
1614 // Saves selected world brushes and whole entities with partial/full selections
1616 bool Map_SaveSelected( const char* filename ){
1617 return MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Selected, filename );
1621 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1623 scene::Node& m_parent;
1625 ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ){
1627 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1628 if ( path.top().get_pointer() != &m_parent
1629 && Node_isPrimitive( path.top() ) ) {
1630 Selectable* selectable = Instance_getSelectable( instance );
1631 if ( selectable != 0
1632 && selectable->isSelected()
1633 && path.size() > 1 ) {
1639 void post( const scene::Path& path, scene::Instance& instance ) const {
1640 if ( path.top().get_pointer() != &m_parent
1641 && Node_isPrimitive( path.top() ) ) {
1642 Selectable* selectable = Instance_getSelectable( instance );
1643 if ( selectable != 0
1644 && selectable->isSelected()
1645 && path.size() > 1 ) {
1646 scene::Node& parent = path.parent();
1647 if ( &parent != &m_parent ) {
1648 NodeSmartReference node( path.top().get() );
1649 Node_getTraversable( parent )->erase( node );
1650 Node_getTraversable( m_parent )->insert( node );
1657 void Scene_parentSelectedBrushesToEntity( scene::Graph& graph, scene::Node& parent ){
1658 graph.traverse( ParentSelectedBrushesToEntityWalker( parent ) );
1661 class CountSelectedBrushes : public scene::Graph::Walker
1663 std::size_t& m_count;
1664 mutable std::size_t m_depth;
1666 CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
1669 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1670 if ( ++m_depth != 1 && path.top().get().isRoot() ) {
1673 Selectable* selectable = Instance_getSelectable( instance );
1674 if ( selectable != 0
1675 && selectable->isSelected()
1676 && Node_isPrimitive( path.top() ) ) {
1681 void post( const scene::Path& path, scene::Instance& instance ) const {
1686 std::size_t Scene_countSelectedBrushes( scene::Graph& graph ){
1688 graph.traverse( CountSelectedBrushes( count ) );
1700 const char* nodetype_get_name( ENodeType type ){
1701 if ( type == eNodeMap ) {
1704 if ( type == eNodeEntity ) {
1707 if ( type == eNodePrimitive ) {
1713 ENodeType node_get_nodetype( scene::Node& node ){
1714 if ( Node_isEntity( node ) ) {
1717 if ( Node_isPrimitive( node ) ) {
1718 return eNodePrimitive;
1720 return eNodeUnknown;
1723 bool contains_entity( scene::Node& node ){
1724 return Node_getTraversable( node ) != 0 && !Node_isBrush( node ) && !Node_isPatch( node ) && !Node_isEntity( node );
1727 bool contains_primitive( scene::Node& node ){
1728 return Node_isEntity( node ) && Node_getTraversable( node ) != 0 && Node_getEntity( node )->isContainer();
1731 ENodeType node_get_contains( scene::Node& node ){
1732 if ( contains_entity( node ) ) {
1735 if ( contains_primitive( node ) ) {
1736 return eNodePrimitive;
1738 return eNodeUnknown;
1741 void Path_parent( const scene::Path& parent, const scene::Path& child ){
1742 ENodeType contains = node_get_contains( parent.top() );
1743 ENodeType type = node_get_nodetype( child.top() );
1745 if ( contains != eNodeUnknown && contains == type ) {
1746 NodeSmartReference node( child.top().get() );
1747 Path_deleteTop( child );
1748 Node_getTraversable( parent.top() )->insert( node );
1749 SceneChangeNotify();
1753 globalErrorStream() << "failed - " << nodetype_get_name( type ) << " cannot be parented to " << nodetype_get_name( contains ) << " container.\n";
1757 void Scene_parentSelected(){
1758 UndoableCommand undo( "parentSelected" );
1760 if ( GlobalSelectionSystem().countSelected() > 1 ) {
1761 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1763 const scene::Path& m_parent;
1765 ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
1767 void visit( scene::Instance& instance ) const {
1768 if ( &m_parent != &instance.path() ) {
1769 Path_parent( m_parent, instance.path() );
1774 ParentSelectedBrushesToEntityWalker visitor( GlobalSelectionSystem().ultimateSelected().path() );
1775 GlobalSelectionSystem().foreachSelected( visitor );
1779 globalOutputStream() << "failed - did not find two selected nodes.\n";
1786 if ( ConfirmModified( "New Map" ) ) {
1793 CopiedString g_mapsPath;
1795 const char* getMapsPath(){
1796 return g_mapsPath.c_str();
1799 const char* getLastFolderPath(){
1800 if (g_strLastFolder.empty()) {
1801 GlobalPreferenceSystem().registerPreference( "LastFolder", CopiedStringImportStringCaller( g_strLastFolder ), CopiedStringExportStringCaller( g_strLastFolder ) );
1802 if (g_strLastFolder.empty()) {
1803 g_strLastFolder = g_qeglobals.m_userGamePath;
1806 return g_strLastFolder.c_str();
1809 const char* map_open( const char* title ){
1810 return MainFrame_getWindow().file_dialog( TRUE, title, getLastFolderPath(), MapFormat::Name(), true, false, false );
1813 const char* map_import( const char* title ){
1814 return MainFrame_getWindow().file_dialog( TRUE, title, getLastFolderPath(), MapFormat::Name(), false, true, false );
1817 const char* map_save( const char* title ){
1818 return MainFrame_getWindow().file_dialog( FALSE, title, getLastFolderPath(), MapFormat::Name(), false, false, true );
1822 if ( !ConfirmModified( "Open Map" ) ) {
1826 const char* filename = map_open( "Open Map" );
1828 if ( filename != NULL ) {
1829 MRU_AddFile( filename );
1832 Map_LoadFile( filename );
1837 const char* filename = map_import( "Import Map" );
1839 if ( filename != NULL ) {
1840 UndoableCommand undo( "mapImport" );
1841 Map_ImportFile( filename );
1846 const char* filename = map_save( "Save Map" );
1848 if ( filename != NULL ) {
1849 g_strLastFolder = g_path_get_dirname( filename );
1850 MRU_AddFile( filename );
1851 Map_Rename( filename );
1862 if ( Map_Unnamed( g_map ) ) {
1865 else if ( Map_Modified( g_map ) ) {
1871 const char* filename = map_save( "Export Selection" );
1873 if ( filename != NULL ) {
1874 g_strLastFolder = g_path_get_dirname( filename );
1875 Map_SaveSelected( filename );
1880 const char* filename = map_save( "Export Region" );
1882 if ( filename != NULL ) {
1883 g_strLastFolder = g_path_get_dirname( filename );
1884 Map_SaveRegion( filename );
1891 SceneChangeNotify();
1896 g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1897 g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
1898 g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1899 g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
1901 SceneChangeNotify();
1906 SceneChangeNotify();
1909 void RegionSelected(){
1910 Map_RegionSelectedBrushes();
1911 SceneChangeNotify();
1918 class BrushFindByIndexWalker : public scene::Traversable::Walker
1920 mutable std::size_t m_index;
1921 scene::Path& m_path;
1923 BrushFindByIndexWalker( std::size_t index, scene::Path& path )
1924 : m_index( index ), m_path( path ){
1926 bool pre( scene::Node& node ) const {
1927 if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
1928 m_path.push( makeReference( node ) );
1934 class EntityFindByIndexWalker : public scene::Traversable::Walker
1936 mutable std::size_t m_index;
1937 scene::Path& m_path;
1939 EntityFindByIndexWalker( std::size_t index, scene::Path& path )
1940 : m_index( index ), m_path( path ){
1942 bool pre( scene::Node& node ) const {
1943 if ( Node_isEntity( node ) && m_index-- == 0 ) {
1944 m_path.push( makeReference( node ) );
1950 void Scene_FindEntityBrush( std::size_t entity, std::size_t brush, scene::Path& path ){
1951 path.push( makeReference( GlobalSceneGraph().root() ) );
1953 Node_getTraversable( path.top() )->traverse( EntityFindByIndexWalker( entity, path ) );
1955 if ( path.size() == 2 ) {
1956 scene::Traversable* traversable = Node_getTraversable( path.top() );
1957 if ( traversable != 0 ) {
1958 traversable->traverse( BrushFindByIndexWalker( brush, path ) );
1963 inline bool Node_hasChildren( scene::Node& node ){
1964 scene::Traversable* traversable = Node_getTraversable( node );
1965 return traversable != 0 && !traversable->empty();
1968 void SelectBrush( int entitynum, int brushnum ){
1970 Scene_FindEntityBrush( entitynum, brushnum, path );
1971 if ( path.size() == 3 || ( path.size() == 2 && !Node_hasChildren( path.top() ) ) ) {
1972 scene::Instance* instance = GlobalSceneGraph().find( path );
1973 ASSERT_MESSAGE( instance != 0, "SelectBrush: path not found in scenegraph" );
1974 Selectable* selectable = Instance_getSelectable( *instance );
1975 ASSERT_MESSAGE( selectable != 0, "SelectBrush: path not selectable" );
1976 selectable->setSelected( true );
1977 g_pParentWnd->GetXYWnd()->PositionView( instance->worldAABB().origin );
1982 class BrushFindIndexWalker : public scene::Graph::Walker
1984 mutable const scene::Node* m_node;
1985 std::size_t& m_count;
1987 BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
1988 : m_node( &node ), m_count( count ){
1990 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1991 if ( Node_isPrimitive( path.top() ) ) {
1992 if ( m_node == path.top().get_pointer() ) {
2003 class EntityFindIndexWalker : public scene::Graph::Walker
2005 mutable const scene::Node* m_node;
2006 std::size_t& m_count;
2008 EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
2009 : m_node( &node ), m_count( count ){
2011 bool pre( const scene::Path& path, scene::Instance& instance ) const {
2012 if ( Node_isEntity( path.top() ) ) {
2013 if ( m_node == path.top().get_pointer() ) {
2024 static void GetSelectionIndex( int *ent, int *brush ){
2025 std::size_t count_brush = 0;
2026 std::size_t count_entity = 0;
2027 if ( GlobalSelectionSystem().countSelected() != 0 ) {
2028 const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2030 GlobalSceneGraph().traverse( BrushFindIndexWalker( path.top(), count_brush ) );
2031 GlobalSceneGraph().traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
2033 *brush = int(count_brush);
2034 *ent = int(count_entity);
2042 ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback ), &dialog );
2044 auto accel = ui::AccelGroup();
2045 window.add_accel_group( accel );
2048 auto vbox = create_dialog_vbox( 4, 4 );
2051 GtkTable* table = create_dialog_table( 2, 2, 4, 4 );
2052 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
2054 ui::Widget label = ui::Label( "Entity number" );
2056 gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 0, 1,
2057 (GtkAttachOptions) ( 0 ),
2058 (GtkAttachOptions) ( 0 ), 0, 0 );
2061 ui::Widget label = ui::Label( "Brush number" );
2063 gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 1, 2,
2064 (GtkAttachOptions) ( 0 ),
2065 (GtkAttachOptions) ( 0 ), 0, 0 );
2068 auto entry = ui::Entry();
2070 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 0, 1,
2071 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
2072 (GtkAttachOptions) ( 0 ), 0, 0 );
2073 gtk_widget_grab_focus( GTK_WIDGET( entry ) );
2077 auto entry = ui::Entry();
2079 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 1, 2,
2080 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
2081 (GtkAttachOptions) ( 0 ), 0, 0 );
2087 GtkHBox* hbox = create_dialog_hbox( 4 );
2088 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), TRUE, TRUE, 0 );
2090 auto button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog );
2091 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
2092 widget_make_default( button );
2093 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
2096 GtkButton* button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
2097 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
2098 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
2103 // Initialize dialog
2107 GetSelectionIndex( &ent, &br );
2108 sprintf( buf, "%i", ent );
2109 gtk_entry_set_text( entity, buf );
2110 sprintf( buf, "%i", br );
2111 gtk_entry_set_text( brush, buf );
2113 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
2114 const char *entstr = gtk_entry_get_text( entity );
2115 const char *brushstr = gtk_entry_get_text( brush );
2116 SelectBrush( atoi( entstr ), atoi( brushstr ) );
2119 gtk_widget_destroy( GTK_WIDGET( window ) );
2122 void Map_constructPreferences( PreferencesPage& page ){
2123 page.appendCheckBox( "", "Load last map on open", g_bLoadLastMap );
2127 class MapEntityClasses : public ModuleObserver
2129 std::size_t m_unrealised;
2131 MapEntityClasses() : m_unrealised( 1 ){
2134 if ( --m_unrealised == 0 ) {
2135 if ( g_map.m_resource != 0 ) {
2136 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
2137 g_map.m_resource->realise();
2142 if ( ++m_unrealised == 1 ) {
2143 if ( g_map.m_resource != 0 ) {
2144 g_map.m_resource->flush();
2145 g_map.m_resource->unrealise();
2151 MapEntityClasses g_MapEntityClasses;
2154 class MapModuleObserver : public ModuleObserver
2156 std::size_t m_unrealised;
2158 MapModuleObserver() : m_unrealised( 1 ){
2161 if ( --m_unrealised == 0 ) {
2162 ASSERT_MESSAGE( !string_empty( g_qeglobals.m_userGamePath.c_str() ), "maps_directory: user-game-path is empty" );
2163 StringOutputStream buffer( 256 );
2164 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2165 Q_mkdir( buffer.c_str() );
2166 g_mapsPath = buffer.c_str();
2170 if ( ++m_unrealised == 1 ) {
2176 MapModuleObserver g_MapModuleObserver;
2178 CopiedString g_strLastMap;
2179 bool g_bLoadLastMap = false;
2181 void Map_Construct(){
2182 GlobalCommands_insert( "RegionOff", FreeCaller<RegionOff>() );
2183 GlobalCommands_insert( "RegionSetXY", FreeCaller<RegionXY>() );
2184 GlobalCommands_insert( "RegionSetBrush", FreeCaller<RegionBrush>() );
2185 GlobalCommands_insert( "RegionSetSelection", FreeCaller<RegionSelected>(), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2187 GlobalPreferenceSystem().registerPreference( "LastMap", CopiedStringImportStringCaller( g_strLastMap ), CopiedStringExportStringCaller( g_strLastMap ) );
2188 GlobalPreferenceSystem().registerPreference( "LoadLastMap", BoolImportStringCaller( g_bLoadLastMap ), BoolExportStringCaller( g_bLoadLastMap ) );
2189 GlobalPreferenceSystem().registerPreference( "MapInfoDlg", WindowPositionImportStringCaller( g_posMapInfoWnd ), WindowPositionExportStringCaller( g_posMapInfoWnd ) );
2191 PreferencesDialog_addSettingsPreferences( FreeCaller1<PreferencesPage&, Map_constructPreferences>() );
2193 GlobalEntityClassManager().attach( g_MapEntityClasses );
2194 Radiant_attachHomePathsObserver( g_MapModuleObserver );
2198 Radiant_detachHomePathsObserver( g_MapModuleObserver );
2199 GlobalEntityClassManager().detach( g_MapEntityClasses );