1 //------------------------------------------------------------------------
2 // LEVEL CUT 'N' PASTE
3 //------------------------------------------------------------------------
4 //
5 // Eureka DOOM Editor
6 //
7 // Copyright (C) 2009-2019 Andrew Apted
8 //
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License
11 // as published by the Free Software Foundation; either version 2
12 // of the License, or (at your option) any later version.
13 //
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU General Public License for more details.
18 //
19 //------------------------------------------------------------------------
20
21 #include "main.h"
22
23 #include <map>
24
25 #include "e_basis.h"
26 #include "e_cutpaste.h"
27 #include "e_hover.h"
28 #include "e_linedef.h"
29 #include "e_main.h"
30 #include "e_sector.h"
31 #include "e_vertex.h"
32 #include "m_game.h"
33 #include "e_objects.h"
34 #include "r_grid.h"
35 #include "r_render.h"
36 #include "w_rawdef.h"
37
38 #include "ui_window.h"
39
40
41 #define INVALID_SECTOR (-999999)
42
43
44 class clipboard_data_c
45 {
46 public:
47 // the mode used when the objects were Copied
48 obj_type_e mode;
49
50 // NOTE:
51 //
52 // The references here normally refer to other objects in here
53 // (as though this was a little self-contained map). For example,
54 // the LineDef::right field is an index into sides[].
55 //
56 // The exception is sector references in the SideDefs. These
57 // values refer to the real map when >= 0, and refer to local
58 // sectors when < 0 (negate and add 1).
59 //
60 // That's because we generally want a copy'n'pasted piece of the
61 // map (linedefs or sectors) to "fit in" with nearby sections of
62 // the map.
63
64 bool uses_real_sectors;
65
66 std::vector<Thing *> things;
67 std::vector<Vertex *> verts;
68 std::vector<Sector *> sectors;
69 std::vector<SideDef *> sides;
70 std::vector<LineDef *> lines;
71
72 public:
clipboard_data_c(obj_type_e _mode)73 clipboard_data_c(obj_type_e _mode) :
74 mode(_mode), uses_real_sectors(false),
75 things(), verts(), sectors(),
76 sides(), lines()
77 {
78 if (mode == OBJ_LINEDEFS || mode == OBJ_SECTORS)
79 uses_real_sectors = true;
80 }
81
~clipboard_data_c()82 ~clipboard_data_c()
83 {
84 unsigned int i;
85
86 for (i = 0 ; i < things.size() ; i++) delete things[i];
87 for (i = 0 ; i < verts.size() ; i++) delete verts[i];
88 for (i = 0 ; i < sectors.size() ; i++) delete sectors[i];
89 for (i = 0 ; i < sides.size() ; i++) delete sides[i];
90 for (i = 0 ; i < lines.size() ; i++) delete lines[i];
91 }
92
TotalSize() const93 int TotalSize() const
94 {
95 size_t num = things.size() + verts.size() + sectors.size() + sides.size() + lines.size();
96
97 return (int)num;
98 }
99
Paste_BA_Message() const100 void Paste_BA_Message() const
101 {
102 size_t t = things.size();
103 size_t v = verts.size();
104 size_t s = sectors.size();
105 size_t l = lines.size();
106
107 if (s > 0)
108 {
109 const char *name = (s == 1) ? "sector" : "sectors";
110 BA_Message("pasted %d %s", s, name);
111 }
112 else if (l > 0)
113 {
114 const char *name = (l == 1) ? "linedef" : "linedefs";
115 BA_Message("pasted %d %s", l, name);
116 }
117 else if (t > 0)
118 {
119 const char *name = (t == 1) ? "thing" : "things";
120 BA_Message("pasted %d %s", t, name);
121 }
122 else if (v > 0)
123 {
124 const char *name = (v == 1) ? "vertex" : "vertices";
125 BA_Message("pasted %d %s", v, name);
126 }
127 else
128 {
129 BA_Message("pasted something");
130 }
131 }
132
CentreOfThings(double * cx,double * cy)133 void CentreOfThings(double *cx, double *cy)
134 {
135 *cx = *cy = 0;
136
137 if (things.empty())
138 return;
139
140 double sum_x = 0;
141 double sum_y = 0;
142
143 for (unsigned int i = 0 ; i < things.size() ; i++)
144 {
145 sum_x += things[i]->x();
146 sum_y += things[i]->y();
147 }
148
149 sum_x /= (double)things.size();
150 sum_y /= (double)things.size();
151
152 *cx = sum_x;
153 *cy = sum_y;
154 }
155
CentreOfVertices(double * cx,double * cy)156 void CentreOfVertices(double *cx, double *cy)
157 {
158 *cx = *cy = 0;
159
160 if (verts.empty())
161 return;
162
163 double sum_x = 0;
164 double sum_y = 0;
165
166 for (unsigned int i = 0 ; i < verts.size() ; i++)
167 {
168 sum_x += verts[i]->x();
169 sum_y += verts[i]->y();
170 }
171
172 sum_x /= (double)verts.size();
173 sum_y /= (double)verts.size();
174
175 *cx = sum_x;
176 *cy = sum_y;
177 }
178
HasSectorRefs(int s1,int s2)179 bool HasSectorRefs(int s1, int s2)
180 {
181 if (! uses_real_sectors)
182 return false;
183
184 for (unsigned int i = 0 ; i < sides.size() ; i++)
185 {
186 if (s1 <= sides[i]->sector && sides[i]->sector <= s2)
187 return true;
188 }
189
190 return false;
191 }
192
InsertRealSector(int snum)193 void InsertRealSector(int snum)
194 {
195 if (! uses_real_sectors)
196 return;
197
198 for (unsigned int i = 0 ; i < sides.size() ; i++)
199 {
200 if (sides[i]->sector >= snum)
201 sides[i]->sector++;
202 }
203 }
204
DeleteRealSector(int snum)205 void DeleteRealSector(int snum)
206 {
207 if (! uses_real_sectors)
208 return;
209
210 for (unsigned int i = 0 ; i < sides.size() ; i++)
211 {
212 if (sides[i]->sector == snum)
213 sides[i]->sector = INVALID_SECTOR;
214 else if (sides[i]->sector > snum)
215 sides[i]->sector--;
216 }
217 }
218
RemoveSectorRefs()219 void RemoveSectorRefs()
220 {
221 if (! uses_real_sectors)
222 return;
223
224 for (unsigned int i = 0 ; i < sides.size() ; i++)
225 {
226 if (sides[i]->sector >= 0)
227 sides[i]->sector = INVALID_SECTOR;
228 }
229 }
230 };
231
232
233 static clipboard_data_c * clip_board;
234
235 static bool clip_doing_paste;
236
237
Clipboard_Clear()238 void Clipboard_Clear()
239 {
240 if (clip_board)
241 {
242 delete clip_board;
243 clip_board = NULL;
244 }
245 }
246
247
248 //
249 // this remove sidedefs which refer to local sectors, allowing the
250 // clipboard geometry to persist when changing maps.
251 //
Clipboard_ClearLocals()252 void Clipboard_ClearLocals()
253 {
254 if (clip_board)
255 clip_board->RemoveSectorRefs();
256 }
257
258
Clipboard_HasStuff()259 static bool Clipboard_HasStuff()
260 {
261 return clip_board ? true : false;
262 }
263
264
Clipboard_NotifyBegin()265 void Clipboard_NotifyBegin()
266 { }
267
Clipboard_NotifyEnd()268 void Clipboard_NotifyEnd()
269 { }
270
271
Clipboard_NotifyInsert(obj_type_e type,int objnum)272 void Clipboard_NotifyInsert(obj_type_e type, int objnum)
273 {
274 // this function notifies us that a new sector is about to be
275 // inserted in the map (causing other sectors to be moved).
276
277 if (type != OBJ_SECTORS)
278 return;
279
280 if (! clip_board)
281 return;
282
283 // paste operations should only insert new sectors at the end
284 if (objnum < NumSectors)
285 {
286 SYS_ASSERT(! clip_doing_paste);
287 }
288
289 #if 0 // OLD WAY
290 if (clip_board->HasSectorRefs(objnum, NumSectors-1))
291 {
292 Clipboard_Clear();
293 }
294 #else
295 clip_board->InsertRealSector(objnum);
296 #endif
297 }
298
299
Clipboard_NotifyDelete(obj_type_e type,int objnum)300 void Clipboard_NotifyDelete(obj_type_e type, int objnum)
301 {
302 // this function notifies us that a sector is about to be deleted
303 // (causing other sectors to be moved).
304
305 if (type != OBJ_SECTORS)
306 return;
307
308 if (! clip_board)
309 return;
310
311 SYS_ASSERT(! clip_doing_paste);
312
313 clip_board->DeleteRealSector(objnum);
314 }
315
316
Clipboard_NotifyChange(obj_type_e type,int objnum,int field)317 void Clipboard_NotifyChange(obj_type_e type, int objnum, int field)
318 {
319 // field changes never affect the clipboard
320 }
321
322
323 //----------------------------------------------------------------------
324 // Texture Clipboard
325 //----------------------------------------------------------------------
326
327 namespace tex_clipboard
328 {
329 std::string tex;
330 std::string flat;
331
332 int thing;
333 };
334
335
Texboard_Clear()336 void Texboard_Clear()
337 {
338 tex_clipboard::tex.clear();
339 tex_clipboard::flat.clear();
340 tex_clipboard::thing = 0;
341 }
342
Texboard_GetTexNum()343 int Texboard_GetTexNum()
344 {
345 if (tex_clipboard::tex.empty())
346 tex_clipboard::tex = default_wall_tex;
347
348 return BA_InternaliseString(tex_clipboard::tex.c_str());
349 }
350
Texboard_GetFlatNum()351 int Texboard_GetFlatNum()
352 {
353 if (tex_clipboard::flat.empty())
354 tex_clipboard::flat = default_floor_tex;
355
356 return BA_InternaliseString(tex_clipboard::flat.c_str());
357 }
358
Texboard_GetThing()359 int Texboard_GetThing()
360 {
361 if (tex_clipboard::thing == 0)
362 return default_thing;
363
364 return tex_clipboard::thing;
365 }
366
Texboard_SetTex(const char * new_tex)367 void Texboard_SetTex(const char *new_tex)
368 {
369 tex_clipboard::tex = new_tex;
370
371 if (Features.mix_textures_flats)
372 tex_clipboard::flat = new_tex;
373 }
374
Texboard_SetFlat(const char * new_flat)375 void Texboard_SetFlat(const char *new_flat)
376 {
377 tex_clipboard::flat = new_flat;
378
379 if (Features.mix_textures_flats)
380 tex_clipboard::tex = new_flat;
381 }
382
Texboard_SetThing(int new_id)383 void Texboard_SetThing(int new_id)
384 {
385 tex_clipboard::thing = new_id;
386 }
387
388
389 //------------------------------------------------------------------------
390
CopyGroupOfObjects(selection_c * list)391 static void CopyGroupOfObjects(selection_c *list)
392 {
393 // this is used for LineDefs and Sectors, where we need to copy
394 // groups of objects and create internal references between them.
395
396 bool is_sectors = (list->what_type() == OBJ_SECTORS) ? true : false;
397
398 selection_c vert_sel(OBJ_VERTICES);
399 selection_c side_sel(OBJ_SIDEDEFS);
400 selection_c line_sel(OBJ_LINEDEFS);
401
402 ConvertSelection(list, &line_sel);
403 ConvertSelection(&line_sel, &vert_sel);
404
405 // determine needed sidedefs
406 for (sel_iter_c it(line_sel) ; !it.done() ; it.next())
407 {
408 LineDef *L = LineDefs[*it];
409
410 if (L->right >= 0) side_sel.set(L->right);
411 if (L->left >= 0) side_sel.set(L->left);
412 }
413
414
415 // these hold the mapping from real index --> clipboard index
416 std::map<int, int> vert_map;
417 std::map<int, int> sector_map;
418 std::map<int, int> side_map;
419
420
421 for (sel_iter_c it(vert_sel) ; !it.done() ; it.next())
422 {
423 vert_map[*it] = (int)clip_board->verts.size();
424
425 Vertex * SD = new Vertex;
426 SD->RawCopy(Vertices[*it]);
427 clip_board->verts.push_back(SD);
428 }
429
430 if (is_sectors)
431 {
432 for (sel_iter_c it(list) ; !it.done() ; it.next())
433 {
434 sector_map[*it] = (int)clip_board->sectors.size();
435
436 Sector * S = new Sector;
437 S->RawCopy(Sectors[*it]);
438 clip_board->sectors.push_back(S);
439 }
440 }
441
442 for (sel_iter_c it(side_sel) ; !it.done() ; it.next())
443 {
444 side_map[*it] = (int)clip_board->sides.size();
445
446 SideDef * SD = new SideDef;
447 SD->RawCopy(SideDefs[*it]);
448 clip_board->sides.push_back(SD);
449
450 // adjust sector references, if needed
451 if (is_sectors && list->get(SD->sector))
452 {
453 SYS_ASSERT(sector_map.find(SD->sector) != sector_map.end());
454 SD->sector = -1 - sector_map[SD->sector];
455 }
456 }
457
458 for (sel_iter_c it(line_sel) ; !it.done() ; it.next())
459 {
460 LineDef * L = new LineDef;
461 L->RawCopy(LineDefs[*it]);
462 clip_board->lines.push_back(L);
463
464 // adjust vertex references
465 SYS_ASSERT(vert_map.find(L->start) != vert_map.end());
466 SYS_ASSERT(vert_map.find(L->end) != vert_map.end());
467
468 L->start = vert_map[L->start];
469 L->end = vert_map[L->end ];
470
471 // adjust sidedef references
472 if (L->right >= 0)
473 {
474 SYS_ASSERT(side_map.find(L->right) != side_map.end());
475 L->right = side_map[L->right];
476 }
477
478 if (L->left >= 0)
479 {
480 SYS_ASSERT(side_map.find(L->left) != side_map.end());
481 L->left = side_map[L->left];
482 }
483 }
484
485 // in sectors mode, copy things too
486 if (is_sectors)
487 {
488 selection_c thing_sel(OBJ_THINGS);
489
490 ConvertSelection(list, &thing_sel);
491
492 for (sel_iter_c it(thing_sel) ; !it.done() ; it.next())
493 {
494 Thing * T = new Thing;
495 T->RawCopy(Things[*it]);
496 clip_board->things.push_back(T);
497 }
498 }
499 }
500
501
Clipboard_DoCopy()502 static bool Clipboard_DoCopy()
503 {
504 soh_type_e unselect = Selection_Or_Highlight();
505 if (unselect == SOH_Empty)
506 return false;
507
508 // create storage for the copied objects
509 if (clip_board)
510 delete clip_board;
511
512 bool result = true;
513
514 clip_board = new clipboard_data_c(edit.mode);
515
516 switch (edit.mode)
517 {
518 case OBJ_THINGS:
519 for (sel_iter_c it(edit.Selected) ; !it.done() ; it.next())
520 {
521 Thing * T = new Thing;
522 T->RawCopy(Things[*it]);
523 clip_board->things.push_back(T);
524 }
525 break;
526
527 case OBJ_VERTICES:
528 for (sel_iter_c it(edit.Selected) ; !it.done() ; it.next())
529 {
530 Vertex * V = new Vertex;
531 V->RawCopy(Vertices[*it]);
532 clip_board->verts.push_back(V);
533 }
534 break;
535
536 case OBJ_LINEDEFS:
537 case OBJ_SECTORS:
538 CopyGroupOfObjects(edit.Selected);
539 break;
540
541 default:
542 result = false;
543 break;
544 }
545
546 int total = edit.Selected->count_obj();
547 if (total == 1)
548 Status_Set("copied %s #%d", NameForObjectType(edit.Selected->what_type()), edit.Selected->find_first());
549 else
550 Status_Set("copied %d %s", total, NameForObjectType(edit.Selected->what_type(), true /* plural */));
551
552 if (unselect == SOH_Unselect)
553 Selection_Clear(true /* nosave */);
554
555 return result;
556 }
557
558
559 //------------------------------------------------------------------------
560
PasteGroupOfObjects(double pos_x,double pos_y)561 static void PasteGroupOfObjects(double pos_x, double pos_y)
562 {
563 double cx, cy;
564 clip_board->CentreOfVertices(&cx, &cy);
565
566 // these hold the mapping from clipboard index --> real index
567 std::map<int, int> vert_map;
568 std::map<int, int> sector_map;
569 std::map<int, int> side_map;
570
571 unsigned int i;
572
573 for (i = 0 ; i < clip_board->verts.size() ; i++)
574 {
575 int new_v = BA_New(OBJ_VERTICES);
576 Vertex * V = Vertices[new_v];
577
578 vert_map[i] = new_v;
579
580 V->RawCopy(clip_board->verts[i]);
581
582 V->SetRawX(V->x() + pos_x - cx);
583 V->SetRawY(V->y() + pos_y - cy);
584 }
585
586 for (i = 0 ; i < clip_board->sectors.size() ; i++)
587 {
588 int new_s = BA_New(OBJ_SECTORS);
589 Sector * S = Sectors[new_s];
590
591 sector_map[i] = new_s;
592
593 S->RawCopy(clip_board->sectors[i]);
594 }
595
596 for (i = 0 ; i < clip_board->sides.size() ; i++)
597 {
598 // handle invalidated sectors (as if sidedef had been deleted)
599 if (clip_board->sides[i]->sector == INVALID_SECTOR)
600 {
601 side_map[i] = -1;
602 continue;
603 }
604
605 int new_sd = BA_New(OBJ_SIDEDEFS);
606 SideDef * SD = SideDefs[new_sd];
607
608 side_map[i] = new_sd;
609
610 SD->RawCopy(clip_board->sides[i]);
611
612 if (SD->sector < 0)
613 {
614 int local = -1 - SD->sector;
615 SYS_ASSERT(sector_map.find(local) != sector_map.end());
616 SD->sector = sector_map[local];
617 }
618 }
619
620 for (i = 0 ; i < clip_board->lines.size() ; i++)
621 {
622 int new_l = BA_New(OBJ_LINEDEFS);
623 LineDef * L = LineDefs[new_l];
624
625 L->RawCopy(clip_board->lines[i]);
626
627 // adjust vertex references
628 SYS_ASSERT(vert_map.find(L->start) != vert_map.end());
629 SYS_ASSERT(vert_map.find(L->end) != vert_map.end());
630
631 L->start = vert_map[L->start];
632 L->end = vert_map[L->end ];
633
634 // adjust sidedef references
635 if (L->Right())
636 {
637 SYS_ASSERT(side_map.find(L->right) != side_map.end());
638 L->right = side_map[L->right];
639 }
640
641 if (L->Left())
642 {
643 SYS_ASSERT(side_map.find(L->left) != side_map.end());
644 L->left = side_map[L->left];
645 }
646
647 // flip linedef if necessary
648 if (L->Left() && ! L->Right())
649 {
650 FlipLineDef(new_l);
651 }
652
653 // if the linedef lost a side, fix texturing
654 if (L->OneSided() && is_null_tex(L->Right()->MidTex()))
655 LD_FixForLostSide(new_l);
656 }
657
658 for (i = 0 ; i < clip_board->things.size() ; i++)
659 {
660 int new_t = BA_New(OBJ_THINGS);
661 Thing * T = Things[new_t];
662
663 T->RawCopy(clip_board->things[i]);
664
665 T->SetRawX(T->x() + pos_x - cx);
666 T->SetRawY(T->y() + pos_y - cy);
667 }
668 }
669
670
ReselectGroup()671 static void ReselectGroup()
672 {
673 // this assumes all the new objects are at the end of their
674 // array (currently true, but not a guarantee of BA_New).
675
676 if (edit.mode == OBJ_THINGS)
677 {
678 if (clip_board->mode == OBJ_THINGS ||
679 clip_board->mode == OBJ_SECTORS)
680 {
681 int count = (int)clip_board->things.size();
682
683 Selection_Clear();
684
685 edit.Selected->frob_range(NumThings - count, NumThings-1, BOP_ADD);
686 }
687 return;
688 }
689
690 bool was_mappy = (clip_board->mode == OBJ_VERTICES ||
691 clip_board->mode == OBJ_LINEDEFS ||
692 clip_board->mode == OBJ_SECTORS);
693
694 bool is_mappy = (edit.mode == OBJ_VERTICES ||
695 edit.mode == OBJ_LINEDEFS ||
696 edit.mode == OBJ_SECTORS);
697
698 if (! (was_mappy && is_mappy))
699 return;
700
701 selection_c new_sel(clip_board->mode);
702
703 if (clip_board->mode == OBJ_VERTICES)
704 {
705 int count = (int)clip_board->verts.size();
706 new_sel.frob_range(NumVertices - count, NumVertices-1, BOP_ADD);
707 }
708 else if (clip_board->mode == OBJ_LINEDEFS)
709 {
710 // Note: this doesn't behave as expected if the editing mode is
711 // SECTORS, because the pasted lines do not completely surround
712 // the sectors (non-pasted lines refer to them too).
713
714 int count = (int)clip_board->lines.size();
715 new_sel.frob_range(NumLineDefs - count, NumLineDefs-1, BOP_ADD);
716 }
717 else
718 {
719 SYS_ASSERT(clip_board->mode == OBJ_SECTORS);
720
721 int count = (int)clip_board->sectors.size();
722 new_sel.frob_range(NumSectors - count, NumSectors-1, BOP_ADD);
723 }
724
725 Selection_Clear();
726
727 ConvertSelection(&new_sel, edit.Selected);
728 }
729
730
Clipboard_DoPaste()731 static bool Clipboard_DoPaste()
732 {
733 bool reselect = true; // CONFIG TODO
734
735 unsigned int i;
736
737 if (! Clipboard_HasStuff())
738 return false;
739
740 // figure out where to put stuff
741 double pos_x = edit.map_x;
742 double pos_y = edit.map_y;
743
744 if (! edit.pointer_in_window)
745 {
746 pos_x = grid.orig_x;
747 pos_y = grid.orig_y;
748 }
749
750 // honor the grid snapping setting
751 pos_x = grid.SnapX(pos_x);
752 pos_y = grid.SnapY(pos_y);
753
754 BA_Begin();
755 clip_board->Paste_BA_Message();
756
757 clip_doing_paste = true;
758
759 switch (clip_board->mode)
760 {
761 case OBJ_THINGS:
762 {
763 double cx, cy;
764 clip_board->CentreOfThings(&cx, &cy);
765
766 for (unsigned int i = 0 ; i < clip_board->things.size() ; i++)
767 {
768 int new_t = BA_New(OBJ_THINGS);
769 Thing * T = Things[new_t];
770
771 T->RawCopy(clip_board->things[i]);
772
773 T->SetRawX(T->x() + pos_x - cx);
774 T->SetRawY(T->y() + pos_y - cy);
775
776 recent_things.insert_number(T->type);
777 }
778 break;
779 }
780
781 case OBJ_VERTICES:
782 {
783 double cx, cy;
784 clip_board->CentreOfVertices(&cx, &cy);
785
786 for (i = 0 ; i < clip_board->verts.size() ; i++)
787 {
788 int new_v = BA_New(OBJ_VERTICES);
789 Vertex * V = Vertices[new_v];
790
791 V->RawCopy(clip_board->verts[i]);
792
793 V->SetRawX(V->x() + pos_x - cx);
794 V->SetRawY(V->y() + pos_y - cy);
795 }
796 break;
797 }
798
799 case OBJ_LINEDEFS:
800 case OBJ_SECTORS:
801 {
802 PasteGroupOfObjects(pos_x, pos_y);
803 break;
804 }
805
806 default:
807 break;
808 }
809
810 clip_doing_paste = false;
811
812 BA_End();
813
814 edit.error_mode = false;
815
816 if (reselect)
817 ReselectGroup();
818
819 return true;
820 }
821
822
823 //------------------------------------------------------------------------
824
CMD_CopyAndPaste()825 void CMD_CopyAndPaste()
826 {
827 if (edit.Selected->empty() && edit.highlight.is_nil())
828 {
829 Beep("Nothing to copy and paste");
830 return;
831 }
832
833 if (Clipboard_DoCopy())
834 {
835 Clipboard_DoPaste();
836 }
837 }
838
839
CMD_Clipboard_Cut()840 void CMD_Clipboard_Cut()
841 {
842 if (main_win->ClipboardOp('x'))
843 return;
844
845 if (edit.render3d && edit.mode != OBJ_THINGS)
846 {
847 Render3D_CB_Cut();
848 return;
849 }
850
851 if (! Clipboard_DoCopy())
852 {
853 Beep("Nothing to cut");
854 return;
855 }
856
857 ExecuteCommand("Delete");
858 }
859
860
CMD_Clipboard_Copy()861 void CMD_Clipboard_Copy()
862 {
863 if (main_win->ClipboardOp('c'))
864 return;
865
866 if (edit.render3d && edit.mode != OBJ_THINGS)
867 {
868 Render3D_CB_Copy();
869 return;
870 }
871
872 if (! Clipboard_DoCopy())
873 {
874 Beep("Nothing to copy");
875 return;
876 }
877 }
878
879
CMD_Clipboard_Paste()880 void CMD_Clipboard_Paste()
881 {
882 if (main_win->ClipboardOp('v'))
883 return;
884
885 if (edit.render3d && edit.mode != OBJ_THINGS)
886 {
887 Render3D_CB_Paste();
888 return;
889 }
890
891 if (! Clipboard_DoPaste())
892 {
893 Beep("Clipboard is empty");
894 return;
895 }
896 }
897
898
899 //------------------------------------------------------------------------
900
UnusedVertices(selection_c * lines,selection_c * result)901 void UnusedVertices(selection_c *lines, selection_c *result)
902 {
903 SYS_ASSERT(lines->what_type() == OBJ_LINEDEFS);
904
905 ConvertSelection(lines, result);
906
907 for (int n = 0 ; n < NumLineDefs ; n++)
908 {
909 // we are interested in the lines we are NOT deleting
910 if (lines->get(n))
911 continue;
912
913 const LineDef *L = LineDefs[n];
914
915 result->clear(L->start);
916 result->clear(L->end);
917 }
918 }
919
920
UnusedSideDefs(selection_c * lines,selection_c * secs,selection_c * result)921 void UnusedSideDefs(selection_c *lines, selection_c *secs, selection_c *result)
922 {
923 SYS_ASSERT(lines->what_type() == OBJ_LINEDEFS);
924
925 ConvertSelection(lines, result);
926
927 for (int n = 0 ; n < NumLineDefs ; n++)
928 {
929 // we are interested in the lines we are NOT deleting
930 if (lines->get(n))
931 continue;
932
933 const LineDef *L = LineDefs[n];
934
935 if (L->Right()) result->clear(L->right);
936 if (L->Left()) result->clear(L->left);
937 }
938
939 for (int i = 0 ; i < NumSideDefs ; i++)
940 {
941 const SideDef *SD = SideDefs[i];
942
943 if (secs && secs->get(SD->sector))
944 result->set(i);
945 }
946 }
947
948
UnusedLineDefs(selection_c * sectors,selection_c * result)949 void UnusedLineDefs(selection_c *sectors, selection_c *result)
950 {
951 SYS_ASSERT(sectors->what_type() == OBJ_SECTORS);
952
953 for (int n = 0 ; n < NumLineDefs ; n++)
954 {
955 const LineDef *L = LineDefs[n];
956
957 // check if touches a to-be-deleted sector
958 // -1 : no side
959 // 0 : deleted side
960 // +1 : kept side
961 int right_m = (L->right < 0) ? -1 : sectors->get(L->Right()->sector) ? 0 : 1;
962 int left_m = (L->left < 0) ? -1 : sectors->get(L->Left() ->sector) ? 0 : 1;
963
964 if (MAX(right_m, left_m) == 0)
965 {
966 result->set(n);
967 }
968 }
969 }
970
971
DuddedSectors(selection_c * verts,selection_c * lines,selection_c * result)972 void DuddedSectors(selection_c *verts, selection_c *lines, selection_c *result)
973 {
974 SYS_ASSERT(verts->what_type() == OBJ_VERTICES);
975 SYS_ASSERT(lines->what_type() == OBJ_LINEDEFS);
976
977 // collect all the sectors that touch a linedef being removed.
978
979 bitvec_c del_lines(NumLineDefs);
980
981 for (int n = 0 ; n < NumLineDefs ; n++)
982 {
983 const LineDef *L = LineDefs[n];
984
985 if (lines->get(n) || verts->get(L->start) || verts->get(L->end))
986 {
987 del_lines.set(n);
988
989 if (L->WhatSector(SIDE_LEFT ) >= 0) result->set(L->WhatSector(SIDE_LEFT ));
990 if (L->WhatSector(SIDE_RIGHT) >= 0) result->set(L->WhatSector(SIDE_RIGHT));
991 }
992 }
993
994 // visit all linedefs NOT being removed, and see if the sector(s)
995 // on it will actually be OK after the delete.
996
997 for (int n = 0 ; n < NumLineDefs ; n++)
998 {
999 const LineDef *L = LineDefs[n];
1000
1001 if (lines->get(n) || verts->get(L->start) || verts->get(L->end))
1002 continue;
1003
1004 for (int pass = 0 ; pass < 2 ; pass++)
1005 {
1006 int what_side = pass ? SIDE_LEFT : SIDE_RIGHT;
1007
1008 int sec_num = L->WhatSector(what_side);
1009
1010 if (sec_num < 0)
1011 continue;
1012
1013 // skip sectors that are not potentials for removal,
1014 // and prevent the expensive tests below...
1015 if (! result->get(sec_num))
1016 continue;
1017
1018 // check if the linedef opposite faces this sector (BUT
1019 // IGNORING any lines being deleted). when found, we
1020 // know that this sector should be kept.
1021
1022 int opp_side;
1023 int opp_ld = OppositeLineDef(n, what_side, &opp_side, &del_lines);
1024
1025 if (opp_ld < 0)
1026 continue;
1027
1028 const LineDef *L2 = LineDefs[opp_ld];
1029
1030 if (L2->WhatSector(opp_side) == sec_num)
1031 result->clear(sec_num);
1032 }
1033 }
1034 }
1035
1036
FixupLineDefs(selection_c * lines,selection_c * sectors)1037 static void FixupLineDefs(selection_c *lines, selection_c *sectors)
1038 {
1039 for (sel_iter_c it(lines) ; !it.done() ; it.next())
1040 {
1041 const LineDef *L = LineDefs[*it];
1042
1043 // the logic is ugly here mainly to handle flipping (in particular,
1044 // not to flip the line when _both_ sides are unlinked).
1045
1046 bool do_right = L->Right() ? sectors->get(L->Right()->sector) : false;
1047 bool do_left = L->Left() ? sectors->get(L->Left() ->sector) : false;
1048
1049 // line shouldn't be in list unless it touches the sector
1050 SYS_ASSERT(do_right || do_left);
1051
1052 if (do_right && do_left)
1053 {
1054 LD_RemoveSideDef(*it, SIDE_RIGHT);
1055 LD_RemoveSideDef(*it, SIDE_LEFT);
1056 }
1057 else if (do_right)
1058 {
1059 LD_RemoveSideDef(*it, SIDE_RIGHT);
1060 FlipLineDef(*it);
1061 }
1062 else // do_left
1063 {
1064 LD_RemoveSideDef(*it, SIDE_LEFT);
1065 }
1066 }
1067 }
1068
1069
DeleteVertex_MergeLineDefs(int v_num)1070 static bool DeleteVertex_MergeLineDefs(int v_num)
1071 {
1072 // returns true if OK, false if would create an overlapping linedef
1073 // [ meaning the vertex should be deleted normally ]
1074
1075 // find the linedefs
1076 int ld1 = -1;
1077 int ld2 = -1;
1078
1079 for (int n = 0 ; n < NumLineDefs ; n++)
1080 {
1081 const LineDef *L = LineDefs[n];
1082
1083 if (L->start == v_num || L->end == v_num)
1084 {
1085 SYS_ASSERT(ld2 < 0);
1086
1087 if (ld1 < 0)
1088 ld1 = n;
1089 else
1090 ld2 = n;
1091 }
1092 }
1093
1094 SYS_ASSERT(ld1 >= 0);
1095 SYS_ASSERT(ld2 >= 0);
1096
1097 LineDef *L1 = LineDefs[ld1];
1098 LineDef *L2 = LineDefs[ld2];
1099
1100 // we merge L2 into L1, unless L1 is significantly shorter
1101 if (L1->CalcLength() < L2->CalcLength() * 0.7)
1102 {
1103 std::swap(ld1, ld2);
1104 std::swap(L1, L2);
1105 }
1106
1107 // determine the remaining vertices
1108 int v1 = (L1->start == v_num) ? L1->end : L1->start;
1109 int v2 = (L2->start == v_num) ? L2->end : L2->start;
1110
1111 // ensure we don't create two directly overlapping linedefs
1112 if (LineDefAlreadyExists(v1, v2))
1113 return false;
1114
1115 // see what sidedefs would become unused
1116 selection_c line_sel(OBJ_LINEDEFS);
1117 selection_c side_sel(OBJ_SIDEDEFS);
1118
1119 line_sel.set(ld2);
1120
1121 UnusedSideDefs(&line_sel, NULL /* sec_sel */, &side_sel);
1122
1123
1124 BA_Begin();
1125 BA_Message("deleted vertex #%d\n", v_num);
1126
1127 if (L1->start == v_num)
1128 BA_ChangeLD(ld1, LineDef::F_START, v2);
1129 else
1130 BA_ChangeLD(ld1, LineDef::F_END, v2);
1131
1132 // NOT-DO: update X offsets on existing linedef
1133
1134 BA_Delete(OBJ_LINEDEFS, ld2);
1135 BA_Delete(OBJ_VERTICES, v_num);
1136
1137 DeleteObjects(&side_sel);
1138
1139 BA_End();
1140
1141 return true;
1142 }
1143
1144
DeleteObjects_WithUnused(selection_c * list,bool keep_things,bool keep_verts,bool keep_lines)1145 void DeleteObjects_WithUnused(selection_c *list, bool keep_things,
1146 bool keep_verts, bool keep_lines)
1147 {
1148 selection_c vert_sel(OBJ_VERTICES);
1149 selection_c side_sel(OBJ_SIDEDEFS);
1150 selection_c line_sel(OBJ_LINEDEFS);
1151 selection_c sec_sel(OBJ_SECTORS);
1152
1153 switch (list->what_type())
1154 {
1155 case OBJ_VERTICES:
1156 vert_sel.merge(*list);
1157 break;
1158
1159 case OBJ_LINEDEFS:
1160 line_sel.merge(*list);
1161 break;
1162
1163 case OBJ_SECTORS:
1164 sec_sel.merge(*list);
1165 break;
1166
1167 default: /* OBJ_THINGS or OBJ_SIDEDEFS */
1168 DeleteObjects(list);
1169 return;
1170 }
1171
1172 if (list->what_type() == OBJ_VERTICES)
1173 {
1174 for (int n = 0 ; n < NumLineDefs ; n++)
1175 {
1176 const LineDef *L = LineDefs[n];
1177
1178 if (list->get(L->start) || list->get(L->end))
1179 line_sel.set(n);
1180 }
1181 }
1182
1183 if (!keep_lines && list->what_type() == OBJ_SECTORS)
1184 {
1185 UnusedLineDefs(&sec_sel, &line_sel);
1186
1187 if (line_sel.notempty())
1188 {
1189 UnusedSideDefs(&line_sel, &sec_sel, &side_sel);
1190
1191 if (!keep_verts)
1192 UnusedVertices(&line_sel, &vert_sel);
1193 }
1194 }
1195
1196 if (!keep_verts && list->what_type() == OBJ_LINEDEFS)
1197 {
1198 UnusedVertices(&line_sel, &vert_sel);
1199 }
1200
1201 // try to detect sectors that become "dudded", where all the
1202 // remaining linedefs of the sector face into the void.
1203 if (list->what_type() == OBJ_VERTICES || list->what_type() == OBJ_LINEDEFS)
1204 {
1205 DuddedSectors(&vert_sel, &line_sel, &sec_sel);
1206 UnusedSideDefs(&line_sel, &sec_sel, &side_sel);
1207 }
1208
1209 // delete things from each deleted sector
1210 if (!keep_things && sec_sel.notempty())
1211 {
1212 selection_c thing_sel(OBJ_THINGS);
1213
1214 ConvertSelection(&sec_sel, &thing_sel);
1215 DeleteObjects(&thing_sel);
1216 }
1217
1218 // perform linedef fixups here (when sectors get removed)
1219 if (sec_sel.notempty())
1220 {
1221 selection_c fixups(OBJ_LINEDEFS);
1222
1223 ConvertSelection(&sec_sel, &fixups);
1224
1225 // skip lines which will get deleted
1226 fixups.unmerge(line_sel);
1227
1228 FixupLineDefs(&fixups, &sec_sel);
1229 }
1230
1231 // actually delete stuff, in the correct order
1232 DeleteObjects(&line_sel);
1233 DeleteObjects(&side_sel);
1234 DeleteObjects(&vert_sel);
1235 DeleteObjects( &sec_sel);
1236 }
1237
1238
CMD_Delete()1239 void CMD_Delete()
1240 {
1241 if (main_win->ClipboardOp('d'))
1242 return;
1243
1244 soh_type_e unselect = Selection_Or_Highlight();
1245 if (unselect == SOH_Empty)
1246 {
1247 Beep("Nothing to delete");
1248 return;
1249 }
1250
1251 bool keep = Exec_HasFlag("/keep");
1252
1253 // special case for a single vertex connected to two linedefs,
1254 // we delete the vertex but merge the two linedefs.
1255 if (edit.mode == OBJ_VERTICES && edit.Selected->count_obj() == 1)
1256 {
1257 int v_num = edit.Selected->find_first();
1258 SYS_ASSERT(v_num >= 0);
1259
1260 if (Vertex_HowManyLineDefs(v_num) == 2)
1261 {
1262 if (DeleteVertex_MergeLineDefs(v_num))
1263 goto success;
1264 }
1265
1266 // delete vertex normally
1267 }
1268
1269 BA_Begin();
1270 BA_MessageForSel("deleted", edit.Selected);
1271
1272 DeleteObjects_WithUnused(edit.Selected, keep, false /* keep_verts */, keep);
1273
1274 BA_End();
1275
1276 success:
1277 Editor_ClearAction();
1278
1279 // always clear the selection (deleting objects invalidates it)
1280 Selection_Clear();
1281
1282 edit.highlight.clear();
1283 edit.split_line.clear();
1284
1285 RedrawMap();
1286 }
1287
1288
1289 //--- editor settings ---
1290 // vi:ts=4:sw=4:noexpandtab
1291