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"
30 MapModules& ReferenceAPI_getMapModules();
32 #include "iselection.h"
36 #include "ireference.h"
37 #include "ifiletypes.h"
43 #include "ifilesystem.h"
44 #include "namespace.h"
45 #include "moduleobserver.h"
49 #include <gdk/gdkkeysyms.h>
50 #include "uilib/uilib.h"
53 #include "transformlib.h"
54 #include "selectionlib.h"
55 #include "instancelib.h"
56 #include "traverselib.h"
58 #include "eclasslib.h"
60 #include "stream/textfilestream.h"
63 #include "uniquenames.h"
64 #include "modulesystem/singletonmodule.h"
65 #include "modulesystem/moduleregistry.h"
66 #include "stream/stringstream.h"
67 #include "signal/signal.h"
69 #include "gtkutil/filechooser.h"
73 #include "filetypes.h"
75 #include "entityinspector.h"
78 #include "camwindow.h"
80 #include "mainframe.h"
81 #include "preferences.h"
82 #include "preferencesystem.h"
83 #include "referencecache.h"
87 #include "brushmodule.h"
90 bool g_writeMapComments = true;
91 bool g_deferredStartupShaders = false;
100 //globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n";
101 m_names.insert( name_read( c_str() ) );
107 //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
108 m_names.erase( name_read( c_str() ) );
112 NameObserver& operator=( const NameObserver& other );
115 NameObserver( UniqueNames& names ) : m_names( names ){
118 NameObserver( const NameObserver& other ) : m_names( other.m_names ), m_name( other.m_name ){
127 return string_empty( c_str() );
130 const char* c_str() const {
131 return m_name.c_str();
134 void nameChanged( const char* name ){
140 typedef MemberCaller<NameObserver, void(const char*), &NameObserver::nameChanged> NameChangedCaller;
143 class BasicNamespace : public Namespace
145 typedef std::map<NameCallback, NameObserver> Names;
147 UniqueNames m_uniqueNames;
150 ASSERT_MESSAGE( m_names.empty(), "namespace: names still registered at shutdown" );
153 void attach( const NameCallback& setName, const NameCallbackCallback& attachObserver ){
154 std::pair<Names::iterator, bool> result = m_names.insert( Names::value_type( setName, m_uniqueNames ) );
155 ASSERT_MESSAGE( result.second, "cannot attach name" );
156 attachObserver( NameObserver::NameChangedCaller( ( *result.first ).second ) );
157 //globalOutputStream() << "attach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
160 void detach( const NameCallback& setName, const NameCallbackCallback& detachObserver ){
161 Names::iterator i = m_names.find( setName );
162 ASSERT_MESSAGE( i != m_names.end(), "cannot detach name" );
163 //globalOutputStream() << "detach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
164 detachObserver( NameObserver::NameChangedCaller( ( *i ).second ) );
168 void makeUnique( const char* name, const NameCallback& setName ) const {
170 name_write( buffer, m_uniqueNames.make_unique( name_read( name ) ) );
174 void mergeNames( const BasicNamespace& other ) const {
175 typedef std::list<NameCallback> SetNameCallbacks;
176 typedef std::map<CopiedString, SetNameCallbacks> NameGroups;
179 UniqueNames uniqueNames( other.m_uniqueNames );
181 for ( Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i )
183 groups[( *i ).second.c_str()].push_back( ( *i ).first );
186 for ( NameGroups::iterator i = groups.begin(); i != groups.end(); ++i )
188 name_t uniqueName( uniqueNames.make_unique( name_read( ( *i ).first.c_str() ) ) );
189 uniqueNames.insert( uniqueName );
192 name_write( buffer, uniqueName );
194 //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n";
196 SetNameCallbacks& setNameCallbacks = ( *i ).second;
198 for ( SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j )
206 BasicNamespace g_defaultNamespace;
207 BasicNamespace g_cloneNamespace;
211 Namespace* m_namespace;
213 typedef Namespace Type;
215 STRING_CONSTANT( Name, "*" );
218 m_namespace = &g_defaultNamespace;
221 Namespace* getTable(){
226 typedef SingletonModule<NamespaceAPI> NamespaceModule;
227 typedef Static<NamespaceModule> StaticNamespaceModule;
228 StaticRegisterModule staticRegisterDefaultNamespace( StaticNamespaceModule::instance() );
231 std::list<Namespaced*> g_cloned;
233 inline Namespaced* Node_getNamespaced( scene::Node& node ){
234 return NodeTypeCast<Namespaced>::cast( node );
237 void Node_gatherNamespaced( scene::Node& node ){
238 Namespaced* namespaced = Node_getNamespaced( node );
239 if ( namespaced != 0 ) {
240 g_cloned.push_back( namespaced );
244 class GatherNamespaced : public scene::Traversable::Walker
247 bool pre( scene::Node& node ) const {
248 Node_gatherNamespaced( node );
253 void Map_gatherNamespaced( scene::Node& root ){
254 Node_traverseSubgraph( root, GatherNamespaced() );
257 void Map_mergeClonedNames(){
258 for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
260 ( *i )->setNamespace( g_cloneNamespace );
262 g_cloneNamespace.mergeNames( g_defaultNamespace );
263 for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
265 ( *i )->setNamespace( g_defaultNamespace );
279 void set( scene::Node* node ){
289 scene::Node* get() const {
295 void Map_SetValid( Map& map, bool valid );
297 void Map_UpdateTitle( const Map& map );
299 void Map_SetWorldspawn( Map& map, scene::Node* node );
302 class Map : public ModuleObserver
306 Resource* m_resource;
311 void ( *m_modified_changed )( const Map& );
313 Signal0 m_mapValidCallbacks;
315 WorldNode m_world_node; // "classname" "worldspawn" !
317 Map() : m_resource( 0 ), m_valid( false ), m_modified_changed( Map_UpdateTitle ){
321 if ( m_resource != 0 ) {
322 if ( Map_Unnamed( *this ) ) {
323 g_map.m_resource->setNode( NewMapRoot( "" ).get_pointer() );
324 MapFile* map = Node_getMapFile( *g_map.m_resource->getNode() );
334 GlobalSceneGraph().insert_root( *m_resource->getNode() );
338 Map_SetValid( g_map, true );
343 if ( m_resource != 0 ) {
344 Map_SetValid( g_map, false );
345 Map_SetWorldspawn( g_map, 0 );
348 GlobalUndoSystem().clear();
350 GlobalSceneGraph().erase_root();
356 Map* g_currentMap = 0;
358 void Map_addValidCallback( Map& map, const SignalHandler& handler ){
359 map.m_mapValidCallbacks.connectLast( handler );
362 bool Map_Valid( const Map& map ){
366 void Map_SetValid( Map& map, bool valid ){
368 map.m_mapValidCallbacks();
372 const char* Map_Name( const Map& map ){
373 return map.m_name.c_str();
376 bool Map_Unnamed( const Map& map ){
377 return string_equal( Map_Name( map ), "unnamed.map" );
380 inline const MapFormat& MapFormat_forFile( const char* filename ){
381 const char* moduleName = findModuleName( GetFileTypeRegistry(), MapFormat::Name(), path_get_extension( filename ) );
382 MapFormat* format = Radiant_getMapModules().findModule( moduleName );
383 ASSERT_MESSAGE( format != 0, "map format not found for file " << makeQuoted( filename ) );
387 const MapFormat& Map_getFormat( const Map& map ){
388 return MapFormat_forFile( Map_Name( map ) );
392 bool Map_Modified( const Map& map ){
393 return map.m_modified;
396 void Map_SetModified( Map& map, bool modified ){
397 if ( map.m_modified ^ modified ) {
398 map.m_modified = modified;
400 map.m_modified_changed( map );
404 void Map_UpdateTitle( const Map& map ){
405 Sys_SetTitle( map.m_name.c_str(), Map_Modified( map ) );
409 scene::Node* Map_GetWorldspawn( const Map& map ){
410 return map.m_world_node.get();
413 void Map_SetWorldspawn( Map& map, scene::Node* node ){
414 map.m_world_node.set( node );
419 // need that in a variable, will have to tweak depending on the game
420 float g_MaxWorldCoord = 64 * 1024;
421 float g_MinWorldCoord = -64 * 1024;
423 void AddRegionBrushes( void );
425 void RemoveRegionBrushes( void );
431 free all map elements, reinitialize the structures that depend on them
437 g_map.m_resource->detach( g_map );
438 GlobalReferenceCache().release( g_map.m_name.c_str() );
439 g_map.m_resource = 0;
444 Brush_unlatchPreferences();
447 class EntityFindByClassname : public scene::Graph::Walker
452 EntityFindByClassname( const char* name, Entity*& entity ) : m_name( name ), m_entity( entity ){
456 bool pre( const scene::Path& path, scene::Instance& instance ) const {
457 if ( m_entity == 0 ) {
458 Entity* entity = Node_getEntity( path.top() );
460 && string_equal( m_name, entity->getKeyValue( "classname" ) ) ) {
468 Entity* Scene_FindEntityByClass( const char* name ){
470 GlobalSceneGraph().traverse( EntityFindByClassname( name, entity ) );
474 Entity *Scene_FindPlayerStart(){
475 typedef const char* StaticString;
476 StaticString strings[] = {
478 "info_player_deathmatch",
479 "team_CTF_redplayer",
480 "team_CTF_blueplayer",
482 "team_CTF_bluespawn",
484 typedef const StaticString* StaticStringIterator;
485 for ( StaticStringIterator i = strings, end = strings + ( sizeof( strings ) / sizeof( StaticString ) ); i != end; ++i )
487 Entity* entity = Scene_FindEntityByClass( *i );
496 // move the view to a start position
500 void FocusViews( const Vector3& point, float angle ){
501 CamWnd& camwnd = *g_pParentWnd->GetCamWnd();
502 Camera_setOrigin( camwnd, point );
503 Vector3 angles( Camera_getAngles( camwnd ) );
504 angles[CAMERA_PITCH] = 0;
505 angles[CAMERA_YAW] = angle;
506 Camera_setAngles( camwnd, angles );
508 XYWnd* xywnd = g_pParentWnd->GetXYWnd();
509 xywnd->SetOrigin( point );
512 #include "stringio.h"
514 void Map_StartPosition(){
515 Entity* entity = Scene_FindPlayerStart();
519 string_parse_vector3( entity->getKeyValue( "origin" ), origin );
520 FocusViews( origin, string_read_float( entity->getKeyValue( "angle" ) ) );
524 FocusViews( g_vector3_identity, 0 );
529 inline bool node_is_worldspawn( scene::Node& node ){
530 Entity* entity = Node_getEntity( node );
531 return entity != 0 && string_equal( entity->getKeyValue( "classname" ), "worldspawn" );
535 // use first worldspawn
536 class entity_updateworldspawn : public scene::Traversable::Walker
539 bool pre( scene::Node& node ) const {
540 if ( node_is_worldspawn( node ) ) {
541 if ( Map_GetWorldspawn( g_map ) == 0 ) {
542 Map_SetWorldspawn( g_map, &node );
549 scene::Node* Map_FindWorldspawn( Map& map ){
550 Map_SetWorldspawn( map, 0 );
552 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
554 return Map_GetWorldspawn( map );
558 class CollectAllWalker : public scene::Traversable::Walker
561 UnsortedNodeSet& m_nodes;
563 CollectAllWalker( scene::Node& root, UnsortedNodeSet& nodes ) : m_root( root ), m_nodes( nodes ){
566 bool pre( scene::Node& node ) const {
567 m_nodes.insert( NodeSmartReference( node ) );
568 Node_getTraversable( m_root )->erase( node );
573 void Node_insertChildFirst( scene::Node& parent, scene::Node& child ){
574 UnsortedNodeSet nodes;
575 Node_getTraversable( parent )->traverse( CollectAllWalker( parent, nodes ) );
576 Node_getTraversable( parent )->insert( child );
578 for ( UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i )
580 Node_getTraversable( parent )->insert( ( *i ) );
584 scene::Node& createWorldspawn(){
585 NodeSmartReference worldspawn( GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "worldspawn", true ) ) );
586 Node_insertChildFirst( GlobalSceneGraph().root(), worldspawn );
590 void Map_UpdateWorldspawn( Map& map ){
591 if ( Map_FindWorldspawn( map ) == 0 ) {
592 Map_SetWorldspawn( map, &createWorldspawn() );
596 scene::Node& Map_FindOrInsertWorldspawn( Map& map ){
597 Map_UpdateWorldspawn( map );
598 return *Map_GetWorldspawn( map );
602 class MapMergeAll : public scene::Traversable::Walker
604 mutable scene::Path m_path;
606 MapMergeAll( const scene::Path& root )
610 bool pre( scene::Node& node ) const {
611 Node_getTraversable( m_path.top() )->insert( node );
612 m_path.push( makeReference( node ) );
613 selectPath( m_path, true );
617 void post( scene::Node& node ) const {
622 class MapMergeEntities : public scene::Traversable::Walker
624 mutable scene::Path m_path;
626 MapMergeEntities( const scene::Path& root )
630 bool pre( scene::Node& node ) const {
631 if ( node_is_worldspawn( node ) ) {
632 scene::Node* world_node = Map_FindWorldspawn( g_map );
633 if ( world_node == 0 ) {
634 Map_SetWorldspawn( g_map, &node );
635 Node_getTraversable( m_path.top().get() )->insert( node );
636 m_path.push( makeReference( node ) );
637 Node_getTraversable( node )->traverse( SelectChildren( m_path ) );
641 m_path.push( makeReference( *world_node ) );
642 Node_getTraversable( node )->traverse( MapMergeAll( m_path ) );
647 Node_getTraversable( m_path.top() )->insert( node );
648 m_path.push( makeReference( node ) );
649 if ( node_is_group( node ) ) {
650 Node_getTraversable( node )->traverse( SelectChildren( m_path ) );
654 selectPath( m_path, true );
660 void post( scene::Node& node ) const {
665 class BasicContainer : public scene::Node::Symbiot
669 NodeTypeCastTable m_casts;
672 NodeContainedCast<BasicContainer, scene::Traversable>::install( m_casts );
675 NodeTypeCastTable& get(){
681 TraversableNodeSet m_traverse;
684 typedef LazyStatic<TypeCasts> StaticTypeCasts;
686 scene::Traversable& get( NullType<scene::Traversable>){
690 BasicContainer() : m_node( this, this, StaticTypeCasts::instance().get() ){
702 /// Merges the map graph rooted at \p node into the global scene-graph.
703 void MergeMap( scene::Node& node ){
704 Node_getTraversable( node )->traverse( MapMergeEntities( scene::Path( makeReference( GlobalSceneGraph().root() ) ) ) );
707 void Map_ImportSelected( TextInputStream& in, const MapFormat& format ){
708 NodeSmartReference node( ( new BasicContainer )->node() );
709 format.readGraph( node, in, GlobalEntityCreator() );
710 Map_gatherNamespaced( node );
711 Map_mergeClonedNames();
715 inline scene::Cloneable* Node_getCloneable( scene::Node& node ){
716 return NodeTypeCast<scene::Cloneable>::cast( node );
719 inline scene::Node& node_clone( scene::Node& node ){
720 scene::Cloneable* cloneable = Node_getCloneable( node );
721 if ( cloneable != 0 ) {
722 return cloneable->clone();
725 return ( new scene::NullNode )->node();
728 class CloneAll : public scene::Traversable::Walker
730 mutable scene::Path m_path;
732 CloneAll( scene::Node& root )
733 : m_path( makeReference( root ) ){
736 bool pre( scene::Node& node ) const {
737 if ( node.isRoot() ) {
741 m_path.push( makeReference( node_clone( node ) ) );
742 m_path.top().get().IncRef();
747 void post( scene::Node& node ) const {
748 if ( node.isRoot() ) {
752 Node_getTraversable( m_path.parent() )->insert( m_path.top() );
754 m_path.top().get().DecRef();
759 scene::Node& Node_Clone( scene::Node& node ){
760 scene::Node& clone = node_clone( node );
761 scene::Traversable* traversable = Node_getTraversable( node );
762 if ( traversable != 0 ) {
763 traversable->traverse( CloneAll( clone ) );
769 typedef std::map<CopiedString, std::size_t> EntityBreakdown;
771 class EntityBreakdownWalker : public scene::Graph::Walker
773 EntityBreakdown& m_entitymap;
775 EntityBreakdownWalker( EntityBreakdown& entitymap )
776 : m_entitymap( entitymap ){
779 bool pre( const scene::Path& path, scene::Instance& instance ) const {
780 Entity* entity = Node_getEntity( path.top() );
782 const EntityClass& eclass = entity->getEntityClass();
783 if ( m_entitymap.find( eclass.name() ) == m_entitymap.end() ) {
784 m_entitymap[eclass.name()] = 1;
787 ++m_entitymap[eclass.name()];
794 void Scene_EntityBreakdown( EntityBreakdown& entitymap ){
795 GlobalSceneGraph().traverse( EntityBreakdownWalker( entitymap ) );
799 WindowPosition g_posMapInfoWnd( c_default_window_pos );
803 ui::Entry brushes_entry{ui::null};
804 ui::Entry entities_entry{ui::null};
805 ui::ListStore EntityBreakdownWalker{ui::null};
807 ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback ), &dialog );
809 window_set_position( window, g_posMapInfoWnd );
812 auto vbox = create_dialog_vbox( 4, 4 );
816 auto hbox = create_dialog_hbox( 4 );
817 vbox.pack_start( hbox, FALSE, TRUE, 0 );
820 auto table = create_dialog_table( 2, 2, 4, 4 );
821 hbox.pack_start( table, TRUE, TRUE, 0 );
824 auto entry = ui::Entry(ui::New);
826 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
827 gtk_editable_set_editable( GTK_EDITABLE(entry), FALSE );
829 brushes_entry = entry;
832 auto entry = ui::Entry(ui::New);
834 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
835 gtk_editable_set_editable( GTK_EDITABLE(entry), FALSE );
837 entities_entry = entry;
840 ui::Widget label = ui::Label( "Total Brushes" );
842 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
843 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
846 ui::Widget label = ui::Label( "Total Entities" );
848 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
849 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
853 auto vbox2 = create_dialog_vbox( 4 );
854 hbox.pack_start( vbox2, FALSE, FALSE, 0 );
857 auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_ok ), &dialog );
858 vbox2.pack_start( button, FALSE, FALSE, 0 );
863 ui::Widget label = ui::Label( "Entity breakdown" );
865 vbox.pack_start( label, FALSE, TRUE, 0 );
866 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
869 auto scr = create_scrolled_window( ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4 );
870 vbox.pack_start( scr, TRUE, TRUE, 0 );
873 auto store = ui::ListStore::from(gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_STRING ));
875 auto view = ui::TreeView(ui::TreeModel::from(store._handle));
876 gtk_tree_view_set_headers_clickable(view, TRUE );
879 auto renderer = ui::CellRendererText(ui::New);
880 auto column = ui::TreeViewColumn( "Entity", renderer, {{"text", 0}} );
881 gtk_tree_view_append_column(view, column );
882 gtk_tree_view_column_set_sort_column_id( column, 0 );
886 auto renderer = ui::CellRendererText(ui::New);
887 auto column = ui::TreeViewColumn( "Count", renderer, {{"text", 1}} );
888 gtk_tree_view_append_column(view, column );
889 gtk_tree_view_column_set_sort_column_id( column, 1 );
896 EntityBreakdownWalker = store;
904 EntityBreakdown entitymap;
905 Scene_EntityBreakdown( entitymap );
907 for ( EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i )
910 sprintf( tmp, "%u", Unsigned( ( *i ).second ) );
911 EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, tmp);
915 EntityBreakdownWalker.unref();
918 sprintf( tmp, "%u", Unsigned( g_brushCount.get() ) );
919 brushes_entry.text(tmp);
920 sprintf( tmp, "%u", Unsigned( g_entityCount.get() ) );
921 entities_entry.text(tmp);
923 modal_dialog_show( window, dialog );
926 window_get_position( window, g_posMapInfoWnd );
936 const char* m_message;
938 ScopeTimer( const char* message )
939 : m_message( message ){
944 double elapsed_time = m_timer.elapsed_msec() / 1000.f;
945 globalOutputStream() << m_message << " timer: " << FloatFormat( elapsed_time, 5, 2 ) << " second(s) elapsed\n";
949 CopiedString g_strLastMapFolder = "";
957 void Map_LoadFile( const char *filename ){
958 g_map.m_name = filename;
960 // refresh VFS to apply new pak filtering based on mapname
961 // needed for daemon DPK VFS
964 globalOutputStream() << "Loading map from " << filename << "\n";
965 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
967 MRU_AddFile( filename );
968 g_strLastMapFolder = g_path_get_dirname( filename );
970 bool switch_format = false;
973 ScopeTimer timer( "map load" );
975 const MapFormat* format = NULL;
976 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
977 if ( string_not_empty( moduleName ) ) {
978 format = ReferenceAPI_getMapModules().findModule( moduleName );
981 for ( int i = 0; i < Brush_toggleFormatCount(); ++i )
986 Brush_toggleFormat( i );
987 Map_UpdateTitle( g_map );
989 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
991 format->wrongFormat = false;
993 g_map.m_resource->attach( g_map );
995 if ( !format->wrongFormat ) {
998 switch_format = !switch_format;
1002 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
1005 globalOutputStream() << "--- LoadMapFile ---\n";
1006 globalOutputStream() << g_map.m_name.c_str() << "\n";
1008 globalOutputStream() << Unsigned( g_brushCount.get() ) << " primitive\n";
1009 globalOutputStream() << Unsigned( g_entityCount.get() ) << " entities\n";
1011 //GlobalEntityCreator().printStatistics();
1014 // move the view to a start position
1016 Map_StartPosition();
1018 g_currentMap = &g_map;
1020 // Load up shaders now that we have the map loaded.
1021 g_deferredStartupShaders = true;
1023 Brush_switchFormat( switch_format );
1029 virtual bool excluded( scene::Node& node ) const = 0;
1032 class ExcludeWalker : public scene::Traversable::Walker
1034 const scene::Traversable::Walker& m_walker;
1035 const Excluder* m_exclude;
1036 mutable bool m_skip;
1038 ExcludeWalker( const scene::Traversable::Walker& walker, const Excluder& exclude )
1039 : m_walker( walker ), m_exclude( &exclude ), m_skip( false ){
1042 bool pre( scene::Node& node ) const {
1043 if ( m_exclude->excluded( node ) || node.isRoot() ) {
1049 m_walker.pre( node );
1054 void post( scene::Node& node ) const {
1060 m_walker.post( node );
1065 class AnyInstanceSelected : public scene::Instantiable::Visitor
1069 AnyInstanceSelected( bool& selected ) : m_selected( selected ){
1073 void visit( scene::Instance& instance ) const {
1074 Selectable* selectable = Instance_getSelectable( instance );
1075 if ( selectable != 0
1076 && selectable->isSelected() ) {
1082 bool Node_instanceSelected( scene::Node& node ){
1083 scene::Instantiable* instantiable = Node_getInstantiable( node );
1084 ASSERT_NOTNULL( instantiable );
1086 instantiable->forEachInstance( AnyInstanceSelected( selected ) );
1090 class SelectedDescendantWalker : public scene::Traversable::Walker
1094 SelectedDescendantWalker( bool& selected ) : m_selected( selected ){
1098 bool pre( scene::Node& node ) const {
1099 if ( node.isRoot() ) {
1103 if ( Node_instanceSelected( node ) ) {
1111 bool Node_selectedDescendant( scene::Node& node ){
1113 Node_traverseSubgraph( node, SelectedDescendantWalker( selected ) );
1117 class SelectionExcluder : public Excluder
1120 bool excluded( scene::Node& node ) const {
1121 return !Node_selectedDescendant( node );
1125 class IncludeSelectedWalker : public scene::Traversable::Walker
1127 const scene::Traversable::Walker& m_walker;
1128 mutable std::size_t m_selected;
1129 mutable bool m_skip;
1131 bool selectedParent() const {
1132 return m_selected != 0;
1136 IncludeSelectedWalker( const scene::Traversable::Walker& walker )
1137 : m_walker( walker ), m_selected( 0 ), m_skip( false ){
1140 bool pre( scene::Node& node ) const {
1142 // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1143 if ( !node.isRoot() && ( Node_selectedDescendant( node ) || selectedParent() ) ) {
1144 if ( Node_instanceSelected( node ) ) {
1147 m_walker.pre( node );
1157 void post( scene::Node& node ) const {
1163 if ( Node_instanceSelected( node ) ) {
1166 m_walker.post( node );
1171 void Map_Traverse_Selected( scene::Node& root, const scene::Traversable::Walker& walker ){
1172 scene::Traversable* traversable = Node_getTraversable( root );
1173 if ( traversable != 0 ) {
1175 traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1177 traversable->traverse( IncludeSelectedWalker( walker ) );
1182 void Map_ExportSelected( TextOutputStream& out, const MapFormat& format ){
1183 format.writeGraph( GlobalSceneGraph().root(), Map_Traverse_Selected, out, g_writeMapComments );
1186 void Map_Traverse( scene::Node& root, const scene::Traversable::Walker& walker ){
1187 scene::Traversable* traversable = Node_getTraversable( root );
1188 if ( traversable != 0 ) {
1189 traversable->traverse( walker );
1193 class RegionExcluder : public Excluder
1196 bool excluded( scene::Node& node ) const {
1197 return node.excluded();
1201 void Map_Traverse_Region( scene::Node& root, const scene::Traversable::Walker& walker ){
1202 scene::Traversable* traversable = Node_getTraversable( root );
1203 if ( traversable != 0 ) {
1204 traversable->traverse( ExcludeWalker( walker, RegionExcluder() ) );
1208 bool Map_SaveRegion( const char *filename ){
1211 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Region, filename );
1213 RemoveRegionBrushes();
1219 void Map_RenameAbsolute( const char* absolute ){
1220 Resource* resource = GlobalReferenceCache().capture( absolute );
1221 NodeSmartReference clone( NewMapRoot( path_make_relative( absolute, GlobalFileSystem().findRoot( absolute ) ) ) );
1222 resource->setNode( clone.get_pointer() );
1225 //ScopeTimer timer("clone subgraph");
1226 Node_getTraversable( GlobalSceneGraph().root() )->traverse( CloneAll( clone ) );
1229 g_map.m_resource->detach( g_map );
1230 GlobalReferenceCache().release( g_map.m_name.c_str() );
1232 g_map.m_resource = resource;
1234 g_map.m_name = absolute;
1235 Map_UpdateTitle( g_map );
1237 g_map.m_resource->attach( g_map );
1238 // refresh VFS to apply new pak filtering based on mapname
1239 // needed for daemon DPK VFS
1243 void Map_Rename( const char* filename ){
1244 if ( !string_equal( g_map.m_name.c_str(), filename ) ) {
1245 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1247 Map_RenameAbsolute( filename );
1249 SceneChangeNotify();
1260 ScopeTimer timer( "map save" );
1262 return true; // assume success..
1272 //globalOutputStream() << "Map_New\n";
1274 g_map.m_name = "unnamed.map";
1275 Map_UpdateTitle( g_map );
1278 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
1279 // ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1280 g_map.m_resource->attach( g_map );
1282 SceneChangeNotify();
1285 FocusViews( g_vector3_identity, 0 );
1287 g_currentMap = &g_map;
1289 // restart VFS to apply new pak filtering based on mapname
1290 // needed for daemon DPK VFS
1293 // Load up shaders now that we have the map loaded.
1294 g_deferredStartupShaders = true;
1297 extern void ConstructRegionBrushes( scene::Node * brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_maxs );
1299 void ConstructRegionStartpoint( scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs ){
1301 \todo we need to make sure that the player start IS inside the region and bail out if it's not
1302 the compiler will refuse to compile a map with a player_start somewhere in empty space..
1303 for now, let's just print an error
1306 Vector3 vOrig( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ) );
1308 for ( int i = 0 ; i < 3 ; i++ )
1310 if ( vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i] ) {
1311 globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1316 // write the info_playerstart
1318 sprintf( sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2] );
1319 Node_getEntity( *startpoint )->setKeyValue( "origin", sTmp );
1320 sprintf( sTmp, "%d", (int)Camera_getAngles( *g_pParentWnd->GetCamWnd() )[CAMERA_YAW] );
1321 Node_getEntity( *startpoint )->setKeyValue( "angle", sTmp );
1325 ===========================================================
1329 ===========================================================
1332 Vector3 region_mins( g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord );
1333 Vector3 region_maxs( g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord );
1335 scene::Node* region_sides[6];
1336 scene::Node* region_startpoint = 0;
1341 a regioned map will have temp walls put up at the region boundary
1342 \todo TODO TTimo old implementation of region brushes
1343 we still add them straight in the worldspawn and take them out after the map is saved
1344 with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1347 void AddRegionBrushes( void ){
1350 for ( i = 0; i < 6; i++ )
1352 region_sides[i] = &GlobalBrushCreator().createBrush();
1353 Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( NodeSmartReference( *region_sides[i] ) );
1356 region_startpoint = &GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "info_player_start", false ) );
1358 ConstructRegionBrushes( region_sides, region_mins, region_maxs );
1359 ConstructRegionStartpoint( region_startpoint, region_mins, region_maxs );
1361 Node_getTraversable( GlobalSceneGraph().root() )->insert( NodeSmartReference( *region_startpoint ) );
1364 void RemoveRegionBrushes( void ){
1365 for ( std::size_t i = 0; i < 6; i++ )
1367 Node_getTraversable( *Map_GetWorldspawn( g_map ) )->erase( *region_sides[i] );
1369 Node_getTraversable( GlobalSceneGraph().root() )->erase( *region_startpoint );
1372 inline void exclude_node( scene::Node& node, bool exclude ){
1374 ? node.enable( scene::Node::eExcluded )
1375 : node.disable( scene::Node::eExcluded );
1378 class ExcludeAllWalker : public scene::Graph::Walker
1382 ExcludeAllWalker( bool exclude )
1383 : m_exclude( exclude ){
1386 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1387 exclude_node( path.top(), m_exclude );
1393 void Scene_Exclude_All( bool exclude ){
1394 GlobalSceneGraph().traverse( ExcludeAllWalker( exclude ) );
1397 bool Instance_isSelected( const scene::Instance& instance ){
1398 const Selectable* selectable = Instance_getSelectable( instance );
1399 return selectable != 0 && selectable->isSelected();
1402 class ExcludeSelectedWalker : public scene::Graph::Walker
1406 ExcludeSelectedWalker( bool exclude )
1407 : m_exclude( exclude ){
1410 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1411 exclude_node( path.top(), ( instance.isSelected() || instance.childSelected() || instance.parentSelected() ) == m_exclude );
1416 void Scene_Exclude_Selected( bool exclude ){
1417 GlobalSceneGraph().traverse( ExcludeSelectedWalker( exclude ) );
1420 class ExcludeRegionedWalker : public scene::Graph::Walker
1424 ExcludeRegionedWalker( bool exclude )
1425 : m_exclude( exclude ){
1428 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1433 aabb_intersects_aabb(
1434 instance.worldAABB(),
1435 aabb_for_minmax( region_mins, region_maxs )
1445 void Scene_Exclude_Region( bool exclude ){
1446 GlobalSceneGraph().traverse( ExcludeRegionedWalker( exclude ) );
1453 Other filtering options may still be on
1456 void Map_RegionOff(){
1457 region_active = false;
1459 region_maxs[0] = g_MaxWorldCoord - 64;
1460 region_mins[0] = g_MinWorldCoord + 64;
1461 region_maxs[1] = g_MaxWorldCoord - 64;
1462 region_mins[1] = g_MinWorldCoord + 64;
1463 region_maxs[2] = g_MaxWorldCoord - 64;
1464 region_mins[2] = g_MinWorldCoord + 64;
1466 Scene_Exclude_All( false );
1469 void Map_ApplyRegion( void ){
1470 region_active = true;
1472 Scene_Exclude_Region( false );
1477 ========================
1478 Map_RegionSelectedBrushes
1479 ========================
1481 void Map_RegionSelectedBrushes( void ){
1484 if ( GlobalSelectionSystem().countSelected() != 0
1485 && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
1486 region_active = true;
1487 Select_GetBounds( region_mins, region_maxs );
1489 Scene_Exclude_Selected( false );
1491 GlobalSelectionSystem().setSelectedAll( false );
1501 void Map_RegionXY( float x_min, float y_min, float x_max, float y_max ){
1504 region_mins[0] = x_min;
1505 region_maxs[0] = x_max;
1506 region_mins[1] = y_min;
1507 region_maxs[1] = y_max;
1508 region_mins[2] = g_MinWorldCoord + 64;
1509 region_maxs[2] = g_MaxWorldCoord - 64;
1514 void Map_RegionBounds( const AABB& bounds ){
1517 region_mins = vector3_subtracted( bounds.origin, bounds.extents );
1518 region_maxs = vector3_added( bounds.origin, bounds.extents );
1530 void Map_RegionBrush( void ){
1531 if ( GlobalSelectionSystem().countSelected() != 0 ) {
1532 scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
1533 Map_RegionBounds( instance.worldAABB() );
1542 bool Map_ImportFile( const char* filename ){
1543 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
1545 g_strLastMapFolder = g_path_get_dirname( filename );
1547 bool success = false;
1549 if ( extension_equal( path_get_extension( filename ), "bsp" ) ) {
1554 const MapFormat* format = NULL;
1555 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
1556 if ( string_not_empty( moduleName ) ) {
1557 format = ReferenceAPI_getMapModules().findModule( moduleName );
1561 format->wrongFormat = false;
1563 Resource* resource = GlobalReferenceCache().capture( filename );
1564 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1565 if ( !resource->load() ) {
1566 GlobalReferenceCache().release( filename );
1570 if ( format->wrongFormat ) {
1571 GlobalReferenceCache().release( filename );
1575 NodeSmartReference clone( NewMapRoot( "" ) );
1576 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1577 Map_gatherNamespaced( clone );
1578 Map_mergeClonedNames();
1581 GlobalReferenceCache().release( filename );
1584 SceneChangeNotify();
1590 const char *type = GlobalRadiant().getGameDescriptionKeyValue( "q3map2_type" );
1591 int n = string_length( path_get_extension( filename ) );
1592 if ( n && ( extension_equal( path_get_extension( filename ), "bsp" ) || extension_equal( path_get_extension( filename ), "map" ) ) ) {
1594 output += AppPath_get();
1596 output += GDEF_OS_EXE_EXT;
1598 output += " -v -game ";
1599 output += ( type && *type ) ? type : "quake3";
1600 output += " -fs_basepath \"";
1601 output += EnginePath_get();
1602 output += "\" -fs_homepath \"";
1603 output += g_qeglobals.m_userEnginePath.c_str();
1607 for ( int i = 0; i < g_pakPathCount; i++ ) {
1608 if ( g_strcmp0( g_strPakPath[i].c_str(), "") ) {
1609 output += " -fs_pakpath \"";
1610 output += g_strPakPath[i].c_str();
1616 if ( g_disableEnginePath ) {
1617 output += " -fs_nobasepath ";
1620 if ( g_disableHomePath ) {
1621 output += " -fs_nohomepath ";
1624 output += " -fs_game ";
1625 output += gamename_get();
1626 output += " -convert -format ";
1627 output += Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map";
1628 if ( extension_equal( path_get_extension( filename ), "map" ) ) {
1629 output += " -readmap ";
1636 Q_Exec( NULL, output.c_str(), NULL, false, true );
1638 // rebuild filename as "filenamewithoutext_converted.map"
1640 output.append( filename, string_length( filename ) - ( n + 1 ) );
1641 output += "_converted.map";
1642 filename = output.c_str();
1645 Resource* resource = GlobalReferenceCache().capture( filename );
1646 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1647 if ( !resource->load() ) {
1648 GlobalReferenceCache().release( filename );
1651 NodeSmartReference clone( NewMapRoot( "" ) );
1652 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1653 Map_gatherNamespaced( clone );
1654 Map_mergeClonedNames();
1657 GlobalReferenceCache().release( filename );
1660 SceneChangeNotify();
1669 bool Map_SaveFile( const char* filename ){
1670 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1671 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse, filename );
1673 // refresh VFS to apply new pak filtering based on mapname
1674 // needed for daemon DPK VFS
1685 // Saves selected world brushes and whole entities with partial/full selections
1687 bool Map_SaveSelected( const char* filename ){
1688 return MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Selected, filename );
1692 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1694 scene::Node& m_parent;
1696 ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ){
1699 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1700 if ( path.top().get_pointer() != &m_parent
1701 && Node_isPrimitive( path.top() ) ) {
1702 Selectable* selectable = Instance_getSelectable( instance );
1703 if ( selectable != 0
1704 && selectable->isSelected()
1705 && path.size() > 1 ) {
1712 void post( const scene::Path& path, scene::Instance& instance ) const {
1713 if ( path.top().get_pointer() != &m_parent
1714 && Node_isPrimitive( path.top() ) ) {
1715 Selectable* selectable = Instance_getSelectable( instance );
1716 if ( selectable != 0
1717 && selectable->isSelected()
1718 && path.size() > 1 ) {
1719 scene::Node& parent = path.parent();
1720 if ( &parent != &m_parent ) {
1721 NodeSmartReference node( path.top().get() );
1722 Node_getTraversable( parent )->erase( node );
1723 Node_getTraversable( m_parent )->insert( node );
1730 void Scene_parentSelectedBrushesToEntity( scene::Graph& graph, scene::Node& parent ){
1731 graph.traverse( ParentSelectedBrushesToEntityWalker( parent ) );
1734 class CountSelectedBrushes : public scene::Graph::Walker
1736 std::size_t& m_count;
1737 mutable std::size_t m_depth;
1739 CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
1743 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1744 if ( ++m_depth != 1 && path.top().get().isRoot() ) {
1747 Selectable* selectable = Instance_getSelectable( instance );
1748 if ( selectable != 0
1749 && selectable->isSelected()
1750 && Node_isPrimitive( path.top() ) ) {
1756 void post( const scene::Path& path, scene::Instance& instance ) const {
1761 std::size_t Scene_countSelectedBrushes( scene::Graph& graph ){
1763 graph.traverse( CountSelectedBrushes( count ) );
1775 const char* nodetype_get_name( ENodeType type ){
1776 if ( type == eNodeMap ) {
1779 if ( type == eNodeEntity ) {
1782 if ( type == eNodePrimitive ) {
1788 ENodeType node_get_nodetype( scene::Node& node ){
1789 if ( Node_isEntity( node ) ) {
1792 if ( Node_isPrimitive( node ) ) {
1793 return eNodePrimitive;
1795 return eNodeUnknown;
1798 bool contains_entity( scene::Node& node ){
1799 return Node_getTraversable( node ) != 0 && !Node_isBrush( node ) && !Node_isPatch( node ) && !Node_isEntity( node );
1802 bool contains_primitive( scene::Node& node ){
1803 return Node_isEntity( node ) && Node_getTraversable( node ) != 0 && Node_getEntity( node )->isContainer();
1806 ENodeType node_get_contains( scene::Node& node ){
1807 if ( contains_entity( node ) ) {
1810 if ( contains_primitive( node ) ) {
1811 return eNodePrimitive;
1813 return eNodeUnknown;
1816 void Path_parent( const scene::Path& parent, const scene::Path& child ){
1817 ENodeType contains = node_get_contains( parent.top() );
1818 ENodeType type = node_get_nodetype( child.top() );
1820 if ( contains != eNodeUnknown && contains == type ) {
1821 NodeSmartReference node( child.top().get() );
1822 Path_deleteTop( child );
1823 Node_getTraversable( parent.top() )->insert( node );
1824 SceneChangeNotify();
1828 globalErrorStream() << "failed - " << nodetype_get_name( type ) << " cannot be parented to " << nodetype_get_name( contains ) << " container.\n";
1832 void Scene_parentSelected(){
1833 UndoableCommand undo( "parentSelected" );
1835 if ( GlobalSelectionSystem().countSelected() > 1 ) {
1836 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1838 const scene::Path& m_parent;
1840 ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
1843 void visit( scene::Instance& instance ) const {
1844 if ( &m_parent != &instance.path() ) {
1845 Path_parent( m_parent, instance.path() );
1850 ParentSelectedBrushesToEntityWalker visitor( GlobalSelectionSystem().ultimateSelected().path() );
1851 GlobalSelectionSystem().foreachSelected( visitor );
1855 globalOutputStream() << "failed - did not find two selected nodes.\n";
1861 if ( ConfirmModified( "New Map" ) ) {
1868 CopiedString g_mapsPath;
1870 const char* getMapsPath(){
1871 return g_mapsPath.c_str();
1874 const char* getLastMapFolderPath(){
1875 if (g_strLastMapFolder.empty()) {
1876 GlobalPreferenceSystem().registerPreference( "LastMapFolder", make_property_string( g_strLastMapFolder ) );
1877 if (g_strLastMapFolder.empty()) {
1878 StringOutputStream buffer( 1024 );
1879 buffer << getMapsPath();
1880 if ( !file_readable( buffer.c_str() ) ) {
1882 buffer << g_qeglobals.m_userGamePath.c_str() << "/";
1884 g_strLastMapFolder = buffer.c_str();
1887 return g_strLastMapFolder.c_str();
1890 const char* map_open( const char* title ){
1891 return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), true, false, false );
1894 const char* map_import( const char* title ){
1895 return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), false, true, false );
1898 const char* map_save( const char* title ){
1899 return MainFrame_getWindow().file_dialog( FALSE, title, getLastMapFolderPath(), MapFormat::Name(), false, false, true );
1903 if ( !ConfirmModified( "Open Map" ) ) {
1907 const char* filename = map_open( "Open Map" );
1909 if ( filename != NULL ) {
1910 MRU_AddFile( filename );
1913 Map_LoadFile( filename );
1918 const char* filename = map_import( "Import Map" );
1920 if ( filename != NULL ) {
1921 UndoableCommand undo( "mapImport" );
1922 Map_ImportFile( filename );
1927 const char* filename = map_save( "Save Map" );
1929 if ( filename != NULL ) {
1930 g_strLastMapFolder = g_path_get_dirname( filename );
1931 MRU_AddFile( filename );
1932 Map_Rename( filename );
1943 if ( Map_Unnamed( g_map ) ) {
1946 else if ( Map_Modified( g_map ) ) {
1952 const char* filename = map_save( "Export Selection" );
1954 if ( filename != NULL ) {
1955 g_strLastMapFolder = g_path_get_dirname( filename );
1956 Map_SaveSelected( filename );
1961 const char* filename = map_save( "Export Region" );
1963 if ( filename != NULL ) {
1964 g_strLastMapFolder = g_path_get_dirname( filename );
1965 Map_SaveRegion( filename );
1972 SceneChangeNotify();
1977 g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1978 g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
1979 g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1980 g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
1982 SceneChangeNotify();
1987 SceneChangeNotify();
1990 void RegionSelected(){
1991 Map_RegionSelectedBrushes();
1992 SceneChangeNotify();
1999 class BrushFindByIndexWalker : public scene::Traversable::Walker
2001 mutable std::size_t m_index;
2002 scene::Path& m_path;
2004 BrushFindByIndexWalker( std::size_t index, scene::Path& path )
2005 : m_index( index ), m_path( path ){
2008 bool pre( scene::Node& node ) const {
2009 if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
2010 m_path.push( makeReference( node ) );
2016 class EntityFindByIndexWalker : public scene::Traversable::Walker
2018 mutable std::size_t m_index;
2019 scene::Path& m_path;
2021 EntityFindByIndexWalker( std::size_t index, scene::Path& path )
2022 : m_index( index ), m_path( path ){
2025 bool pre( scene::Node& node ) const {
2026 if ( Node_isEntity( node ) && m_index-- == 0 ) {
2027 m_path.push( makeReference( node ) );
2033 void Scene_FindEntityBrush( std::size_t entity, std::size_t brush, scene::Path& path ){
2034 path.push( makeReference( GlobalSceneGraph().root() ) );
2036 Node_getTraversable( path.top() )->traverse( EntityFindByIndexWalker( entity, path ) );
2038 if ( path.size() == 2 ) {
2039 scene::Traversable* traversable = Node_getTraversable( path.top() );
2040 if ( traversable != 0 ) {
2041 traversable->traverse( BrushFindByIndexWalker( brush, path ) );
2046 inline bool Node_hasChildren( scene::Node& node ){
2047 scene::Traversable* traversable = Node_getTraversable( node );
2048 return traversable != 0 && !traversable->empty();
2051 void SelectBrush( int entitynum, int brushnum ){
2053 Scene_FindEntityBrush( entitynum, brushnum, path );
2054 if ( path.size() == 3 || ( path.size() == 2 && !Node_hasChildren( path.top() ) ) ) {
2055 scene::Instance* instance = GlobalSceneGraph().find( path );
2056 ASSERT_MESSAGE( instance != 0, "SelectBrush: path not found in scenegraph" );
2057 Selectable* selectable = Instance_getSelectable( *instance );
2058 ASSERT_MESSAGE( selectable != 0, "SelectBrush: path not selectable" );
2059 selectable->setSelected( true );
2060 g_pParentWnd->GetXYWnd()->PositionView( instance->worldAABB().origin );
2065 class BrushFindIndexWalker : public scene::Graph::Walker
2067 mutable const scene::Node* m_node;
2068 std::size_t& m_count;
2070 BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
2071 : m_node( &node ), m_count( count ){
2074 bool pre( const scene::Path& path, scene::Instance& instance ) const {
2075 if ( Node_isPrimitive( path.top() ) ) {
2076 if ( m_node == path.top().get_pointer() ) {
2087 class EntityFindIndexWalker : public scene::Graph::Walker
2089 mutable const scene::Node* m_node;
2090 std::size_t& m_count;
2092 EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
2093 : m_node( &node ), m_count( count ){
2096 bool pre( const scene::Path& path, scene::Instance& instance ) const {
2097 if ( Node_isEntity( path.top() ) ) {
2098 if ( m_node == path.top().get_pointer() ) {
2109 static void GetSelectionIndex( int *ent, int *brush ){
2110 std::size_t count_brush = 0;
2111 std::size_t count_entity = 0;
2112 if ( GlobalSelectionSystem().countSelected() != 0 ) {
2113 const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2115 GlobalSceneGraph().traverse( BrushFindIndexWalker( path.top(), count_brush ) );
2116 GlobalSceneGraph().traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
2118 *brush = int(count_brush);
2119 *ent = int(count_entity);
2124 ui::Entry entity{ui::null};
2125 ui::Entry brush{ui::null};
2127 ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback ), &dialog );
2129 auto accel = ui::AccelGroup(ui::New);
2130 window.add_accel_group( accel );
2133 auto vbox = create_dialog_vbox( 4, 4 );
2136 auto table = create_dialog_table( 2, 2, 4, 4 );
2137 vbox.pack_start( table, TRUE, TRUE, 0 );
2139 ui::Widget label = ui::Label( "Entity number" );
2141 (table).attach(label, {0, 1, 0, 1}, {0, 0});
2144 ui::Widget label = ui::Label( "Brush number" );
2146 (table).attach(label, {0, 1, 1, 2}, {0, 0});
2149 auto entry = ui::Entry(ui::New);
2151 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
2152 gtk_widget_grab_focus( entry );
2156 auto entry = ui::Entry(ui::New);
2158 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
2164 auto hbox = create_dialog_hbox( 4 );
2165 vbox.pack_start( hbox, TRUE, TRUE, 0 );
2167 auto button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog );
2168 hbox.pack_start( button, FALSE, FALSE, 0 );
2169 widget_make_default( button );
2170 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
2173 auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
2174 hbox.pack_start( button, FALSE, FALSE, 0 );
2175 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
2180 // Initialize dialog
2184 GetSelectionIndex( &ent, &br );
2185 sprintf( buf, "%i", ent );
2187 sprintf( buf, "%i", br );
2190 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
2191 const char *entstr = gtk_entry_get_text( entity );
2192 const char *brushstr = gtk_entry_get_text( brush );
2193 SelectBrush( atoi( entstr ), atoi( brushstr ) );
2199 void Map_constructPreferences( PreferencesPage& page ){
2200 page.appendCheckBox( "", "Load last map at startup", g_bLoadLastMap );
2201 page.appendCheckBox( "", "Add entity and brush number comments on map write", g_writeMapComments );
2205 class MapEntityClasses : public ModuleObserver
2207 std::size_t m_unrealised;
2209 MapEntityClasses() : m_unrealised( 1 ){
2213 if ( --m_unrealised == 0 ) {
2214 if ( g_map.m_resource != 0 ) {
2215 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
2216 g_map.m_resource->realise();
2222 if ( ++m_unrealised == 1 ) {
2223 if ( g_map.m_resource != 0 ) {
2224 g_map.m_resource->flush();
2225 g_map.m_resource->unrealise();
2231 MapEntityClasses g_MapEntityClasses;
2234 class MapModuleObserver : public ModuleObserver
2236 std::size_t m_unrealised;
2238 MapModuleObserver() : m_unrealised( 1 ){
2242 if ( --m_unrealised == 0 ) {
2243 ASSERT_MESSAGE( !string_empty( g_qeglobals.m_userGamePath.c_str() ), "maps_directory: user-game-path is empty" );
2244 StringOutputStream buffer( 256 );
2245 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2246 Q_mkdir( buffer.c_str() );
2247 g_mapsPath = buffer.c_str();
2252 if ( ++m_unrealised == 1 ) {
2258 MapModuleObserver g_MapModuleObserver;
2260 CopiedString g_strLastMap;
2261 bool g_bLoadLastMap = false;
2263 void Map_Construct(){
2264 GlobalCommands_insert( "RegionOff", makeCallbackF(RegionOff) );
2265 GlobalCommands_insert( "RegionSetXY", makeCallbackF(RegionXY) );
2266 GlobalCommands_insert( "RegionSetBrush", makeCallbackF(RegionBrush) );
2267 GlobalCommands_insert( "RegionSetSelection", makeCallbackF(RegionSelected), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2269 GlobalPreferenceSystem().registerPreference( "LastMap", make_property_string( g_strLastMap ) );
2270 GlobalPreferenceSystem().registerPreference( "LoadLastMap", make_property_string( g_bLoadLastMap ) );
2271 GlobalPreferenceSystem().registerPreference( "MapInfoDlg", make_property<WindowPosition_String>( g_posMapInfoWnd ) );
2272 GlobalPreferenceSystem().registerPreference( "WriteMapComments", make_property_string( g_writeMapComments ) );
2274 PreferencesDialog_addSettingsPreferences( makeCallbackF(Map_constructPreferences) );
2276 GlobalEntityClassManager().attach( g_MapEntityClasses );
2277 Radiant_attachHomePathsObserver( g_MapModuleObserver );
2281 Radiant_detachHomePathsObserver( g_MapModuleObserver );
2282 GlobalEntityClassManager().detach( g_MapEntityClasses );