root/cafu/trunk/CaWE/MapDocument.cpp

Revision 455, 68.0 KB (checked in by Carsten, 4 months ago)

Updated copyright banners (in C++ files).

Line 
1/*
2=================================================================================
3This file is part of Cafu, the open-source game engine and graphics engine
4for multiplayer, cross-platform, real-time 3D action.
5Copyright (C) 2002-2012 Carsten Fuchs Software.
6
7Cafu is free software: you can redistribute it and/or modify it under the terms
8of the GNU General Public License as published by the Free Software Foundation,
9either version 3 of the License, or (at your option) any later version.
10
11Cafu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
12without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
13PURPOSE. See the GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with Cafu. If not, see <http://www.gnu.org/licenses/>.
17
18For support and more information about Cafu, visit us at <http://www.cafu.de>.
19=================================================================================
20*/
21
22#include "ChildFrame.hpp"
23#include "ChildFrameViewWin.hpp"
24#include "DialogEditSurfaceProps.hpp"
25#include "DialogInspector.hpp"
26#include "EntityClass.hpp"
27#include "GameConfig.hpp"
28#include "DialogGotoPrimitive.hpp"
29#include "ParentFrame.hpp"
30#include "DialogMapCheck.hpp"
31#include "MapDocument.hpp"
32#include "MapBezierPatch.hpp"
33#include "MapEntity.hpp"
34#include "MapModel.hpp"         // Only needed for some TypeInfo test...
35#include "MapPlant.hpp"         // Only needed for some TypeInfo test...
36#include "MapTerrain.hpp"       // Only needed for some TypeInfo test...
37#include "EntityClassVar.hpp"
38#include "DialogMapInfo.hpp"
39#include "MapBrush.hpp"
40#include "MapWorld.hpp"
41#include "DialogOptions.hpp"
42#include "Options.hpp"
43#include "OrthoBspTree.hpp"
44#include "DialogPasteSpecial.hpp"
45#include "DialogReplaceMaterials.hpp"
46#include "MapCommands/Transform.hpp"
47#include "MapCommands/Align.hpp"
48#include "MapCommands/ApplyMaterial.hpp"
49#include "MapCommands/AssignPrimToEnt.hpp"
50#include "MapCommands/Carve.hpp"
51#include "MapCommands/Delete.hpp"
52#include "MapCommands/Mirror.hpp"
53#include "MapCommands/SnapToGrid.hpp"
54#include "MapCommands/MakeHollow.hpp"
55#include "MapCommands/NewEntity.hpp"
56#include "MapCommands/Paste.hpp"
57#include "MapCommands/Select.hpp"
58#include "MapCommands/Group_Assign.hpp"
59#include "MapCommands/Group_Delete.hpp"
60#include "MapCommands/Group_New.hpp"
61#include "MapCommands/Group_SetVisibility.hpp"
62
63#include "ToolbarMaterials.hpp"     // Only needed for setting the ref to NULL in the dtor.
64#include "ToolCamera.hpp"
65#include "ToolEditSurface.hpp"
66#include "ToolMorph.hpp"
67#include "ToolManager.hpp"
68#include "ToolOptionsBars.hpp"
69
70#include "EditorMaterial.hpp"
71#include "EditorMaterialManager.hpp"
72#include "DialogTransform.hpp"
73#include "Group.hpp"
74#include "Camera.hpp"
75
76#include "Math3D/Misc.hpp"
77#include "Templates/Array.hpp"
78#include "TextParser/TextParser.hpp"
79
80#include "wx/wx.h"
81#include "wx/datetime.h"
82#include "wx/file.h"
83#include "wx/filename.h"
84#include "wx/numdlg.h"
85
86extern "C"
87{
88    #include <lua.h>
89    #include <lualib.h>
90    #include <lauxlib.h>
91}
92
93
94#if defined(_WIN32) && defined(_MSC_VER)
95    #if (_MSC_VER<1300)
96        #define for if (false) ; else for
97    #endif
98
99    // Turn off warning 4355: "'this' : wird in Initialisierungslisten fuer Basisklasse verwendet".
100    #pragma warning(disable:4355)
101#endif
102
103
104/// The class represents the "clipboard".
105/// The clipboard is a singleton, global to the application,
106/// in order to allow users to copy elements from one document and paste them into another.
107class ClipboardT
108{
109    public:
110
111    // TODO: Fully and properly implement Singleton pattern.
112    ~ClipboardT();
113
114    ArrayT<MapElementT*> Objects;
115    Vector3fT            OriginalCenter;
116};
117
118ClipboardT::~ClipboardT()
119{
120    // Make sure that we don't leak memory at application exit.
121    for (unsigned long ElemNr=0; ElemNr<Objects.Size(); ElemNr++)
122        delete Objects[ElemNr];
123}
124
125static ClipboardT s_Clipboard;
126
127
128/*static*/ const unsigned int MapDocumentT::CMAP_FILE_VERSION=13;
129
130
131BEGIN_EVENT_TABLE(MapDocumentT, wxEvtHandler)
132    EVT_MENU  (wxID_UNDO,                                         MapDocumentT::OnEditUndoRedo)
133    EVT_MENU  (wxID_REDO,                                         MapDocumentT::OnEditUndoRedo)
134    EVT_MENU  (wxID_CUT,                                          MapDocumentT::OnEditCut)
135    EVT_MENU  (wxID_COPY,                                         MapDocumentT::OnEditCopy)
136    EVT_MENU  (wxID_PASTE,                                        MapDocumentT::OnEditPaste)
137    EVT_MENU  (ChildFrameT::ID_MENU_EDIT_PASTE_SPECIAL,           MapDocumentT::OnEditPasteSpecial)
138    EVT_MENU  (ChildFrameT::ID_MENU_EDIT_DELETE,                  MapDocumentT::OnEditDelete)
139    EVT_BUTTON(ChildFrameT::ID_MENU_EDIT_DELETE,                  MapDocumentT::OnEditDelete)
140    EVT_MENU  (ChildFrameT::ID_MENU_EDIT_SELECT_NONE,             MapDocumentT::OnEditSelectNone)
141    EVT_MENU  (wxID_SELECTALL,                                    MapDocumentT::OnEditSelectAll)
142
143    EVT_UPDATE_UI(wxID_UNDO,                                    MapDocumentT::OnUpdateEditUndoRedo)
144    EVT_UPDATE_UI(wxID_REDO,                                    MapDocumentT::OnUpdateEditUndoRedo)
145    EVT_UPDATE_UI(wxID_CUT,                                     MapDocumentT::OnUpdateEditCutCopyDelete)
146    EVT_UPDATE_UI(wxID_COPY,                                    MapDocumentT::OnUpdateEditCutCopyDelete)
147    EVT_UPDATE_UI(ChildFrameT::ID_MENU_EDIT_DELETE,             MapDocumentT::OnUpdateEditCutCopyDelete)
148    EVT_UPDATE_UI(wxID_PASTE,                                   MapDocumentT::OnUpdateEditPasteSpecial)
149    EVT_UPDATE_UI(ChildFrameT::ID_MENU_EDIT_PASTE_SPECIAL,      MapDocumentT::OnUpdateEditPasteSpecial)
150
151    EVT_MENU  (ChildFrameT::ID_MENU_SELECTION_APPLY_MATERIAL,   MapDocumentT::OnSelectionApplyMaterial)
152    EVT_BUTTON(ChildFrameT::ID_MENU_SELECTION_APPLY_MATERIAL,   MapDocumentT::OnSelectionApplyMaterial)
153
154    EVT_UPDATE_UI(ChildFrameT::ID_MENU_SELECTION_APPLY_MATERIAL, MapDocumentT::OnUpdateSelectionApplyMaterial)
155
156    EVT_MENU(ChildFrameT::ID_MENU_MAP_SNAP_TO_GRID,             MapDocumentT::OnMapSnapToGrid)
157    EVT_MENU(ChildFrameT::ID_MENU_MAP_SHOW_GRID_2D,             MapDocumentT::OnMapToggleGrid2D)
158    EVT_MENU(ChildFrameT::ID_MENU_MAP_FINER_GRID,               MapDocumentT::OnMapFinerGrid)
159    EVT_MENU(ChildFrameT::ID_MENU_MAP_COARSER_GRID,             MapDocumentT::OnMapCoarserGrid)
160    EVT_MENU(ChildFrameT::ID_MENU_MAP_GOTO_PRIMITIVE,           MapDocumentT::OnMapGotoPrimitive)
161    EVT_MENU(ChildFrameT::ID_MENU_MAP_SHOW_INFO,                MapDocumentT::OnMapShowInfo)
162    EVT_MENU(ChildFrameT::ID_MENU_MAP_CHECK_FOR_PROBLEMS,       MapDocumentT::OnMapCheckForProblems)
163    EVT_MENU(ChildFrameT::ID_MENU_MAP_PROPERTIES,               MapDocumentT::OnMapProperties)
164    EVT_MENU(ChildFrameT::ID_MENU_MAP_LOAD_POINTFILE,           MapDocumentT::OnMapLoadPointFile)
165    EVT_MENU(ChildFrameT::ID_MENU_MAP_UNLOAD_POINTFILE,         MapDocumentT::OnMapUnloadPointFile)
166
167    EVT_MENU  (ChildFrameT::ID_MENU_VIEW_SHOW_ENTITY_INFO,        MapDocumentT::OnViewShowEntityInfo)
168    EVT_MENU  (ChildFrameT::ID_MENU_VIEW_SHOW_ENTITY_TARGETS,     MapDocumentT::OnViewShowEntityTargets)
169    EVT_MENU  (ChildFrameT::ID_MENU_VIEW_HIDE_SELECTED_OBJECTS,   MapDocumentT::OnViewHideSelectedObjects)
170    EVT_BUTTON(ChildFrameT::ID_MENU_VIEW_HIDE_SELECTED_OBJECTS,   MapDocumentT::OnViewHideSelectedObjects)
171    EVT_MENU  (ChildFrameT::ID_MENU_VIEW_HIDE_UNSELECTED_OBJECTS, MapDocumentT::OnViewHideUnselectedObjects)
172    EVT_BUTTON(ChildFrameT::ID_MENU_VIEW_HIDE_UNSELECTED_OBJECTS, MapDocumentT::OnViewHideUnselectedObjects)
173    EVT_MENU  (ChildFrameT::ID_MENU_VIEW_SHOW_HIDDEN_OBJECTS,     MapDocumentT::OnViewShowHiddenObjects)
174
175    EVT_UPDATE_UI(ChildFrameT::ID_MENU_VIEW_SHOW_ENTITY_INFO,     MapDocumentT::OnUpdateViewShowEntityInfo)
176    EVT_UPDATE_UI(ChildFrameT::ID_MENU_VIEW_SHOW_ENTITY_TARGETS,  MapDocumentT::OnUpdateViewShowEntityTargets)
177
178    EVT_MENU  (ChildFrameT::ID_MENU_TOOLS_CARVE,                  MapDocumentT::OnToolsCarve)
179    EVT_MENU  (ChildFrameT::ID_MENU_TOOLS_MAKE_HOLLOW,            MapDocumentT::OnToolsHollow)
180    EVT_MENU  (ChildFrameT::ID_MENU_TOOLS_GROUP,                  MapDocumentT::OnViewHideSelectedObjects)
181    EVT_BUTTON(ChildFrameT::ID_MENU_TOOLS_GROUP,                  MapDocumentT::OnViewHideSelectedObjects)
182    EVT_MENU  (ChildFrameT::ID_MENU_TOOLS_ASSIGN_PRIM_TO_ENTITY,  MapDocumentT::OnToolsAssignPrimToEntity)
183    EVT_BUTTON(ChildFrameT::ID_MENU_TOOLS_ASSIGN_PRIM_TO_ENTITY,  MapDocumentT::OnToolsAssignPrimToEntity)
184    EVT_MENU  (ChildFrameT::ID_MENU_TOOLS_ASSIGN_PRIM_TO_WORLD,   MapDocumentT::OnToolsAssignPrimToWorld)
185    EVT_BUTTON(ChildFrameT::ID_MENU_TOOLS_ASSIGN_PRIM_TO_WORLD,   MapDocumentT::OnToolsAssignPrimToWorld)
186    EVT_MENU  (ChildFrameT::ID_MENU_TOOLS_REPLACE_MATERIALS,      MapDocumentT::OnToolsReplaceMaterials)
187    EVT_MENU  (ChildFrameT::ID_MENU_TOOLS_MATERIAL_LOCK,          MapDocumentT::OnToolsMaterialLock)
188    EVT_MENU  (ChildFrameT::ID_MENU_TOOLS_SNAP_SELECTION_TO_GRID, MapDocumentT::OnToolsSnapSelectionToGrid)
189    EVT_MENU  (ChildFrameT::ID_MENU_TOOLS_TRANSFORM,              MapDocumentT::OnToolsTransform)
190    EVT_MENU  (ChildFrameT::ID_MENU_TOOLS_ALIGN_LEFT,             MapDocumentT::OnToolsAlign)
191    EVT_MENU  (ChildFrameT::ID_MENU_TOOLS_ALIGN_RIGHT,            MapDocumentT::OnToolsAlign)
192    EVT_MENU  (ChildFrameT::ID_MENU_TOOLS_ALIGN_HOR_CENTER,       MapDocumentT::OnToolsAlign)
193    EVT_MENU  (ChildFrameT::ID_MENU_TOOLS_ALIGN_TOP,              MapDocumentT::OnToolsAlign)
194    EVT_MENU  (ChildFrameT::ID_MENU_TOOLS_ALIGN_BOTTOM,           MapDocumentT::OnToolsAlign)
195    EVT_MENU  (ChildFrameT::ID_MENU_TOOLS_ALIGN_VERT_CENTER,      MapDocumentT::OnToolsAlign)
196    EVT_MENU  (ChildFrameT::ID_MENU_TOOLS_MIRROR_HOR,             MapDocumentT::OnToolsMirror)
197    EVT_MENU  (ChildFrameT::ID_MENU_TOOLS_MIRROR_VERT,            MapDocumentT::OnToolsMirror)
198
199    EVT_UPDATE_UI(ChildFrameT::ID_MENU_TOOLS_MATERIAL_LOCK, MapDocumentT::OnUpdateToolsMaterialLock)
200END_EVENT_TABLE()
201
202
203MapDocumentT::MapDocumentT(GameConfigT* GameConfig)
204    : wxEvtHandler(),
205      SubjectT(),
206      m_ChildFrame(NULL),
207      m_FileName("New Map"),
208      m_Entities(),
209      m_BspTree(NULL),
210      m_GameConfig(GameConfig),
211      m_PlantDescrMan(std::string(m_GameConfig->ModDir)),
212      m_History(),
213      m_Selection(),
214      m_SelectionBB(Vector3fT(-64.0f, -64.0f, 0.0f), Vector3fT(64.0f, 64.0f, 64.0f)),
215      m_PointFilePoints(),
216      m_PointFileColors(),
217      m_SnapToGrid(true),
218      m_GridSpacing(Options.Grid.InitialSpacing),
219      m_ShowGrid(true)
220{
221    m_Entities.PushBack(new MapWorldT(*this));
222
223    ArrayT<MapElementT*> AllElems;
224    GetAllElems(AllElems);    // There are none at this time...
225    m_BspTree=new OrthoBspTreeT(AllElems, m_GameConfig->GetMaxMapBB());
226}
227
228
229MapDocumentT::MapDocumentT(GameConfigT* GameConfig, wxProgressDialog* ProgressDialog, const wxString& FileName)
230    : wxEvtHandler(),
231      SubjectT(),
232      m_ChildFrame(NULL),
233      m_FileName(FileName),
234      m_Entities(),
235      m_BspTree(NULL),
236      m_GameConfig(GameConfig),
237      m_PlantDescrMan(std::string(m_GameConfig->ModDir)),
238      m_History(),
239      m_Selection(),
240      m_SelectionBB(Vector3fT(-64.0f, -64.0f, 0.0f), Vector3fT(64.0f, 64.0f, 64.0f)),
241      m_PointFilePoints(),
242      m_PointFileColors(),
243      m_SnapToGrid(true),
244      m_GridSpacing(Options.Grid.InitialSpacing),
245      m_ShowGrid(true)
246{
247    // This sets the cursor to the busy cursor in its ctor, and back to the default cursor in the dtor.
248    wxBusyCursor BusyCursor;
249
250    MapWorldT* World=new MapWorldT(*this);
251    m_Entities.PushBack(World);
252
253    TextParserT TP(FileName.c_str(), "({})");
254
255    if (TP.IsAtEOF())
256    {
257        delete m_Entities[0];
258        throw LoadErrorT();
259    }
260
261    try
262    {
263        World->Load_cmap(TP, *this, ProgressDialog, 0);
264
265        // Load the entities.
266        while (!TP.IsAtEOF())
267        {
268            MapEntityT* Entity=new MapEntityT;
269
270            Entity->Load_cmap(TP, *this, ProgressDialog, m_Entities.Size());
271            m_Entities.PushBack(Entity);
272        }
273    }
274    catch (const TextParserT::ParseError&)
275    {
276        wxMessageBox(wxString::Format(
277            "I'm sorry, but I was not able to load the map, due to a file error.\n"
278            "Worse, I cannot even say where the error occured, except near byte %lu (%.3f%%) of the file.\n"
279            "Later versions of CaWE will provide more detailed information.\n"
280            "Please use a text editor to make sure that the file you tried to open is a proper cmap file,\n"
281            "and/or post at the Cafu support forums.", TP.GetReadPosByte(), TP.GetReadPosPercent()*100.0),
282            wxString("Could not load ")+FileName, wxOK | wxICON_EXCLAMATION);
283
284        delete m_Entities[0];   //XXX TODO: Call Cleanup() method instead (same code as dtor).
285        throw LoadErrorT();
286    }
287
288
289    ArrayT<MapElementT*> AllElems;
290    GetAllElems(AllElems);
291
292    if (AllElems.Size()>0)
293    {
294        // The world itself is never inserted into the BSP tree.
295        wxASSERT(AllElems[0]==World);
296        wxASSERT(AllElems[0]->GetType()==&MapWorldT::TypeInfo);
297        AllElems.RemoveAt(0);
298    }
299
300    m_BspTree=new OrthoBspTreeT(AllElems, m_GameConfig->GetMaxMapBB());
301}
302
303
304/*static*/ MapDocumentT* MapDocumentT::ImportHalfLife1Map(GameConfigT* GameConfig, wxProgressDialog* ProgressDialog, const wxString& FileName)
305{
306    // This sets the cursor to the busy cursor in its ctor, and back to the default cursor in the dtor.
307    wxBusyCursor BusyCursor;
308
309    MapDocumentT* Doc=new MapDocumentT(GameConfig);
310    TextParserT   TP(FileName.c_str(), "({})");
311
312    if (TP.IsAtEOF())
313    {
314        delete Doc;
315        Doc=NULL;
316        throw LoadErrorT();
317    }
318
319    try
320    {
321        Doc->GetEntities()[0]->Load_HL1_map(TP, *Doc, ProgressDialog, 0);
322
323        // Load the entities.
324        while (!TP.IsAtEOF())
325        {
326            MapEntityT* Entity=new MapEntityT;
327
328            Entity->Load_HL1_map(TP, *Doc, ProgressDialog, Doc->m_Entities.Size());
329            Doc->m_Entities.PushBack(Entity);
330        }
331    }
332    catch (const TextParserT::ParseError&)
333    {
334        wxMessageBox(wxString::Format(
335            "I'm sorry, but I was not able to import the map, due to a file error.\n"
336            "Worse, I cannot even say where the error occured, except near byte %lu (%.3f%%) of the file.\n"
337            "Later versions of CaWE will provide more detailed information.\n"
338            "Please use a text editor to make sure that the file you tried to open is a proper HL1 map file,\n"
339            "and/or post at the Cafu support forums.", TP.GetReadPosByte(), TP.GetReadPosPercent()*100.0),
340            wxString("Could not import HL1 map ")+FileName, wxOK | wxICON_EXCLAMATION);
341
342        delete Doc;
343        Doc=NULL;
344        throw LoadErrorT();
345    }
346
347
348    ArrayT<MapElementT*> AllElems;
349    Doc->GetAllElems(AllElems);
350
351    if (AllElems.Size()>0)
352    {
353        // The world itself is never inserted into the BSP tree.
354        wxASSERT(AllElems[0]==Doc->GetEntities()[0]);
355        wxASSERT(AllElems[0]->GetType()==&MapWorldT::TypeInfo);
356        AllElems.RemoveAt(0);
357    }
358
359    delete Doc->m_BspTree;
360    Doc->m_BspTree=new OrthoBspTreeT(AllElems, Doc->m_GameConfig->GetMaxMapBB());
361
362    Doc->m_FileName=FileName;
363    return Doc;
364}
365
366
367/*static*/ MapDocumentT* MapDocumentT::ImportHalfLife2Vmf(GameConfigT* GameConfig, wxProgressDialog* ProgressDialog, const wxString& FileName)
368{
369    // This sets the cursor to the busy cursor in its ctor, and back to the default cursor in the dtor.
370    wxBusyCursor BusyCursor;
371
372    MapDocumentT* Doc=new MapDocumentT(GameConfig);
373    TextParserT TP(FileName.c_str(), "{}");
374
375    if (TP.IsAtEOF())
376    {
377        delete Doc;
378        Doc=NULL;
379        throw LoadErrorT();
380    }
381
382    try
383    {
384        // Read all the chunks.
385        while (!TP.IsAtEOF())
386        {
387            const std::string ChunkName=TP.GetNextToken();
388
389            if (ChunkName=="world")
390            {
391                Doc->GetEntities()[0]->Load_HL2_vmf(TP, *Doc, ProgressDialog, 0);
392            }
393            else if (ChunkName=="entity")
394            {
395                MapEntityT* Entity=new MapEntityT;
396
397                Entity->Load_HL2_vmf(TP, *Doc, ProgressDialog, Doc->m_Entities.Size());
398                Doc->m_Entities.PushBack(Entity);
399            }
400            else
401            {
402                // It's a chunk that we cannot deal with, so just skip it.
403                TP.SkipBlock("{", "}", false);
404            }
405        }
406    }
407    catch (const TextParserT::ParseError&)
408    {
409        wxMessageBox(wxString::Format(
410            "I'm sorry, but I was not able to import the map, due to a file error.\n"
411            "Worse, I cannot even say where the error occured, except near byte %lu (%.3f%%) of the file.\n"
412            "Later versions of CaWE will provide more detailed information.\n"
413            "Please use a text editor to make sure that the file you tried to open is a proper HL2 vmf file,\n"
414            "and/or post at the Cafu support forums.", TP.GetReadPosByte(), TP.GetReadPosPercent()*100.0),
415            wxString("Could not import HL2 map ")+FileName, wxOK | wxICON_EXCLAMATION);
416
417        delete Doc;
418        Doc=NULL;
419        throw LoadErrorT();
420    }
421
422
423    ArrayT<MapElementT*> AllElems;
424    Doc->GetAllElems(AllElems);
425
426    if (AllElems.Size()>0)
427    {
428        // The world itself is never inserted into the BSP tree.
429        wxASSERT(AllElems[0]==Doc->GetEntities()[0]);
430        wxASSERT(AllElems[0]->GetType()==&MapWorldT::TypeInfo);
431        AllElems.RemoveAt(0);
432    }
433
434    delete Doc->m_BspTree;
435    Doc->m_BspTree=new OrthoBspTreeT(AllElems, Doc->m_GameConfig->GetMaxMapBB());
436
437    Doc->m_FileName=FileName;
438    return Doc;
439}
440
441
442/*static*/ MapDocumentT* MapDocumentT::ImportDoom3Map(GameConfigT* GameConfig, wxProgressDialog* ProgressDialog, const wxString& FileName)
443{
444    // This sets the cursor to the busy cursor in its ctor, and back to the default cursor in the dtor.
445    wxBusyCursor BusyCursor;
446
447    MapDocumentT* Doc=new MapDocumentT(GameConfig);
448    TextParserT   TP(FileName.c_str(), "({})");
449
450    if (TP.IsAtEOF())
451    {
452        delete Doc;
453        Doc=NULL;
454        throw LoadErrorT();
455    }
456
457    try
458    {
459        Doc->GetEntities()[0]->Load_D3_map(TP, *Doc, ProgressDialog, 0);
460
461        // Load the entities.
462        while (!TP.IsAtEOF())
463        {
464            MapEntityT* Entity=new MapEntityT;
465
466            Entity->Load_D3_map(TP, *Doc, ProgressDialog, Doc->m_Entities.Size());
467            Doc->m_Entities.PushBack(Entity);
468        }
469    }
470    catch (const TextParserT::ParseError&)
471    {
472        wxMessageBox(wxString::Format(
473            "I'm sorry, but I was not able to import the map, due to a file error.\n"
474            "Worse, I cannot even say where the error occured, except near byte %lu (%.3f%%) of the file.\n"
475            "Later versions of CaWE will provide more detailed information.\n"
476            "Please use a text editor to make sure that the file you tried to open is a proper Doom3 map file,\n"
477            "and/or post at the Cafu support forums.", TP.GetReadPosByte(), TP.GetReadPosPercent()*100.0),
478            wxString("Could not import Doom3 map ")+FileName, wxOK | wxICON_EXCLAMATION);
479
480        delete Doc;
481        Doc=NULL;
482        throw LoadErrorT();
483    }
484
485
486    ArrayT<MapElementT*> AllElems;
487    Doc->GetAllElems(AllElems);
488
489    if (AllElems.Size()>0)
490    {
491        // The world itself is never inserted into the BSP tree.
492        wxASSERT(AllElems[0]==Doc->GetEntities()[0]);
493        wxASSERT(AllElems[0]->GetType()==&MapWorldT::TypeInfo);
494        AllElems.RemoveAt(0);
495    }
496
497    delete Doc->m_BspTree;
498    Doc->m_BspTree=new OrthoBspTreeT(AllElems, Doc->m_GameConfig->GetMaxMapBB());
499
500    Doc->m_FileName=FileName;
501    return Doc;
502}
503
504
505MapDocumentT::~MapDocumentT()
506{
507    delete m_BspTree;
508    m_BspTree=NULL;
509
510    for (unsigned long EntNr=0; EntNr<m_Entities.Size(); EntNr++) delete m_Entities[EntNr];
511    m_Entities.Clear();
512
513    for (unsigned long GroupNr=0; GroupNr<m_Groups.Size(); GroupNr++) delete m_Groups[GroupNr];
514    m_Groups.Clear();
515
516    for (unsigned long uecNr=0; uecNr<m_UnknownEntClasses.Size(); uecNr++) delete m_UnknownEntClasses[uecNr];
517    m_UnknownEntClasses.Clear();
518
519    m_Selection.Clear();
520}
521
522
523bool MapDocumentT::OnSaveDocument(const wxString& FileName, bool IsAutoSave)
524{
525    // This sets the cursor to the busy cursor in its ctor, and back to the default cursor in the dtor.
526    wxBusyCursor BusyCursor;
527
528    // Backup the previous file before overwriting it.
529    if (!IsAutoSave && wxFileExists(FileName))
530        if (!wxCopyFile(FileName, FileName+"_bak"))
531        {
532            wxMessageBox("Sorry, creating the backup file \""+FileName+"_bak\" before saving the map to \""+FileName+"\" didn't work out.\n"
533                         "Please check the path and file permissions, "
534                         "or use 'File -> Save As...' to save the current map elsewhere.", "File not saved!", wxOK | wxICON_ERROR);
535            return false;
536        }
537
538    if (FileName.Right(5).MakeLower()==".cmap")
539    {
540        std::ofstream OutFile(FileName.fn_str());
541
542        if (!OutFile.is_open())
543        {
544            wxMessageBox("The file \""+FileName+"\" could not be opened for writing.\nPlease check the path and file permissions, "
545                         "or use 'File -> Save As...' to save the current map elsewhere.", "File not saved!", wxOK | wxICON_ERROR);
546            return false;
547        }
548
549        // From MSDN documentation: "digits10 returns the number of decimal digits that the type can represent without loss of precision."
550        // For floats, that's usually 6, for doubles, that's usually 15. However, we want to use the number of *significant* decimal digits here,
551        // that is, max_digits10. See http://www.open-std.org/JTC1/sc22/wg21/docs/papers/2006/n2005.pdf for details.
552        OutFile.precision(std::numeric_limits<float>::digits10 + 3);
553
554        OutFile << "// Cafu Map File\n"
555                << "// Written by CaWE, the Cafu World Editor.\n"
556                << "Version " << CMAP_FILE_VERSION << "\n"
557                << "\n";
558
559        // Save groups.
560        for (unsigned long GroupNr=0; GroupNr<m_Groups.Size(); GroupNr++)
561            m_Groups[GroupNr]->Save_cmap(OutFile, GroupNr);
562
563        // Save entities.
564        for (unsigned long EntNr=0/*with world*/; EntNr<m_Entities.Size(); EntNr++)
565        {
566            const BoundingBox3fT* Intersecting=NULL;
567            const MapEntityBaseT* Ent=m_Entities[EntNr];
568
569            if (!Intersecting || Ent->GetBB().Intersects(*Intersecting))
570            {
571                Ent->Save_cmap(*this, OutFile, EntNr, Intersecting);
572            }
573        }
574
575        if (OutFile.fail())
576        {
577            wxMessageBox("There was an error when saving the file. Please try again.", "File not saved!", wxOK | wxICON_ERROR);
578            return false;
579        }
580
581        // If this was an auto-save, do not change the filename (nor set the document as "not modified").
582        if (IsAutoSave) return true;
583
584        m_FileName=FileName;
585        return true;
586    }
587
588    // if (FileName.Right(4).MakeLower()==".map") ...;      // Export to different file format.
589
590    wxMessageBox("Sorry, extension of this filename not recognized.", FileName);
591    return false;
592}
593
594
595bool MapDocumentT::SaveAs()
596{
597    static wxString  LastUsedDir=m_GameConfig->ModDir+"/Maps";
598    const wxFileName FN(m_FileName);
599
600    wxFileDialog SaveFileDialog(NULL,                               // parent
601                                "Save (or Export) File",            // message
602                                (FN.IsOk() && wxDirExists(FN.GetPath())) ? FN.GetPath() : LastUsedDir, // default dir
603                                "",                                 // default file
604                                "Cafu Map Files (*.cmap)|*.cmap",   // wildcard
605                             // "|Export Hammer (HL1) Maps (*.map)|*.map"
606                             // "|Export Hammer RMFs (*.rmf)|*.rmf"
607                                wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
608
609    if (SaveFileDialog.ShowModal()!=wxID_OK) return false;
610
611    LastUsedDir=SaveFileDialog.GetDirectory();
612
613
614    wxString Path=SaveFileDialog.GetPath();     // directory + filename
615
616    if (!wxFileName(Path).HasExt())
617    {
618        switch (SaveFileDialog.GetFilterIndex())
619        {
620            case  0: Path+=".cmap"; break;
621         // case  1: Path+=".map";  break;
622         // case  2: Path+=".rmf";  break;
623            default: Path+=".cmap"; break;
624        }
625    }
626
627    return OnSaveDocument(Path, false);
628}
629
630
631bool MapDocumentT::Save()
632{
633    if (m_FileName=="" || m_FileName=="New Map") return SaveAs();
634    if (m_FileName.Right(5).MakeLower()!=".cmap") return SaveAs();
635    if (!wxFileExists(m_FileName)) return SaveAs();
636    if (!wxFile::Access(m_FileName, wxFile::write)) return SaveAs();
637
638    return OnSaveDocument(m_FileName, false);
639}
640
641
642void MapDocumentT::GetAllElems(ArrayT<MapElementT*>& Elems) const
643{
644    for (unsigned long EntNr=0; EntNr<m_Entities.Size(); EntNr++)
645    {
646        MapEntityBaseT*               Ent=m_Entities[EntNr];
647        const ArrayT<MapPrimitiveT*>& Primitives=Ent->GetPrimitives();
648
649        // Add the entity itself...
650        Elems.PushBack(Ent);
651
652        // ... and all of its primitives.
653        for (unsigned long PrimNr=0; PrimNr<Primitives.Size(); PrimNr++)
654            Elems.PushBack(Primitives[PrimNr]);
655    }
656}
657
658
659bool MapDocumentT::IterateElems(IterationHandlerI& IH)
660{
661    for (unsigned long EntNr=0; EntNr<m_Entities.Size(); EntNr++)
662    {
663        MapEntityBaseT*               Ent=m_Entities[EntNr];
664        const ArrayT<MapPrimitiveT*>& Primitives=Ent->GetPrimitives();
665
666        // If not the world, have the entity itself handled...
667        if (EntNr>0)
668            if (!IH.Handle(Ent)) return false;
669
670        // ... then all of its primitives.
671        for (unsigned long PrimNr=0; PrimNr<Primitives.Size(); PrimNr++)
672            if (!IH.Handle(Primitives[PrimNr])) return false;
673    }
674
675    return true;
676}
677
678
679const EntityClassT* MapDocumentT::FindOrCreateUnknownClass(const wxString& Name, bool HasOrigin)
680{
681    wxASSERT(m_GameConfig->FindClass(Name)==NULL);
682
683    for (unsigned long ClassNr=0; ClassNr<m_UnknownEntClasses.Size(); ClassNr++)
684        if (m_UnknownEntClasses[ClassNr]->GetName()==Name)
685        {
686            // Somehow inform the user if this happens (it never should).
687            // if (!m_UnknownEntClasses[ClassNr]->HasOrigin() && HasOrigin) wxLog("...");
688
689            return m_UnknownEntClasses[ClassNr];
690        }
691
692    const EntityClassT* NewUnknownClass=new EntityClassT(*m_GameConfig, Name, HasOrigin);
693
694    m_UnknownEntClasses.PushBack(NewUnknownClass);
695    return NewUnknownClass;
696}
697
698
699void MapDocumentT::Insert(MapEntityT* Ent)
700{
701    wxASSERT(Ent!=NULL);
702    if (Ent==NULL) return;
703
704    // FIXME: Just drop this here, and add another check into the MapCheckDialogT.
705    Ent->CheckUniqueValues(*this);
706
707    // Should not have Ent already.
708    wxASSERT(m_Entities.Find(Ent)==-1);
709    m_Entities.PushBack(Ent);
710
711    // Insert all primitives of Ent and Ent itself into the BSP tree.
712    for (unsigned long PrimNr=0; PrimNr<Ent->GetPrimitives().Size(); PrimNr++)
713        m_BspTree->Insert(Ent->GetPrimitives()[PrimNr]);
714
715    m_BspTree->Insert(Ent);
716}
717
718
719void MapDocumentT::Insert(MapPrimitiveT* Prim, MapEntityBaseT* ParentEnt)
720{
721    wxASSERT(Prim!=NULL);
722    if (Prim==NULL) return;
723
724    if (ParentEnt==NULL) ParentEnt=m_Entities[0];
725    wxASSERT(m_Entities.Find(ParentEnt)>=0);
726
727    ParentEnt->AddPrim(Prim);
728    m_BspTree->Insert(Prim);
729}
730
731
732void MapDocumentT::Remove(MapElementT* Elem)
733{
734    wxASSERT(Elem!=NULL);
735    if (Elem==NULL) return;
736
737    MapPrimitiveT* Prim=dynamic_cast<MapPrimitiveT*>(Elem);
738    if (Prim)
739    {
740        MapEntityBaseT* ParentEnt=Prim->GetParent();
741
742        // The first assert is actually redundant in the second, but keep it for clarity.
743        wxASSERT(ParentEnt!=NULL);
744        wxASSERT(m_Entities.Find(ParentEnt)>=0);
745
746        if (ParentEnt!=NULL)
747        {
748            ParentEnt->RemovePrim(Prim);
749            wxASSERT(Prim->GetParent()==NULL);
750        }
751
752        m_BspTree->Remove(Prim);
753        return;
754    }
755
756    MapEntityT* Ent=dynamic_cast<MapEntityT*>(Elem);
757    if (Ent)
758    {
759        const int Index=m_Entities.Find(Ent);
760
761        // -1 means not found, 0 means the world. Both should not happen.
762        wxASSERT(Index>0);
763        if (Index>0)
764        {
765            // Keeping the order helps when map files are diff'ed or manually compared.
766            m_Entities.RemoveAtAndKeepOrder(Index);
767        }
768
769        // Remove all primitives of Ent and Ent itself from the BSP tree.
770        for (unsigned long PrimNr=0; PrimNr<Ent->GetPrimitives().Size(); PrimNr++)
771            m_BspTree->Remove(Ent->GetPrimitives()[PrimNr]);
772
773        m_BspTree->Remove(Ent);
774        return;
775    }
776
777    // We should never get here, because then Elem is neither a MapPrimitiveT nor a MapEntityT.
778    wxASSERT(false);
779}
780
781
782// Hmmm. This would make a wonderful member function...   (TODO!)
783static bool IsElemInBox(const MapElementT* Elem, const BoundingBox3fT& Box, bool InsideOnly, bool CenterOnly)
784{
785    const BoundingBox3fT ElemBB=Elem->GetBB();
786
787    if (CenterOnly) return Box.Contains(ElemBB.GetCenter());
788    if (InsideOnly) return Box.Contains(ElemBB.Min) && Box.Contains(ElemBB.Max);
789
790    return ElemBB.Intersects(Box);
791}
792
793
794ArrayT<MapElementT*> MapDocumentT::GetElementsIn(const BoundingBox3fT& Box, bool InsideOnly, bool CenterOnly) const
795{
796    ArrayT<MapElementT*> Result;
797
798    for (unsigned long EntNr=0; EntNr<m_Entities.Size(); EntNr++)
799    {
800        MapEntityBaseT*               Ent=m_Entities[EntNr];
801        const ArrayT<MapPrimitiveT*>& Primitives=Ent->GetPrimitives();
802
803        // If not the world, have the entity itself handled...
804        if (EntNr>0)
805            if (IsElemInBox(Ent, Box, InsideOnly, CenterOnly)) Result.PushBack(Ent);
806
807        // ... then all of its primitives.
808        for (unsigned long PrimNr=0; PrimNr<Primitives.Size(); PrimNr++)
809            if (IsElemInBox(Primitives[PrimNr], Box, InsideOnly, CenterOnly)) Result.PushBack(Primitives[PrimNr]);
810    }
811
812    return Result;
813}
814
815
816void MapDocumentT::SetSelection(const ArrayT<MapElementT*>& NewSelection)
817{
818    if (&m_Selection==&NewSelection) return;
819
820    // Clear the previous selection.
821    for (unsigned long SelNr=0; SelNr<m_Selection.Size(); SelNr++)
822        m_Selection[SelNr]->SetSelected(false);
823
824    m_Selection.Overwrite();
825
826    for (unsigned long SelNr=0; SelNr<NewSelection.Size(); SelNr++)
827    {
828        m_Selection.PushBack(NewSelection[SelNr]);
829        NewSelection[SelNr]->SetSelected();
830    }
831}
832
833
834void MapDocumentT::GetUsedMaterials(ArrayT<EditorMaterialI*>& UsedMaterials) const
835{
836    std::map<EditorMaterialI*, int> UsedMatMap;
837
838    for (unsigned long EntNr=0; EntNr<m_Entities.Size(); EntNr++)
839    {
840        const ArrayT<MapPrimitiveT*>& Primitives=m_Entities[EntNr]->GetPrimitives();
841
842        for (unsigned long PrimNr=0; PrimNr<Primitives.Size(); PrimNr++)
843        {
844            MapElementT* Prim=Primitives[PrimNr];
845
846            MapBrushT* Brush=dynamic_cast<MapBrushT*>(Prim);
847            if (Brush!=NULL)
848            {
849                EditorMaterialI* LastMat=NULL;
850
851                for (unsigned long FaceNr=0; FaceNr<Brush->GetFaces().Size(); FaceNr++)
852                {
853                    EditorMaterialI* Mat=Brush->GetFaces()[FaceNr].GetMaterial();
854
855                    if (Mat!=NULL && Mat!=LastMat)
856                    {
857                        UsedMatMap[Mat]=1;
858                        LastMat=Mat;
859                    }
860                }
861            }
862
863            MapBezierPatchT* BezierPatch=dynamic_cast<MapBezierPatchT*>(Prim);
864            if (BezierPatch!=NULL)
865            {
866                EditorMaterialI* Mat=BezierPatch->GetMaterial();
867
868                if (Mat!=NULL)
869                    UsedMatMap[Mat]=1;
870            }
871
872            MapTerrainT* Terrain=dynamic_cast<MapTerrainT*>(Prim);
873            if (Terrain!=NULL)
874            {
875                EditorMaterialI* Mat=Terrain->GetMaterial();
876
877                if (Mat!=NULL)
878                    UsedMatMap[Mat]=1;
879            }
880        }
881    }
882
883    // Copy the std::map<> into the ArrayT<>.
884    UsedMaterials.Overwrite();
885    for (std::map<EditorMaterialI*, int>::const_iterator It=UsedMatMap.begin(); It!=UsedMatMap.end(); ++It)
886        UsedMaterials.PushBack(It->first);
887}
888
889
890/**********************/
891/*** Event Handlers ***/
892/**********************/
893
894void MapDocumentT::OnEditUndoRedo(wxCommandEvent& CE)
895{
896    // The undo system doesn't keep track of selected faces, so clear the face selection just to be safe.
897    if (m_ChildFrame->GetToolManager().GetActiveToolType()==&ToolEditSurfaceT::TypeInfo)
898        m_ChildFrame->GetSurfacePropsDialog()->ClearSelection();
899
900    // Step forward or backward in the command history.
901    if (CE.GetId()==wxID_UNDO) m_History.Undo();
902                          else m_History.Redo();
903}
904
905
906void MapDocumentT::OnUpdateEditUndoRedo(wxUpdateUIEvent& UE)
907{
908    if (UE.GetId()==wxID_UNDO)
909    {
910        const CommandT* Cmd=m_History.GetUndoCommand();
911
912        UE.SetText(Cmd!=NULL ? "Undo "+Cmd->GetName()+"\tCtrl+Z" : "Cannot Undo\tCtrl+Z");
913        UE.Enable(Cmd!=NULL);
914    }
915    else
916    {
917        const CommandT* Cmd=m_History.GetRedoCommand();
918
919        UE.SetText(Cmd!=NULL ? "Redo "+Cmd->GetName()+"\tCtrl+Y" : "Cannot Redo\tCtrl+Y");
920        UE.Enable(Cmd!=NULL);
921    }
922}
923
924
925void MapDocumentT::OnEditCut(wxCommandEvent& CE)
926{
927    OnEditCopy(CE);
928    OnEditDelete(CE);
929}
930
931
932void MapDocumentT::OnEditCopy(wxCommandEvent& CE)
933{
934    wxBusyCursor BusyCursor;
935
936    // First delete the previous contents of the clipboard.
937    for (unsigned long SelNr=0; SelNr<s_Clipboard.Objects.Size(); SelNr++)
938        delete s_Clipboard.Objects[SelNr];
939
940    s_Clipboard.Objects.Clear();
941    s_Clipboard.OriginalCenter=GetMostRecentSelBB().GetCenter();
942
943    // Assign a copy of the current selection as the new clipboard contents.
944    for (unsigned long SelNr=0; SelNr<m_Selection.Size(); SelNr++)
945        s_Clipboard.Objects.PushBack(m_Selection[SelNr]->Clone());
946}
947
948
949void MapDocumentT::OnEditPaste(wxCommandEvent& CE)
950{
951    wxBusyCursor BusyCursor;
952
953    GetHistory().SubmitCommand(new CommandPasteT(*this, s_Clipboard.Objects, s_Clipboard.OriginalCenter, m_ChildFrame->GuessUserVisiblePoint()));
954
955    m_ChildFrame->GetToolManager().SetActiveTool(GetToolTIM().FindTypeInfoByName("ToolSelectionT"));
956}
957
958
959void MapDocumentT::OnEditPasteSpecial(wxCommandEvent& CE)
960{
961    BoundingBox3fT ClipboardBB;
962
963    for (unsigned long ElemNr=0; ElemNr<s_Clipboard.Objects.Size(); ElemNr++)
964        ClipboardBB.InsertValid(s_Clipboard.Objects[ElemNr]->GetBB());
965
966    if (s_Clipboard.Objects.Size()==0) return;
967    if (!ClipboardBB.IsInited()) return;
968
969    PasteSpecialDialogT PasteSpecialDialog(ClipboardBB);
970
971    if (PasteSpecialDialog.ShowModal()==wxID_CANCEL) return;
972
973    wxBusyCursor BusyCursor;
974
975    const Vector3fT Translation=Vector3fT(PasteSpecialDialog.TranslateX, PasteSpecialDialog.TranslateY, PasteSpecialDialog.TranslateZ);
976    const Vector3fT Rotation   =Vector3fT(PasteSpecialDialog.RotateX,    PasteSpecialDialog.RotateY,    PasteSpecialDialog.RotateZ);
977
978    CommandT* Command=new CommandPasteT(*this, s_Clipboard.Objects, s_Clipboard.OriginalCenter, m_ChildFrame->GuessUserVisiblePoint(),
979                                        Translation, Rotation, PasteSpecialDialog.NrOfCopies, PasteSpecialDialog.GroupCopies,
980                                        PasteSpecialDialog.CenterAtOriginal);
981
982    GetHistory().SubmitCommand(Command);
983
984    m_ChildFrame->GetToolManager().SetActiveTool(GetToolTIM().FindTypeInfoByName("ToolSelectionT"));
985}
986
987
988void MapDocumentT::OnEditDelete(wxCommandEvent& CE)
989{
990    // If the camera tool is the currently active tool, delete its active camera.
991    ToolCameraT* CameraTool=dynamic_cast<ToolCameraT*>(m_ChildFrame->GetToolManager().GetActiveTool());
992
993    if (CameraTool)
994    {
995        CameraTool->DeleteActiveCamera();
996        return;
997    }
998
999    if (m_Selection.Size()>0)
1000    {
1001        // Do the actual deletion of the selected elements.
1002        GetHistory().SubmitCommand(new CommandDeleteT(*this, m_Selection));
1003
1004        // If there are any empty groups (usually as a result from the deletion), purge them now.
1005        // We use an explicit command for deleting the groups (instead of putting everything into a macro command)
1006        // so that the user has the option to undo the purge (separately from the deletion) if he wishes.
1007        const ArrayT<GroupT*> EmptyGroups=GetAbandonedGroups();
1008
1009        if (EmptyGroups.Size()>0)
1010            GetHistory().SubmitCommand(new CommandDeleteGroupT(*this, EmptyGroups));
1011    }
1012}
1013
1014
1015void MapDocumentT::OnEditSelectNone(wxCommandEvent& CE)
1016{
1017    if (m_ChildFrame->GetToolManager().GetActiveToolType()!=&ToolEditSurfaceT::TypeInfo)
1018    {
1019        GetHistory().SubmitCommand(CommandSelectT::Clear(this));
1020    }
1021    else
1022    {
1023        m_ChildFrame->GetSurfacePropsDialog()->ClearSelection();
1024    }
1025}
1026
1027
1028void MapDocumentT::OnEditSelectAll(wxCommandEvent& CE)
1029{
1030    ArrayT<MapElementT*> NewSelection;
1031    GetAllElems(NewSelection);
1032
1033    // Remove the world entity at index 0.
1034    if (NewSelection.Size()>0)
1035    {
1036        wxASSERT(NewSelection[0]->GetType()==&MapWorldT::TypeInfo);
1037        NewSelection.RemoveAt(0);
1038    }
1039
1040    // Remove all invisible elements.
1041    for (unsigned long ElemNr=0; ElemNr<NewSelection.Size(); ElemNr++)
1042        if (!NewSelection[ElemNr]->IsVisible())
1043        {
1044            NewSelection.RemoveAt(ElemNr);
1045            ElemNr--;
1046        }
1047
1048    m_History.SubmitCommand(CommandSelectT::Add(this, NewSelection));
1049}
1050
1051
1052void MapDocumentT::OnUpdateSelectionApplyMaterial(wxUpdateUIEvent& UE)
1053{
1054    UE.Enable(m_ChildFrame->GetToolManager().GetActiveToolType()!=&ToolEditSurfaceT::TypeInfo);
1055}
1056
1057
1058void MapDocumentT::OnSelectionApplyMaterial(wxCommandEvent& CE)
1059{
1060    CommandT* Command=new CommandApplyMaterialT(*this, m_Selection, m_GameConfig->GetMatMan().GetDefaultMaterial());
1061
1062    GetHistory().SubmitCommand(Command);
1063}
1064
1065
1066void MapDocumentT::OnMapSnapToGrid(wxCommandEvent& CE)
1067{
1068    m_SnapToGrid=CE.IsChecked();
1069    UpdateAllObservers(UPDATE_GRID);
1070}
1071
1072
1073void MapDocumentT::OnMapToggleGrid2D(wxCommandEvent& CE)
1074{
1075    m_ShowGrid=CE.IsChecked();
1076    UpdateAllObservers(UPDATE_GRID);
1077}
1078
1079
1080void MapDocumentT::OnMapFinerGrid(wxCommandEvent& CE)
1081{
1082    const int NewGridSpacing=m_GridSpacing/2;
1083
1084    // Make sure that the new grid spacing is invertible: m_GridSpacing must not drop from 1 to 0,
1085    // and when m_GridSpacing is an odd number like 3 or 5, we cannot have a finer grid either.
1086    if (NewGridSpacing*2!=m_GridSpacing) return;
1087
1088    m_GridSpacing=NewGridSpacing;
1089
1090    UpdateAllObservers(UPDATE_GRID);
1091}
1092
1093
1094void MapDocumentT::OnMapCoarserGrid(wxCommandEvent& CE)
1095{
1096    if (m_GridSpacing>=512) return;
1097
1098    m_GridSpacing*=2;
1099    if (m_GridSpacing<1) m_GridSpacing=1;   // Just in case it was 0 before.
1100
1101    UpdateAllObservers(UPDATE_GRID);
1102}
1103
1104
1105void MapDocumentT::OnMapGotoPrimitive(wxCommandEvent& CE)
1106{
1107    GotoPrimitiveDialogT GotoPrimDialog;
1108
1109    if (GotoPrimDialog.ShowModal()!=wxID_OK) return;
1110
1111    if (GotoPrimDialog.m_EntityNumber>=int(m_Entities.Size()))
1112    {
1113        wxMessageBox("The entity with the given index number does not exist.", "Goto Primitive");
1114        return;
1115    }
1116
1117    if (GotoPrimDialog.m_PrimitiveNumber>=int(m_Entities[GotoPrimDialog.m_EntityNumber]->GetPrimitives().Size()))
1118    {
1119        wxMessageBox("The primitive with the given index number does not exist (in the specified entity).", "Goto Primitive");
1120        return;
1121    }
1122
1123    MapPrimitiveT* Prim=m_Entities[GotoPrimDialog.m_EntityNumber]->GetPrimitives()[GotoPrimDialog.m_PrimitiveNumber];
1124
1125    if (!Prim->IsVisible())
1126    {
1127        wxMessageBox("The primitive is currently invisible in group \""+Prim->GetGroup()->Name+"\".", "Goto Primitive");
1128        return;
1129    }
1130
1131    // The primitive was found and is visible, now select it and center the 2D views on it.
1132    m_History.SubmitCommand(CommandSelectT::Set(this, Prim));
1133    m_ChildFrame->All2DViews_Center(Prim->GetBB().GetCenter());
1134}
1135
1136
1137void MapDocumentT::OnMapShowInfo(wxCommandEvent& CE)
1138{
1139    MapInfoDialogT MapInfoDialog(*this);
1140    MapInfoDialog.ShowModal();
1141}
1142
1143
1144void MapDocumentT::OnMapCheckForProblems(wxCommandEvent& CE)
1145{
1146    MapCheckDialogT MapCheckDialog(NULL, *this);
1147    MapCheckDialog.ShowModal();
1148}
1149
1150
1151void MapDocumentT::OnMapProperties(wxCommandEvent& CE)
1152{
1153    // Select the worldspawn entity, then open the inspector dialog.
1154    m_History.SubmitCommand(CommandSelectT::Set(this, m_Entities[0]));
1155
1156    GetChildFrame()->GetInspectorDialog()->ChangePage(1);
1157    GetChildFrame()->ShowPane(GetChildFrame()->GetInspectorDialog());
1158}
1159
1160
1161void MapDocumentT::OnMapLoadPointFile(wxCommandEvent& CE)
1162{
1163    wxString PointFileName      =m_FileName.BeforeLast('.')+".pts";
1164    bool     LoadCustomPointFile=true;
1165
1166    if (PointFileName==".pts") PointFileName=m_FileName+".pts";
1167
1168    if (wxFileExists(PointFileName))
1169    {
1170        wxDateTime DT =wxFileName(PointFileName).GetModificationTime();
1171        wxString   Ask=wxString("The default pointfile is:\n")+
1172                       PointFileName+"\n"
1173                       "last modified on "+DT.FormatISODate()+", "+DT.FormatISOTime()+".\n\n"+
1174                       "Load the default pointfile?";
1175
1176        LoadCustomPointFile=(wxMessageBox(Ask, "Load default pointfile?", wxYES_NO | wxICON_QUESTION)==wxNO);
1177    }
1178
1179    if (LoadCustomPointFile)
1180    {
1181        PointFileName=wxFileSelector("Select Pointfile", "", PointFileName, ".pts", "Pointfile (*.pts)|*.pts", wxFD_OPEN | wxFD_FILE_MUST_EXIST /*, this (parent)*/);
1182        if (PointFileName.IsEmpty()) return;
1183    }
1184
1185
1186    // Create a new Lua state.
1187    lua_State* LuaState=lua_open();
1188
1189    try
1190    {
1191        if (LuaState==NULL) throw wxString("Couldn't open Lua state.");
1192        if (!wxFileExists(PointFileName)) throw wxString("The file does not exist.");
1193
1194        lua_pushcfunction(LuaState, luaopen_base);    lua_pushstring(LuaState, "");              lua_call(LuaState, 1, 0);  // Opens the basic library.
1195        lua_pushcfunction(LuaState, luaopen_package); lua_pushstring(LuaState, LUA_LOADLIBNAME); lua_call(LuaState, 1, 0);  // Opens the package library.
1196        lua_pushcfunction(LuaState, luaopen_table);   lua_pushstring(LuaState, LUA_TABLIBNAME);  lua_call(LuaState, 1, 0);  // Opens the table library.
1197        lua_pushcfunction(LuaState, luaopen_io);      lua_pushstring(LuaState, LUA_IOLIBNAME);   lua_call(LuaState, 1, 0);  // Opens the I/O library.
1198        lua_pushcfunction(LuaState, luaopen_os);      lua_pushstring(LuaState, LUA_OSLIBNAME);   lua_call(LuaState, 1, 0);  // Opens the OS library.
1199        lua_pushcfunction(LuaState, luaopen_string);  lua_pushstring(LuaState, LUA_STRLIBNAME);  lua_call(LuaState, 1, 0);  // Opens the string lib.
1200        lua_pushcfunction(LuaState, luaopen_math);    lua_pushstring(LuaState, LUA_MATHLIBNAME); lua_call(LuaState, 1, 0);  // Opens the math lib.
1201
1202        // Load and process the Lua program that defines the path.
1203        if (luaL_loadfile(LuaState, PointFileName.c_str())!=0 || lua_pcall(LuaState, 0, 0, 0)!=0)
1204            throw wxString("Couldn't load the file:\n")+lua_tostring(LuaState, -1);
1205
1206        // Read the points.
1207        wxASSERT(lua_gettop(LuaState)==0);
1208        m_PointFilePoints.Clear();
1209        lua_getglobal(LuaState, "Points");
1210        const size_t NumPoints=lua_objlen(LuaState, 1);
1211
1212        for (size_t PointNr=1; PointNr<=NumPoints; PointNr++)
1213        {
1214            // Put the table of the current point onto the stack (at index 2).
1215            lua_rawgeti(LuaState, 1, PointNr);
1216            m_PointFilePoints.PushBackEmpty();
1217
1218            lua_rawgeti(LuaState, 2, 1);
1219            m_PointFilePoints[PointNr-1].Time=lua_tonumber(LuaState, 3);
1220            lua_pop(LuaState, 1);
1221
1222            for (size_t i=2; i<=4; i++)
1223            {
1224                lua_rawgeti(LuaState, 2, i);
1225                m_PointFilePoints[PointNr-1].Pos[i-2]=lua_tonumber(LuaState, 3);
1226                lua_pop(LuaState, 1);
1227            }
1228
1229            lua_rawgeti(LuaState, 2, 5);
1230            m_PointFilePoints[PointNr-1].Heading=lua_tonumber(LuaState, 3);
1231            lua_pop(LuaState, 1);
1232
1233            lua_rawgeti(LuaState, 2, 6);
1234            const char* InfoStr=lua_tostring(LuaState, 3);
1235            m_PointFilePoints[PointNr-1].Info=InfoStr ? InfoStr : "";
1236            lua_pop(LuaState, 1);
1237
1238            // Remove the processed points table from the stack again.
1239            lua_pop(LuaState, 1);
1240        }
1241
1242        wxASSERT(lua_gettop(LuaState)==1);
1243        lua_pop(LuaState, 1);
1244
1245        // Read the colors.
1246        wxASSERT(lua_gettop(LuaState)==0);
1247        m_PointFileColors.Clear();
1248        lua_getglobal(LuaState, "Colors");
1249        const size_t NumColors=lua_objlen(LuaState, 1);
1250
1251        for (size_t ColorNr=1; ColorNr<=NumColors; ColorNr++)
1252        {
1253            // Put the string with the current color onto the stack (at index 2).
1254            lua_rawgeti(LuaState, 1, ColorNr);
1255
1256            const char* ColorName=lua_tostring(LuaState, 2);
1257            m_PointFileColors.PushBack(ColorName ? wxColour(ColorName) : wxNullColour);
1258
1259            // Remove the processed color from the stack again.
1260            lua_pop(LuaState, 1);
1261        }
1262
1263        // If there are profound problems with the colors, fix them.
1264        while (m_PointFileColors.Size()<4) m_PointFileColors.PushBack(wxNullColour);
1265        if (!m_PointFileColors[1].IsOk()) m_PointFileColors[1]=(*wxRED);
1266
1267        wxASSERT(lua_gettop(LuaState)==1);
1268        lua_pop(LuaState, 1);
1269
1270        UpdateAllObservers(UPDATE_POINTFILE);
1271    }
1272    catch (const wxString& msg)
1273    {
1274        wxMessageBox(msg, "Error loading "+PointFileName, wxOK | wxICON_ERROR);
1275    }
1276
1277    // Close the Lua state.
1278    if (LuaState) lua_close(LuaState);
1279}
1280
1281
1282void MapDocumentT::OnMapUnloadPointFile(wxCommandEvent& CE)
1283{
1284    m_PointFilePoints.Clear();
1285    m_PointFileColors.Clear();
1286
1287    UpdateAllObservers(UPDATE_POINTFILE);
1288}
1289
1290
1291void MapDocumentT::OnViewShowEntityInfo(wxCommandEvent& CE)
1292{
1293    Options.view2d.ShowEntityInfo=!Options.view2d.ShowEntityInfo;
1294    UpdateAllObservers(UPDATE_GLOBALOPTIONS);
1295}
1296
1297
1298void MapDocumentT::OnViewShowEntityTargets(wxCommandEvent& CE)
1299{
1300    Options.view2d.ShowEntityTargets=!Options.view2d.ShowEntityTargets;
1301    UpdateAllObservers(UPDATE_GLOBALOPTIONS);
1302}
1303
1304
1305void MapDocumentT::OnViewHideSelectedObjects(wxCommandEvent& CE)
1306{
1307    if (m_Selection.Size()==0) return;
1308
1309    // Warn/inform the user when he tries to group elements with "mixed affiliation"?  (grouped or not, in entity or not)
1310    // e.g. like "From N selected map elements, there are X in the world, Y in entities and Z in groups."
1311    // This is pretty difficult to implement though, as there are many cases to distinguish, and might confuse the user.
1312    // Alternatively, just warn when grouping here *partially* breaks an existing group??
1313    ArrayT<CommandT*> SubCommands;
1314
1315    // 1. Create a new group.
1316    CommandNewGroupT* CmdNewGroup=new CommandNewGroupT(*this, wxString::Format("%lu item%s", m_Selection.Size(), m_Selection.Size()==1 ? "" : "s"));
1317    GroupT*              NewGroup=CmdNewGroup->GetGroup();
1318
1319    CmdNewGroup->Do();
1320    SubCommands.PushBack(CmdNewGroup);
1321
1322    // 2. Put the m_Selection into the new group.
1323    CommandAssignGroupT* CmdAssign=new CommandAssignGroupT(*this, m_Selection, NewGroup);
1324
1325    CmdAssign->Do();
1326    SubCommands.PushBack(CmdAssign);
1327
1328    // 3. Hide the new group (set the visibility to false).
1329    if (CE.GetId()==ChildFrameT::ID_MENU_VIEW_HIDE_SELECTED_OBJECTS)
1330    {
1331        CommandGroupSetVisibilityT* CmdVis=new CommandGroupSetVisibilityT(*this, NewGroup, false);
1332
1333        CmdVis->Do();
1334        SubCommands.PushBack(CmdVis);
1335    }
1336
1337    // 4. Purge all groups that became empty (abandoned) by the (re-)assignment of map elements in step 2.
1338    CommandDeleteGroupT* CmdPurgeGroups=new CommandDeleteGroupT(*this, GetAbandonedGroups());
1339
1340    CmdPurgeGroups->Do();
1341    SubCommands.PushBack(CmdPurgeGroups);
1342
1343    // 5. Submit the composite macro command.
1344    m_History.SubmitCommand(new CommandMacroT(SubCommands, "Hide "+NewGroup->Name));
1345}
1346
1347
1348/// An iteration handler that collects all map elements that are unselected (and not in a group).
1349class CollectUnselectedT : public IterationHandlerI
1350{
1351    public:
1352
1353    CollectUnselectedT()
1354        : m_Unselected()
1355    {
1356    }
1357
1358    bool Handle(MapElementT* Child)
1359    {
1360        if (Child->IsSelected() || Child->GetGroup()) return true;
1361
1362        m_Unselected.PushBack(Child);
1363        return true;
1364    }
1365
1366    const ArrayT<MapElementT*>& GetUnselected() const
1367    {
1368        return m_Unselected;
1369    }
1370
1371
1372    private:
1373
1374    ArrayT<MapElementT*> m_Unselected;
1375};
1376
1377
1378void MapDocumentT::OnViewHideUnselectedObjects(wxCommandEvent& CE)
1379{
1380    // Find all unselected map elements that are not in a group already.
1381    CollectUnselectedT CollectUnselectedCallBack;
1382
1383    IterateElems(CollectUnselectedCallBack);
1384
1385    const ArrayT<MapElementT*>& HideElems=CollectUnselectedCallBack.GetUnselected();
1386
1387    // If no relevant elements were found, do nothing.
1388    if (HideElems.Size()==0) return;
1389
1390
1391    ArrayT<CommandT*> SubCommands;
1392
1393    // 1. Create a new group.
1394    CommandNewGroupT* CmdNewGroup=new CommandNewGroupT(*this, wxString::Format("%lu item%s", HideElems.Size(), HideElems.Size()==1 ? "" : "s"));
1395    GroupT*              NewGroup=CmdNewGroup->GetGroup();
1396
1397    CmdNewGroup->Do();
1398    SubCommands.PushBack(CmdNewGroup);
1399
1400    // 2. Put the HideElems into the new group.
1401    CommandAssignGroupT* CmdAssign=new CommandAssignGroupT(*this, HideElems, NewGroup);
1402
1403    CmdAssign->Do();
1404    SubCommands.PushBack(CmdAssign);
1405
1406    // 3. Hide the new group (set the visibility to false).
1407    CommandGroupSetVisibilityT* CmdVis=new CommandGroupSetVisibilityT(*this, NewGroup, false);
1408
1409    CmdVis->Do();
1410    SubCommands.PushBack(CmdVis);
1411
1412    // 4. Purge all groups that became empty (abandoned) by the (re-)assignment of map elements in step 2.
1413    CommandDeleteGroupT* CmdPurgeGroups=new CommandDeleteGroupT(*this, GetAbandonedGroups());
1414
1415    CmdPurgeGroups->Do();
1416    SubCommands.PushBack(CmdPurgeGroups);
1417
1418    // 5. Submit the composite macro command.
1419    m_History.SubmitCommand(new CommandMacroT(SubCommands, "Hide "+NewGroup->Name));
1420}
1421
1422
1423void MapDocumentT::OnViewShowHiddenObjects(wxCommandEvent& CE)
1424{
1425    ArrayT<GroupT*> HiddenGroups;
1426
1427    for (unsigned long GroupNr=0; GroupNr<m_Groups.Size(); GroupNr++)
1428        if (!m_Groups[GroupNr]->IsVisible)
1429            HiddenGroups.PushBack(m_Groups[GroupNr]);
1430
1431    if (HiddenGroups.Size()==0)
1432    {
1433        // wxMessageBox("All groups are already visible.");    // Should be a status bar update.
1434        return;
1435    }
1436
1437    if (HiddenGroups.Size()==1)
1438    {
1439        m_History.SubmitCommand(new CommandGroupSetVisibilityT(*this, HiddenGroups[0], true /*NewVis*/));
1440        return;
1441    }
1442
1443    ArrayT<CommandT*> SubCommands;
1444
1445    for (unsigned long GroupNr=0; GroupNr<HiddenGroups.Size(); GroupNr++)
1446        SubCommands.PushBack(new CommandGroupSetVisibilityT(*this, HiddenGroups[GroupNr], true /*NewVis*/));
1447
1448    m_History.SubmitCommand(new CommandMacroT(SubCommands, "Show all groups"));
1449}
1450
1451
1452void MapDocumentT::OnToolsCarve(wxCommandEvent& CE)
1453{
1454    ArrayT<const MapBrushT*> Carvers;
1455
1456    for (unsigned long SelNr=0; SelNr<GetSelection().Size(); SelNr++)
1457    {
1458        MapBrushT* Brush=dynamic_cast<MapBrushT*>(GetSelection()[SelNr]);
1459
1460        if (Brush)
1461        {
1462            Carvers.PushBack(Brush);
1463        }
1464    }
1465
1466    if (Carvers.Size()==0) return;
1467
1468    // Do the actual carve.
1469    GetHistory().SubmitCommand(new CommandCarveT(*this, Carvers));
1470
1471    // If there are any empty groups (usually as a result from an implicit deletion by the carve), purge them now.
1472    // We use an explicit command for deleting the groups (instead of putting everything into a macro command)
1473    // so that the user has the option to undo the purge (separately from the deletion) if he wishes.
1474    const ArrayT<GroupT*> EmptyGroups=GetAbandonedGroups();
1475
1476    if (EmptyGroups.Size()>0)
1477        GetHistory().SubmitCommand(new CommandDeleteGroupT(*this, EmptyGroups));
1478}
1479
1480
1481void MapDocumentT::OnToolsHollow(wxCommandEvent& CE)
1482{
1483    unsigned long BrushesInSelection=0;
1484
1485    for (unsigned long SelNr=0; SelNr<m_Selection.Size(); SelNr++)
1486        if (m_Selection[SelNr]->GetType()==&MapBrushT::TypeInfo)
1487            BrushesInSelection++;
1488
1489    if (BrushesInSelection==0)
1490    {
1491        wxMessageBox("Only brushes can be made hollow,\nbut there are currently no brushes selected.", "No brushes selected.", wxOK | wxICON_INFORMATION);
1492        return;
1493    }
1494
1495    // Confirm the operation if there is more than one brush selected.
1496    if (BrushesInSelection>1)
1497        if (wxMessageBox("Do you want to make each of the selected brushes hollow?", "Multiple brushes selected", wxYES_NO | wxICON_QUESTION)==wxNO)
1498            return;
1499
1500
1501    // Prompt for the wall thickness, which is kept as the default value for the next call.
1502    static int WallWidth=32;
1503
1504    const int NewWidth=wxGetNumberFromUser("Please enter the thickness of the walls for hollowing the brush(es).\n(Negative numbers to hollow outward do currently not work. Sorry.)", "Wall width:", "Hollow Brushes", WallWidth, 2, 1024);
1505
1506    if (NewWidth==-1)
1507    {
1508        wxMessageBox("You either cancelled the previous dialog,\nor the width of the walls was not between 2 and 1024.\nPlease try again.", "Cannot hollow the brushes");
1509        return;
1510    }
1511
1512    WallWidth=NewWidth;
1513
1514    // Do the actual hollowing.
1515    // Note that there is no need to ever purge abandoned groups here: hollowing might replace, but never lessen their members.
1516    GetHistory().SubmitCommand(new CommandMakeHollowT(*this, WallWidth, m_Selection));
1517}
1518
1519
1520void MapDocumentT::OnToolsAssignPrimToEntity(wxCommandEvent& CE)
1521{
1522    ToolT*           NewEntityTool=m_ChildFrame->GetToolManager().GetTool(*GetToolTIM().FindTypeInfoByName("ToolNewEntityT")); if (!NewEntityTool) return;
1523    OptionsBar_NewEntityToolT* Bar=dynamic_cast<OptionsBar_NewEntityToolT*>(NewEntityTool->GetOptionsBar()); if (!Bar) return;
1524
1525    ArrayT<MapEntityT*>    SelEntities;     // All entities   that are in the selection.
1526    ArrayT<MapPrimitiveT*> SelPrimitives;   // All primitives that are in the selection.
1527
1528    for (unsigned long SelNr=0; SelNr<m_Selection.Size(); SelNr++)
1529    {
1530        MapElementT* Elem=m_Selection[SelNr];
1531
1532        if (MapEntityT::TypeInfo.HierarchyHas(Elem->GetType()))
1533        {
1534            SelEntities.PushBack(static_cast<MapEntityT*>(Elem));
1535        }
1536        else if (MapPrimitiveT::TypeInfo.HierarchyHas(Elem->GetType()))
1537        {
1538            SelPrimitives.PushBack(static_cast<MapPrimitiveT*>(Elem));
1539        }
1540    }
1541
1542    // If there were no primitives among the selected map elements, quit here.
1543    if (SelPrimitives.Size()==0)
1544    {
1545        wxMessageBox("Nothing is selected that could be tied to an entity.");
1546        return;
1547    }
1548
1549    // The details on how to proceed depend on the number of entities in the selection.
1550    if (SelEntities.Size()==0)
1551    {
1552        // Don't query the user, but create (below) a new entity of default class.
1553        SelEntities.PushBack(NULL);
1554    }
1555    else if (SelEntities.Size()==1)
1556    {
1557        const int Result=wxMessageBox("Would you like to keep and re-use the selected \""+SelEntities[0]->GetClass()->GetName()+"\" entity?\n\n"
1558            "If you answer 'No', a new \""+Bar->m_SolidEntityChoice->GetStringSelection()+"\" entity will be created\n"
1559            "and all selected map elements added to it, including those of the of the selected entity.", "Re-use entity?", wxYES_NO | wxCANCEL | wxICON_QUESTION);
1560
1561        switch (Result)
1562        {
1563            case wxYES: break;                      // Do nothing, SelEntities[0] is ok as-is.
1564            case wxNO:  SelEntities[0]=NULL; break; // The user wants a new default entity, not this one.
1565            default:    return;                     // User pressed Cancel.
1566        }
1567    }
1568    else
1569    {
1570        wxArrayString EntityNames;
1571
1572        for (unsigned long EntNr=0; EntNr<SelEntities.Size(); EntNr++)
1573            EntityNames.Add(SelEntities[EntNr]->GetClass()->GetName()+"   ("+SelEntities[EntNr]->GetClass()->GetDescription()+")");
1574
1575        const int Choice=wxGetSingleChoiceIndex("There are multiple entities in the selection.\n"
1576                                                "Please choose the one that you want to keep and that will receive all the map elements of the others.\n"
1577                                                "Or press Cancel, select only one entity, and start over.",
1578                                                "Please select the destination entity",
1579                                                EntityNames);
1580
1581        if (Choice<0) return;                 // The user pressed Cancel.
1582        SelEntities[0]=SelEntities[Choice];   // Make sure that the chosen entity is at index 0.
1583    }
1584
1585    if (SelEntities[0]!=NULL)
1586    {
1587        GetHistory().SubmitCommand(new CommandAssignPrimToEntT(*this, SelPrimitives, SelEntities[0]));
1588    }
1589    else
1590    {
1591        const EntityClassT* NewEntityClass=m_GameConfig->FindClass(Bar->m_SolidEntityChoice->GetStringSelection());
1592        if (NewEntityClass==NULL) return;
1593
1594        ArrayT<CommandT*> SubCommands;
1595
1596        // 1. Create a new entity.
1597        CommandNewEntityT* CmdNewEnt=new CommandNewEntityT(*this, NewEntityClass, SnapToGrid(GetMostRecentSelBB().GetCenter(), false /*Toggle*/, -1 /*AxisNoSnap*/));
1598
1599        CmdNewEnt->Do();
1600        SubCommands.PushBack(CmdNewEnt);
1601
1602        // 2. Assign the primitives to the new entity.
1603        CommandAssignPrimToEntT* CmdAssignToEnt=new CommandAssignPrimToEntT(*this, SelPrimitives, CmdNewEnt->GetEntity());
1604
1605        CmdAssignToEnt->Do();
1606        SubCommands.PushBack(CmdAssignToEnt);
1607
1608        // 3. Submit the composite macro command.
1609        GetHistory().SubmitCommand(new CommandMacroT(SubCommands, "tie to new entity"));
1610    }
1611
1612    // This is very rare - only fires when a parent entity became empty, thus implicitly deleted, and was the last element in its group:
1613    // If there are any empty groups (usually as a result from the deletion), purge them now.
1614    // We use an explicit command for deleting the groups (instead of putting everything into a macro command)
1615    // so that the user has the option to undo the purge (separately from the deletion) if he wishes.
1616    {
1617        const ArrayT<GroupT*> EmptyGroups=GetAbandonedGroups();
1618
1619        if (EmptyGroups.Size()>0)
1620            GetHistory().SubmitCommand(new CommandDeleteGroupT(*this, EmptyGroups));
1621    }
1622
1623    m_ChildFrame->GetToolManager().SetActiveTool(GetToolTIM().FindTypeInfoByName("ToolSelectionT"));
1624    m_ChildFrame->GetInspectorDialog()->ChangePage(1);
1625    m_ChildFrame->ShowPane(m_ChildFrame->GetInspectorDialog());
1626}
1627
1628
1629void MapDocumentT::OnToolsAssignPrimToWorld(wxCommandEvent& CE)
1630{
1631    ArrayT<MapPrimitiveT*> SelPrimitives;   // All primitives that are in the selection.
1632
1633    for (unsigned long SelNr=0; SelNr<m_Selection.Size(); SelNr++)
1634    {
1635        MapPrimitiveT* Prim=dynamic_cast<MapPrimitiveT*>(m_Selection[SelNr]);
1636
1637        if (Prim)
1638            SelPrimitives.PushBack(Prim);
1639    }
1640
1641    // If there were no primitives among the selected map elements, quit here.
1642    if (SelPrimitives.Size()==0) return;
1643
1644    GetHistory().SubmitCommand(new CommandAssignPrimToEntT(*this, SelPrimitives, m_Entities[0]));
1645
1646    // This is very rare - only fires when a parent entity became empty, thus implicitly deleted, and was the last element in its group:
1647    // If there are any empty groups (usually as a result from the deletion), purge them now.
1648    // We use an explicit command for deleting the groups (instead of putting everything into a macro command)
1649    // so that the user has the option to undo the purge (separately from the deletion) if he wishes.
1650    {
1651        const ArrayT<GroupT*> EmptyGroups=GetAbandonedGroups();
1652
1653        if (EmptyGroups.Size()>0)
1654            GetHistory().SubmitCommand(new CommandDeleteGroupT(*this, EmptyGroups));
1655    }
1656
1657    m_ChildFrame->GetToolManager().SetActiveTool(GetToolTIM().FindTypeInfoByName("ToolSelectionT"));
1658}
1659
1660
1661void MapDocumentT::OnToolsReplaceMaterials(wxCommandEvent& CE)
1662{
1663    ReplaceMaterialsDialogT ReplaceMatsDlg(GetSelection().Size()>0, *this, m_GameConfig->GetMatMan().GetDefaultMaterial()->GetName());
1664    ReplaceMatsDlg.ShowModal();
1665}
1666
1667
1668void MapDocumentT::OnToolsMaterialLock(wxCommandEvent& CE)
1669{
1670    wxASSERT(m_ChildFrame!=NULL);
1671
1672    Options.general.LockingTextures=!Options.general.LockingTextures;
1673
1674    m_ChildFrame->SetStatusText(Options.general.LockingTextures ? "Texture locking on" : "Texture locking off", ChildFrameT::SBP_MENU_HELP);
1675}
1676
1677
1678void MapDocumentT::OnToolsSnapSelectionToGrid(wxCommandEvent& CE)
1679{
1680    if (!GetSelection().Size()) return;
1681
1682    CommandT* Command=new CommandSnapToGridT(*this, m_Selection);
1683
1684    GetHistory().SubmitCommand(Command);
1685}
1686
1687
1688void MapDocumentT::OnToolsTransform(wxCommandEvent& CE)
1689{
1690    if (m_Selection.Size()==0)
1691    {
1692        wxMessageBox("Well, nothing is selected,\nso there is nothing to transform.");
1693        return;
1694    }
1695
1696    TransformDialogT TransDlg;
1697
1698    if (TransDlg.ShowModal()==wxID_OK)
1699    {
1700        CommandTransformT::TransModeT Mode =CommandTransformT::MODE_TRANSLATE;
1701        Vector3fT                     Value=TransDlg.m_Value.AsVectorOfFloat();
1702        Vector3fT                     RefPoint;
1703
1704        // Set the proper transformation mode.
1705        switch (TransDlg.m_Mode)
1706        {
1707            case 1: Mode=CommandTransformT::MODE_SCALE;  break;
1708            case 2: Mode=CommandTransformT::MODE_ROTATE; break;
1709        }
1710
1711        // Check for identity transformations: 1.0 scales, 0.0 translations or 0.0 rotations.
1712        if (Mode==CommandTransformT::MODE_SCALE)
1713        {
1714            if (fabs(Value.x)<0.001f) Value.x=1.0f;
1715            if (fabs(Value.y)<0.001f) Value.y=1.0f;
1716            if (fabs(Value.z)<0.001f) Value.z=1.0f;
1717
1718            if (Value.x==1.0f && Value.y==1.0f && Value.z==1.0f) return;
1719        }
1720        else
1721        {
1722            // Translation or rotation.
1723            if (Value.x==0.0f && Value.y==0.0f && Value.z==0.0f) return;
1724        }
1725
1726        // For scales and rotations, set the proper reference point.
1727        if (Mode!=CommandTransformT::MODE_TRANSLATE)
1728        {
1729            // Compute the center of the selected elements.
1730            RefPoint=GetMostRecentSelBB().GetCenter();
1731        }
1732
1733        GetHistory().SubmitCommand(
1734            new CommandTransformT(*this, m_Selection, Mode, RefPoint, Value, false /*don't clone*/));
1735    }
1736}
1737
1738
1739void MapDocumentT::OnToolsAlign(wxCommandEvent& CE)
1740{
1741    wxASSERT(m_ChildFrame!=NULL);
1742
1743    const ArrayT<ViewWindowT*>& ViewWindows=m_ChildFrame->GetViewWindows();
1744
1745    if (ViewWindows.Size()==0)
1746    {
1747        wxMessageBox("I'm sorry, but I was not able to determine the most recently used view for this operation.\n"
1748                     "One view however must be active, so that I precisely know how to perform the operation.\n\n"
1749                     "You can activate a view by simply moving the mouse over it.\n"
1750                     "The last view that was touched by the mouse is taken as the active view for the operation.");
1751        return;
1752    }
1753
1754    GetHistory().SubmitCommand(
1755        new CommandAlignT(*this, m_Selection, ViewWindows[0]->GetAxesInfo(), GetMostRecentSelBB(), CE.GetId()));
1756}
1757
1758
1759void MapDocumentT::OnToolsMirror(wxCommandEvent& CE)
1760{
1761    wxASSERT(m_ChildFrame!=NULL);
1762
1763    const ArrayT<ViewWindowT*>& ViewWindows=m_ChildFrame->GetViewWindows();
1764
1765    if (ViewWindows.Size()==0)
1766    {
1767        wxMessageBox("I'm sorry, but I was not able to determine the most recently used view for this operation.\n"
1768                     "One view however must be active, so that I precisely know how to perform the operation.\n\n"
1769                     "You can activate a view by simply moving the mouse over it.\n"
1770                     "The last view that was touched by the mouse is taken as the active view for the operation.");
1771        return;
1772    }
1773
1774    if (!m_Selection.Size()) return;
1775
1776    const AxesInfoT    AxesInfo  =ViewWindows[0]->GetAxesInfo();
1777    const unsigned int NormalAxis=(CE.GetId()==ChildFrameT::ID_MENU_TOOLS_MIRROR_HOR) ? AxesInfo.HorzAxis : AxesInfo.VertAxis;
1778    const float        Dist      =GetMostRecentSelBB().GetCenter()[NormalAxis];
1779
1780    GetHistory().SubmitCommand(new CommandMirrorT(*this, m_Selection, NormalAxis, Dist));
1781}
1782
1783
1784void MapDocumentT::OnUpdateToolsMaterialLock(wxUpdateUIEvent& UE)
1785{
1786    UE.Check(Options.general.LockingTextures);
1787}
1788
1789
1790void MapDocumentT::OnUpdateEditPasteSpecial(wxUpdateUIEvent& UE)
1791{
1792    UE.Enable(s_Clipboard.Objects.Size()>0 && m_ChildFrame->GetToolManager().GetActiveToolType()!=&ToolEditSurfaceT::TypeInfo);
1793}
1794
1795
1796void MapDocumentT::OnUpdateViewShowEntityInfo(wxUpdateUIEvent& UE)
1797{
1798    UE.Check(Options.view2d.ShowEntityInfo);
1799}
1800
1801
1802void MapDocumentT::OnUpdateViewShowEntityTargets(wxUpdateUIEvent& UE)
1803{
1804    UE.Check(Options.view2d.ShowEntityTargets);
1805}
1806
1807
1808void MapDocumentT::OnUpdateEditCutCopyDelete(wxUpdateUIEvent& UE)
1809{
1810    UE.Enable(m_ChildFrame->GetToolManager().GetActiveToolType()!=&ToolEditSurfaceT::TypeInfo);
1811}
1812
1813
1814float MapDocumentT::SnapToGrid(float f, bool Toggle) const
1815{
1816    // "!=" implements "xor" for bools.
1817    const float GridSpacing=(m_SnapToGrid!=Toggle) ? m_GridSpacing : 1.0f;
1818
1819    return cf::math::round(f/GridSpacing) * GridSpacing;
1820}
1821
1822
1823Vector3fT MapDocumentT::SnapToGrid(const Vector3fT& Pos, bool Toggle, int AxisNoSnap) const
1824{
1825    Vector3fT SnappedPos=Pos;
1826
1827    for (int AxisNr=0; AxisNr<3; AxisNr++)
1828        if (AxisNoSnap!=AxisNr) SnappedPos[AxisNr]=SnapToGrid(Pos[AxisNr], Toggle);
1829
1830    return SnappedPos;
1831}
1832
1833
1834const BoundingBox3fT& MapDocumentT::GetMostRecentSelBB() const
1835{
1836    // Must re-determine the bounding-box each time anew, because e.g. a transformation may have caused
1837    // it to change even if the selection itself (the set of selected elements) did not change.
1838    BoundingBox3fT SelBB;
1839
1840    for (unsigned long SelNr=0; SelNr<m_Selection.Size(); SelNr++)
1841        SelBB.InsertValid(m_Selection[SelNr]->GetBB());
1842
1843    if (SelBB.IsInited())
1844    {
1845        // This is the key: Observe that m_SelectionBB is overwritten only if SelBB is inited!
1846        // Reversely, if the selection is empty or somehow doesn't yield an inited SelBB,
1847        // the previous bounding-box is kept!
1848        m_SelectionBB=SelBB;
1849    }
1850
1851    return m_SelectionBB;
1852}
1853
1854
1855ArrayT<GroupT*> MapDocumentT::GetAbandonedGroups() const
1856{
1857    ArrayT<GroupT*> EmptyGroups;
1858
1859    for (unsigned long GroupNr=0; GroupNr<m_Groups.Size(); GroupNr++)
1860    {
1861        bool IsEmpty=true;
1862
1863        for (unsigned long EntNr=0; EntNr<m_Entities.Size(); EntNr++)
1864        {
1865            // Check the entity first...
1866            const MapEntityBaseT* Ent=m_Entities[EntNr];
1867
1868            if (Ent->GetGroup()==m_Groups[GroupNr])
1869            {
1870                IsEmpty=false;
1871                break;
1872            }
1873
1874            /// ... then all its primitives.
1875            const ArrayT<MapPrimitiveT*>& Primitives=Ent->GetPrimitives();
1876
1877            for (unsigned long PrimNr=0; PrimNr<Primitives.Size(); PrimNr++)
1878            {
1879                if (Primitives[PrimNr]->GetGroup()==m_Groups[GroupNr])
1880                {
1881                    IsEmpty=false;
1882                    break;
1883                }
1884            }
1885
1886            if (!IsEmpty) break;
1887        }
1888
1889        if (IsEmpty) EmptyGroups.PushBack(m_Groups[GroupNr]);
1890    }
1891
1892    return EmptyGroups;
1893}
Note: See TracBrowser for help on using the browser.