/*
- Copyright (C) 1999-2007 id Software, Inc. and contributors.
+ Copyright (C) 1999-2006 Id Software, Inc. and contributors.
For a list of contributors, see the accompanying CONTRIBUTORS file.
This file is part of GtkRadiant.
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include "stdafx.h"
-#include <string.h>
-#if defined ( __linux__ ) || defined ( __APPLE__ )
-#include <unistd.h>
-#endif
-#include "preferences.h"
+#include "map.h"
+
+#include <gtk/gtk.h>
+
+#include "debugging/debugging.h"
+
+#include "imap.h"
+
+MapModules &ReferenceAPI_getMapModules();
+
+#include "iselection.h"
+#include "iundo.h"
+#include "ibrush.h"
+#include "ifilter.h"
+#include "ireference.h"
+#include "ifiletypes.h"
+#include "ieclass.h"
+#include "irender.h"
+#include "ientity.h"
+#include "editable.h"
+#include "iarchive.h"
+#include "ifilesystem.h"
+#include "namespace.h"
+#include "moduleobserver.h"
+
+#include <set>
+
+#include <gdk/gdkkeysyms.h>
+#include "uilib/uilib.h"
+
+#include "scenelib.h"
+#include "transformlib.h"
+#include "selectionlib.h"
+#include "instancelib.h"
+#include "traverselib.h"
+#include "maplib.h"
+#include "eclasslib.h"
+#include "cmdlib.h"
+#include "stream/textfilestream.h"
+#include "os/path.h"
+#include "uniquenames.h"
+#include "modulesystem/singletonmodule.h"
+#include "modulesystem/moduleregistry.h"
+#include "stream/stringstream.h"
+#include "signal/signal.h"
+
+#include "gtkutil/filechooser.h"
+#include "timer.h"
+#include "select.h"
+#include "plugin.h"
+#include "filetypes.h"
+#include "gtkdlgs.h"
+#include "entityinspector.h"
+#include "points.h"
+#include "qe3.h"
+#include "camwindow.h"
+#include "xywindow.h"
#include "mainframe.h"
-#include "gtkmisc.h"
-#include "filters.h"
+#include "preferences.h"
+#include "preferencesystem.h"
+#include "referencecache.h"
+#include "mru.h"
+#include "commands.h"
+#include "autosave.h"
+#include "brushmodule.h"
+#include "brush.h"
+
+class NameObserver {
+ UniqueNames &m_names;
+ CopiedString m_name;
+
+ void construct()
+ {
+ if (!empty()) {
+ //globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n";
+ m_names.insert(name_read(c_str()));
+ }
+ }
+
+ void destroy()
+ {
+ if (!empty()) {
+ //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
+ m_names.erase(name_read(c_str()));
+ }
+ }
+
+ NameObserver &operator=(const NameObserver &other);
+
+public:
+ NameObserver(UniqueNames &names) : m_names(names)
+ {
+ construct();
+ }
+
+ NameObserver(const NameObserver &other) : m_names(other.m_names), m_name(other.m_name)
+ {
+ construct();
+ }
+
+ ~NameObserver()
+ {
+ destroy();
+ }
+
+ bool empty() const
+ {
+ return string_empty(c_str());
+ }
+
+ const char *c_str() const
+ {
+ return m_name.c_str();
+ }
+
+ void nameChanged(const char *name)
+ {
+ destroy();
+ m_name = name;
+ construct();
+ }
+
+ typedef MemberCaller<NameObserver, void(const char *), &NameObserver::nameChanged> NameChangedCaller;
+};
+
+class BasicNamespace : public Namespace {
+ typedef std::map<NameCallback, NameObserver> Names;
+ Names m_names;
+ UniqueNames m_uniqueNames;
+public:
+ ~BasicNamespace()
+ {
+ ASSERT_MESSAGE(m_names.empty(), "namespace: names still registered at shutdown");
+ }
+
+ void attach(const NameCallback &setName, const NameCallbackCallback &attachObserver)
+ {
+ std::pair<Names::iterator, bool> result = m_names.insert(Names::value_type(setName, m_uniqueNames));
+ ASSERT_MESSAGE(result.second, "cannot attach name");
+ attachObserver(NameObserver::NameChangedCaller((*result.first).second));
+ //globalOutputStream() << "attach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
+ }
+
+ void detach(const NameCallback &setName, const NameCallbackCallback &detachObserver)
+ {
+ Names::iterator i = m_names.find(setName);
+ ASSERT_MESSAGE(i != m_names.end(), "cannot detach name");
+ //globalOutputStream() << "detach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
+ detachObserver(NameObserver::NameChangedCaller((*i).second));
+ m_names.erase(i);
+ }
+
+ void makeUnique(const char *name, const NameCallback &setName) const
+ {
+ char buffer[1024];
+ name_write(buffer, m_uniqueNames.make_unique(name_read(name)));
+ setName(buffer);
+ }
+
+ void mergeNames(const BasicNamespace &other) const
+ {
+ typedef std::list<NameCallback> SetNameCallbacks;
+ typedef std::map<CopiedString, SetNameCallbacks> NameGroups;
+ NameGroups groups;
+
+ UniqueNames uniqueNames(other.m_uniqueNames);
+
+ for (Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i) {
+ groups[(*i).second.c_str()].push_back((*i).first);
+ }
+
+ for (NameGroups::iterator i = groups.begin(); i != groups.end(); ++i) {
+ name_t uniqueName(uniqueNames.make_unique(name_read((*i).first.c_str())));
+ uniqueNames.insert(uniqueName);
+
+ char buffer[1024];
+ name_write(buffer, uniqueName);
+
+ //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n";
+
+ SetNameCallbacks &setNameCallbacks = (*i).second;
+
+ for (SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j) {
+ (*j)(buffer);
+ }
+ }
+ }
+};
+
+BasicNamespace g_defaultNamespace;
+BasicNamespace g_cloneNamespace;
+
+class NamespaceAPI {
+ Namespace *m_namespace;
+public:
+ typedef Namespace Type;
+
+ STRING_CONSTANT(Name, "*");
+
+ NamespaceAPI()
+ {
+ m_namespace = &g_defaultNamespace;
+ }
+
+ Namespace *getTable()
+ {
+ return m_namespace;
+ }
+};
+
+typedef SingletonModule<NamespaceAPI> NamespaceModule;
+typedef Static<NamespaceModule> StaticNamespaceModule;
+StaticRegisterModule staticRegisterDefaultNamespace(StaticNamespaceModule::instance());
+
+
+std::list<Namespaced *> g_cloned;
+
+inline Namespaced *Node_getNamespaced(scene::Node &node)
+{
+ return NodeTypeCast<Namespaced>::cast(node);
+}
+
+void Node_gatherNamespaced(scene::Node &node)
+{
+ Namespaced *namespaced = Node_getNamespaced(node);
+ if (namespaced != 0) {
+ g_cloned.push_back(namespaced);
+ }
+}
+
+class GatherNamespaced : public scene::Traversable::Walker {
+public:
+ bool pre(scene::Node &node) const
+ {
+ Node_gatherNamespaced(node);
+ return true;
+ }
+};
+
+void Map_gatherNamespaced(scene::Node &root)
+{
+ Node_traverseSubgraph(root, GatherNamespaced());
+}
-extern MainFrame* g_pParentWnd;
+void Map_mergeClonedNames()
+{
+ for (std::list<Namespaced *>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i) {
+ (*i)->setNamespace(g_cloneNamespace);
+ }
+ g_cloneNamespace.mergeNames(g_defaultNamespace);
+ for (std::list<Namespaced *>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i) {
+ (*i)->setNamespace(g_defaultNamespace);
+ }
+
+ g_cloned.clear();
+}
-int modified; // for quit confirmation (0 = clean, 1 = unsaved,
-// 2 = autosaved, but not regular saved)
+class WorldNode {
+ scene::Node *m_node;
+public:
+ WorldNode()
+ : m_node(0)
+ {
+ }
-char currentmap[1024];
+ void set(scene::Node *node)
+ {
+ if (m_node != 0) {
+ m_node->DecRef();
+ }
+ m_node = node;
+ if (m_node != 0) {
+ m_node->IncRef();
+ }
+ }
-brush_t active_brushes; // brushes currently being displayed
-brush_t selected_brushes; // highlighted
+ scene::Node *get() const
+ {
+ return m_node;
+ }
+};
-face_t *selected_face;
-brush_t *selected_face_brush;
+class Map;
-brush_t filtered_brushes; // brushes that have been filtered or regioned
+void Map_SetValid(Map &map, bool valid);
-entity_t entities; // head/tail of doubly linked list
+void Map_UpdateTitle(const Map &map);
-entity_t *world_entity = NULL; // "classname" "worldspawn" !
+void Map_SetWorldspawn(Map &map, scene::Node *node);
-void Map_Init(){
- Map_Free();
-}
+class Map : public ModuleObserver {
+public:
+ CopiedString m_name;
+ Resource *m_resource;
+ bool m_valid;
-bool g_bCancel_Map_LoadFile; // Hydra: moved this here
+ bool m_modified;
-// TTimo
-// need that in a variable, will have to tweak depending on the game
-int g_MaxWorldCoord = 64 * 1024;
-int g_MinWorldCoord = -64 * 1024;
+ void ( *m_modified_changed )(const Map &);
-// the max size we allow on brushes, this is dependant on world coords too
-// makes more sense to say smaller I think?
-int g_MaxBrushSize = ( g_MaxWorldCoord - 1 ) * 2;
+ Signal0 m_mapValidCallbacks;
-void AddRegionBrushes( void );
-void RemoveRegionBrushes( void );
+ WorldNode m_world_node; // "classname" "worldspawn" !
-/*
- =============================================================
+ Map() : m_resource(0), m_valid(false), m_modified_changed(Map_UpdateTitle)
+ {
+ }
- Cross map selection saving
+ void realise()
+ {
+ if (m_resource != 0) {
+ if (Map_Unnamed(*this)) {
+ g_map.m_resource->setNode(NewMapRoot("").get_pointer());
+ MapFile *map = Node_getMapFile(*g_map.m_resource->getNode());
+ if (map != 0) {
+ map->save();
+ }
+ } else {
+ m_resource->load();
+ }
- this could fuck up if you have only part of a complex entity selected...
- =============================================================
- */
+ GlobalSceneGraph().insert_root(*m_resource->getNode());
-brush_t between_brushes;
-entity_t between_entities;
+ AutoSave_clear();
-bool g_bRestoreBetween = false;
+ Map_SetValid(g_map, true);
+ }
+ }
-void Map_SaveBetween( void ){
- if ( g_pParentWnd->ActiveXY() ) {
- g_bRestoreBetween = true;
- g_pParentWnd->ActiveXY()->Copy();
- }
- return;
+ void unrealise()
+ {
+ if (m_resource != 0) {
+ Map_SetValid(g_map, false);
+ Map_SetWorldspawn(g_map, 0);
+
+
+ GlobalUndoSystem().clear();
+
+ GlobalSceneGraph().erase_root();
+ }
+ }
+};
+
+Map g_map;
+Map *g_currentMap = 0;
+
+void Map_addValidCallback(Map &map, const SignalHandler &handler)
+{
+ map.m_mapValidCallbacks.connectLast(handler);
+}
+
+bool Map_Valid(const Map &map)
+{
+ return map.m_valid;
}
-void Map_RestoreBetween( void ){
- if ( g_pParentWnd->ActiveXY() && g_bRestoreBetween ) {
- g_pParentWnd->ActiveXY()->Paste();
- }
+void Map_SetValid(Map &map, bool valid)
+{
+ map.m_valid = valid;
+ map.m_mapValidCallbacks();
}
-//============================================================================
-bool CheckForTinyBrush( brush_t* b, int n, float fSize ){
- bool bTiny = false;
- for ( int i = 0 ; i < 3 ; i++ )
- {
- if ( b->maxs[i] - b->mins[i] < fSize ) {
- bTiny = true;
- }
- }
- if ( bTiny ) {
- Sys_Printf( "Possible problem brush (too small) #%i ", n );
- }
- return bTiny;
+const char *Map_Name(const Map &map)
+{
+ return map.m_name.c_str();
+}
+
+bool Map_Unnamed(const Map &map)
+{
+ return string_equal(Map_Name(map), "unnamed.map");
+}
+
+inline const MapFormat &MapFormat_forFile(const char *filename)
+{
+ const char *moduleName = findModuleName(GetFileTypeRegistry(), MapFormat::Name(), path_get_extension(filename));
+ MapFormat *format = Radiant_getMapModules().findModule(moduleName);
+ ASSERT_MESSAGE(format != 0, "map format not found for file " << makeQuoted(filename));
+ return *format;
+}
+
+const MapFormat &Map_getFormat(const Map &map)
+{
+ return MapFormat_forFile(Map_Name(map));
}
-void Map_BuildBrushData( void ){
- brush_t *b, *next;
- if ( active_brushes.next == NULL ) {
- return;
- }
+bool Map_Modified(const Map &map)
+{
+ return map.m_modified;
+}
+
+void Map_SetModified(Map &map, bool modified)
+{
+ if (map.m_modified ^ modified) {
+ map.m_modified = modified;
- Sys_BeginWait(); // this could take a while
+ map.m_modified_changed(map);
+ }
+}
- int n = 0;
- for ( b = active_brushes.next ; b != NULL && b != &active_brushes ; b = next )
- {
- next = b->next;
- Brush_Build( b, true, false, false );
- if ( !b->brush_faces || ( g_PrefsDlg.m_bCleanTiny && CheckForTinyBrush( b, n++, g_PrefsDlg.m_fTinySize ) ) ) {
- Brush_Free( b );
- Sys_Printf( "Removed degenerate brush\n" );
- }
- }
- Sys_EndWait();
+void Map_UpdateTitle(const Map &map)
+{
+ Sys_SetTitle(map.m_name.c_str(), Map_Modified(map));
}
-entity_t *Map_FindClass( const char *cname ){
- entity_t *ent;
- for ( ent = entities.next ; ent != &entities ; ent = ent->next )
- {
- if ( !strcmp( cname, ValueForKey( ent, "classname" ) ) ) {
- return ent;
- }
- }
- return NULL;
+scene::Node *Map_GetWorldspawn(const Map &map)
+{
+ return map.m_world_node.get();
}
+void Map_SetWorldspawn(Map &map, scene::Node *node)
+{
+ map.m_world_node.set(node);
+}
+
+
+// TTimo
+// need that in a variable, will have to tweak depending on the game
+float g_MaxWorldCoord = 64 * 1024;
+float g_MinWorldCoord = -64 * 1024;
+
+void AddRegionBrushes(void);
+
+void RemoveRegionBrushes(void);
+
+
/*
================
Map_Free
free all map elements, reinitialize the structures that depend on them
================
*/
-void Map_Free( void ){
- g_bRestoreBetween = false;
- if ( selected_brushes.next &&
- ( selected_brushes.next != &selected_brushes ) ) {
- if ( gtk_MessageBox( g_pParentWnd->m_pWidget, "Copy selection?", " ", MB_YESNO ) == IDYES ) {
- Map_SaveBetween();
- }
- }
-
- QERApp_ActiveShaders_SetInUse( false );
- Pointfile_Clear();
- g_qeglobals.d_num_entities = 0;
-
- if ( !active_brushes.next ) {
- // first map
- active_brushes.prev = active_brushes.next = &active_brushes;
- selected_brushes.prev = selected_brushes.next = &selected_brushes;
- filtered_brushes.prev = filtered_brushes.next = &filtered_brushes;
- entities.prev = entities.next = &entities;
- }
- else
- {
- // free selected faces array
- g_ptrSelectedFaces.RemoveAll();
- g_ptrSelectedFaceBrushes.RemoveAll();
- while ( active_brushes.next != &active_brushes )
- Brush_Free( active_brushes.next );
- while ( selected_brushes.next != &selected_brushes )
- Brush_Free( selected_brushes.next );
- while ( filtered_brushes.next != &filtered_brushes )
- Brush_Free( filtered_brushes.next );
- while ( entities.next != &entities )
- Entity_Free( entities.next );
- }
-
- if ( world_entity ) {
- Entity_Free( world_entity );
- }
- world_entity = NULL;
-}
-
-entity_t *AngledEntity(){
- entity_t *ent = Map_FindClass( "info_player_start" );
- if ( !ent ) {
- ent = Map_FindClass( "info_player_deathmatch" );
- }
- if ( !ent ) {
- ent = Map_FindClass( "info_player_deathmatch" );
- }
- if ( !ent ) {
- ent = Map_FindClass( "team_CTF_redplayer" );
- }
- if ( !ent ) {
- ent = Map_FindClass( "team_CTF_blueplayer" );
- }
- if ( !ent ) {
- ent = Map_FindClass( "team_CTF_redspawn" );
- }
- if ( !ent ) {
- ent = Map_FindClass( "team_CTF_bluespawn" );
- }
- return ent;
+void Map_Free()
+{
+ Pointfile_Clear();
+
+ g_map.m_resource->detach(g_map);
+ GlobalReferenceCache().release(g_map.m_name.c_str());
+ g_map.m_resource = 0;
+
+ FlushReferences();
+
+ g_currentMap = 0;
+ Brush_unlatchPreferences();
+}
+
+class EntityFindByClassname : public scene::Graph::Walker {
+ const char *m_name;
+ Entity *&m_entity;
+public:
+ EntityFindByClassname(const char *name, Entity *&entity) : m_name(name), m_entity(entity)
+ {
+ m_entity = 0;
+ }
+
+ bool pre(const scene::Path &path, scene::Instance &instance) const
+ {
+ if (m_entity == 0) {
+ Entity *entity = Node_getEntity(path.top());
+ if (entity != 0
+ && string_equal(m_name, entity->getKeyValue("classname"))) {
+ m_entity = entity;
+ }
+ }
+ return true;
+ }
+};
+
+Entity *Scene_FindEntityByClass(const char *name)
+{
+ Entity *entity;
+ GlobalSceneGraph().traverse(EntityFindByClassname(name, entity));
+ return entity;
+}
+
+Entity *Scene_FindPlayerStart()
+{
+ typedef const char *StaticString;
+ StaticString strings[] = {
+ "info_player_start",
+ "info_player_deathmatch",
+ "team_CTF_redplayer",
+ "team_CTF_blueplayer",
+ "team_CTF_redspawn",
+ "team_CTF_bluespawn",
+ };
+ typedef const StaticString *StaticStringIterator;
+ for (StaticStringIterator i = strings, end = strings + (sizeof(strings) / sizeof(StaticString)); i != end; ++i) {
+ Entity *entity = Scene_FindEntityByClass(*i);
+ if (entity != 0) {
+ return entity;
+ }
+ }
+ return 0;
}
//
// move the view to a start position
//
-void Map_StartPosition(){
- entity_t *ent = AngledEntity();
-
- g_pParentWnd->GetCamWnd()->Camera()->angles[PITCH] = 0;
- if ( ent ) {
- GetVectorForKey( ent, "origin", g_pParentWnd->GetCamWnd()->Camera()->origin );
- GetVectorForKey( ent, "origin", g_pParentWnd->GetXYWnd()->GetOrigin() );
- g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] = FloatForKey( ent, "angle" );
- }
- else
- {
- g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] = 0;
- VectorCopy( vec3_origin, g_pParentWnd->GetCamWnd()->Camera()->origin );
- VectorCopy( vec3_origin, g_pParentWnd->GetXYWnd()->GetOrigin() );
- }
-}
-
-void Map_FreeEntities( CPtrArray *ents ){
- int i, j, num_ents, num_brushes;
- entity_t* e;
- CPtrArray* brushes;
-
- num_ents = ents->GetSize();
- for ( i = 0; i < num_ents; i++ )
- {
- e = (entity_t*)ents->GetAt( i );
- brushes = (CPtrArray*)e->pData;
- num_brushes = brushes->GetSize();
- for ( j = 0; j < num_brushes; j++ )
- Brush_Free( (brush_t*)brushes->GetAt( j ) );
- brushes->RemoveAll();
- delete (CPtrArray*)e->pData;
- e->pData = NULL;
- Entity_Free( e );
- }
- ents->RemoveAll();
-}
-
-/*!\todo Possibly make the import Undo-friendly by calling Undo_End for new brushes and ents */
-void Map_ImportEntities( CPtrArray *ents, bool bAddSelected = false ){
- int num_ents, num_brushes;
- CPtrArray *brushes;
- vec3_t mins, maxs;
- entity_t *e;
- brush_t *b;
- face_t *f;
- int i,j;
-
- GPtrArray *new_ents = g_ptr_array_new();
-
- g_qeglobals.bPrimitBrushes = false;
-
- brush_t *pBrushList = ( bAddSelected ) ? &selected_brushes : &active_brushes;
-
- bool bDoneBPCheck = false;
- g_qeglobals.bNeedConvert = false;
- // HACK: find out if this map file was a BP one
- // check the first brush in the file that is NOT a patch
- // this will not be necessary when we allow both formats in the same file
- num_ents = ents->GetSize();
- for ( i = 0; !bDoneBPCheck && i < num_ents; i++ )
- {
- e = (entity_t*)ents->GetAt( i );
- brushes = (CPtrArray*)e->pData;
- num_brushes = brushes->GetSize();
- for ( j = 0; !bDoneBPCheck && j < num_brushes; j++ )
- {
- /*!todo Allow mixing texdef formats per-face. */
- b = (brush_t *)brushes->GetAt( j );
- if ( b->patchBrush ) {
- continue;
- }
- bDoneBPCheck = true;
- int BP_param = -1;
- if ( b->bBrushDef && !g_qeglobals.m_bBrushPrimitMode ) {
- BP_param = 0;
- }
- else if ( !b->bBrushDef && g_qeglobals.m_bBrushPrimitMode ) {
- BP_param = 1;
- }
-
- if ( BP_param != -1 ) {
- switch ( BP_MessageBox( BP_param ) )
- {
- case 0:
- Map_FreeEntities( ents );
- return;
- case 1:
- g_qeglobals.bNeedConvert = true;
- break;
- case 2:
- g_qeglobals.bNeedConvert = false;
- break;
- }
- }
- }
- }
-
- // process the entities into the world geometry
- num_ents = ents->GetSize();
- for ( i = 0; i < num_ents; i++ )
- {
- num_brushes = 0;
- e = (entity_t*)ents->GetAt( i );
- brushes = (CPtrArray*)e->pData;
-
- num_brushes = brushes->GetSize();
- // link brushes into entity
- for ( j = 0; j < num_brushes; j++ )
- {
- Entity_LinkBrush( e, (brush_t *)brushes->GetAt( j ) );
- g_qeglobals.d_parsed_brushes++;
- }
- brushes->RemoveAll();
- delete brushes;
- e->pData = NULL;
-
- // set entity origin
- GetVectorForKey( e, "origin", e->origin );
- // set entity eclass
- /*!\todo Make SetKeyValue check for "classname" change and assign appropriate eclass */
- e->eclass = Eclass_ForName( ValueForKey( e, "classname" ),
- ( e->brushes.onext != &e->brushes ) );
-
- // go through all parsed brushes and build stuff
- for ( b = e->brushes.onext; b != &e->brushes; b = b->onext )
- {
- for ( f = b->brush_faces; f != NULL; f = f->next )
- {
- f->pShader = QERApp_Shader_ForName( f->texdef.GetName() );
- f->d_texture = f->pShader->getTexture();
- }
-
- // when brushes are in final state, build the planes and windings
- // NOTE: also converts BP brushes if g_qeglobals.bNeedConvert is true
- Brush_Build( b );
- }
-
-//#define TERRAIN_HACK
-#undef TERRAIN_HACK
-
-#ifdef TERRAIN_HACK
- if ( ( strcmp( ValueForKey( e, "terrain" ),"1" ) == 0 && strcmp( e->eclass->name,"func_group" ) == 0 ) ) {
-
- // two aux pointers to the shaders used in the terrain entity
- // we don't keep refcount on them since they are only temporary
- // this avoids doing expensive lookups by name for all faces
- IShader *pTerrainShader, *pCaulk;
-
- pTerrainShader = NULL;
- pCaulk = QERApp_Shader_ForName( SHADER_CAULK );
-
- for ( b = e->brushes.onext; b != &e->brushes; b = b->onext )
- {
- if ( pTerrainShader == NULL ) {
- for ( f = b->brush_faces; f != NULL; f = f->next )
- if ( strcmp( f->texdef.GetName(), SHADER_CAULK ) != 0 ) {
- pTerrainShader = f->pShader;
- }
- }
-
- if ( pTerrainShader ) {
- for ( f = b->brush_faces; f != NULL; f = f->next )
- {
- if ( strcmp( f->texdef.GetName(), SHADER_CAULK ) != 0 ) { // not caulk
- Face_SetShader( f, pTerrainShader->getName() );
- }
- else{
- Face_SetShader( f, pCaulk->getName() );
- }
- }
- }
- else{
- Sys_Printf( "WARNING: no terrain shader found for brush\n" );
- }
- }
- }
-#endif
-#define PATCH_HACK
-#ifdef PATCH_HACK
- for ( b = e->brushes.onext; b != &e->brushes; b = b->onext )
- {
- // patch hack, to be removed when dependency on brush_faces is removed
- if ( b->patchBrush ) {
- Patch_CalcBounds( b->pPatch, mins, maxs );
- for ( int i = 0; i < 3; i++ )
- {
- if ( (int)mins[i] == (int)maxs[i] ) {
- mins[i] -= 4;
- maxs[i] += 4;
- }
- }
- Brush_Resize( b, mins, maxs );
- Brush_Build( b );
- }
- }
-#endif
- // add brush for fixedsize entity
- if ( e->eclass->fixedsize ) {
- vec3_t mins, maxs;
- VectorAdd( e->eclass->mins, e->origin, mins );
- VectorAdd( e->eclass->maxs, e->origin, maxs );
- b = Brush_Create( mins, maxs, &e->eclass->texdef );
- Entity_LinkBrush( e, b );
- Brush_Build( b );
- }
-
- for ( b = e->brushes.onext; b != &e->brushes; b = b->onext )
- Brush_AddToList( b, pBrushList );
-
- if ( strcmp( e->eclass->name, "worldspawn" ) == 0 ) {
- if ( world_entity ) {
- while ( e->brushes.onext != &e->brushes )
- {
- b = e->brushes.onext;
- Entity_UnlinkBrush( b );
- Entity_LinkBrush( world_entity, b );
- }
- Entity_Free( e );
- }
- else
- {
- world_entity = e;
- }
- }
- else if ( strcmp( e->eclass->name, "group_info" ) == 0 ) {
- // it's a group thing!
- Group_Add( e );
- Entity_Free( e );
- }
- else
- {
- // fix target/targetname collisions
- if ( ( g_PrefsDlg.m_bDoTargetFix ) && ( strcmp( ValueForKey( e, "target" ), "" ) != 0 ) ) {
- GPtrArray *t_ents = g_ptr_array_new();
- entity_t *e_target;
- const char *target = ValueForKey( e, "target" );
- qboolean bCollision = FALSE;
-
- // check the current map entities for an actual collision
- for ( e_target = entities.next; e_target != &entities; e_target = e_target->next )
- {
- if ( !strcmp( target, ValueForKey( e_target, "target" ) ) ) {
- bCollision = TRUE;
- // make sure the collision is not between two imported entities
- for ( j = 0; j < (int)new_ents->len; j++ )
- {
- if ( e_target == g_ptr_array_index( new_ents, j ) ) {
- bCollision = FALSE;
- }
- }
- }
- }
-
- // find the matching targeted entity(s)
- if ( bCollision ) {
- for ( j = num_ents - 1; j > 0; j-- )
- {
- e_target = (entity_t*)ents->GetAt( j );
- if ( e_target != NULL && e_target != e ) {
- const char *targetname = ValueForKey( e_target, "targetname" );
- if ( ( targetname != NULL ) && ( strcmp( target, targetname ) == 0 ) ) {
- g_ptr_array_add( t_ents, (gpointer)e_target );
- }
- }
- }
- if ( t_ents->len > 0 ) {
- // link the first to get a unique target/targetname
- Entity_Connect( e, (entity_t*)g_ptr_array_index( t_ents,0 ) );
- // set the targetname of the rest of them manually
- for ( j = 1; j < (int)t_ents->len; j++ )
- SetKeyValue( (entity_t*)g_ptr_array_index( t_ents, j ), "targetname", ValueForKey( e, "target" ) );
- }
- g_ptr_array_free( t_ents, FALSE );
- }
- }
-
- // add the entity to the end of the entity list
- Entity_AddToList( e, &entities );
- g_qeglobals.d_num_entities++;
-
- // keep a list of ents added to avoid testing collisions against them
- g_ptr_array_add( new_ents, (gpointer)e );
- }
- }
- g_ptr_array_free( new_ents, FALSE );
-
- ents->RemoveAll();
-
- g_qeglobals.bNeedConvert = false;
-}
-
-void Map_Import( IDataStream *in, const char *type, bool bAddSelected ){
- CPtrArray ents;
-
- g_pParentWnd->GetSynapseClient().ImportMap( in, &ents, type );
- Map_ImportEntities( &ents, bAddSelected );
-}
-/*
- ================
- Map_LoadFile
- ================
- */
-void Map_LoadFile( const char *filename ){
- clock_t start, finish;
- double elapsed_time;
- start = clock();
-
- Sys_BeginWait();
- Select_Deselect();
- /*!
- \todo FIXME TTimo why is this commented out?
- stability issues maybe? or duplicate feature?
- forcing to show the console during map load was a good thing IMO
- */
- //SetInspectorMode(W_CONSOLE);
- Sys_Printf( "Loading map from %s\n", filename );
+void FocusViews(const Vector3 &point, float angle)
+{
+ CamWnd &camwnd = *g_pParentWnd->GetCamWnd();
+ Camera_setOrigin(camwnd, point);
+ Vector3 angles(Camera_getAngles(camwnd));
+ angles[CAMERA_PITCH] = 0;
+ angles[CAMERA_YAW] = angle;
+ Camera_setAngles(camwnd, angles);
- Map_Free();
- //++timo FIXME: maybe even easier to have Group_Init called from Map_Free?
- Group_Init();
- g_qeglobals.d_num_entities = 0;
- g_qeglobals.d_parsed_brushes = 0;
+ XYWnd *xywnd = g_pParentWnd->GetXYWnd();
+ xywnd->SetOrigin(point);
+}
+#include "stringio.h"
- // cancel the map loading process
- // used when conversion between standard map format and BP format is required and the user cancels the process
- g_bCancel_Map_LoadFile = false;
+void Map_StartPosition()
+{
+ Entity *entity = Scene_FindPlayerStart();
- strcpy( currentmap, filename );
+ if (entity) {
+ Vector3 origin;
+ string_parse_vector3(entity->getKeyValue("origin"), origin);
+ FocusViews(origin, string_read_float(entity->getKeyValue("angle")));
+ } else {
+ FocusViews(g_vector3_identity, 0);
+ }
+}
- g_bScreenUpdates = false; // leo: avoid redraws while loading the map (see fenris:1952)
- // prepare to let the map module do the parsing
- FileStream file;
- const char* type = strrchr( filename,'.' );
- if ( type != NULL ) {
- type++;
- }
- // NOTE TTimo opening has binary doesn't make a lot of sense
- // but opening as text confuses the scriptlib parser
- // this may be a problem if we "rb" and use the XML parser, might have an incompatibility
- if ( file.Open( filename, "rb" ) ) {
- Map_Import( &file, type );
- }
- else{
- Sys_FPrintf( SYS_ERR, "ERROR: failed to open %s for read\n", filename );
- }
- file.Close();
+inline bool node_is_worldspawn(scene::Node &node)
+{
+ Entity *entity = Node_getEntity(node);
+ return entity != 0 && string_equal(entity->getKeyValue("classname"), "worldspawn");
+}
- g_bScreenUpdates = true;
- if ( g_bCancel_Map_LoadFile ) {
- Sys_Printf( "Map_LoadFile canceled\n" );
- Map_New();
- Sys_EndWait();
- return;
- }
+// use first worldspawn
+class entity_updateworldspawn : public scene::Traversable::Walker {
+public:
+ bool pre(scene::Node &node) const
+ {
+ if (node_is_worldspawn(node)) {
+ if (Map_GetWorldspawn(g_map) == 0) {
+ Map_SetWorldspawn(g_map, &node);
+ }
+ }
+ return false;
+ }
+};
- if ( !world_entity ) {
- Sys_Printf( "No worldspawn in map.\n" );
- Map_New();
- Sys_EndWait();
- return;
- }
- finish = clock();
- elapsed_time = (double)( finish - start ) / CLOCKS_PER_SEC;
+scene::Node *Map_FindWorldspawn(Map &map)
+{
+ Map_SetWorldspawn(map, 0);
- Sys_Printf( "--- LoadMapFile ---\n" );
- Sys_Printf( "%s\n", filename );
+ Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
- Sys_Printf( "%5i brushes\n", g_qeglobals.d_parsed_brushes );
- Sys_Printf( "%5i entities\n", g_qeglobals.d_num_entities );
- Sys_Printf( "%5.2f second(s) load time\n", elapsed_time );
+ return Map_GetWorldspawn(map);
+}
- Sys_EndWait();
- Map_RestoreBetween();
+class CollectAllWalker : public scene::Traversable::Walker {
+ scene::Node &m_root;
+ UnsortedNodeSet &m_nodes;
+public:
+ CollectAllWalker(scene::Node &root, UnsortedNodeSet &nodes) : m_root(root), m_nodes(nodes)
+ {
+ }
+
+ bool pre(scene::Node &node) const
+ {
+ m_nodes.insert(NodeSmartReference(node));
+ Node_getTraversable(m_root)->erase(node);
+ return false;
+ }
+};
+
+void Node_insertChildFirst(scene::Node &parent, scene::Node &child)
+{
+ UnsortedNodeSet nodes;
+ Node_getTraversable(parent)->traverse(CollectAllWalker(parent, nodes));
+ Node_getTraversable(parent)->insert(child);
+
+ for (UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i) {
+ Node_getTraversable(parent)->insert((*i));
+ }
+}
- //
- // move the view to a start position
- //
- Map_StartPosition();
+scene::Node &createWorldspawn()
+{
+ NodeSmartReference worldspawn(
+ GlobalEntityCreator().createEntity(GlobalEntityClassManager().findOrInsert("worldspawn", true)));
+ Node_insertChildFirst(GlobalSceneGraph().root(), worldspawn);
+ return worldspawn;
+}
- Map_RegionOff();
+void Map_UpdateWorldspawn(Map &map)
+{
+ if (Map_FindWorldspawn(map) == 0) {
+ Map_SetWorldspawn(map, &createWorldspawn());
+ }
+}
- modified = false;
- Sys_SetTitle( filename );
+scene::Node &Map_FindOrInsertWorldspawn(Map &map)
+{
+ Map_UpdateWorldspawn(map);
+ return *Map_GetWorldspawn(map);
+}
- Texture_ShowInuse();
- QERApp_SortActiveShaders();
- Sys_UpdateWindows( W_ALL );
+class MapMergeAll : public scene::Traversable::Walker {
+ mutable scene::Path m_path;
+public:
+ MapMergeAll(const scene::Path &root)
+ : m_path(root)
+ {
+ }
+
+ bool pre(scene::Node &node) const
+ {
+ Node_getTraversable(m_path.top())->insert(node);
+ m_path.push(makeReference(node));
+ selectPath(m_path, true);
+ return false;
+ }
+
+ void post(scene::Node &node) const
+ {
+ m_path.pop();
+ }
+};
+
+class MapMergeEntities : public scene::Traversable::Walker {
+ mutable scene::Path m_path;
+public:
+ MapMergeEntities(const scene::Path &root)
+ : m_path(root)
+ {
+ }
+
+ bool pre(scene::Node &node) const
+ {
+ if (node_is_worldspawn(node)) {
+ scene::Node *world_node = Map_FindWorldspawn(g_map);
+ if (world_node == 0) {
+ Map_SetWorldspawn(g_map, &node);
+ Node_getTraversable(m_path.top().get())->insert(node);
+ m_path.push(makeReference(node));
+ Node_getTraversable(node)->traverse(SelectChildren(m_path));
+ } else {
+ m_path.push(makeReference(*world_node));
+ Node_getTraversable(node)->traverse(MapMergeAll(m_path));
+ }
+ } else {
+ Node_getTraversable(m_path.top())->insert(node);
+ m_path.push(makeReference(node));
+ if (node_is_group(node)) {
+ Node_getTraversable(node)->traverse(SelectChildren(m_path));
+ } else {
+ selectPath(m_path, true);
+ }
+ }
+ return false;
+ }
+
+ void post(scene::Node &node) const
+ {
+ m_path.pop();
+ }
+};
+
+class BasicContainer : public scene::Node::Symbiot {
+ class TypeCasts {
+ NodeTypeCastTable m_casts;
+ public:
+ TypeCasts()
+ {
+ NodeContainedCast<BasicContainer, scene::Traversable>::install(m_casts);
+ }
+
+ NodeTypeCastTable &get()
+ {
+ return m_casts;
+ }
+ };
+
+ scene::Node m_node;
+ TraversableNodeSet m_traverse;
+public:
+
+ typedef LazyStatic<TypeCasts> StaticTypeCasts;
+
+ scene::Traversable &get(NullType<scene::Traversable>)
+ {
+ return m_traverse;
+ }
+
+ BasicContainer() : m_node(this, this, StaticTypeCasts::instance().get())
+ {
+ }
+
+ void release()
+ {
+ delete this;
+ }
+
+ scene::Node &node()
+ {
+ return m_node;
+ }
+};
+
+/// Merges the map graph rooted at \p node into the global scene-graph.
+void MergeMap(scene::Node &node)
+{
+ Node_getTraversable(node)->traverse(MapMergeEntities(scene::Path(makeReference(GlobalSceneGraph().root()))));
}
-/*!
- ===========
- Supporting functions for Map_SaveFile, builds a CPtrArray with the filtered / non filtered brushes
- ===========
- */
-void CleanFilter( entity_t *ent ){
- if ( ent->pData ) {
- delete static_cast<CPtrArray*>( ent->pData );
- ent->pData = NULL;
- }
+void Map_ImportSelected(TextInputStream &in, const MapFormat &format)
+{
+ NodeSmartReference node((new BasicContainer)->node());
+ format.readGraph(node, in, GlobalEntityCreator());
+ Map_gatherNamespaced(node);
+ Map_mergeClonedNames();
+ MergeMap(node);
}
-/*!
- filters out the region brushes if necessary
- returns true if this entity as a whole is out of the region
- (if all brushes are filtered out, then the entity will be completely dropped .. except if it's worldspawn of course)
- */
-bool FilterChildren( entity_t *ent, bool bRegionOnly = false, bool bSelectedOnly = false ){
- if ( ent->brushes.onext == &ent->brushes ) {
- return false;
- }
- // entity without a brush, ignore it... this can be caused by Undo
-
- // filter fixedsize ents by their eclass bounding box
- // don't add their brushes
- if ( ent->eclass->fixedsize ) {
- if ( bSelectedOnly && !IsBrushSelected( ent->brushes.onext ) ) {
- return false;
- }
-
- if ( bRegionOnly && region_active ) {
- for ( int i = 0 ; i < 3 ; i++ )
- {
- if ( ( ent->origin[i] + ent->eclass->mins[i] ) > region_maxs[i] ) {
- return false;
- }
- if ( ( ent->origin[i] + ent->eclass->maxs[i] ) < region_mins[i] ) {
- return false;
- }
- }
- }
- }
- else
- {
- for ( brush_t *b = ent->brushes.onext ; b != &ent->brushes ; b = b->onext )
- {
- // set flag to use brushprimit_texdef
- if ( g_qeglobals.m_bBrushPrimitMode ) {
- b->bBrushDef = true;
- }
- else{
- b->bBrushDef = false;
- }
-
- // add brush, unless it's excluded by region
- if ( !( bRegionOnly && Map_IsBrushFiltered( b ) ) &&
- !( bSelectedOnly && !IsBrushSelected( b ) ) ) {
- ( (CPtrArray*)ent->pData )->Add( b );
- }
- }
-
- if ( ( (CPtrArray*)ent->pData )->GetSize() <= 0 ) {
- return false;
- }
- }
- return true;
-}
-
-entity_t *region_startpoint = NULL;
-void Map_ExportEntities( CPtrArray* ents, bool bRegionOnly = false, bool bSelectedOnly = false ){
- int i;
- entity_t *e;
-
- /*!
- \todo the entity_t needs to be reworked and asbtracted some more
-
- keeping the entity_t as the struct providing access to a list of map objects, a list of epairs and various other info?
- but separating some more the data that belongs to the entity_t and the 'sons' data
- on a side note, I don't think that doing that with linked list would be a good thing
-
- for now, we use the blind void* in entity_t casted to a CPtrArray of brush_t* to hand out a list of the brushes for map write
- the next step is very likely to be a change of the brush_t* to a more abstract object?
- */
+inline scene::Cloneable *Node_getCloneable(scene::Node &node)
+{
+ return NodeTypeCast<scene::Cloneable>::cast(node);
+}
- FilterChildren( world_entity, bRegionOnly, bSelectedOnly );
- ents->Add( world_entity );
+inline scene::Node &node_clone(scene::Node &node)
+{
+ scene::Cloneable *cloneable = Node_getCloneable(node);
+ if (cloneable != 0) {
+ return cloneable->clone();
+ }
- for ( e = entities.next ; e != &entities ; e = e->next )
- {
- // not sure this still happens, probably safe to leave it in
- if ( ( !strcmp( ValueForKey( e, "classname" ), "worldspawn" ) ) && ( e != world_entity ) ) {
- Sys_FPrintf( SYS_ERR, "Dropping parasite worldspawn entity\n" );
- continue;
- }
+ return (new scene::NullNode)->node();
+}
- // entities which brushes are completely filtered out by regioning are not printed to the map
- if ( FilterChildren( e, bRegionOnly, bSelectedOnly ) ) {
- ents->Add( e );
- }
- }
+class CloneAll : public scene::Traversable::Walker {
+ mutable scene::Path m_path;
+public:
+ CloneAll(scene::Node &root)
+ : m_path(makeReference(root))
+ {
+ }
+
+ bool pre(scene::Node &node) const
+ {
+ if (node.isRoot()) {
+ return false;
+ }
+
+ m_path.push(makeReference(node_clone(node)));
+ m_path.top().get().IncRef();
+
+ return true;
+ }
+
+ void post(scene::Node &node) const
+ {
+ if (node.isRoot()) {
+ return;
+ }
+
+ Node_getTraversable(m_path.parent())->insert(m_path.top());
+
+ m_path.top().get().DecRef();
+ m_path.pop();
+ }
+};
+
+scene::Node &Node_Clone(scene::Node &node)
+{
+ scene::Node &clone = node_clone(node);
+ scene::Traversable *traversable = Node_getTraversable(node);
+ if (traversable != 0) {
+ traversable->traverse(CloneAll(clone));
+ }
+ return clone;
+}
- if ( bRegionOnly && region_active ) {
- for ( i = 0; i < 6; i++ )
- ( (CPtrArray*)world_entity->pData )->Add( region_sides[i] );
- ents->Add( region_startpoint );
- }
+typedef std::map<CopiedString, std::size_t> EntityBreakdown;
+
+class EntityBreakdownWalker : public scene::Graph::Walker {
+ EntityBreakdown &m_entitymap;
+public:
+ EntityBreakdownWalker(EntityBreakdown &entitymap)
+ : m_entitymap(entitymap)
+ {
+ }
+
+ bool pre(const scene::Path &path, scene::Instance &instance) const
+ {
+ Entity *entity = Node_getEntity(path.top());
+ if (entity != 0) {
+ const EntityClass &eclass = entity->getEntityClass();
+ if (m_entitymap.find(eclass.name()) == m_entitymap.end()) {
+ m_entitymap[eclass.name()] = 1;
+ } else { ++m_entitymap[eclass.name()]; }
+ }
+ return true;
+ }
+};
+
+void Scene_EntityBreakdown(EntityBreakdown &entitymap)
+{
+ GlobalSceneGraph().traverse(EntityBreakdownWalker(entitymap));
}
-void Map_Export( IDataStream *out, const char *type, bool bRegionOnly, bool bSelectedOnly ){
- entity_t *e;
- CPtrArray ents;
+WindowPosition g_posMapInfoWnd(c_default_window_pos);
+
+void DoMapInfo()
+{
+ ModalDialog dialog;
+ ui::Entry brushes_entry{ui::null};
+ ui::Entry entities_entry{ui::null};
+ ui::ListStore EntityBreakdownWalker{ui::null};
+
+ ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback),
+ &dialog);
+
+ window_set_position(window, g_posMapInfoWnd);
+
+ {
+ auto vbox = create_dialog_vbox(4, 4);
+ window.add(vbox);
+
+ {
+ auto hbox = create_dialog_hbox(4);
+ vbox.pack_start(hbox, FALSE, TRUE, 0);
+
+ {
+ auto table = create_dialog_table(2, 2, 4, 4);
+ hbox.pack_start(table, TRUE, TRUE, 0);
+
+ {
+ auto entry = ui::Entry(ui::New);
+ entry.show();
+ table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
+ gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
+
+ brushes_entry = entry;
+ }
+ {
+ auto entry = ui::Entry(ui::New);
+ entry.show();
+ table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
+ gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
+
+ entities_entry = entry;
+ }
+ {
+ ui::Widget label = ui::Label("Total Brushes");
+ label.show();
+ table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+ }
+ {
+ ui::Widget label = ui::Label("Total Entities");
+ label.show();
+ table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+ }
+ }
+ {
+ auto vbox2 = create_dialog_vbox(4);
+ hbox.pack_start(vbox2, FALSE, FALSE, 0);
+
+ {
+ auto button = create_dialog_button("Close", G_CALLBACK(dialog_button_ok), &dialog);
+ vbox2.pack_start(button, FALSE, FALSE, 0);
+ }
+ }
+ }
+ {
+ ui::Widget label = ui::Label("Entity breakdown");
+ label.show();
+ vbox.pack_start(label, FALSE, TRUE, 0);
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+ }
+ {
+ auto scr = create_scrolled_window(ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4);
+ vbox.pack_start(scr, TRUE, TRUE, 0);
+
+ {
+ auto store = ui::ListStore::from(gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING));
+
+ auto view = ui::TreeView(ui::TreeModel::from(store._handle));
+ gtk_tree_view_set_headers_clickable(view, TRUE);
+
+ {
+ auto renderer = ui::CellRendererText(ui::New);
+ auto column = ui::TreeViewColumn("Entity", renderer, {{"text", 0}});
+ gtk_tree_view_append_column(view, column);
+ gtk_tree_view_column_set_sort_column_id(column, 0);
+ }
+
+ {
+ auto renderer = ui::CellRendererText(ui::New);
+ auto column = ui::TreeViewColumn("Count", renderer, {{"text", 1}});
+ gtk_tree_view_append_column(view, column);
+ gtk_tree_view_column_set_sort_column_id(column, 1);
+ }
+
+ view.show();
+
+ scr.add(view);
+
+ EntityBreakdownWalker = store;
+ }
+ }
+ }
+
+ // Initialize fields
+
+ {
+ EntityBreakdown entitymap;
+ Scene_EntityBreakdown(entitymap);
+
+ for (EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i) {
+ char tmp[16];
+ sprintf(tmp, "%u", Unsigned((*i).second));
+ EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, tmp);
+ }
+ }
+
+ EntityBreakdownWalker.unref();
+
+ char tmp[16];
+ sprintf(tmp, "%u", Unsigned(g_brushCount.get()));
+ brushes_entry.text(tmp);
+ sprintf(tmp, "%u", Unsigned(g_entityCount.get()));
+ entities_entry.text(tmp);
+
+ modal_dialog_show(window, dialog);
+
+ // save before exit
+ window_get_position(window, g_posMapInfoWnd);
+
+ window.destroy();
+}
- if ( bRegionOnly && region_active ) {
- AddRegionBrushes();
- }
- // create the filters
- world_entity->pData = new CPtrArray();
- for ( e = entities.next; e != &entities; e = e->next )
- e->pData = new CPtrArray();
+class ScopeTimer {
+ Timer m_timer;
+ const char *m_message;
+public:
+ ScopeTimer(const char *message)
+ : m_message(message)
+ {
+ m_timer.start();
+ }
- Map_ExportEntities( &ents, bRegionOnly, bSelectedOnly );
+ ~ScopeTimer()
+ {
+ double elapsed_time = m_timer.elapsed_msec() / 1000.f;
+ globalOutputStream() << m_message << " timer: " << FloatFormat(elapsed_time, 5, 2) << " second(s) elapsed\n";
+ }
+};
- g_pParentWnd->GetSynapseClient().ExportMap( &ents, out, type );
+CopiedString g_strLastFolder = "";
- // cleanup the filters
- CleanFilter( world_entity );
- for ( e = entities.next ; e != &entities ; e = e->next )
- CleanFilter( e );
+/*
+ ================
+ Map_LoadFile
+ ================
+ */
- if ( bRegionOnly && region_active ) {
- RemoveRegionBrushes();
- }
+void Map_LoadFile(const char *filename)
+{
+ globalOutputStream() << "Loading map from " << filename << "\n";
+ ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
+
+ MRU_AddFile(filename);
+ g_strLastFolder = g_path_get_dirname(filename);
+
+ {
+ ScopeTimer timer("map load");
+
+ const MapFormat *format = NULL;
+ const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
+ if (string_not_empty(moduleName)) {
+ format = ReferenceAPI_getMapModules().findModule(moduleName);
+ }
+
+ for (int i = 0; i < Brush_toggleFormatCount(); ++i) {
+ if (i) {
+ Map_Free();
+ }
+ Brush_toggleFormat(i);
+ g_map.m_name = filename;
+ Map_UpdateTitle(g_map);
+ g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
+ if (format) {
+ format->wrongFormat = false;
+ }
+ g_map.m_resource->attach(g_map);
+ if (format) {
+ if (!format->wrongFormat) {
+ break;
+ }
+ }
+ }
+
+ Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
+ }
+
+ globalOutputStream() << "--- LoadMapFile ---\n";
+ globalOutputStream() << g_map.m_name.c_str() << "\n";
+
+ globalOutputStream() << Unsigned(g_brushCount.get()) << " primitive\n";
+ globalOutputStream() << Unsigned(g_entityCount.get()) << " entities\n";
+
+ //GlobalEntityCreator().printStatistics();
+
+ //
+ // move the view to a start position
+ //
+ Map_StartPosition();
+
+ g_currentMap = &g_map;
+
+ // refresh VFS to apply new pak filtering based on mapname
+ // needed for daemon DPK VFS
+ VFS_Refresh();
}
-const char* filename_get_extension( const char* filename ){
- const char* type = strrchr( filename,'.' );
- if ( type != NULL ) {
- return ++type;
- }
- return "";
+class Excluder {
+public:
+ virtual bool excluded(scene::Node &node) const = 0;
+};
+
+class ExcludeWalker : public scene::Traversable::Walker {
+ const scene::Traversable::Walker &m_walker;
+ const Excluder *m_exclude;
+ mutable bool m_skip;
+public:
+ ExcludeWalker(const scene::Traversable::Walker &walker, const Excluder &exclude)
+ : m_walker(walker), m_exclude(&exclude), m_skip(false)
+ {
+ }
+
+ bool pre(scene::Node &node) const
+ {
+ if (m_exclude->excluded(node) || node.isRoot()) {
+ m_skip = true;
+ return false;
+ } else {
+ m_walker.pre(node);
+ }
+ return true;
+ }
+
+ void post(scene::Node &node) const
+ {
+ if (m_skip) {
+ m_skip = false;
+ } else {
+ m_walker.post(node);
+ }
+ }
+};
+
+class AnyInstanceSelected : public scene::Instantiable::Visitor {
+ bool &m_selected;
+public:
+ AnyInstanceSelected(bool &selected) : m_selected(selected)
+ {
+ m_selected = false;
+ }
+
+ void visit(scene::Instance &instance) const
+ {
+ Selectable *selectable = Instance_getSelectable(instance);
+ if (selectable != 0
+ && selectable->isSelected()) {
+ m_selected = true;
+ }
+ }
+};
+
+bool Node_instanceSelected(scene::Node &node)
+{
+ scene::Instantiable *instantiable = Node_getInstantiable(node);
+ ASSERT_NOTNULL(instantiable);
+ bool selected;
+ instantiable->forEachInstance(AnyInstanceSelected(selected));
+ return selected;
}
-/*
- ===========
- Map_SaveFile
- \todo FIXME remove the use_region, this is broken .. work with a global flag to set region mode or not
- ===========
- */
-void Map_SaveFile( const char *filename, qboolean use_region ){
- clock_t start, finish;
- double elapsed_time;
- start = clock();
- Sys_Printf( "Saving map to %s\n",filename );
+class SelectedDescendantWalker : public scene::Traversable::Walker {
+ bool &m_selected;
+public:
+ SelectedDescendantWalker(bool &selected) : m_selected(selected)
+ {
+ m_selected = false;
+ }
+
+ bool pre(scene::Node &node) const
+ {
+ if (node.isRoot()) {
+ return false;
+ }
+
+ if (Node_instanceSelected(node)) {
+ m_selected = true;
+ }
+
+ return true;
+ }
+};
+
+bool Node_selectedDescendant(scene::Node &node)
+{
+ bool selected;
+ Node_traverseSubgraph(node, SelectedDescendantWalker(selected));
+ return selected;
+}
+
+class SelectionExcluder : public Excluder {
+public:
+ bool excluded(scene::Node &node) const
+ {
+ return !Node_selectedDescendant(node);
+ }
+};
+
+class IncludeSelectedWalker : public scene::Traversable::Walker {
+ const scene::Traversable::Walker &m_walker;
+ mutable std::size_t m_selected;
+ mutable bool m_skip;
+
+ bool selectedParent() const
+ {
+ return m_selected != 0;
+ }
+
+public:
+ IncludeSelectedWalker(const scene::Traversable::Walker &walker)
+ : m_walker(walker), m_selected(0), m_skip(false)
+ {
+ }
+
+ bool pre(scene::Node &node) const
+ {
+ // include node if:
+ // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
+ if (!node.isRoot() && (Node_selectedDescendant(node) || selectedParent())) {
+ if (Node_instanceSelected(node)) {
+ ++m_selected;
+ }
+ m_walker.pre(node);
+ return true;
+ } else {
+ m_skip = true;
+ return false;
+ }
+ }
+
+ void post(scene::Node &node) const
+ {
+ if (m_skip) {
+ m_skip = false;
+ } else {
+ if (Node_instanceSelected(node)) {
+ --m_selected;
+ }
+ m_walker.post(node);
+ }
+ }
+};
+
+void Map_Traverse_Selected(scene::Node &root, const scene::Traversable::Walker &walker)
+{
+ scene::Traversable *traversable = Node_getTraversable(root);
+ if (traversable != 0) {
+#if 0
+ traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
+#else
+ traversable->traverse(IncludeSelectedWalker(walker));
+#endif
+ }
+}
+
+void Map_ExportSelected(TextOutputStream &out, const MapFormat &format)
+{
+ format.writeGraph(GlobalSceneGraph().root(), Map_Traverse_Selected, out);
+}
+
+void Map_Traverse(scene::Node &root, const scene::Traversable::Walker &walker)
+{
+ scene::Traversable *traversable = Node_getTraversable(root);
+ if (traversable != 0) {
+ traversable->traverse(walker);
+ }
+}
+
+class RegionExcluder : public Excluder {
+public:
+ bool excluded(scene::Node &node) const
+ {
+ return node.excluded();
+ }
+};
+
+void Map_Traverse_Region(scene::Node &root, const scene::Traversable::Walker &walker)
+{
+ scene::Traversable *traversable = Node_getTraversable(root);
+ if (traversable != 0) {
+ traversable->traverse(ExcludeWalker(walker, RegionExcluder()));
+ }
+}
+
+bool Map_SaveRegion(const char *filename)
+{
+ AddRegionBrushes();
+
+ bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Region,
+ filename);
- Pointfile_Clear();
+ RemoveRegionBrushes();
- if ( !use_region ) {
- char backup[1024];
+ return success;
+}
- // rename current to .bak
- strcpy( backup, filename );
- StripExtension( backup );
- strcat( backup, ".bak" );
- unlink( backup );
- rename( filename, backup );
- }
- Sys_Printf( "Map_SaveFile: %s\n", filename );
+void Map_RenameAbsolute(const char *absolute)
+{
+ Resource *resource = GlobalReferenceCache().capture(absolute);
+ NodeSmartReference clone(NewMapRoot(path_make_relative(absolute, GlobalFileSystem().findRoot(absolute))));
+ resource->setNode(clone.get_pointer());
- // build the out data stream
- FileStream file;
- if ( !file.Open( filename,"w" ) ) {
- Sys_FPrintf( SYS_ERR, "ERROR: couldn't open %s for write\n", filename );
- return;
- }
+ {
+ //ScopeTimer timer("clone subgraph");
+ Node_getTraversable(GlobalSceneGraph().root())->traverse(CloneAll(clone));
+ }
- // extract filetype
- Map_Export( &file, filename_get_extension( filename ), use_region );
+ g_map.m_resource->detach(g_map);
+ GlobalReferenceCache().release(g_map.m_name.c_str());
- file.Close();
+ g_map.m_resource = resource;
- finish = clock();
- elapsed_time = (double)( finish - start ) / CLOCKS_PER_SEC;
+ g_map.m_name = absolute;
+ Map_UpdateTitle(g_map);
- Sys_Printf( "Saved in %-.2f second(s).\n",elapsed_time );
- modified = false;
+ g_map.m_resource->attach(g_map);
+ // refresh VFS to apply new pak filtering based on mapname
+ // needed for daemon DPK VFS
+ VFS_Refresh();
+}
- if ( !strstr( filename, "autosave" ) ) {
- Sys_SetTitle( filename );
- }
+void Map_Rename(const char *filename)
+{
+ if (!string_equal(g_map.m_name.c_str(), filename)) {
+ ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
- if ( !use_region ) {
- time_t timer;
+ Map_RenameAbsolute(filename);
- time( &timer );
+ SceneChangeNotify();
+ } else {
+ SaveReferences();
+ }
+}
- Sys_Beep();
+bool Map_Save()
+{
+ Pointfile_Clear();
- Sys_Status( "Saved.", 0 );
- }
+ ScopeTimer timer("map save");
+ SaveReferences();
+ return true; // assume success..
}
/*
===========
*/
-void Map_New( void ){
- Sys_Printf( "Map_New\n" );
- Map_Free();
+void Map_New()
+{
+ //globalOutputStream() << "Map_New\n";
- strcpy( currentmap, "unnamed.map" );
- Sys_SetTitle( currentmap );
+ g_map.m_name = "unnamed.map";
+ Map_UpdateTitle(g_map);
- world_entity = (entity_s*)qmalloc( sizeof( *world_entity ) );
- world_entity->brushes.onext =
- world_entity->brushes.oprev = &world_entity->brushes;
- SetKeyValue( world_entity, "classname", "worldspawn" );
- world_entity->eclass = Eclass_ForName( "worldspawn", true );
+ {
+ g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
+// ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
+ g_map.m_resource->attach(g_map);
- g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] = 0;
- g_pParentWnd->GetCamWnd()->Camera()->angles[PITCH] = 0;
- VectorCopy( vec3_origin, g_pParentWnd->GetCamWnd()->Camera()->origin );
- g_pParentWnd->GetCamWnd()->Camera()->origin[2] = 48;
- VectorCopy( vec3_origin, g_pParentWnd->GetXYWnd()->GetOrigin() );
+ SceneChangeNotify();
+ }
- Map_RestoreBetween();
+ FocusViews(g_vector3_identity, 0);
- Group_Init();
+ g_currentMap = &g_map;
- Sys_UpdateWindows( W_ALL );
- modified = false;
+ // restart VFS to apply new pak filtering based on mapname
+ // needed for daemon DPK VFS
+ VFS_Restart();
+}
+
+extern void ConstructRegionBrushes(scene::Node *brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_maxs);
+
+void ConstructRegionStartpoint(scene::Node *startpoint, const Vector3 ®ion_mins, const Vector3 ®ion_maxs)
+{
+ /*!
+ \todo we need to make sure that the player start IS inside the region and bail out if it's not
+ the compiler will refuse to compile a map with a player_start somewhere in empty space..
+ for now, let's just print an error
+ */
+
+ Vector3 vOrig(Camera_getOrigin(*g_pParentWnd->GetCamWnd()));
+
+ for (int i = 0; i < 3; i++) {
+ if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i]) {
+ globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
+ break;
+ }
+ }
+
+ // write the info_playerstart
+ char sTmp[1024];
+ sprintf(sTmp, "%d %d %d", (int) vOrig[0], (int) vOrig[1], (int) vOrig[2]);
+ Node_getEntity(*startpoint)->setKeyValue("origin", sTmp);
+ sprintf(sTmp, "%d", (int) Camera_getAngles(*g_pParentWnd->GetCamWnd())[CAMERA_YAW]);
+ Node_getEntity(*startpoint)->setKeyValue("angle", sTmp);
}
/*
===========================================================
*/
-qboolean region_active;
-vec3_t region_mins = {g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord};
-vec3_t region_maxs = {g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord};
+bool region_active;
+Vector3 region_mins(g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord);
+Vector3 region_maxs(g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord);
-brush_t *region_sides[6];
+scene::Node *region_sides[6];
+scene::Node *region_startpoint = 0;
/*
===========
with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
===========
*/
-void AddRegionBrushes( void ){
- vec3_t mins, maxs;
- int i;
- texdef_t td;
-
- if ( !region_active ) {
-#ifdef _DEBUG
- Sys_FPrintf( SYS_WRN, "Unexpected AddRegionBrushes call.\n" );
-#endif
- return;
- }
-
- memset( &td, 0, sizeof( td ) );
- td.SetName( SHADER_NOT_FOUND );
-
- // set mins
- VectorSet( mins, region_mins[0] - 32, region_mins[1] - 32, region_mins[2] - 32 );
-
- // vary maxs
- for ( i = 0; i < 3; i++ )
- {
- VectorSet( maxs, region_maxs[0] + 32, region_maxs[1] + 32, region_maxs[2] + 32 );
- maxs[i] = region_mins[i];
- region_sides[i] = Brush_Create( mins, maxs, &td );
- }
-
- // set maxs
- VectorSet( maxs, region_maxs[0] + 32, region_maxs[1] + 32, region_maxs[2] + 32 );
-
- // vary mins
- for ( i = 0; i < 3; i++ )
- {
- VectorSet( mins, region_mins[0] - 32, region_mins[1] - 32, region_mins[2] - 32 );
- mins[i] = region_maxs[i];
- region_sides[i + 3] = Brush_Create( mins, maxs, &td );
- }
-
-
- // this is a safe check, but it should not really happen anymore
- vec3_t vOrig;
- VectorSet( vOrig,
- (int)g_pParentWnd->GetCamWnd()->Camera()->origin[0],
- (int)g_pParentWnd->GetCamWnd()->Camera()->origin[1],
- (int)g_pParentWnd->GetCamWnd()->Camera()->origin[2] );
-
- for ( i = 0 ; i < 3 ; i++ )
- {
- if ( vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i] ) {
- Sys_FPrintf( SYS_ERR, "Camera is NOT in the region, it's likely that the region won't compile correctly\n" );
- }
- }
-
- // write the info_playerstart
- region_startpoint = Entity_Alloc();
- SetKeyValue( region_startpoint, "classname", "info_player_start" );
- region_startpoint->eclass = Eclass_ForName( "info_player_start", false );
- char sTmp[1024];
- sprintf( sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2] );
- SetKeyValue( region_startpoint, "origin", sTmp );
- sprintf( sTmp, "%d", (int)g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] );
- SetKeyValue( region_startpoint, "angle", sTmp );
- // empty array of children
- region_startpoint->pData = new CPtrArray;
-}
-
-void RemoveRegionBrushes( void ){
- int i;
-
- if ( !region_active ) {
- return;
- }
- for ( i = 0 ; i < 6 ; i++ )
- Brush_Free( region_sides[i] );
-
- CleanFilter( region_startpoint );
- Entity_Free( region_startpoint );
-}
-
-qboolean Map_IsBrushFiltered( brush_t *b ){
- int i;
-
- for ( i = 0 ; i < 3 ; i++ )
- {
- if ( b->mins[i] > region_maxs[i] ) {
- return true;
- }
- if ( b->maxs[i] < region_mins[i] ) {
- return true;
- }
- }
- return false;
+void AddRegionBrushes(void)
+{
+ int i;
+
+ for (i = 0; i < 6; i++) {
+ region_sides[i] = &GlobalBrushCreator().createBrush();
+ Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(NodeSmartReference(*region_sides[i]));
+ }
+
+ region_startpoint = &GlobalEntityCreator().createEntity(
+ GlobalEntityClassManager().findOrInsert("info_player_start", false));
+
+ ConstructRegionBrushes(region_sides, region_mins, region_maxs);
+ ConstructRegionStartpoint(region_startpoint, region_mins, region_maxs);
+
+ Node_getTraversable(GlobalSceneGraph().root())->insert(NodeSmartReference(*region_startpoint));
+}
+
+void RemoveRegionBrushes(void)
+{
+ for (std::size_t i = 0; i < 6; i++) {
+ Node_getTraversable(*Map_GetWorldspawn(g_map))->erase(*region_sides[i]);
+ }
+ Node_getTraversable(GlobalSceneGraph().root())->erase(*region_startpoint);
+}
+
+inline void exclude_node(scene::Node &node, bool exclude)
+{
+ exclude
+ ? node.enable(scene::Node::eExcluded)
+ : node.disable(scene::Node::eExcluded);
+}
+
+class ExcludeAllWalker : public scene::Graph::Walker {
+ bool m_exclude;
+public:
+ ExcludeAllWalker(bool exclude)
+ : m_exclude(exclude)
+ {
+ }
+
+ bool pre(const scene::Path &path, scene::Instance &instance) const
+ {
+ exclude_node(path.top(), m_exclude);
+
+ return true;
+ }
+};
+
+void Scene_Exclude_All(bool exclude)
+{
+ GlobalSceneGraph().traverse(ExcludeAllWalker(exclude));
+}
+
+bool Instance_isSelected(const scene::Instance &instance)
+{
+ const Selectable *selectable = Instance_getSelectable(instance);
+ return selectable != 0 && selectable->isSelected();
+}
+
+class ExcludeSelectedWalker : public scene::Graph::Walker {
+ bool m_exclude;
+public:
+ ExcludeSelectedWalker(bool exclude)
+ : m_exclude(exclude)
+ {
+ }
+
+ bool pre(const scene::Path &path, scene::Instance &instance) const
+ {
+ exclude_node(path.top(),
+ (instance.isSelected() || instance.childSelected() || instance.parentSelected()) == m_exclude);
+ return true;
+ }
+};
+
+void Scene_Exclude_Selected(bool exclude)
+{
+ GlobalSceneGraph().traverse(ExcludeSelectedWalker(exclude));
+}
+
+class ExcludeRegionedWalker : public scene::Graph::Walker {
+ bool m_exclude;
+public:
+ ExcludeRegionedWalker(bool exclude)
+ : m_exclude(exclude)
+ {
+ }
+
+ bool pre(const scene::Path &path, scene::Instance &instance) const
+ {
+ exclude_node(
+ path.top(),
+ !(
+ (
+ aabb_intersects_aabb(
+ instance.worldAABB(),
+ aabb_for_minmax(region_mins, region_maxs)
+ ) != 0
+ ) ^ m_exclude
+ )
+ );
+
+ return true;
+ }
+};
+
+void Scene_Exclude_Region(bool exclude)
+{
+ GlobalSceneGraph().traverse(ExcludeRegionedWalker(exclude));
}
/*
Other filtering options may still be on
===========
*/
-void Map_RegionOff( void ){
- brush_t *b, *next;
- int i;
-
- region_active = false;
- for ( i = 0 ; i < 3 ; i++ )
- {
- region_maxs[i] = g_MaxWorldCoord - 64;
- region_mins[i] = g_MinWorldCoord + 64;
- }
-
- for ( b = filtered_brushes.next ; b != &filtered_brushes ; b = next )
- {
- next = b->next;
- if ( Map_IsBrushFiltered( b ) ) {
- continue; // still filtered
- }
- Brush_RemoveFromList( b );
- if ( active_brushes.next == NULL || active_brushes.prev == NULL ) {
- active_brushes.next = &active_brushes;
- active_brushes.prev = &active_brushes;
- }
- Brush_AddToList( b, &active_brushes );
- b->bFiltered = FilterBrush( b );
- }
- Sys_UpdateWindows( W_ALL );
-}
-
-void Map_ApplyRegion( void ){
- brush_t *b, *next;
-
- region_active = true;
- for ( b = active_brushes.next ; b != &active_brushes ; b = next )
- {
- next = b->next;
- if ( !Map_IsBrushFiltered( b ) ) {
- continue; // still filtered
- }
- Brush_RemoveFromList( b );
- Brush_AddToList( b, &filtered_brushes );
- }
-
- Sys_UpdateWindows( W_ALL );
+void Map_RegionOff()
+{
+ region_active = false;
+
+ region_maxs[0] = g_MaxWorldCoord - 64;
+ region_mins[0] = g_MinWorldCoord + 64;
+ region_maxs[1] = g_MaxWorldCoord - 64;
+ region_mins[1] = g_MinWorldCoord + 64;
+ region_maxs[2] = g_MaxWorldCoord - 64;
+ region_mins[2] = g_MinWorldCoord + 64;
+
+ Scene_Exclude_All(false);
+}
+
+void Map_ApplyRegion(void)
+{
+ region_active = true;
+
+ Scene_Exclude_Region(false);
}
Map_RegionSelectedBrushes
========================
*/
-void Map_RegionSelectedBrushes( void ){
- Map_RegionOff();
-
- if ( selected_brushes.next == &selected_brushes ) { // nothing selected
- Sys_Printf( "Tried to region with no selection...\n" );
- return;
- }
- region_active = true;
- Select_GetBounds( region_mins, region_maxs );
-
-#ifdef _DEBUG
- if ( filtered_brushes.next != &filtered_brushes ) {
- Sys_Printf( "WARNING: filtered_brushes list may not be empty in Map_RegionSelectedBrushes\n" );
- }
-#endif
+void Map_RegionSelectedBrushes(void)
+{
+ Map_RegionOff();
- if ( active_brushes.next == &active_brushes ) {
- // just have an empty filtered_brushes list
- // this happens if you set region after selecting all the brushes in your map (some weird people do that, ask MrE!)
- filtered_brushes.next = filtered_brushes.prev = &filtered_brushes;
- }
- else
- {
- // move the entire active_brushes list to filtered_brushes
- filtered_brushes.next = active_brushes.next;
- filtered_brushes.prev = active_brushes.prev;
- filtered_brushes.next->prev = &filtered_brushes;
- filtered_brushes.prev->next = &filtered_brushes;
- }
+ if (GlobalSelectionSystem().countSelected() != 0
+ && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive) {
+ region_active = true;
+ Select_GetBounds(region_mins, region_maxs);
- // move the entire selected_brushes list to active_brushes
- active_brushes.next = selected_brushes.next;
- active_brushes.prev = selected_brushes.prev;
- active_brushes.next->prev = &active_brushes;
- active_brushes.prev->next = &active_brushes;
+ Scene_Exclude_Selected(false);
- // deselect patches
- for ( brush_t *b = active_brushes.next; b != &active_brushes; b = b->next )
- if ( b->patchBrush ) {
- b->pPatch->bSelected = false;
- }
+ GlobalSelectionSystem().setSelectedAll(false);
+ }
+}
- // clear selected_brushes
- selected_brushes.next = selected_brushes.prev = &selected_brushes;
- Sys_UpdateWindows( W_ALL );
+/*
+ ===========
+ Map_RegionXY
+ ===========
+ */
+void Map_RegionXY(float x_min, float y_min, float x_max, float y_max)
+{
+ Map_RegionOff();
+
+ region_mins[0] = x_min;
+ region_maxs[0] = x_max;
+ region_mins[1] = y_min;
+ region_maxs[1] = y_max;
+ region_mins[2] = g_MinWorldCoord + 64;
+ region_maxs[2] = g_MaxWorldCoord - 64;
+
+ Map_ApplyRegion();
}
+void Map_RegionBounds(const AABB &bounds)
+{
+ Map_RegionOff();
+
+ region_mins = vector3_subtracted(bounds.origin, bounds.extents);
+ region_maxs = vector3_added(bounds.origin, bounds.extents);
+
+ deleteSelection();
+
+ Map_ApplyRegion();
+}
/*
===========
- Map_RegionXY
+ Map_RegionBrush
===========
*/
-void Map_RegionXY( void ){
- Map_RegionOff();
+void Map_RegionBrush(void)
+{
+ if (GlobalSelectionSystem().countSelected() != 0) {
+ scene::Instance &instance = GlobalSelectionSystem().ultimateSelected();
+ Map_RegionBounds(instance.worldAABB());
+ }
+}
- region_mins[0] = g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5 * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale();
- region_maxs[0] = g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5 * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale();
- region_mins[1] = g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5 * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale();
- region_maxs[1] = g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5 * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale();
- region_mins[2] = g_MinWorldCoord + 64;
- region_maxs[2] = g_MaxWorldCoord - 64;
- Map_ApplyRegion();
+//
+//================
+//Map_ImportFile
+//================
+//
+bool Map_ImportFile(const char *filename)
+{
+ ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
+
+ g_strLastFolder = g_path_get_dirname(filename);
+
+ bool success = false;
+
+ if (extension_equal(path_get_extension(filename), "bsp")) {
+ goto tryDecompile;
+ }
+
+ {
+ const MapFormat *format = NULL;
+ const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
+ if (string_not_empty(moduleName)) {
+ format = ReferenceAPI_getMapModules().findModule(moduleName);
+ }
+
+ if (format) {
+ format->wrongFormat = false;
+ }
+ Resource *resource = GlobalReferenceCache().capture(filename);
+ resource->refresh(); // avoid loading old version if map has changed on disk since last import
+ if (!resource->load()) {
+ GlobalReferenceCache().release(filename);
+ goto tryDecompile;
+ }
+ if (format) {
+ if (format->wrongFormat) {
+ GlobalReferenceCache().release(filename);
+ goto tryDecompile;
+ }
+ }
+ NodeSmartReference clone(NewMapRoot(""));
+ Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
+ Map_gatherNamespaced(clone);
+ Map_mergeClonedNames();
+ MergeMap(clone);
+ success = true;
+ GlobalReferenceCache().release(filename);
+ }
+
+ SceneChangeNotify();
+
+ return success;
+
+ tryDecompile:
+
+ const char *type = GlobalRadiant().getGameDescriptionKeyValue("q3map2_type");
+ int n = string_length(path_get_extension(filename));
+ if (n && (extension_equal(path_get_extension(filename), "bsp") ||
+ extension_equal(path_get_extension(filename), "map"))) {
+ StringBuffer output;
+ output.push_string(AppPath_get());
+ output.push_string("q3map2.");
+ output.push_string(RADIANT_EXECUTABLE);
+ output.push_string(" -v -game ");
+ output.push_string((type && *type) ? type : "quake3");
+ output.push_string(" -fs_basepath \"");
+ output.push_string(EnginePath_get());
+ output.push_string("\" -fs_homepath \"");
+ output.push_string(g_qeglobals.m_userEnginePath.c_str());
+ output.push_string("\"");
+
+ // extra pakpaths
+ for (int i = 0; i < g_pakPathCount; i++) {
+ if (g_strcmp0(g_strPakPath[i].c_str(), "")) {
+ output.push_string(" -fs_pakpath \"");
+ output.push_string(g_strPakPath[i].c_str());
+ output.push_string("\"");
+ }
+ }
+
+ // extra switches
+ if (g_disableEnginePath) {
+ output.push_string(" -fs_nobasepath ");
+ }
+
+ if (g_disableHomePath) {
+ output.push_string(" -fs_nohomepath ");
+ }
+
+ output.push_string(" -fs_game ");
+ output.push_string(gamename_get());
+ output.push_string(" -convert -format ");
+ output.push_string(Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map");
+ if (extension_equal(path_get_extension(filename), "map")) {
+ output.push_string(" -readmap ");
+ }
+ output.push_string(" \"");
+ output.push_string(filename);
+ output.push_string("\"");
+
+ // run
+ Q_Exec(NULL, output.c_str(), NULL, false, true);
+
+ // rebuild filename as "filenamewithoutext_converted.map"
+ output.clear();
+ output.push_range(filename, filename + string_length(filename) - (n + 1));
+ output.push_string("_converted.map");
+ filename = output.c_str();
+
+ // open
+ Resource *resource = GlobalReferenceCache().capture(filename);
+ resource->refresh(); // avoid loading old version if map has changed on disk since last import
+ if (!resource->load()) {
+ GlobalReferenceCache().release(filename);
+ goto tryDecompile;
+ }
+ NodeSmartReference clone(NewMapRoot(""));
+ Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
+ Map_gatherNamespaced(clone);
+ Map_mergeClonedNames();
+ MergeMap(clone);
+ success = true;
+ GlobalReferenceCache().release(filename);
+ }
+
+ SceneChangeNotify();
+ return success;
}
/*
===========
- Map_RegionTallBrush
+ Map_SaveFile
===========
*/
-void Map_RegionTallBrush( void ){
- brush_t *b;
+bool Map_SaveFile(const char *filename)
+{
+ ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
+ bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse, filename);
+ if (success) {
+ // refresh VFS to apply new pak filtering based on mapname
+ // needed for daemon DPK VFS
+ VFS_Refresh();
+ }
+ return success;
+}
- if ( !QE_SingleBrush() ) {
- return;
- }
+//
+//===========
+//Map_SaveSelected
+//===========
+//
+// Saves selected world brushes and whole entities with partial/full selections
+//
+bool Map_SaveSelected(const char *filename)
+{
+ return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Selected,
+ filename);
+}
- b = selected_brushes.next;
- Map_RegionOff();
+class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker {
+ scene::Node &m_parent;
+public:
+ ParentSelectedBrushesToEntityWalker(scene::Node &parent) : m_parent(parent)
+ {
+ }
+
+ bool pre(const scene::Path &path, scene::Instance &instance) const
+ {
+ if (path.top().get_pointer() != &m_parent
+ && Node_isPrimitive(path.top())) {
+ Selectable *selectable = Instance_getSelectable(instance);
+ if (selectable != 0
+ && selectable->isSelected()
+ && path.size() > 1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void post(const scene::Path &path, scene::Instance &instance) const
+ {
+ if (path.top().get_pointer() != &m_parent
+ && Node_isPrimitive(path.top())) {
+ Selectable *selectable = Instance_getSelectable(instance);
+ if (selectable != 0
+ && selectable->isSelected()
+ && path.size() > 1) {
+ scene::Node &parent = path.parent();
+ if (&parent != &m_parent) {
+ NodeSmartReference node(path.top().get());
+ Node_getTraversable(parent)->erase(node);
+ Node_getTraversable(m_parent)->insert(node);
+ }
+ }
+ }
+ }
+};
+
+void Scene_parentSelectedBrushesToEntity(scene::Graph &graph, scene::Node &parent)
+{
+ graph.traverse(ParentSelectedBrushesToEntityWalker(parent));
+}
- VectorCopy( b->mins, region_mins );
- VectorCopy( b->maxs, region_maxs );
- region_mins[2] = g_MinWorldCoord + 64;
- region_maxs[2] = g_MaxWorldCoord - 64;
+class CountSelectedBrushes : public scene::Graph::Walker {
+ std::size_t &m_count;
+ mutable std::size_t m_depth;
+public:
+ CountSelectedBrushes(std::size_t &count) : m_count(count), m_depth(0)
+ {
+ m_count = 0;
+ }
+
+ bool pre(const scene::Path &path, scene::Instance &instance) const
+ {
+ if (++m_depth != 1 && path.top().get().isRoot()) {
+ return false;
+ }
+ Selectable *selectable = Instance_getSelectable(instance);
+ if (selectable != 0
+ && selectable->isSelected()
+ && Node_isPrimitive(path.top())) {
+ ++m_count;
+ }
+ return true;
+ }
+
+ void post(const scene::Path &path, scene::Instance &instance) const
+ {
+ --m_depth;
+ }
+};
+
+std::size_t Scene_countSelectedBrushes(scene::Graph &graph)
+{
+ std::size_t count;
+ graph.traverse(CountSelectedBrushes(count));
+ return count;
+}
- Undo_Start( "delete" );
- Undo_AddBrushList( &selected_brushes );
- Undo_AddEntity( b->owner );
- Select_Delete();
- Undo_EndBrushList( &selected_brushes );
- Undo_End();
+enum ENodeType {
+ eNodeUnknown,
+ eNodeMap,
+ eNodeEntity,
+ eNodePrimitive,
+};
+
+const char *nodetype_get_name(ENodeType type)
+{
+ if (type == eNodeMap) {
+ return "map";
+ }
+ if (type == eNodeEntity) {
+ return "entity";
+ }
+ if (type == eNodePrimitive) {
+ return "primitive";
+ }
+ return "unknown";
+}
- Map_ApplyRegion();
+ENodeType node_get_nodetype(scene::Node &node)
+{
+ if (Node_isEntity(node)) {
+ return eNodeEntity;
+ }
+ if (Node_isPrimitive(node)) {
+ return eNodePrimitive;
+ }
+ return eNodeUnknown;
}
-/*
- ===========
- Map_RegionBrush
- ===========
- */
-void Map_RegionBrush( void ){
- brush_t *b;
+bool contains_entity(scene::Node &node)
+{
+ return Node_getTraversable(node) != 0 && !Node_isBrush(node) && !Node_isPatch(node) && !Node_isEntity(node);
+}
- if ( !QE_SingleBrush() ) {
- return;
- }
+bool contains_primitive(scene::Node &node)
+{
+ return Node_isEntity(node) && Node_getTraversable(node) != 0 && Node_getEntity(node)->isContainer();
+}
- b = selected_brushes.next;
+ENodeType node_get_contains(scene::Node &node)
+{
+ if (contains_entity(node)) {
+ return eNodeEntity;
+ }
+ if (contains_primitive(node)) {
+ return eNodePrimitive;
+ }
+ return eNodeUnknown;
+}
- Map_RegionOff();
+void Path_parent(const scene::Path &parent, const scene::Path &child)
+{
+ ENodeType contains = node_get_contains(parent.top());
+ ENodeType type = node_get_nodetype(child.top());
+
+ if (contains != eNodeUnknown && contains == type) {
+ NodeSmartReference node(child.top().get());
+ Path_deleteTop(child);
+ Node_getTraversable(parent.top())->insert(node);
+ SceneChangeNotify();
+ } else {
+ globalErrorStream() << "failed - " << nodetype_get_name(type) << " cannot be parented to "
+ << nodetype_get_name(contains) << " container.\n";
+ }
+}
- VectorCopy( b->mins, region_mins );
- VectorCopy( b->maxs, region_maxs );
+void Scene_parentSelected()
+{
+ UndoableCommand undo("parentSelected");
+
+ if (GlobalSelectionSystem().countSelected() > 1) {
+ class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor {
+ const scene::Path &m_parent;
+ public:
+ ParentSelectedBrushesToEntityWalker(const scene::Path &parent) : m_parent(parent)
+ {
+ }
+
+ void visit(scene::Instance &instance) const
+ {
+ if (&m_parent != &instance.path()) {
+ Path_parent(m_parent, instance.path());
+ }
+ }
+ };
+
+ ParentSelectedBrushesToEntityWalker visitor(GlobalSelectionSystem().ultimateSelected().path());
+ GlobalSelectionSystem().foreachSelected(visitor);
+ } else {
+ globalOutputStream() << "failed - did not find two selected nodes.\n";
+ }
+}
- Undo_Start( "delete" );
- Undo_AddBrushList( &selected_brushes );
- Undo_AddEntity( b->owner );
- Select_Delete();
- Undo_EndBrushList( &selected_brushes );
- Undo_End();
- Map_ApplyRegion();
+void NewMap()
+{
+ if (ConfirmModified("New Map")) {
+ Map_RegionOff();
+ Map_Free();
+ Map_New();
+ }
}
-GList *find_string( GList *glist, const char *buf ){
- while ( glist )
- {
- if ( strcmp( (char *)glist->data, buf ) == 0 ) {
- break; // this name is in our list already
- }
- glist = glist->next;
- }
- return glist;
+CopiedString g_mapsPath;
+
+const char *getMapsPath()
+{
+ return g_mapsPath.c_str();
}
-void Map_ImportBuffer( char *buf ){
- Select_Deselect();
+const char *getLastFolderPath()
+{
+ if (g_strLastFolder.empty()) {
+ GlobalPreferenceSystem().registerPreference("LastFolder", make_property_string(g_strLastFolder));
+ if (g_strLastFolder.empty()) {
+ g_strLastFolder = g_qeglobals.m_userGamePath;
+ }
+ }
+ return g_strLastFolder.c_str();
+}
+
+const char *map_open(const char *title)
+{
+ return MainFrame_getWindow().file_dialog(TRUE, title, getLastFolderPath(), MapFormat::Name(), true, false, false);
+}
- Undo_Start( "import buffer" );
+const char *map_import(const char *title)
+{
+ return MainFrame_getWindow().file_dialog(TRUE, title, getLastFolderPath(), MapFormat::Name(), false, true, false);
+}
- MemStream stream;
+const char *map_save(const char *title)
+{
+ return MainFrame_getWindow().file_dialog(FALSE, title, getLastFolderPath(), MapFormat::Name(), false, false, true);
+}
- stream.Write( buf, strlen( buf ) );
- Map_Import( &stream, "xmap" );
- stream.Close();
+void OpenMap()
+{
+ if (!ConfirmModified("Open Map")) {
+ return;
+ }
- Sys_UpdateWindows( W_ALL );
- Sys_MarkMapModified();
+ const char *filename = map_open("Open Map");
- Undo_End();
+ if (filename != NULL) {
+ MRU_AddFile(filename);
+ Map_RegionOff();
+ Map_Free();
+ Map_LoadFile(filename);
+ }
}
+void ImportMap()
+{
+ const char *filename = map_import("Import Map");
-//
-//================
-//Map_ImportFile
-//================
-//
-void Map_ImportFile( const char *filename ){
- FileStream file;
- Sys_BeginWait();
+ if (filename != NULL) {
+ UndoableCommand undo("mapImport");
+ Map_ImportFile(filename);
+ }
+}
- Sys_Printf( "Importing map from %s\n",filename );
+bool Map_SaveAs()
+{
+ const char *filename = map_save("Save Map");
+
+ if (filename != NULL) {
+ g_strLastFolder = g_path_get_dirname(filename);
+ MRU_AddFile(filename);
+ Map_Rename(filename);
+ return Map_Save();
+ }
+ return false;
+}
- const char* type = strrchr( filename,'.' );
- if ( type != NULL ) {
- type++;
- }
- /*!\todo Resolve "r" problem in scriptlib" */
- if ( file.Open( filename, "rb" ) ) {
- Map_Import( &file, type, true );
- }
- else{
- Sys_FPrintf( SYS_ERR, "ERROR: couldn't open %s for read\n", filename );
- }
+void SaveMapAs()
+{
+ Map_SaveAs();
+}
- file.Close();
+void SaveMap()
+{
+ if (Map_Unnamed(g_map)) {
+ SaveMapAs();
+ } else if (Map_Modified(g_map)) {
+ Map_Save();
+ }
+}
- Sys_UpdateWindows( W_ALL );
- modified = true;
- Sys_EndWait();
+void ExportMap()
+{
+ const char *filename = map_save("Export Selection");
+
+ if (filename != NULL) {
+ g_strLastFolder = g_path_get_dirname(filename);
+ Map_SaveSelected(filename);
+ }
}
-//
-//===========
-//Map_SaveSelected
-//===========
-//
-// Saves selected world brushes and whole entities with partial/full selections
-//
-void Map_SaveSelected( const char* filename ){
- FileStream file;
+void SaveRegion()
+{
+ const char *filename = map_save("Export Region");
- Sys_Printf( "Saving selection to %s\n",filename );
+ if (filename != NULL) {
+ g_strLastFolder = g_path_get_dirname(filename);
+ Map_SaveRegion(filename);
+ }
+}
- const char* type = strrchr( filename,'.' );
- if ( type != NULL ) {
- type++;
- }
- if ( file.Open( filename, "w" ) ) {
- Map_Export( &file, type, false, true );
- }
- else{
- Sys_FPrintf( SYS_ERR, "ERROR: failed to open %s for write\n", filename );
- }
- file.Close();
+void RegionOff()
+{
+ Map_RegionOff();
+ SceneChangeNotify();
+}
+void RegionXY()
+{
+ Map_RegionXY(
+ g_pParentWnd->GetXYWnd()->GetOrigin()[0] -
+ 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
+ g_pParentWnd->GetXYWnd()->GetOrigin()[1] -
+ 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
+ g_pParentWnd->GetXYWnd()->GetOrigin()[0] +
+ 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
+ g_pParentWnd->GetXYWnd()->GetOrigin()[1] +
+ 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
+ );
+ SceneChangeNotify();
}
-//
-//===========
-//Map_SaveSelected
-//===========
-//
-// Saves selected world brushes and whole entities with partial/full selections
-//
-void Map_SaveSelected( MemStream* pMemFile, MemStream* pPatchFile ){
- Map_Export( pMemFile, "xmap", false, true );
-
- /*
- // write world entity first
- Entity_WriteSelected(world_entity, pMemFile);
-
- // then write all other ents
- count = 1;
- for (e=entities.next ; e != &entities ; e=next)
- {
- MemFile_fprintf(pMemFile, "// entity %i\n", count);
- count++;
- Entity_WriteSelected(e, pMemFile);
- next = e->next;
- }
-
- //if (pPatchFile)
- // Patch_WriteFile(pPatchFile);
- */
+void RegionBrush()
+{
+ Map_RegionBrush();
+ SceneChangeNotify();
+}
+
+void RegionSelected()
+{
+ Map_RegionSelectedBrushes();
+ SceneChangeNotify();
}
-void MemFile_fprintf( MemStream* pMemFile, const char* pText, ... ){
- char Buffer[4096];
- va_list args;
- va_start( args,pText );
- vsprintf( Buffer, pText, args );
- pMemFile->Write( Buffer, strlen( Buffer ) );
+class BrushFindByIndexWalker : public scene::Traversable::Walker {
+ mutable std::size_t m_index;
+ scene::Path &m_path;
+public:
+ BrushFindByIndexWalker(std::size_t index, scene::Path &path)
+ : m_index(index), m_path(path)
+ {
+ }
+
+ bool pre(scene::Node &node) const
+ {
+ if (Node_isPrimitive(node) && m_index-- == 0) {
+ m_path.push(makeReference(node));
+ }
+ return false;
+ }
+};
+
+class EntityFindByIndexWalker : public scene::Traversable::Walker {
+ mutable std::size_t m_index;
+ scene::Path &m_path;
+public:
+ EntityFindByIndexWalker(std::size_t index, scene::Path &path)
+ : m_index(index), m_path(path)
+ {
+ }
+
+ bool pre(scene::Node &node) const
+ {
+ if (Node_isEntity(node) && m_index-- == 0) {
+ m_path.push(makeReference(node));
+ }
+ return false;
+ }
+};
+
+void Scene_FindEntityBrush(std::size_t entity, std::size_t brush, scene::Path &path)
+{
+ path.push(makeReference(GlobalSceneGraph().root()));
+ {
+ Node_getTraversable(path.top())->traverse(EntityFindByIndexWalker(entity, path));
+ }
+ if (path.size() == 2) {
+ scene::Traversable *traversable = Node_getTraversable(path.top());
+ if (traversable != 0) {
+ traversable->traverse(BrushFindByIndexWalker(brush, path));
+ }
+ }
}
-/*!
- ==============
- Region_SpawnPoint
- push the region spawn point
- \todo FIXME TTimo this was in the #1 MAP module implementation (in the core)
- not sure it has any use anymore, should prolly drop it
- ==============
- */
-void Region_SpawnPoint( FILE *f ){
- // write the info_player_start, we use the camera position
- fprintf( f, "{\n" );
- fprintf( f, "\"classname\" \"info_player_start\"\n" );
- fprintf( f, "\"origin\" \"%i %i %i\"\n",
- (int)g_pParentWnd->GetCamWnd()->Camera()->origin[0],
- (int)g_pParentWnd->GetCamWnd()->Camera()->origin[1],
- (int)g_pParentWnd->GetCamWnd()->Camera()->origin[2] );
- fprintf( f, "\"angle\" \"%i\"\n", (int)g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] );
- fprintf( f, "}\n" );
+inline bool Node_hasChildren(scene::Node &node)
+{
+ scene::Traversable *traversable = Node_getTraversable(node);
+ return traversable != 0 && !traversable->empty();
+}
+
+void SelectBrush(int entitynum, int brushnum)
+{
+ scene::Path path;
+ Scene_FindEntityBrush(entitynum, brushnum, path);
+ if (path.size() == 3 || (path.size() == 2 && !Node_hasChildren(path.top()))) {
+ scene::Instance *instance = GlobalSceneGraph().find(path);
+ ASSERT_MESSAGE(instance != 0, "SelectBrush: path not found in scenegraph");
+ Selectable *selectable = Instance_getSelectable(*instance);
+ ASSERT_MESSAGE(selectable != 0, "SelectBrush: path not selectable");
+ selectable->setSelected(true);
+ g_pParentWnd->GetXYWnd()->PositionView(instance->worldAABB().origin);
+ }
+}
+
+
+class BrushFindIndexWalker : public scene::Graph::Walker {
+ mutable const scene::Node *m_node;
+ std::size_t &m_count;
+public:
+ BrushFindIndexWalker(const scene::Node &node, std::size_t &count)
+ : m_node(&node), m_count(count)
+ {
+ }
+
+ bool pre(const scene::Path &path, scene::Instance &instance) const
+ {
+ if (Node_isPrimitive(path.top())) {
+ if (m_node == path.top().get_pointer()) {
+ m_node = 0;
+ }
+ if (m_node) {
+ ++m_count;
+ }
+ }
+ return true;
+ }
+};
+
+class EntityFindIndexWalker : public scene::Graph::Walker {
+ mutable const scene::Node *m_node;
+ std::size_t &m_count;
+public:
+ EntityFindIndexWalker(const scene::Node &node, std::size_t &count)
+ : m_node(&node), m_count(count)
+ {
+ }
+
+ bool pre(const scene::Path &path, scene::Instance &instance) const
+ {
+ if (Node_isEntity(path.top())) {
+ if (m_node == path.top().get_pointer()) {
+ m_node = 0;
+ }
+ if (m_node) {
+ ++m_count;
+ }
+ }
+ return true;
+ }
+};
+
+static void GetSelectionIndex(int *ent, int *brush)
+{
+ std::size_t count_brush = 0;
+ std::size_t count_entity = 0;
+ if (GlobalSelectionSystem().countSelected() != 0) {
+ const scene::Path &path = GlobalSelectionSystem().ultimateSelected().path();
+
+ GlobalSceneGraph().traverse(BrushFindIndexWalker(path.top(), count_brush));
+ GlobalSceneGraph().traverse(EntityFindIndexWalker(path.parent(), count_entity));
+ }
+ *brush = int(count_brush);
+ *ent = int(count_entity);
+}
+
+void DoFind()
+{
+ ModalDialog dialog;
+ ui::Entry entity{ui::null};
+ ui::Entry brush{ui::null};
+
+ ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback),
+ &dialog);
+
+ auto accel = ui::AccelGroup(ui::New);
+ window.add_accel_group(accel);
+
+ {
+ auto vbox = create_dialog_vbox(4, 4);
+ window.add(vbox);
+ {
+ auto table = create_dialog_table(2, 2, 4, 4);
+ vbox.pack_start(table, TRUE, TRUE, 0);
+ {
+ ui::Widget label = ui::Label("Entity number");
+ label.show();
+ (table).attach(label, {0, 1, 0, 1}, {0, 0});
+ }
+ {
+ ui::Widget label = ui::Label("Brush number");
+ label.show();
+ (table).attach(label, {0, 1, 1, 2}, {0, 0});
+ }
+ {
+ auto entry = ui::Entry(ui::New);
+ entry.show();
+ table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
+ gtk_widget_grab_focus(entry);
+ entity = entry;
+ }
+ {
+ auto entry = ui::Entry(ui::New);
+ entry.show();
+ table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
+
+ brush = entry;
+ }
+ }
+ {
+ auto hbox = create_dialog_hbox(4);
+ vbox.pack_start(hbox, TRUE, TRUE, 0);
+ {
+ auto button = create_dialog_button("Find", G_CALLBACK(dialog_button_ok), &dialog);
+ hbox.pack_start(button, FALSE, FALSE, 0);
+ widget_make_default(button);
+ gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (GdkModifierType) 0,
+ (GtkAccelFlags) 0);
+ }
+ {
+ auto button = create_dialog_button("Close", G_CALLBACK(dialog_button_cancel), &dialog);
+ hbox.pack_start(button, FALSE, FALSE, 0);
+ gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Escape, (GdkModifierType) 0,
+ (GtkAccelFlags) 0);
+ }
+ }
+ }
+
+ // Initialize dialog
+ char buf[16];
+ int ent, br;
+
+ GetSelectionIndex(&ent, &br);
+ sprintf(buf, "%i", ent);
+ entity.text(buf);
+ sprintf(buf, "%i", br);
+ brush.text(buf);
+
+ if (modal_dialog_show(window, dialog) == eIDOK) {
+ const char *entstr = gtk_entry_get_text(entity);
+ const char *brushstr = gtk_entry_get_text(brush);
+ SelectBrush(atoi(entstr), atoi(brushstr));
+ }
+
+ window.destroy();
+}
+
+void Map_constructPreferences(PreferencesPage &page)
+{
+ page.appendCheckBox("", "Load last map on open", g_bLoadLastMap);
+}
+
+
+class MapEntityClasses : public ModuleObserver {
+ std::size_t m_unrealised;
+public:
+ MapEntityClasses() : m_unrealised(1)
+ {
+ }
+
+ void realise()
+ {
+ if (--m_unrealised == 0) {
+ if (g_map.m_resource != 0) {
+ ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
+ g_map.m_resource->realise();
+ }
+ }
+ }
+
+ void unrealise()
+ {
+ if (++m_unrealised == 1) {
+ if (g_map.m_resource != 0) {
+ g_map.m_resource->flush();
+ g_map.m_resource->unrealise();
+ }
+ }
+ }
+};
+
+MapEntityClasses g_MapEntityClasses;
+
+
+class MapModuleObserver : public ModuleObserver {
+ std::size_t m_unrealised;
+public:
+ MapModuleObserver() : m_unrealised(1)
+ {
+ }
+
+ void realise()
+ {
+ if (--m_unrealised == 0) {
+ ASSERT_MESSAGE(!string_empty(g_qeglobals.m_userGamePath.c_str()),
+ "maps_directory: user-game-path is empty");
+ StringOutputStream buffer(256);
+ buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
+ Q_mkdir(buffer.c_str());
+ g_mapsPath = buffer.c_str();
+ }
+ }
+
+ void unrealise()
+ {
+ if (++m_unrealised == 1) {
+ g_mapsPath = "";
+ }
+ }
+};
+
+MapModuleObserver g_MapModuleObserver;
+
+CopiedString g_strLastMap;
+bool g_bLoadLastMap = false;
+
+void Map_Construct()
+{
+ GlobalCommands_insert("RegionOff", makeCallbackF(RegionOff));
+ GlobalCommands_insert("RegionSetXY", makeCallbackF(RegionXY));
+ GlobalCommands_insert("RegionSetBrush", makeCallbackF(RegionBrush));
+ GlobalCommands_insert("RegionSetSelection", makeCallbackF(RegionSelected),
+ Accelerator('R', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK)));
+
+ GlobalPreferenceSystem().registerPreference("LastMap", make_property_string(g_strLastMap));
+ GlobalPreferenceSystem().registerPreference("LoadLastMap", make_property_string(g_bLoadLastMap));
+ GlobalPreferenceSystem().registerPreference("MapInfoDlg", make_property<WindowPosition_String>(g_posMapInfoWnd));
+
+ PreferencesDialog_addSettingsPreferences(makeCallbackF(Map_constructPreferences));
+
+ GlobalEntityClassManager().attach(g_MapEntityClasses);
+ Radiant_attachHomePathsObserver(g_MapModuleObserver);
+}
+
+void Map_Destroy()
+{
+ Radiant_detachHomePathsObserver(g_MapModuleObserver);
+ GlobalEntityClassManager().detach(g_MapEntityClasses);
}