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 MemberCaller<NameObserver, void(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 ui::Entry brushes_entry{ui::null};
767 ui::Entry entities_entry{ui::null};
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 auto hbox = create_dialog_hbox( 4 );
780 vbox.pack_start( hbox, FALSE, TRUE, 0 );
783 auto table = create_dialog_table( 2, 2, 4, 4 );
784 hbox.pack_start( table, TRUE, TRUE, 0 );
787 auto entry = ui::Entry(ui::New);
789 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
790 gtk_editable_set_editable( GTK_EDITABLE(entry), FALSE );
792 brushes_entry = entry;
795 auto entry = ui::Entry(ui::New);
797 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
798 gtk_editable_set_editable( GTK_EDITABLE(entry), FALSE );
800 entities_entry = entry;
803 ui::Widget label = ui::Label( "Total Brushes" );
805 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
806 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
809 ui::Widget label = ui::Label( "Total Entities" );
811 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
812 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
816 auto vbox2 = create_dialog_vbox( 4 );
817 hbox.pack_start( vbox2, FALSE, FALSE, 0 );
820 auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_ok ), &dialog );
821 vbox2.pack_start( button, FALSE, FALSE, 0 );
826 ui::Widget label = ui::Label( "Entity breakdown" );
828 vbox.pack_start( label, FALSE, TRUE, 0 );
829 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
832 auto scr = create_scrolled_window( ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4 );
833 vbox.pack_start( scr, TRUE, TRUE, 0 );
836 auto store = ui::ListStore::from(gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_STRING ));
838 auto view = ui::TreeView(ui::TreeModel::from(store._handle));
839 gtk_tree_view_set_headers_clickable(view, TRUE );
842 auto renderer = ui::CellRendererText(ui::New);
843 auto column = ui::TreeViewColumn( "Entity", renderer, {{"text", 0}} );
844 gtk_tree_view_append_column(view, column );
845 gtk_tree_view_column_set_sort_column_id( column, 0 );
849 auto renderer = ui::CellRendererText(ui::New);
850 auto column = ui::TreeViewColumn( "Count", renderer, {{"text", 1}} );
851 gtk_tree_view_append_column(view, column );
852 gtk_tree_view_column_set_sort_column_id( column, 1 );
859 EntityBreakdownWalker = store;
867 EntityBreakdown entitymap;
868 Scene_EntityBreakdown( entitymap );
870 for ( EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i )
873 sprintf( tmp, "%u", Unsigned( ( *i ).second ) );
874 EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, tmp);
878 EntityBreakdownWalker.unref();
881 sprintf( tmp, "%u", Unsigned( g_brushCount.get() ) );
882 brushes_entry.text(tmp);
883 sprintf( tmp, "%u", Unsigned( g_entityCount.get() ) );
884 entities_entry.text(tmp);
886 modal_dialog_show( window, dialog );
889 window_get_position( window, g_posMapInfoWnd );
899 const char* m_message;
901 ScopeTimer( const char* message )
902 : m_message( message ){
906 double elapsed_time = m_timer.elapsed_msec() / 1000.f;
907 globalOutputStream() << m_message << " timer: " << FloatFormat( elapsed_time, 5, 2 ) << " second(s) elapsed\n";
911 CopiedString g_strLastFolder = "";
919 void Map_LoadFile( const char *filename ){
920 globalOutputStream() << "Loading map from " << filename << "\n";
921 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
923 MRU_AddFile( filename );
924 g_strLastFolder = g_path_get_dirname( filename );
927 ScopeTimer timer( "map load" );
929 const MapFormat* format = NULL;
930 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
931 if ( string_not_empty( moduleName ) ) {
932 format = ReferenceAPI_getMapModules().findModule( moduleName );
935 for ( int i = 0; i < Brush_toggleFormatCount(); ++i )
940 Brush_toggleFormat( i );
941 g_map.m_name = filename;
942 Map_UpdateTitle( g_map );
943 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
945 format->wrongFormat = false;
947 g_map.m_resource->attach( g_map );
949 if ( !format->wrongFormat ) {
955 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
958 globalOutputStream() << "--- LoadMapFile ---\n";
959 globalOutputStream() << g_map.m_name.c_str() << "\n";
961 globalOutputStream() << Unsigned( g_brushCount.get() ) << " primitive\n";
962 globalOutputStream() << Unsigned( g_entityCount.get() ) << " entities\n";
964 //GlobalEntityCreator().printStatistics();
967 // move the view to a start position
971 g_currentMap = &g_map;
973 // restart VFS to apply new pak filtering based on mapname
974 // needed for daemon DPK VFS
981 virtual bool excluded( scene::Node& node ) const = 0;
984 class ExcludeWalker : public scene::Traversable::Walker
986 const scene::Traversable::Walker& m_walker;
987 const Excluder* m_exclude;
990 ExcludeWalker( const scene::Traversable::Walker& walker, const Excluder& exclude )
991 : m_walker( walker ), m_exclude( &exclude ), m_skip( false ){
993 bool pre( scene::Node& node ) const {
994 if ( m_exclude->excluded( node ) || node.isRoot() ) {
1000 m_walker.pre( node );
1004 void post( scene::Node& node ) const {
1010 m_walker.post( node );
1015 class AnyInstanceSelected : public scene::Instantiable::Visitor
1019 AnyInstanceSelected( bool& selected ) : m_selected( selected ){
1022 void visit( scene::Instance& instance ) const {
1023 Selectable* selectable = Instance_getSelectable( instance );
1024 if ( selectable != 0
1025 && selectable->isSelected() ) {
1031 bool Node_instanceSelected( scene::Node& node ){
1032 scene::Instantiable* instantiable = Node_getInstantiable( node );
1033 ASSERT_NOTNULL( instantiable );
1035 instantiable->forEachInstance( AnyInstanceSelected( selected ) );
1039 class SelectedDescendantWalker : public scene::Traversable::Walker
1043 SelectedDescendantWalker( bool& selected ) : m_selected( selected ){
1047 bool pre( scene::Node& node ) const {
1048 if ( node.isRoot() ) {
1052 if ( Node_instanceSelected( node ) ) {
1060 bool Node_selectedDescendant( scene::Node& node ){
1062 Node_traverseSubgraph( node, SelectedDescendantWalker( selected ) );
1066 class SelectionExcluder : public Excluder
1069 bool excluded( scene::Node& node ) const {
1070 return !Node_selectedDescendant( node );
1074 class IncludeSelectedWalker : public scene::Traversable::Walker
1076 const scene::Traversable::Walker& m_walker;
1077 mutable std::size_t m_selected;
1078 mutable bool m_skip;
1080 bool selectedParent() const {
1081 return m_selected != 0;
1084 IncludeSelectedWalker( const scene::Traversable::Walker& walker )
1085 : m_walker( walker ), m_selected( 0 ), m_skip( false ){
1087 bool pre( scene::Node& node ) const {
1089 // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1090 if ( !node.isRoot() && ( Node_selectedDescendant( node ) || selectedParent() ) ) {
1091 if ( Node_instanceSelected( node ) ) {
1094 m_walker.pre( node );
1103 void post( scene::Node& node ) const {
1109 if ( Node_instanceSelected( node ) ) {
1112 m_walker.post( node );
1117 void Map_Traverse_Selected( scene::Node& root, const scene::Traversable::Walker& walker ){
1118 scene::Traversable* traversable = Node_getTraversable( root );
1119 if ( traversable != 0 ) {
1121 traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1123 traversable->traverse( IncludeSelectedWalker( walker ) );
1128 void Map_ExportSelected( TextOutputStream& out, const MapFormat& format ){
1129 format.writeGraph( GlobalSceneGraph().root(), Map_Traverse_Selected, out );
1132 void Map_Traverse( scene::Node& root, const scene::Traversable::Walker& walker ){
1133 scene::Traversable* traversable = Node_getTraversable( root );
1134 if ( traversable != 0 ) {
1135 traversable->traverse( walker );
1139 class RegionExcluder : public Excluder
1142 bool excluded( scene::Node& node ) const {
1143 return node.excluded();
1147 void Map_Traverse_Region( scene::Node& root, const scene::Traversable::Walker& walker ){
1148 scene::Traversable* traversable = Node_getTraversable( root );
1149 if ( traversable != 0 ) {
1150 traversable->traverse( ExcludeWalker( walker, RegionExcluder() ) );
1154 bool Map_SaveRegion( const char *filename ){
1157 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Region, filename );
1159 RemoveRegionBrushes();
1165 void Map_RenameAbsolute( const char* absolute ){
1166 Resource* resource = GlobalReferenceCache().capture( absolute );
1167 NodeSmartReference clone( NewMapRoot( path_make_relative( absolute, GlobalFileSystem().findRoot( absolute ) ) ) );
1168 resource->setNode( clone.get_pointer() );
1171 //ScopeTimer timer("clone subgraph");
1172 Node_getTraversable( GlobalSceneGraph().root() )->traverse( CloneAll( clone ) );
1175 g_map.m_resource->detach( g_map );
1176 GlobalReferenceCache().release( g_map.m_name.c_str() );
1178 g_map.m_resource = resource;
1180 g_map.m_name = absolute;
1181 Map_UpdateTitle( g_map );
1183 g_map.m_resource->attach( g_map );
1184 // refresh VFS to apply new pak filtering based on mapname
1185 // needed for daemon DPK VFS
1189 void Map_Rename( const char* filename ){
1190 if ( !string_equal( g_map.m_name.c_str(), filename ) ) {
1191 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1193 Map_RenameAbsolute( filename );
1195 SceneChangeNotify();
1206 ScopeTimer timer( "map save" );
1208 return true; // assume success..
1218 //globalOutputStream() << "Map_New\n";
1220 g_map.m_name = "unnamed.map";
1221 Map_UpdateTitle( g_map );
1224 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
1225 // ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1226 g_map.m_resource->attach( g_map );
1228 SceneChangeNotify();
1231 FocusViews( g_vector3_identity, 0 );
1233 g_currentMap = &g_map;
1235 // restart VFS to apply new pak filtering based on mapname
1236 // needed for daemon DPK VFS
1240 extern void ConstructRegionBrushes( scene::Node * brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_maxs );
1242 void ConstructRegionStartpoint( scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs ){
1244 \todo we need to make sure that the player start IS inside the region and bail out if it's not
1245 the compiler will refuse to compile a map with a player_start somewhere in empty space..
1246 for now, let's just print an error
1249 Vector3 vOrig( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ) );
1251 for ( int i = 0 ; i < 3 ; i++ )
1253 if ( vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i] ) {
1254 globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1259 // write the info_playerstart
1261 sprintf( sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2] );
1262 Node_getEntity( *startpoint )->setKeyValue( "origin", sTmp );
1263 sprintf( sTmp, "%d", (int)Camera_getAngles( *g_pParentWnd->GetCamWnd() )[CAMERA_YAW] );
1264 Node_getEntity( *startpoint )->setKeyValue( "angle", sTmp );
1268 ===========================================================
1272 ===========================================================
1275 Vector3 region_mins( g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord );
1276 Vector3 region_maxs( g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord );
1278 scene::Node* region_sides[6];
1279 scene::Node* region_startpoint = 0;
1284 a regioned map will have temp walls put up at the region boundary
1285 \todo TODO TTimo old implementation of region brushes
1286 we still add them straight in the worldspawn and take them out after the map is saved
1287 with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1290 void AddRegionBrushes( void ){
1293 for ( i = 0; i < 6; i++ )
1295 region_sides[i] = &GlobalBrushCreator().createBrush();
1296 Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( NodeSmartReference( *region_sides[i] ) );
1299 region_startpoint = &GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "info_player_start", false ) );
1301 ConstructRegionBrushes( region_sides, region_mins, region_maxs );
1302 ConstructRegionStartpoint( region_startpoint, region_mins, region_maxs );
1304 Node_getTraversable( GlobalSceneGraph().root() )->insert( NodeSmartReference( *region_startpoint ) );
1307 void RemoveRegionBrushes( void ){
1308 for ( std::size_t i = 0; i < 6; i++ )
1310 Node_getTraversable( *Map_GetWorldspawn( g_map ) )->erase( *region_sides[i] );
1312 Node_getTraversable( GlobalSceneGraph().root() )->erase( *region_startpoint );
1315 inline void exclude_node( scene::Node& node, bool exclude ){
1317 ? node.enable( scene::Node::eExcluded )
1318 : node.disable( scene::Node::eExcluded );
1321 class ExcludeAllWalker : public scene::Graph::Walker
1325 ExcludeAllWalker( bool exclude )
1326 : m_exclude( exclude ){
1328 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1329 exclude_node( path.top(), m_exclude );
1335 void Scene_Exclude_All( bool exclude ){
1336 GlobalSceneGraph().traverse( ExcludeAllWalker( exclude ) );
1339 bool Instance_isSelected( const scene::Instance& instance ){
1340 const Selectable* selectable = Instance_getSelectable( instance );
1341 return selectable != 0 && selectable->isSelected();
1344 class ExcludeSelectedWalker : public scene::Graph::Walker
1348 ExcludeSelectedWalker( bool exclude )
1349 : m_exclude( exclude ){
1351 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1352 exclude_node( path.top(), ( instance.isSelected() || instance.childSelected() || instance.parentSelected() ) == m_exclude );
1357 void Scene_Exclude_Selected( bool exclude ){
1358 GlobalSceneGraph().traverse( ExcludeSelectedWalker( exclude ) );
1361 class ExcludeRegionedWalker : public scene::Graph::Walker
1365 ExcludeRegionedWalker( bool exclude )
1366 : m_exclude( exclude ){
1368 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1373 aabb_intersects_aabb(
1374 instance.worldAABB(),
1375 aabb_for_minmax( region_mins, region_maxs )
1385 void Scene_Exclude_Region( bool exclude ){
1386 GlobalSceneGraph().traverse( ExcludeRegionedWalker( exclude ) );
1393 Other filtering options may still be on
1396 void Map_RegionOff(){
1397 region_active = false;
1399 region_maxs[0] = g_MaxWorldCoord - 64;
1400 region_mins[0] = g_MinWorldCoord + 64;
1401 region_maxs[1] = g_MaxWorldCoord - 64;
1402 region_mins[1] = g_MinWorldCoord + 64;
1403 region_maxs[2] = g_MaxWorldCoord - 64;
1404 region_mins[2] = g_MinWorldCoord + 64;
1406 Scene_Exclude_All( false );
1409 void Map_ApplyRegion( void ){
1410 region_active = true;
1412 Scene_Exclude_Region( false );
1417 ========================
1418 Map_RegionSelectedBrushes
1419 ========================
1421 void Map_RegionSelectedBrushes( void ){
1424 if ( GlobalSelectionSystem().countSelected() != 0
1425 && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
1426 region_active = true;
1427 Select_GetBounds( region_mins, region_maxs );
1429 Scene_Exclude_Selected( false );
1431 GlobalSelectionSystem().setSelectedAll( false );
1441 void Map_RegionXY( float x_min, float y_min, float x_max, float y_max ){
1444 region_mins[0] = x_min;
1445 region_maxs[0] = x_max;
1446 region_mins[1] = y_min;
1447 region_maxs[1] = y_max;
1448 region_mins[2] = g_MinWorldCoord + 64;
1449 region_maxs[2] = g_MaxWorldCoord - 64;
1454 void Map_RegionBounds( const AABB& bounds ){
1457 region_mins = vector3_subtracted( bounds.origin, bounds.extents );
1458 region_maxs = vector3_added( bounds.origin, bounds.extents );
1470 void Map_RegionBrush( void ){
1471 if ( GlobalSelectionSystem().countSelected() != 0 ) {
1472 scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
1473 Map_RegionBounds( instance.worldAABB() );
1482 bool Map_ImportFile( const char* filename ){
1483 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
1485 g_strLastFolder = g_path_get_dirname( filename );
1487 bool success = false;
1489 if ( extension_equal( path_get_extension( filename ), "bsp" ) ) {
1494 const MapFormat* format = NULL;
1495 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
1496 if ( string_not_empty( moduleName ) ) {
1497 format = ReferenceAPI_getMapModules().findModule( moduleName );
1501 format->wrongFormat = false;
1503 Resource* resource = GlobalReferenceCache().capture( filename );
1504 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1505 if ( !resource->load() ) {
1506 GlobalReferenceCache().release( filename );
1510 if ( format->wrongFormat ) {
1511 GlobalReferenceCache().release( filename );
1515 NodeSmartReference clone( NewMapRoot( "" ) );
1516 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1517 Map_gatherNamespaced( clone );
1518 Map_mergeClonedNames();
1521 GlobalReferenceCache().release( filename );
1524 SceneChangeNotify();
1530 const char *type = GlobalRadiant().getRequiredGameDescriptionKeyValue( "q3map2_type" );
1531 int n = string_length( path_get_extension( filename ) );
1532 if ( n && ( extension_equal( path_get_extension( filename ), "bsp" ) || extension_equal( path_get_extension( filename ), "map" ) ) ) {
1533 StringBuffer output;
1534 output.push_string( AppPath_get() );
1535 output.push_string( "q3map2." );
1536 output.push_string( RADIANT_EXECUTABLE );
1537 output.push_string( " -v -game " );
1538 output.push_string( ( type && *type ) ? type : "quake3" );
1539 output.push_string( " -fs_basepath \"" );
1540 output.push_string( EnginePath_get() );
1541 output.push_string( "\" -fs_homepath \"" );
1542 output.push_string( g_qeglobals.m_userEnginePath.c_str() );
1543 output.push_string( "\"" );
1546 for ( int i = 0; i < g_pakPathCount; i++ ) {
1547 if ( g_strcmp0( g_strPakPath[i].c_str(), "") ) {
1548 output.push_string( " -fs_pakpath \"" );
1549 output.push_string( g_strPakPath[i].c_str() );
1550 output.push_string( "\"" );
1554 output.push_string( " -fs_game " );
1555 output.push_string( gamename_get() );
1556 output.push_string( " -convert -format " );
1557 output.push_string( Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map" );
1558 if ( extension_equal( path_get_extension( filename ), "map" ) ) {
1559 output.push_string( " -readmap " );
1561 output.push_string( " \"" );
1562 output.push_string( filename );
1563 output.push_string( "\"" );
1566 Q_Exec( NULL, output.c_str(), NULL, false, true );
1568 // rebuild filename as "filenamewithoutext_converted.map"
1570 output.push_range( filename, filename + string_length( filename ) - ( n + 1 ) );
1571 output.push_string( "_converted.map" );
1572 filename = output.c_str();
1575 Resource* resource = GlobalReferenceCache().capture( filename );
1576 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1577 if ( !resource->load() ) {
1578 GlobalReferenceCache().release( filename );
1581 NodeSmartReference clone( NewMapRoot( "" ) );
1582 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1583 Map_gatherNamespaced( clone );
1584 Map_mergeClonedNames();
1587 GlobalReferenceCache().release( filename );
1590 SceneChangeNotify();
1599 bool Map_SaveFile( const char* filename ){
1600 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1601 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse, filename );
1603 // refresh VFS to apply new pak filtering based on mapname
1604 // needed for daemon DPK VFS
1615 // Saves selected world brushes and whole entities with partial/full selections
1617 bool Map_SaveSelected( const char* filename ){
1618 return MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Selected, filename );
1622 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1624 scene::Node& m_parent;
1626 ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ){
1628 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1629 if ( path.top().get_pointer() != &m_parent
1630 && Node_isPrimitive( path.top() ) ) {
1631 Selectable* selectable = Instance_getSelectable( instance );
1632 if ( selectable != 0
1633 && selectable->isSelected()
1634 && path.size() > 1 ) {
1640 void post( const scene::Path& path, scene::Instance& instance ) const {
1641 if ( path.top().get_pointer() != &m_parent
1642 && Node_isPrimitive( path.top() ) ) {
1643 Selectable* selectable = Instance_getSelectable( instance );
1644 if ( selectable != 0
1645 && selectable->isSelected()
1646 && path.size() > 1 ) {
1647 scene::Node& parent = path.parent();
1648 if ( &parent != &m_parent ) {
1649 NodeSmartReference node( path.top().get() );
1650 Node_getTraversable( parent )->erase( node );
1651 Node_getTraversable( m_parent )->insert( node );
1658 void Scene_parentSelectedBrushesToEntity( scene::Graph& graph, scene::Node& parent ){
1659 graph.traverse( ParentSelectedBrushesToEntityWalker( parent ) );
1662 class CountSelectedBrushes : public scene::Graph::Walker
1664 std::size_t& m_count;
1665 mutable std::size_t m_depth;
1667 CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
1670 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1671 if ( ++m_depth != 1 && path.top().get().isRoot() ) {
1674 Selectable* selectable = Instance_getSelectable( instance );
1675 if ( selectable != 0
1676 && selectable->isSelected()
1677 && Node_isPrimitive( path.top() ) ) {
1682 void post( const scene::Path& path, scene::Instance& instance ) const {
1687 std::size_t Scene_countSelectedBrushes( scene::Graph& graph ){
1689 graph.traverse( CountSelectedBrushes( count ) );
1701 const char* nodetype_get_name( ENodeType type ){
1702 if ( type == eNodeMap ) {
1705 if ( type == eNodeEntity ) {
1708 if ( type == eNodePrimitive ) {
1714 ENodeType node_get_nodetype( scene::Node& node ){
1715 if ( Node_isEntity( node ) ) {
1718 if ( Node_isPrimitive( node ) ) {
1719 return eNodePrimitive;
1721 return eNodeUnknown;
1724 bool contains_entity( scene::Node& node ){
1725 return Node_getTraversable( node ) != 0 && !Node_isBrush( node ) && !Node_isPatch( node ) && !Node_isEntity( node );
1728 bool contains_primitive( scene::Node& node ){
1729 return Node_isEntity( node ) && Node_getTraversable( node ) != 0 && Node_getEntity( node )->isContainer();
1732 ENodeType node_get_contains( scene::Node& node ){
1733 if ( contains_entity( node ) ) {
1736 if ( contains_primitive( node ) ) {
1737 return eNodePrimitive;
1739 return eNodeUnknown;
1742 void Path_parent( const scene::Path& parent, const scene::Path& child ){
1743 ENodeType contains = node_get_contains( parent.top() );
1744 ENodeType type = node_get_nodetype( child.top() );
1746 if ( contains != eNodeUnknown && contains == type ) {
1747 NodeSmartReference node( child.top().get() );
1748 Path_deleteTop( child );
1749 Node_getTraversable( parent.top() )->insert( node );
1750 SceneChangeNotify();
1754 globalErrorStream() << "failed - " << nodetype_get_name( type ) << " cannot be parented to " << nodetype_get_name( contains ) << " container.\n";
1758 void Scene_parentSelected(){
1759 UndoableCommand undo( "parentSelected" );
1761 if ( GlobalSelectionSystem().countSelected() > 1 ) {
1762 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1764 const scene::Path& m_parent;
1766 ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
1768 void visit( scene::Instance& instance ) const {
1769 if ( &m_parent != &instance.path() ) {
1770 Path_parent( m_parent, instance.path() );
1775 ParentSelectedBrushesToEntityWalker visitor( GlobalSelectionSystem().ultimateSelected().path() );
1776 GlobalSelectionSystem().foreachSelected( visitor );
1780 globalOutputStream() << "failed - did not find two selected nodes.\n";
1787 if ( ConfirmModified( "New Map" ) ) {
1794 CopiedString g_mapsPath;
1796 const char* getMapsPath(){
1797 return g_mapsPath.c_str();
1800 const char* getLastFolderPath(){
1801 if (g_strLastFolder.empty()) {
1802 GlobalPreferenceSystem().registerPreference( "LastFolder", make_property_string( g_strLastFolder ) );
1803 if (g_strLastFolder.empty()) {
1804 g_strLastFolder = g_qeglobals.m_userGamePath;
1807 return g_strLastFolder.c_str();
1810 const char* map_open( const char* title ){
1811 return MainFrame_getWindow().file_dialog( TRUE, title, getLastFolderPath(), MapFormat::Name(), true, false, false );
1814 const char* map_import( const char* title ){
1815 return MainFrame_getWindow().file_dialog( TRUE, title, getLastFolderPath(), MapFormat::Name(), false, true, false );
1818 const char* map_save( const char* title ){
1819 return MainFrame_getWindow().file_dialog( FALSE, title, getLastFolderPath(), MapFormat::Name(), false, false, true );
1823 if ( !ConfirmModified( "Open Map" ) ) {
1827 const char* filename = map_open( "Open Map" );
1829 if ( filename != NULL ) {
1830 MRU_AddFile( filename );
1833 Map_LoadFile( filename );
1838 const char* filename = map_import( "Import Map" );
1840 if ( filename != NULL ) {
1841 UndoableCommand undo( "mapImport" );
1842 Map_ImportFile( filename );
1847 const char* filename = map_save( "Save Map" );
1849 if ( filename != NULL ) {
1850 g_strLastFolder = g_path_get_dirname( filename );
1851 MRU_AddFile( filename );
1852 Map_Rename( filename );
1863 if ( Map_Unnamed( g_map ) ) {
1866 else if ( Map_Modified( g_map ) ) {
1872 const char* filename = map_save( "Export Selection" );
1874 if ( filename != NULL ) {
1875 g_strLastFolder = g_path_get_dirname( filename );
1876 Map_SaveSelected( filename );
1881 const char* filename = map_save( "Export Region" );
1883 if ( filename != NULL ) {
1884 g_strLastFolder = g_path_get_dirname( filename );
1885 Map_SaveRegion( filename );
1892 SceneChangeNotify();
1897 g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1898 g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
1899 g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1900 g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
1902 SceneChangeNotify();
1907 SceneChangeNotify();
1910 void RegionSelected(){
1911 Map_RegionSelectedBrushes();
1912 SceneChangeNotify();
1919 class BrushFindByIndexWalker : public scene::Traversable::Walker
1921 mutable std::size_t m_index;
1922 scene::Path& m_path;
1924 BrushFindByIndexWalker( std::size_t index, scene::Path& path )
1925 : m_index( index ), m_path( path ){
1927 bool pre( scene::Node& node ) const {
1928 if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
1929 m_path.push( makeReference( node ) );
1935 class EntityFindByIndexWalker : public scene::Traversable::Walker
1937 mutable std::size_t m_index;
1938 scene::Path& m_path;
1940 EntityFindByIndexWalker( std::size_t index, scene::Path& path )
1941 : m_index( index ), m_path( path ){
1943 bool pre( scene::Node& node ) const {
1944 if ( Node_isEntity( node ) && m_index-- == 0 ) {
1945 m_path.push( makeReference( node ) );
1951 void Scene_FindEntityBrush( std::size_t entity, std::size_t brush, scene::Path& path ){
1952 path.push( makeReference( GlobalSceneGraph().root() ) );
1954 Node_getTraversable( path.top() )->traverse( EntityFindByIndexWalker( entity, path ) );
1956 if ( path.size() == 2 ) {
1957 scene::Traversable* traversable = Node_getTraversable( path.top() );
1958 if ( traversable != 0 ) {
1959 traversable->traverse( BrushFindByIndexWalker( brush, path ) );
1964 inline bool Node_hasChildren( scene::Node& node ){
1965 scene::Traversable* traversable = Node_getTraversable( node );
1966 return traversable != 0 && !traversable->empty();
1969 void SelectBrush( int entitynum, int brushnum ){
1971 Scene_FindEntityBrush( entitynum, brushnum, path );
1972 if ( path.size() == 3 || ( path.size() == 2 && !Node_hasChildren( path.top() ) ) ) {
1973 scene::Instance* instance = GlobalSceneGraph().find( path );
1974 ASSERT_MESSAGE( instance != 0, "SelectBrush: path not found in scenegraph" );
1975 Selectable* selectable = Instance_getSelectable( *instance );
1976 ASSERT_MESSAGE( selectable != 0, "SelectBrush: path not selectable" );
1977 selectable->setSelected( true );
1978 g_pParentWnd->GetXYWnd()->PositionView( instance->worldAABB().origin );
1983 class BrushFindIndexWalker : public scene::Graph::Walker
1985 mutable const scene::Node* m_node;
1986 std::size_t& m_count;
1988 BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
1989 : m_node( &node ), m_count( count ){
1991 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1992 if ( Node_isPrimitive( path.top() ) ) {
1993 if ( m_node == path.top().get_pointer() ) {
2004 class EntityFindIndexWalker : public scene::Graph::Walker
2006 mutable const scene::Node* m_node;
2007 std::size_t& m_count;
2009 EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
2010 : m_node( &node ), m_count( count ){
2012 bool pre( const scene::Path& path, scene::Instance& instance ) const {
2013 if ( Node_isEntity( path.top() ) ) {
2014 if ( m_node == path.top().get_pointer() ) {
2025 static void GetSelectionIndex( int *ent, int *brush ){
2026 std::size_t count_brush = 0;
2027 std::size_t count_entity = 0;
2028 if ( GlobalSelectionSystem().countSelected() != 0 ) {
2029 const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2031 GlobalSceneGraph().traverse( BrushFindIndexWalker( path.top(), count_brush ) );
2032 GlobalSceneGraph().traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
2034 *brush = int(count_brush);
2035 *ent = int(count_entity);
2040 ui::Entry entity{ui::null};
2041 ui::Entry brush{ui::null};
2043 ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback ), &dialog );
2045 auto accel = ui::AccelGroup(ui::New);
2046 window.add_accel_group( accel );
2049 auto vbox = create_dialog_vbox( 4, 4 );
2052 auto table = create_dialog_table( 2, 2, 4, 4 );
2053 vbox.pack_start( table, TRUE, TRUE, 0 );
2055 ui::Widget label = ui::Label( "Entity number" );
2057 (table).attach(label, {0, 1, 0, 1}, {0, 0});
2060 ui::Widget label = ui::Label( "Brush number" );
2062 (table).attach(label, {0, 1, 1, 2}, {0, 0});
2065 auto entry = ui::Entry(ui::New);
2067 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
2068 gtk_widget_grab_focus( entry );
2072 auto entry = ui::Entry(ui::New);
2074 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
2080 auto hbox = create_dialog_hbox( 4 );
2081 vbox.pack_start( hbox, TRUE, TRUE, 0 );
2083 auto button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog );
2084 hbox.pack_start( button, FALSE, FALSE, 0 );
2085 widget_make_default( button );
2086 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
2089 auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
2090 hbox.pack_start( button, FALSE, FALSE, 0 );
2091 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
2096 // Initialize dialog
2100 GetSelectionIndex( &ent, &br );
2101 sprintf( buf, "%i", ent );
2103 sprintf( buf, "%i", br );
2106 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
2107 const char *entstr = gtk_entry_get_text( entity );
2108 const char *brushstr = gtk_entry_get_text( brush );
2109 SelectBrush( atoi( entstr ), atoi( brushstr ) );
2115 void Map_constructPreferences( PreferencesPage& page ){
2116 page.appendCheckBox( "", "Load last map on open", g_bLoadLastMap );
2120 class MapEntityClasses : public ModuleObserver
2122 std::size_t m_unrealised;
2124 MapEntityClasses() : m_unrealised( 1 ){
2127 if ( --m_unrealised == 0 ) {
2128 if ( g_map.m_resource != 0 ) {
2129 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
2130 g_map.m_resource->realise();
2135 if ( ++m_unrealised == 1 ) {
2136 if ( g_map.m_resource != 0 ) {
2137 g_map.m_resource->flush();
2138 g_map.m_resource->unrealise();
2144 MapEntityClasses g_MapEntityClasses;
2147 class MapModuleObserver : public ModuleObserver
2149 std::size_t m_unrealised;
2151 MapModuleObserver() : m_unrealised( 1 ){
2154 if ( --m_unrealised == 0 ) {
2155 ASSERT_MESSAGE( !string_empty( g_qeglobals.m_userGamePath.c_str() ), "maps_directory: user-game-path is empty" );
2156 StringOutputStream buffer( 256 );
2157 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2158 Q_mkdir( buffer.c_str() );
2159 g_mapsPath = buffer.c_str();
2163 if ( ++m_unrealised == 1 ) {
2169 MapModuleObserver g_MapModuleObserver;
2171 CopiedString g_strLastMap;
2172 bool g_bLoadLastMap = false;
2174 void Map_Construct(){
2175 GlobalCommands_insert( "RegionOff", makeCallbackF(RegionOff) );
2176 GlobalCommands_insert( "RegionSetXY", makeCallbackF(RegionXY) );
2177 GlobalCommands_insert( "RegionSetBrush", makeCallbackF(RegionBrush) );
2178 GlobalCommands_insert( "RegionSetSelection", makeCallbackF(RegionSelected), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2180 GlobalPreferenceSystem().registerPreference( "LastMap", make_property_string( g_strLastMap ) );
2181 GlobalPreferenceSystem().registerPreference( "LoadLastMap", make_property_string( g_bLoadLastMap ) );
2182 GlobalPreferenceSystem().registerPreference( "MapInfoDlg", make_property<WindowPosition_String>( g_posMapInfoWnd ) );
2184 PreferencesDialog_addSettingsPreferences( makeCallbackF(Map_constructPreferences) );
2186 GlobalEntityClassManager().attach( g_MapEntityClasses );
2187 Radiant_attachHomePathsObserver( g_MapModuleObserver );
2191 Radiant_detachHomePathsObserver( g_MapModuleObserver );
2192 GlobalEntityClassManager().detach( g_MapEntityClasses );