1 //------------------------------------------------------------------------
2 // OBJECT OPERATIONS
3 //------------------------------------------------------------------------
4 //
5 // Eureka DOOM Editor
6 //
7 // Copyright (C) 2001-2019 Andrew Apted
8 // Copyright (C) 1997-2003 Andr� Majorel et al
9 //
10 // This program is free software; you can redistribute it and/or
11 // modify it under the terms of the GNU General Public License
12 // as published by the Free Software Foundation; either version 2
13 // of the License, or (at your option) any later version.
14 //
15 // This program is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
19 //
20 //------------------------------------------------------------------------
21 //
22 // Based on Yadex which incorporated code from DEU 5.21 that was put
23 // in the public domain in 1994 by Rapha�l Quinet and Brendon Wyber.
24 //
25 //------------------------------------------------------------------------
26
27 #include "main.h"
28
29 #include <algorithm>
30
31 #include "e_hover.h"
32 #include "e_linedef.h"
33 #include "e_main.h"
34 #include "e_sector.h"
35 #include "e_things.h"
36 #include "e_vertex.h"
37 #include "m_game.h"
38 #include "e_objects.h"
39 #include "r_grid.h"
40 #include "w_rawdef.h"
41
42 #include "ui_window.h"
43
44
45 // config items
46 int new_sector_size = 128;
47
48 bool select_verts_of_new_sectors = true;
49
50
51 //
52 // delete a group of objects.
53 // this is very raw, e.g. it does not check for stuff that will
54 // remain unused afterwards.
55 //
DeleteObjects(selection_c * list)56 void DeleteObjects(selection_c *list)
57 {
58 // we need to process the object numbers from highest to lowest,
59 // because each deletion invalidates all higher-numbered refs
60 // in the selection. Our selection iterator cannot give us
61 // what we need, hence put them into a vector for sorting.
62
63 if (list->empty())
64 return;
65
66 std::vector<int> objnums;
67
68 for (sel_iter_c it(list) ; !it.done() ; it.next())
69 objnums.push_back(*it);
70
71 std::sort(objnums.begin(), objnums.end());
72
73 for (int i = (int)objnums.size()-1 ; i >= 0 ; i--)
74 {
75 BA_Delete(list->what_type(), objnums[i]);
76 }
77 }
78
79
CreateSquare(int model)80 static void CreateSquare(int model)
81 {
82 int new_sec = BA_New(OBJ_SECTORS);
83
84 if (model >= 0)
85 Sectors[new_sec]->RawCopy(Sectors[model]);
86 else
87 Sectors[new_sec]->SetDefaults();
88
89 double x1 = grid.QuantSnapX(edit.map_x, false);
90 double y1 = grid.QuantSnapX(edit.map_y, false);
91
92 double x2 = x1 + new_sector_size;
93 double y2 = y1 + new_sector_size;
94
95 for (int i = 0 ; i < 4 ; i++)
96 {
97 int new_v = BA_New(OBJ_VERTICES);
98 Vertex *V = Vertices[new_v];
99
100 V->SetRawX((i >= 2) ? x2 : x1);
101 V->SetRawY((i==1 || i==2) ? y2 : y1);
102
103 int new_sd = BA_New(OBJ_SIDEDEFS);
104
105 SideDefs[new_sd]->SetDefaults(false);
106 SideDefs[new_sd]->sector = new_sec;
107
108 int new_ld = BA_New(OBJ_LINEDEFS);
109
110 LineDef * L = LineDefs[new_ld];
111
112 L->start = new_v;
113 L->end = (i == 3) ? (new_v - 3) : new_v + 1;
114 L->flags = MLF_Blocking;
115 L->right = new_sd;
116 }
117
118 // select it
119 Selection_Clear();
120
121 edit.Selected->set(new_sec);
122 }
123
124
Insert_Thing()125 static void Insert_Thing()
126 {
127 int model = -1;
128
129 if (edit.Selected->notempty())
130 model = edit.Selected->find_first();
131
132
133 BA_Begin();
134
135 int new_t = BA_New(OBJ_THINGS);
136 Thing *T = Things[new_t];
137
138 if (model >= 0)
139 T->RawCopy(Things[model]);
140 else
141 {
142 T->type = default_thing;
143 T->options = MTF_Easy | MTF_Medium | MTF_Hard;
144
145 if (Level_format != MAPF_Doom)
146 {
147 T->options |= MTF_Hexen_SP | MTF_Hexen_COOP | MTF_Hexen_DM;
148 T->options |= MTF_Hexen_Fighter | MTF_Hexen_Cleric | MTF_Hexen_Mage;
149 }
150 }
151
152 T->SetRawX(grid.SnapX(edit.map_x));
153 T->SetRawY(grid.SnapY(edit.map_y));
154
155 recent_things.insert_number(T->type);
156
157 BA_Message("added thing #%d", new_t);
158 BA_End();
159
160
161 // select it
162 Selection_Clear();
163
164 edit.Selected->set(new_t);
165 }
166
167
Sector_New(int model=-1,int model2=-1,int model3=-1)168 static int Sector_New(int model = -1, int model2 = -1, int model3 = -1)
169 {
170 int new_sec = BA_New(OBJ_SECTORS);
171
172 if (model < 0) model = model2;
173 if (model < 0) model = model3;
174
175 if (model < 0)
176 Sectors[new_sec]->SetDefaults();
177 else
178 Sectors[new_sec]->RawCopy(Sectors[model]);
179
180 return new_sec;
181 }
182
183
CheckClosedLoop(int new_ld,int v1,int v2,selection_c * flip)184 static bool CheckClosedLoop(int new_ld, int v1, int v2, selection_c *flip)
185 {
186 // returns true if we assigned a sector (so drawing should stop)
187
188 struct check_closed_data_t
189 {
190 lineloop_c loop;
191
192 bool ok;
193
194 // what sector we face, -1 for VOID
195 int sec;
196
197 double length;
198
199 } left, right;
200
201 // trace the loops on either side of the new line
202
203 left.ok = TraceLineLoop(new_ld, SIDE_LEFT, left.loop);
204 right.ok = TraceLineLoop(new_ld, SIDE_RIGHT, right.loop);
205
206 #ifdef DEBUG_LOOP
207 fprintf(stderr, "CLOSED LOOP : left_ok:%d right_ok:%d\n",
208 left.ok ? 1 : 0, right.ok ? 1 : 0);
209 #endif
210
211 if (! (left.ok && right.ok))
212 {
213 // this is harder to trigger than you might think.
214
215 // TODO : find some cases, see what is needed
216
217 #ifdef DEBUG_LOOP
218 fprintf(stderr, "--> bad bad bad bad bad <--\n");
219 #endif
220 return false;
221 }
222
223
224 // check if the loops are the same, which means we have NOT
225 // split the sector (or created a new one)
226
227 if ( left.loop.get(right.loop.lines[0], right.loop.sides[0]) ||
228 right.loop.get( left.loop.lines[0], left.loop.sides[0]))
229 {
230 // nothing to do, let user keep drawing
231 return false;
232 }
233
234
235 #ifdef DEBUG_LOOP
236 fprintf(stderr, "--> %s / %s\n",
237 left.loop.faces_outward ? "OUTIE" : "innie",
238 right.loop.faces_outward ? "OUTIE" : "innie");
239 #endif
240
241 left.sec = left.loop.DetermineSector();
242 right.sec = right.loop.DetermineSector();
243
244 #ifdef DEBUG_LOOP
245 fprintf(stderr, "sec %d / %d\n", left.sec, right.sec);
246 #endif
247
248 left.length = left.loop.TotalLength();
249 right.length = right.loop.TotalLength();
250
251 if (! left.loop.faces_outward) left.loop.FindIslands();
252 if (! right.loop.faces_outward) right.loop.FindIslands();
253
254
255 /* --- handle outie --- */
256
257 // it is probably impossible for both loops to face outward, so
258 // we only need to handle two cases: both innie, or innie + outie.
259
260 if (left.loop.faces_outward && left.sec >= 0)
261 {
262 left.loop.AssignSector(left.sec, flip);
263 }
264 else if (right.loop.faces_outward && right.sec >= 0)
265 {
266 right.loop.AssignSector(right.sec, flip);
267 }
268
269 // create a void island when drawing anti-clockwise inside an
270 // existing sector, unless new island surrounds other islands.
271 if (right.loop.faces_outward && right.sec >= 0 &&
272 left.loop.AllBare() && left.loop.islands.empty())
273 {
274 return true;
275 }
276
277 if (left.loop.faces_outward || right.loop.faces_outward)
278 {
279 lineloop_c& innie = left.loop.faces_outward ? right.loop : left.loop;
280
281 // TODO : REVIEW NeighboringSector(), it's a bit random what we get
282 int new_sec = Sector_New(innie.NeighboringSector());
283
284 innie.AssignSector(new_sec, flip);
285 return true;
286 }
287
288
289 /* --- handle two innies --- */
290
291 // check if the sectors in each loop are different.
292 // this is not the usual situation! we assume the user has
293 // deleted a linedef or two and is correcting the geometry.
294
295 if (left.sec != right.sec)
296 {
297 if (right.sec >= 0) right.loop.AssignSector(right.sec, flip);
298 if ( left.sec >= 0) left.loop.AssignSector( left.sec, flip);
299
300 return true;
301 }
302
303 // we are creating a NEW sector in one loop (the smallest),
304 // and updating the other loop (unless it is VOID).
305
306 // the ordering here is significant, and ensures that the
307 // new linedef usually ends at v2 (the final vertex).
308
309 if (left.length < right.length)
310 {
311 int new_sec = Sector_New(left.sec, right.sec, left.loop.NeighboringSector());
312
313 if (right.sec >= 0)
314 right.loop.AssignSector(right.sec, flip);
315
316 left.loop.AssignSector(new_sec, flip);
317 }
318 else
319 {
320 int new_sec = Sector_New(right.sec, left.sec, right.loop.NeighboringSector());
321
322 right.loop.AssignSector(new_sec, flip);
323
324 if (left.sec >= 0)
325 left.loop.AssignSector(left.sec, flip);
326 }
327
328 return true;
329 }
330
331
Insert_LineDef(int v1,int v2,bool no_fill=false)332 static void Insert_LineDef(int v1, int v2, bool no_fill = false)
333 {
334 if (LineDefAlreadyExists(v1, v2))
335 return;
336
337 int new_ld = BA_New(OBJ_LINEDEFS);
338
339 LineDef * L = LineDefs[new_ld];
340
341 L->start = v1;
342 L->end = v2;
343 L->flags = MLF_Blocking;
344
345 if (no_fill)
346 return;
347
348 if (Vertex_HowManyLineDefs(v1) >= 2 &&
349 Vertex_HowManyLineDefs(v2) >= 2)
350 {
351 selection_c flip(OBJ_LINEDEFS);
352
353 CheckClosedLoop(new_ld, v1, v2, &flip);
354
355 FlipLineDefGroup(&flip);
356 }
357 }
358
359
Insert_LineDef_autosplit(int v1,int v2,bool no_fill=false)360 static void Insert_LineDef_autosplit(int v1, int v2, bool no_fill = false)
361 {
362 // Find a linedef which this new line would cross, and if it exists
363 // add a vertex there and create TWO lines. Also handle a vertex
364 // that this line crosses (sits on) similarly.
365
366 /// fprintf(stderr, "Insert_LineDef_autosplit %d..%d\n", v1, v2);
367
368 crossing_state_c cross;
369
370 FindCrossingPoints(cross,
371 Vertices[v1]->x(), Vertices[v1]->y(), v1,
372 Vertices[v2]->x(), Vertices[v2]->y(), v2);
373
374 cross.SplitAllLines();
375
376 int cur_v = v1;
377
378 for (unsigned int k = 0 ; k < cross.points.size() ; k++)
379 {
380 int next_v = cross.points[k].vert;
381
382 SYS_ASSERT(next_v != v1);
383 SYS_ASSERT(next_v != v2);
384
385 Insert_LineDef(cur_v, next_v, no_fill);
386
387 cur_v = next_v;
388 }
389
390 Insert_LineDef(cur_v, v2, no_fill);
391 }
392
393
Insert_Vertex(bool force_continue,bool no_fill)394 static void Insert_Vertex(bool force_continue, bool no_fill)
395 {
396 bool closed_a_loop = false;
397
398 // when these both >= 0, we will add a linedef between them
399 int old_vert = -1;
400 int new_vert = -1;
401
402 double new_x = grid.SnapX(edit.map_x);
403 double new_y = grid.SnapY(edit.map_y);
404
405 int orig_num_sectors = NumSectors;
406
407
408 // are we drawing a line?
409 if (edit.action == ACT_DRAW_LINE)
410 {
411 old_vert = edit.draw_from.num;
412
413 new_x = edit.draw_to_x;
414 new_y = edit.draw_to_y;
415 }
416
417 // a linedef which we are splitting (usually none)
418 int split_ld = edit.split_line.valid() ? edit.split_line.num : -1;
419
420 if (split_ld >= 0)
421 {
422 new_x = edit.split_x;
423 new_y = edit.split_y;
424
425 // prevent creating an overlapping line when splitting
426 if (old_vert >= 0 &&
427 LineDefs[split_ld]->TouchesVertex(old_vert))
428 {
429 old_vert = -1;
430 }
431 }
432 else
433 {
434 // not splitting a line.
435 // check if there is a "nearby" vertex (e.g. the highlighted one)
436
437 if (edit.highlight.valid())
438 new_vert = edit.highlight.num;
439
440 // if no highlight, look for a vertex at snapped coord
441 if (new_vert < 0 && grid.snap && ! (edit.action == ACT_DRAW_LINE))
442 new_vert = Vertex_FindExact(TO_COORD(new_x), TO_COORD(new_y));
443
444 //
445 // handle a highlighted/snapped vertex.
446 // either start drawing from it, or finish a loop at it.
447 //
448 if (new_vert >= 0)
449 {
450 // just ignore when highlight is same as drawing-start
451 if (old_vert >= 0 &&
452 Vertices[old_vert]->Matches(Vertices[new_vert]))
453 {
454 edit.Selected->set(old_vert);
455 return;
456 }
457
458 // a plain INSERT will attempt to fix a dangling vertex
459 if (edit.action == ACT_NOTHING)
460 {
461 if (Vertex_TryFixDangler(new_vert))
462 {
463 // a vertex was deleted, selection/highlight is now invalid
464 return;
465 }
466 }
467
468 // our insertion point is an existing vertex, and we are not
469 // in drawing mode, so there is no edit operation to perform.
470 if (old_vert < 0)
471 {
472 old_vert = new_vert;
473 new_vert = -1;
474
475 goto begin_drawing;
476 }
477
478 // handle case where a line already exists between the two vertices
479 if (LineDefAlreadyExists(old_vert, new_vert))
480 {
481 // just continue drawing from the second vertex
482 edit.draw_from = Objid(OBJ_VERTICES, new_vert);
483 edit.Selected->set(new_vert);
484 return;
485 }
486 }
487 }
488
489 // at here: if new_vert >= 0, then old_vert >= 0 and split_ld < 0
490
491
492 // would we create a new vertex on top of an existing one?
493 if (new_vert < 0 && old_vert >= 0 &&
494 Vertices[old_vert]->Matches(MakeValidCoord(new_x), MakeValidCoord(new_y)))
495 {
496 edit.Selected->set(old_vert);
497 return;
498 }
499
500
501 BA_Begin();
502
503
504 if (new_vert < 0)
505 {
506 new_vert = BA_New(OBJ_VERTICES);
507
508 Vertex *V = Vertices[new_vert];
509
510 V->SetRawXY(new_x, new_y);
511
512 edit.draw_from = Objid(OBJ_VERTICES, new_vert);
513 edit.Selected->set(new_vert);
514
515 // splitting an existing line?
516 if (split_ld >= 0)
517 {
518 SplitLineDefAtVertex(split_ld, new_vert);
519 BA_Message("split linedef #%d", split_ld);
520 }
521 else
522 {
523 BA_Message("added vertex #%d", new_vert);
524 }
525 }
526
527
528 if (old_vert < 0)
529 {
530 // there is no starting vertex, therefore no linedef can be added
531 old_vert = new_vert;
532 new_vert = -1;
533 }
534 else
535 {
536 // closing a loop?
537 if (!force_continue && Vertex_HowManyLineDefs(new_vert) > 0)
538 {
539 closed_a_loop = true;
540 }
541
542 //
543 // adding a linedef
544 //
545 SYS_ASSERT(old_vert != new_vert);
546
547 // this can make new sectors too
548 Insert_LineDef_autosplit(old_vert, new_vert, no_fill);
549
550 BA_Message("added linedef");
551
552 edit.draw_from = Objid(OBJ_VERTICES, new_vert);
553 edit.Selected->set(new_vert);
554 }
555
556
557 BA_End();
558
559
560 begin_drawing:
561 // begin drawing mode?
562 if (edit.action == ACT_NOTHING && !closed_a_loop &&
563 old_vert >= 0 && new_vert < 0)
564 {
565 Selection_Clear();
566
567 edit.draw_from = Objid(OBJ_VERTICES, old_vert);
568 edit.Selected->set(old_vert);
569
570 edit.draw_to_x = Vertices[old_vert]->x();
571 edit.draw_to_y = Vertices[old_vert]->y();
572
573 Editor_SetAction(ACT_DRAW_LINE);
574 }
575
576 // stop drawing mode?
577 if (closed_a_loop && !force_continue)
578 {
579 Editor_ClearAction();
580 }
581
582 // select vertices of a newly created sector?
583 if (select_verts_of_new_sectors && closed_a_loop &&
584 NumSectors > orig_num_sectors)
585 {
586 selection_c sel(OBJ_SECTORS);
587
588 // more than one sector may have been created, pick the last
589 sel.set(NumSectors - 1);
590
591 edit.Selected->change_type(edit.mode);
592 ConvertSelection(&sel, edit.Selected);
593 }
594
595 RedrawMap();
596 }
597
598
Insert_Sector()599 static void Insert_Sector()
600 {
601 int sel_count = edit.Selected->count_obj();
602 if (sel_count > 1)
603 {
604 Beep("Too many sectors to copy from");
605 return;
606 }
607
608 // if outside of the map, create a square
609 if (PointOutsideOfMap(edit.map_x, edit.map_y))
610 {
611 BA_Begin();
612 BA_Message("added sector (outside map)");
613
614 int model = -1;
615 if (sel_count > 0)
616 model = edit.Selected->find_first();
617
618 CreateSquare(model);
619
620 BA_End();
621 return;
622 }
623
624
625 // --- adding a NEW sector to the area ---
626
627 // determine a model sector to copy properties from
628 int model;
629
630 if (sel_count > 0)
631 model = edit.Selected->find_first();
632 else if (edit.highlight.valid())
633 model = edit.highlight.num;
634 else
635 model = -1; // look for a neighbor to copy
636
637
638 BA_Begin();
639 BA_Message("added new sector");
640
641 bool ok = AssignSectorToSpace(edit.map_x, edit.map_y, -1 /* create */, model);
642
643 BA_End();
644
645 // select the new sector
646 if (ok)
647 {
648 Selection_Clear();
649 edit.Selected->set(NumSectors - 1);
650 }
651
652 RedrawMap();
653 }
654
655
CMD_Insert()656 void CMD_Insert()
657 {
658 bool force_cont;
659 bool no_fill;
660
661 if (edit.render3d && edit.mode != OBJ_THINGS)
662 {
663 Beep("Cannot insert in this mode");
664 return;
665 }
666
667 switch (edit.mode)
668 {
669 case OBJ_THINGS:
670 Insert_Thing();
671 break;
672
673 case OBJ_VERTICES:
674 force_cont = Exec_HasFlag("/continue");
675 no_fill = Exec_HasFlag("/nofill");
676 Insert_Vertex(force_cont, no_fill);
677 break;
678
679 case OBJ_SECTORS:
680 Insert_Sector();
681 break;
682
683 default:
684 Beep("Cannot insert in this mode");
685 break;
686 }
687
688 RedrawMap();
689 }
690
691
692 //
693 // check if any part of a LineDef is inside the given box
694 //
LineTouchesBox(int ld,double x0,double y0,double x1,double y1)695 bool LineTouchesBox(int ld, double x0, double y0, double x1, double y1)
696 {
697 double lx0 = LineDefs[ld]->Start()->x();
698 double ly0 = LineDefs[ld]->Start()->y();
699 double lx1 = LineDefs[ld]->End()->x();
700 double ly1 = LineDefs[ld]->End()->y();
701
702 double i;
703
704 // start is entirely inside the square?
705 if (lx0 >= x0 && lx0 <= x1 && ly0 >= y0 && ly0 <= y1)
706 return true;
707
708 // end is entirely inside the square?
709 if (lx1 >= x0 && lx1 <= x1 && ly1 >= y0 && ly1 <= y1)
710 return true;
711
712
713 if ((ly0 > y0) != (ly1 > y0))
714 {
715 i = lx0 + (y0 - ly0) * (lx1 - lx0) / (ly1 - ly0);
716 if (i >= x0 && i <= x1)
717 return true; /* the linedef crosses the left side */
718 }
719 if ((ly0 > y1) != (ly1 > y1))
720 {
721 i = lx0 + (y1 - ly0) * (lx1 - lx0) / (ly1 - ly0);
722 if (i >= x0 && i <= x1)
723 return true; /* the linedef crosses the right side */
724 }
725 if ((lx0 > x0) != (lx1 > x0))
726 {
727 i = ly0 + (x0 - lx0) * (ly1 - ly0) / (lx1 - lx0);
728 if (i >= y0 && i <= y1)
729 return true; /* the linedef crosses the bottom side */
730 }
731 if ((lx0 > x1) != (lx1 > x1))
732 {
733 i = ly0 + (x1 - lx0) * (ly1 - ly0) / (lx1 - lx0);
734 if (i >= y0 && i <= y1)
735 return true; /* the linedef crosses the top side */
736 }
737
738 return false;
739 }
740
741
DoMoveObjects(selection_c * list,double delta_x,double delta_y,double delta_z)742 static void DoMoveObjects(selection_c *list, double delta_x, double delta_y, double delta_z)
743 {
744 fixcoord_t fdx = MakeValidCoord(delta_x);
745 fixcoord_t fdy = MakeValidCoord(delta_y);
746 fixcoord_t fdz = MakeValidCoord(delta_z);
747
748 switch (list->what_type())
749 {
750 case OBJ_THINGS:
751 for (sel_iter_c it(list) ; !it.done() ; it.next())
752 {
753 const Thing * T = Things[*it];
754
755 BA_ChangeTH(*it, Thing::F_X, T->raw_x + fdx);
756 BA_ChangeTH(*it, Thing::F_Y, T->raw_y + fdy);
757 BA_ChangeTH(*it, Thing::F_H, MAX(0, T->raw_h + fdz));
758 }
759 break;
760
761 case OBJ_VERTICES:
762 for (sel_iter_c it(list) ; !it.done() ; it.next())
763 {
764 const Vertex * V = Vertices[*it];
765
766 BA_ChangeVT(*it, Vertex::F_X, V->raw_x + fdx);
767 BA_ChangeVT(*it, Vertex::F_Y, V->raw_y + fdy);
768 }
769 break;
770
771 case OBJ_SECTORS:
772 // apply the Z delta first
773 for (sel_iter_c it(list) ; !it.done() ; it.next())
774 {
775 const Sector * S = Sectors[*it];
776
777 BA_ChangeSEC(*it, Sector::F_FLOORH, S->floorh + (int)delta_z);
778 BA_ChangeSEC(*it, Sector::F_CEILH, S->ceilh + (int)delta_z);
779 }
780
781 /* FALL-THROUGH !! */
782
783 case OBJ_LINEDEFS:
784 {
785 selection_c verts(OBJ_VERTICES);
786 ConvertSelection(list, &verts);
787
788 DoMoveObjects(&verts, delta_x, delta_y, delta_z);
789 }
790 break;
791
792 default:
793 break;
794 }
795 }
796
797
MoveObjects(selection_c * list,double delta_x,double delta_y,double delta_z)798 void MoveObjects(selection_c *list, double delta_x, double delta_y, double delta_z)
799 {
800 if (list->empty())
801 return;
802
803 BA_Begin();
804 BA_MessageForSel("moved", list);
805
806 // move things in sectors too (must do it _before_ moving the
807 // sectors, otherwise we fail trying to determine which sectors
808 // each thing is in).
809 if (edit.mode == OBJ_SECTORS)
810 {
811 selection_c thing_sel(OBJ_THINGS);
812 ConvertSelection(list, &thing_sel);
813
814 DoMoveObjects(&thing_sel, delta_x, delta_y, 0);
815 }
816
817 DoMoveObjects(list, delta_x, delta_y, delta_z);
818
819 BA_End();
820 }
821
822
DragSingleObject(Objid & obj,double delta_x,double delta_y,double delta_z)823 void DragSingleObject(Objid& obj, double delta_x, double delta_y, double delta_z)
824 {
825 if (edit.mode != OBJ_VERTICES)
826 {
827 selection_c list(edit.mode);
828 list.set(obj.num);
829
830 MoveObjects(&list, delta_x, delta_y, delta_z);
831 return;
832 }
833
834 /* move a single vertex */
835
836 BA_Begin();
837
838 int did_split_line = -1;
839
840 // handle a single vertex merging onto an existing one
841 if (edit.highlight.valid())
842 {
843 BA_Message("merge vertex #%d", obj.num);
844
845 SYS_ASSERT(obj.num != edit.highlight.num);
846
847 selection_c verts(OBJ_VERTICES);
848
849 verts.set(edit.highlight.num); // keep the highlight
850 verts.set(obj.num);
851
852 Vertex_MergeList(&verts);
853
854 BA_End();
855 return;
856 }
857
858 // handle a single vertex splitting a linedef
859 if (edit.split_line.valid())
860 {
861 did_split_line = edit.split_line.num;
862
863 SplitLineDefAtVertex(edit.split_line.num, obj.num);
864
865 // now move the vertex!
866 }
867
868 selection_c list(edit.mode);
869
870 list.set(obj.num);
871
872 DoMoveObjects(&list, delta_x, delta_y, delta_z);
873
874 if (did_split_line >= 0)
875 BA_Message("split linedef #%d", did_split_line);
876 else
877 BA_MessageForSel("moved", &list);
878
879 BA_End();
880 }
881
882
TransferThingProperties(int src_thing,int dest_thing)883 static void TransferThingProperties(int src_thing, int dest_thing)
884 {
885 const Thing * T = Things[src_thing];
886
887 BA_ChangeTH(dest_thing, Thing::F_TYPE, T->type);
888 BA_ChangeTH(dest_thing, Thing::F_OPTIONS, T->options);
889 // BA_ChangeTH(dest_thing, Thing::F_ANGLE, T->angle);
890
891 BA_ChangeTH(dest_thing, Thing::F_TID, T->tid);
892 BA_ChangeTH(dest_thing, Thing::F_SPECIAL, T->special);
893
894 BA_ChangeTH(dest_thing, Thing::F_ARG1, T->arg1);
895 BA_ChangeTH(dest_thing, Thing::F_ARG2, T->arg2);
896 BA_ChangeTH(dest_thing, Thing::F_ARG3, T->arg3);
897 BA_ChangeTH(dest_thing, Thing::F_ARG4, T->arg4);
898 BA_ChangeTH(dest_thing, Thing::F_ARG5, T->arg5);
899 }
900
901
TransferSectorProperties(int src_sec,int dest_sec)902 static void TransferSectorProperties(int src_sec, int dest_sec)
903 {
904 const Sector * SEC = Sectors[src_sec];
905
906 BA_ChangeSEC(dest_sec, Sector::F_FLOORH, SEC->floorh);
907 BA_ChangeSEC(dest_sec, Sector::F_FLOOR_TEX, SEC->floor_tex);
908 BA_ChangeSEC(dest_sec, Sector::F_CEILH, SEC->ceilh);
909 BA_ChangeSEC(dest_sec, Sector::F_CEIL_TEX, SEC->ceil_tex);
910
911 BA_ChangeSEC(dest_sec, Sector::F_LIGHT, SEC->light);
912 BA_ChangeSEC(dest_sec, Sector::F_TYPE, SEC->type);
913 BA_ChangeSEC(dest_sec, Sector::F_TAG, SEC->tag);
914 }
915
916
917 #define LINEDEF_FLAG_KEEP (MLF_Blocking + MLF_TwoSided)
918
TransferLinedefProperties(int src_line,int dest_line,bool do_tex)919 static void TransferLinedefProperties(int src_line, int dest_line, bool do_tex)
920 {
921 const LineDef * L1 = LineDefs[src_line];
922 const LineDef * L2 = LineDefs[dest_line];
923
924 // don't transfer certain flags
925 int flags = LineDefs[dest_line]->flags;
926 flags = (flags & LINEDEF_FLAG_KEEP) | (L1->flags & ~LINEDEF_FLAG_KEEP);
927
928 // handle textures
929 if (do_tex && L1->Right() && L2->Right())
930 {
931 /* There are four cases, depending on number of sides:
932 *
933 * (a) single --> single : easy
934 *
935 * (b) single --> double : copy mid_tex to both sides upper and lower
936 * [alternate idea: copy mid_tex to VISIBLE sides]
937 *
938 * (c) double --> single : pick a texture (e.g. visible lower) to copy
939 *
940 * (d) double --> double : copy each side, but possibly flip the
941 * second linedef based on floor or ceil diff.
942 */
943 if (! L1->Left())
944 {
945 int tex = L1->Right()->mid_tex;
946
947 if (! L2->Left())
948 {
949 BA_ChangeSD(L2->right, SideDef::F_MID_TEX, tex);
950 }
951 else
952 {
953 BA_ChangeSD(L2->right, SideDef::F_LOWER_TEX, tex);
954 BA_ChangeSD(L2->right, SideDef::F_UPPER_TEX, tex);
955
956 BA_ChangeSD(L2->left, SideDef::F_LOWER_TEX, tex);
957 BA_ChangeSD(L2->left, SideDef::F_UPPER_TEX, tex);
958
959 // this is debatable.... CONFIG ITEM?
960 flags |= MLF_LowerUnpegged;
961 flags |= MLF_UpperUnpegged;
962 }
963 }
964 else if (! L2->Left())
965 {
966 /* pick which texture to copy */
967
968 const Sector *front = L1->Right()->SecRef();
969 const Sector *back = L1-> Left()->SecRef();
970
971 int f_l = L1->Right()->lower_tex;
972 int f_u = L1->Right()->upper_tex;
973 int b_l = L1-> Left()->lower_tex;
974 int b_u = L1-> Left()->upper_tex;
975
976 // ignore missing textures
977 if (is_null_tex(BA_GetString(f_l))) f_l = 0;
978 if (is_null_tex(BA_GetString(f_u))) f_u = 0;
979 if (is_null_tex(BA_GetString(b_l))) b_l = 0;
980 if (is_null_tex(BA_GetString(b_u))) b_u = 0;
981
982 // try hard to find a usable texture
983 int tex = -1;
984
985 if (front->floorh < back->floorh && f_l > 0) tex = f_l;
986 else if (front->floorh > back->floorh && b_l > 0) tex = b_l;
987 else if (front-> ceilh > back-> ceilh && f_u > 0) tex = f_u;
988 else if (front-> ceilh < back-> ceilh && b_u > 0) tex = b_u;
989 else if (f_l > 0) tex = f_l;
990 else if (b_l > 0) tex = b_l;
991 else if (f_u > 0) tex = f_u;
992 else if (b_u > 0) tex = b_u;
993
994 if (tex > 0)
995 {
996 BA_ChangeSD(L2->right, SideDef::F_MID_TEX, tex);
997 }
998 }
999 else
1000 {
1001 const SideDef *RS = L1->Right();
1002 const SideDef *LS = L1->Left();
1003
1004 const Sector *F1 = L1->Right()->SecRef();
1005 const Sector *B1 = L1-> Left()->SecRef();
1006 const Sector *F2 = L2->Right()->SecRef();
1007 const Sector *B2 = L2-> Left()->SecRef();
1008
1009 // logic to determine which sides we copy
1010
1011 int f_diff1 = B1->floorh - F1->floorh;
1012 int f_diff2 = B2->floorh - F2->floorh;
1013 int c_diff1 = B1->ceilh - F1->ceilh;
1014 int c_diff2 = B2->ceilh - F2->ceilh;
1015
1016 if (f_diff1 * f_diff2 > 0)
1017 { /* no change */ }
1018 else if (f_diff1 * f_diff2 < 0)
1019 std::swap(LS, RS);
1020 else if (c_diff1 * c_diff2 > 0)
1021 { /* no change */ }
1022 else if (c_diff1 * c_diff2 < 0)
1023 std::swap(LS, RS);
1024 else if (L1->start == L2->end || L1->end == L2->start)
1025 { /* no change */ }
1026 else if (L1->start == L2->start || L1->end == L2->end)
1027 std::swap(LS, RS);
1028 else if (F1 == F2 || B1 == B2)
1029 { /* no change */ }
1030 else if (F1 == B1 || F1 == B2 || F2 == B1 || F2 == B2)
1031 std::swap(LS, RS);
1032
1033 // TODO; review if we should copy '-' into lowers or uppers
1034
1035 BA_ChangeSD(L2->right, SideDef::F_LOWER_TEX, RS->lower_tex);
1036 BA_ChangeSD(L2->right, SideDef::F_MID_TEX, RS->mid_tex);
1037 BA_ChangeSD(L2->right, SideDef::F_UPPER_TEX, RS->upper_tex);
1038
1039 BA_ChangeSD(L2->left, SideDef::F_LOWER_TEX, LS->lower_tex);
1040 BA_ChangeSD(L2->left, SideDef::F_MID_TEX, LS->mid_tex);
1041 BA_ChangeSD(L2->left, SideDef::F_UPPER_TEX, LS->upper_tex);
1042 }
1043 }
1044
1045 BA_ChangeLD(dest_line, LineDef::F_FLAGS, flags);
1046
1047 BA_ChangeLD(dest_line, LineDef::F_TYPE, L1->type);
1048 BA_ChangeLD(dest_line, LineDef::F_TAG, L1->tag);
1049
1050 BA_ChangeLD(dest_line, LineDef::F_ARG2, L1->arg2);
1051 BA_ChangeLD(dest_line, LineDef::F_ARG3, L1->arg3);
1052 BA_ChangeLD(dest_line, LineDef::F_ARG4, L1->arg4);
1053 BA_ChangeLD(dest_line, LineDef::F_ARG5, L1->arg5);
1054 }
1055
1056
CMD_CopyProperties()1057 void CMD_CopyProperties()
1058 {
1059 if (edit.highlight.is_nil())
1060 {
1061 Beep("No target for CopyProperties");
1062 return;
1063 }
1064 else if (edit.Selected->empty())
1065 {
1066 Beep("No source for CopyProperties");
1067 return;
1068 }
1069 else if (edit.mode == OBJ_VERTICES)
1070 {
1071 Beep("No properties to copy");
1072 return;
1073 }
1074
1075
1076 /* normal mode, SEL --> HILITE */
1077
1078 if (! Exec_HasFlag("/reverse"))
1079 {
1080 if (edit.Selected->count_obj() != 1)
1081 {
1082 Beep("Too many sources for CopyProperties");
1083 return;
1084 }
1085
1086 int source = edit.Selected->find_first();
1087 int target = edit.highlight.num;
1088
1089 // silently allow copying onto self
1090 if (source == target)
1091 return;
1092
1093 BA_Begin();
1094 BA_Message("copied properties");
1095
1096 switch (edit.mode)
1097 {
1098 case OBJ_SECTORS:
1099 TransferSectorProperties(source, target);
1100 break;
1101
1102 case OBJ_THINGS:
1103 TransferThingProperties(source, target);
1104 break;
1105
1106 case OBJ_LINEDEFS:
1107 TransferLinedefProperties(source, target, true /* do_tex */);
1108 break;
1109
1110 default: break;
1111 }
1112
1113 BA_End();
1114
1115 }
1116 else /* reverse mode, HILITE --> SEL */
1117 {
1118 if (edit.Selected->count_obj() == 1 && edit.Selected->find_first() == edit.highlight.num)
1119 {
1120 Beep("No selection for CopyProperties");
1121 return;
1122 }
1123
1124 int source = edit.highlight.num;
1125
1126 BA_Begin();
1127 BA_Message("copied properties");
1128
1129 for (sel_iter_c it(edit.Selected) ; !it.done() ; it.next())
1130 {
1131 if (*it == source)
1132 continue;
1133
1134 switch (edit.mode)
1135 {
1136 case OBJ_SECTORS:
1137 TransferSectorProperties(source, *it);
1138 break;
1139
1140 case OBJ_THINGS:
1141 TransferThingProperties(source, *it);
1142 break;
1143
1144 case OBJ_LINEDEFS:
1145 TransferLinedefProperties(source, *it, true /* do_tex */);
1146 break;
1147
1148 default: break;
1149 }
1150 }
1151
1152 BA_End();
1153 }
1154 }
1155
1156
Drag_CountOnGrid_Worker(int obj_type,int objnum,int * count,int * total)1157 static void Drag_CountOnGrid_Worker(int obj_type, int objnum, int *count, int *total)
1158 {
1159 switch (obj_type)
1160 {
1161 case OBJ_THINGS:
1162 *total += 1;
1163 if (grid.OnGrid(Things[objnum]->x(), Things[objnum]->y()))
1164 *count += 1;
1165 break;
1166
1167 case OBJ_VERTICES:
1168 *total += 1;
1169 if (grid.OnGrid(Vertices[objnum]->x(), Vertices[objnum]->y()))
1170 *count += 1;
1171 break;
1172
1173 case OBJ_LINEDEFS:
1174 Drag_CountOnGrid_Worker(OBJ_VERTICES, LineDefs[objnum]->start, count, total);
1175 Drag_CountOnGrid_Worker(OBJ_VERTICES, LineDefs[objnum]->end, count, total);
1176 break;
1177
1178 case OBJ_SECTORS:
1179 for (int n = 0 ; n < NumLineDefs ; n++)
1180 {
1181 LineDef *L = LineDefs[n];
1182
1183 if (! L->TouchesSector(objnum))
1184 continue;
1185
1186 Drag_CountOnGrid_Worker(OBJ_LINEDEFS, n, count, total);
1187 }
1188 break;
1189
1190 default:
1191 break;
1192 }
1193 }
1194
1195
Drag_CountOnGrid(int * count,int * total)1196 static void Drag_CountOnGrid(int *count, int *total)
1197 {
1198 // Note: the results are approximate, vertices can be counted two
1199 // or more times.
1200
1201 for (sel_iter_c it(edit.Selected) ; !it.done() ; it.next())
1202 {
1203 Drag_CountOnGrid_Worker(edit.mode, *it, count, total);
1204 }
1205 }
1206
1207
Drag_UpdateCurrentDist(int obj_type,int objnum,double * x,double * y,double * best_dist,double ptr_x,double ptr_y,bool only_grid)1208 static void Drag_UpdateCurrentDist(int obj_type, int objnum, double *x, double *y,
1209 double *best_dist, double ptr_x, double ptr_y,
1210 bool only_grid)
1211 {
1212 double x2, y2;
1213
1214 switch (obj_type)
1215 {
1216 case OBJ_THINGS:
1217 x2 = Things[objnum]->x();
1218 y2 = Things[objnum]->y();
1219 break;
1220
1221 case OBJ_VERTICES:
1222 x2 = Vertices[objnum]->x();
1223 y2 = Vertices[objnum]->y();
1224 break;
1225
1226 case OBJ_LINEDEFS:
1227 {
1228 LineDef *L = LineDefs[objnum];
1229
1230 Drag_UpdateCurrentDist(OBJ_VERTICES, L->start, x, y, best_dist,
1231 ptr_x, ptr_y, only_grid);
1232
1233 Drag_UpdateCurrentDist(OBJ_VERTICES, L->end, x, y, best_dist,
1234 ptr_x, ptr_y, only_grid);
1235 }
1236 return;
1237
1238 case OBJ_SECTORS:
1239 // recursively handle all vertices belonging to the sector
1240 // (some vertices can be processed two or more times, that
1241 // won't matter though).
1242
1243 for (int n = 0 ; n < NumLineDefs ; n++)
1244 {
1245 LineDef *L = LineDefs[n];
1246
1247 if (! L->TouchesSector(objnum))
1248 continue;
1249
1250 Drag_UpdateCurrentDist(OBJ_LINEDEFS, n, x, y, best_dist,
1251 ptr_x, ptr_y, only_grid);
1252 }
1253 return;
1254
1255 default:
1256 return;
1257 }
1258
1259 // handle OBJ_THINGS and OBJ_VERTICES
1260
1261 if (only_grid && !grid.OnGrid(x2, y2))
1262 return;
1263
1264 double dist = hypot(x2 - ptr_x, y2 - ptr_y);
1265
1266 if (dist < *best_dist)
1267 {
1268 *x = x2;
1269 *y = y2;
1270 *best_dist = dist;
1271 }
1272 }
1273
1274
1275 //
1276 // Determine the focus coordinate for dragging multiple objects.
1277 // The focus only has an effect when grid snapping is on, and
1278 // allows a mostly-grid-snapped set of objects to stay snapped
1279 // to the grid.
1280 //
GetDragFocus(double * x,double * y,double ptr_x,double ptr_y)1281 void GetDragFocus(double *x, double *y, double ptr_x, double ptr_y)
1282 {
1283 *x = 0;
1284 *y = 0;
1285
1286 // determine whether a majority of the object(s) are already on
1287 // the grid. If they are, then pick a coordinate that also lies
1288 // on the grid.
1289 bool only_grid = false;
1290
1291 int count = 0;
1292 int total = 0;
1293
1294 if (grid.snap)
1295 {
1296 Drag_CountOnGrid(&count, &total);
1297
1298 if (total > 0 && count > total / 2)
1299 only_grid = true;
1300 }
1301
1302 // determine object which is closest to mouse pointer AND which
1303 // honors the 'only_grid' property (when set).
1304 double best_dist = 9e9;
1305
1306 if (edit.dragged.valid()) // a single object
1307 {
1308 Drag_UpdateCurrentDist(edit.mode, edit.dragged.num, x, y, &best_dist,
1309 ptr_x, ptr_y, only_grid);
1310 return;
1311 }
1312
1313 for (sel_iter_c it(edit.Selected) ; !it.done() ; it.next())
1314 {
1315 Drag_UpdateCurrentDist(edit.mode, *it, x, y, &best_dist,
1316 ptr_x, ptr_y, only_grid);
1317 }
1318 }
1319
1320
1321 //------------------------------------------------------------------------
1322
1323
Clear()1324 void transform_t::Clear()
1325 {
1326 mid_x = mid_y = 0;
1327 scale_x = scale_y = 1;
1328 skew_x = skew_y = 0;
1329 rotate = 0;
1330 }
1331
1332
Apply(double * x,double * y) const1333 void transform_t::Apply(double *x, double *y) const
1334 {
1335 double x0 = *x - mid_x;
1336 double y0 = *y - mid_y;
1337
1338 if (rotate)
1339 {
1340 double s = sin(rotate * M_PI / 32768.0);
1341 double c = cos(rotate * M_PI / 32768.0);
1342
1343 double x1 = x0;
1344 double y1 = y0;
1345
1346 x0 = x1 * c - y1 * s;
1347 y0 = y1 * c + x1 * s;
1348 }
1349
1350 if (skew_x || skew_y)
1351 {
1352 double x1 = x0;
1353 double y1 = y0;
1354
1355 x0 = x1 + y1 * skew_x;
1356 y0 = y1 + x1 * skew_y;
1357 }
1358
1359 *x = mid_x + x0 * scale_x;
1360 *y = mid_y + y0 * scale_y;
1361 }
1362
1363
1364 //
1365 // Return the coordinate of the centre of a group of objects.
1366 //
1367 // This is computed using an average of all the coordinates, which can
1368 // often give a different result than using the middle of the bounding
1369 // box.
1370 //
Objs_CalcMiddle(selection_c * list,double * x,double * y)1371 void Objs_CalcMiddle(selection_c * list, double *x, double *y)
1372 {
1373 *x = *y = 0;
1374
1375 if (list->empty())
1376 return;
1377
1378 double sum_x = 0;
1379 double sum_y = 0;
1380
1381 int count = 0;
1382
1383 switch (list->what_type())
1384 {
1385 case OBJ_THINGS:
1386 {
1387 for (sel_iter_c it(list) ; !it.done() ; it.next(), ++count)
1388 {
1389 sum_x += Things[*it]->x();
1390 sum_y += Things[*it]->y();
1391 }
1392 break;
1393 }
1394
1395 case OBJ_VERTICES:
1396 {
1397 for (sel_iter_c it(list) ; !it.done() ; it.next(), ++count)
1398 {
1399 sum_x += Vertices[*it]->x();
1400 sum_y += Vertices[*it]->y();
1401 }
1402 break;
1403 }
1404
1405 // everything else: just use the vertices
1406 default:
1407 {
1408 selection_c verts(OBJ_VERTICES);
1409 ConvertSelection(list, &verts);
1410
1411 Objs_CalcMiddle(&verts, x, y);
1412 return;
1413 }
1414 }
1415
1416 SYS_ASSERT(count > 0);
1417
1418 *x = sum_x / count;
1419 *y = sum_y / count;
1420 }
1421
1422
1423 //
1424 // returns a bounding box that completely includes a list of objects.
1425 // when the list is empty, bottom-left coordinate is arbitrary.
1426 //
Objs_CalcBBox(selection_c * list,double * x1,double * y1,double * x2,double * y2)1427 void Objs_CalcBBox(selection_c * list, double *x1, double *y1, double *x2, double *y2)
1428 {
1429 if (list->empty())
1430 {
1431 *x1 = *y1 = 0;
1432 *x2 = *y2 = 0;
1433 return;
1434 }
1435
1436 *x1 = *y1 = +9e9;
1437 *x2 = *y2 = -9e9;
1438
1439 switch (list->what_type())
1440 {
1441 case OBJ_THINGS:
1442 {
1443 for (sel_iter_c it(list) ; !it.done() ; it.next())
1444 {
1445 const Thing *T = Things[*it];
1446 double Tx = T->x();
1447 double Ty = T->y();
1448
1449 const thingtype_t *info = M_GetThingType(T->type);
1450 int r = info->radius;
1451
1452 if (Tx - r < *x1) *x1 = Tx - r;
1453 if (Ty - r < *y1) *y1 = Ty - r;
1454 if (Tx + r > *x2) *x2 = Tx + r;
1455 if (Ty + r > *y2) *y2 = Ty + r;
1456 }
1457 break;
1458 }
1459
1460 case OBJ_VERTICES:
1461 {
1462 for (sel_iter_c it(list) ; !it.done() ; it.next())
1463 {
1464 const Vertex *V = Vertices[*it];
1465 double Vx = V->x();
1466 double Vy = V->y();
1467
1468 if (Vx < *x1) *x1 = Vx;
1469 if (Vy < *y1) *y1 = Vy;
1470 if (Vx > *x2) *x2 = Vx;
1471 if (Vy > *y2) *y2 = Vy;
1472 }
1473 break;
1474 }
1475
1476 // everything else: just use the vertices
1477 default:
1478 {
1479 selection_c verts(OBJ_VERTICES);
1480 ConvertSelection(list, &verts);
1481
1482 Objs_CalcBBox(&verts, x1, y1, x2, y2);
1483 return;
1484 }
1485 }
1486
1487 SYS_ASSERT(*x1 <= *x2);
1488 SYS_ASSERT(*y1 <= *y2);
1489 }
1490
1491
DoMirrorThings(selection_c * list,bool is_vert,double mid_x,double mid_y)1492 static void DoMirrorThings(selection_c *list, bool is_vert, double mid_x, double mid_y)
1493 {
1494 fixcoord_t fix_mx = MakeValidCoord(mid_x);
1495 fixcoord_t fix_my = MakeValidCoord(mid_y);
1496
1497 for (sel_iter_c it(list) ; !it.done() ; it.next())
1498 {
1499 const Thing * T = Things[*it];
1500
1501 if (is_vert)
1502 {
1503 BA_ChangeTH(*it, Thing::F_Y, 2*fix_my - T->raw_y);
1504
1505 if (T->angle != 0)
1506 BA_ChangeTH(*it, Thing::F_ANGLE, 360 - T->angle);
1507 }
1508 else
1509 {
1510 BA_ChangeTH(*it, Thing::F_X, 2*fix_mx - T->raw_x);
1511
1512 if (T->angle > 180)
1513 BA_ChangeTH(*it, Thing::F_ANGLE, 540 - T->angle);
1514 else
1515 BA_ChangeTH(*it, Thing::F_ANGLE, 180 - T->angle);
1516 }
1517 }
1518 }
1519
1520
DoMirrorVertices(selection_c * list,bool is_vert,double mid_x,double mid_y)1521 static void DoMirrorVertices(selection_c *list, bool is_vert, double mid_x, double mid_y)
1522 {
1523 fixcoord_t fix_mx = MakeValidCoord(mid_x);
1524 fixcoord_t fix_my = MakeValidCoord(mid_y);
1525
1526 selection_c verts(OBJ_VERTICES);
1527 ConvertSelection(list, &verts);
1528
1529 for (sel_iter_c it(verts) ; !it.done() ; it.next())
1530 {
1531 const Vertex * V = Vertices[*it];
1532
1533 if (is_vert)
1534 BA_ChangeVT(*it, Vertex::F_Y, 2*fix_my - V->raw_y);
1535 else
1536 BA_ChangeVT(*it, Vertex::F_X, 2*fix_mx - V->raw_x);
1537 }
1538
1539 // flip linedefs too !!
1540 selection_c lines(OBJ_LINEDEFS);
1541 ConvertSelection(&verts, &lines);
1542
1543 for (sel_iter_c it(lines) ; !it.done() ; it.next())
1544 {
1545 LineDef * L = LineDefs[*it];
1546
1547 int start = L->start;
1548 int end = L->end;
1549
1550 BA_ChangeLD(*it, LineDef::F_START, end);
1551 BA_ChangeLD(*it, LineDef::F_END, start);
1552 }
1553 }
1554
1555
DoMirrorStuff(selection_c * list,bool is_vert,double mid_x,double mid_y)1556 static void DoMirrorStuff(selection_c *list, bool is_vert, double mid_x, double mid_y)
1557 {
1558 if (edit.mode == OBJ_THINGS)
1559 {
1560 DoMirrorThings(list, is_vert, mid_x, mid_y);
1561 return;
1562 }
1563
1564 // everything else just modifies the vertices
1565
1566 if (edit.mode == OBJ_SECTORS)
1567 {
1568 // handle things in Sectors mode too
1569 selection_c things(OBJ_THINGS);
1570 ConvertSelection(list, &things);
1571
1572 DoMirrorThings(&things, is_vert, mid_x, mid_y);
1573 }
1574
1575 DoMirrorVertices(list, is_vert, mid_x, mid_y);
1576 }
1577
1578
CMD_Mirror()1579 void CMD_Mirror()
1580 {
1581 soh_type_e unselect = Selection_Or_Highlight();
1582 if (unselect == SOH_Empty)
1583 {
1584 Beep("No objects to mirror");
1585 return;
1586 }
1587
1588 bool is_vert = false;
1589
1590 if (tolower(EXEC_Param[0][0]) == 'v')
1591 is_vert = true;
1592
1593 double mid_x, mid_y;
1594 Objs_CalcMiddle(edit.Selected, &mid_x, &mid_y);
1595
1596 BA_Begin();
1597 BA_MessageForSel("mirrored", edit.Selected, is_vert ? " vertically" : " horizontally");
1598
1599 DoMirrorStuff(edit.Selected, is_vert, mid_x, mid_y);
1600
1601 BA_End();
1602
1603 if (unselect == SOH_Unselect)
1604 Selection_Clear(true /* nosave */);
1605 }
1606
1607
DoRotate90Things(selection_c * list,bool anti_clockwise,double mid_x,double mid_y)1608 static void DoRotate90Things(selection_c *list, bool anti_clockwise,
1609 double mid_x, double mid_y)
1610 {
1611 fixcoord_t fix_mx = MakeValidCoord(mid_x);
1612 fixcoord_t fix_my = MakeValidCoord(mid_y);
1613
1614 for (sel_iter_c it(list) ; !it.done() ; it.next())
1615 {
1616 const Thing * T = Things[*it];
1617
1618 fixcoord_t old_x = T->raw_x;
1619 fixcoord_t old_y = T->raw_y;
1620
1621 if (anti_clockwise)
1622 {
1623 BA_ChangeTH(*it, Thing::F_X, fix_mx - old_y + fix_my);
1624 BA_ChangeTH(*it, Thing::F_Y, fix_my + old_x - fix_mx);
1625
1626 BA_ChangeTH(*it, Thing::F_ANGLE, calc_new_angle(T->angle, +90));
1627 }
1628 else
1629 {
1630 BA_ChangeTH(*it, Thing::F_X, fix_mx + old_y - fix_my);
1631 BA_ChangeTH(*it, Thing::F_Y, fix_my - old_x + fix_mx);
1632
1633 BA_ChangeTH(*it, Thing::F_ANGLE, calc_new_angle(T->angle, -90));
1634 }
1635 }
1636 }
1637
1638
CMD_Rotate90()1639 void CMD_Rotate90()
1640 {
1641 if (EXEC_Param[0] == 0)
1642 {
1643 Beep("Rotate90: missing keyword");
1644 return;
1645 }
1646
1647 bool anti_clockwise = (tolower(EXEC_Param[0][0]) == 'a');
1648
1649 soh_type_e unselect = Selection_Or_Highlight();
1650 if (unselect == SOH_Empty)
1651 {
1652 Beep("No objects to rotate");
1653 return;
1654 }
1655
1656 double mid_x, mid_y;
1657 Objs_CalcMiddle(edit.Selected, &mid_x, &mid_y);
1658
1659 BA_Begin();
1660 BA_MessageForSel("rotated", edit.Selected, anti_clockwise ? " anti-clockwise" : " clockwise");
1661
1662 if (edit.mode == OBJ_THINGS)
1663 {
1664 DoRotate90Things(edit.Selected, anti_clockwise, mid_x, mid_y);
1665 }
1666 else
1667 {
1668 // handle things inside sectors
1669 if (edit.mode == OBJ_SECTORS)
1670 {
1671 selection_c things(OBJ_THINGS);
1672 ConvertSelection(edit.Selected, &things);
1673
1674 DoRotate90Things(&things, anti_clockwise, mid_x, mid_y);
1675 }
1676
1677 // everything else just rotates the vertices
1678 selection_c verts(OBJ_VERTICES);
1679 ConvertSelection(edit.Selected, &verts);
1680
1681 fixcoord_t fix_mx = MakeValidCoord(mid_x);
1682 fixcoord_t fix_my = MakeValidCoord(mid_y);
1683
1684 for (sel_iter_c it(verts) ; !it.done() ; it.next())
1685 {
1686 const Vertex * V = Vertices[*it];
1687
1688 fixcoord_t old_x = V->raw_x;
1689 fixcoord_t old_y = V->raw_y;
1690
1691 if (anti_clockwise)
1692 {
1693 BA_ChangeVT(*it, Vertex::F_X, fix_mx - old_y + fix_my);
1694 BA_ChangeVT(*it, Vertex::F_Y, fix_my + old_x - fix_mx);
1695 }
1696 else
1697 {
1698 BA_ChangeVT(*it, Vertex::F_X, fix_mx + old_y - fix_my);
1699 BA_ChangeVT(*it, Vertex::F_Y, fix_my - old_x + fix_mx);
1700 }
1701 }
1702 }
1703
1704 BA_End();
1705
1706 if (unselect == SOH_Unselect)
1707 Selection_Clear(true /* nosave */);
1708 }
1709
1710
DoScaleTwoThings(selection_c * list,transform_t & param)1711 static void DoScaleTwoThings(selection_c *list, transform_t& param)
1712 {
1713 for (sel_iter_c it(list) ; !it.done() ; it.next())
1714 {
1715 const Thing * T = Things[*it];
1716
1717 double new_x = T->x();
1718 double new_y = T->y();
1719
1720 param.Apply(&new_x, &new_y);
1721
1722 BA_ChangeTH(*it, Thing::F_X, MakeValidCoord(new_x));
1723 BA_ChangeTH(*it, Thing::F_Y, MakeValidCoord(new_y));
1724
1725 float rot1 = param.rotate / 8192.0;
1726
1727 int ang_diff = I_ROUND(rot1) * 45.0;
1728
1729 if (ang_diff)
1730 {
1731 BA_ChangeTH(*it, Thing::F_ANGLE, calc_new_angle(T->angle, ang_diff));
1732 }
1733 }
1734 }
1735
1736
DoScaleTwoVertices(selection_c * list,transform_t & param)1737 static void DoScaleTwoVertices(selection_c *list, transform_t& param)
1738 {
1739 selection_c verts(OBJ_VERTICES);
1740 ConvertSelection(list, &verts);
1741
1742 for (sel_iter_c it(verts) ; !it.done() ; it.next())
1743 {
1744 const Vertex * V = Vertices[*it];
1745
1746 double new_x = V->x();
1747 double new_y = V->y();
1748
1749 param.Apply(&new_x, &new_y);
1750
1751 BA_ChangeVT(*it, Vertex::F_X, MakeValidCoord(new_x));
1752 BA_ChangeVT(*it, Vertex::F_Y, MakeValidCoord(new_y));
1753 }
1754 }
1755
1756
DoScaleTwoStuff(selection_c * list,transform_t & param)1757 static void DoScaleTwoStuff(selection_c *list, transform_t& param)
1758 {
1759 if (edit.mode == OBJ_THINGS)
1760 {
1761 DoScaleTwoThings(list, param);
1762 return;
1763 }
1764
1765 // everything else just modifies the vertices
1766
1767 if (edit.mode == OBJ_SECTORS)
1768 {
1769 // handle things in Sectors mode too
1770 selection_c things(OBJ_THINGS);
1771 ConvertSelection(list, &things);
1772
1773 DoScaleTwoThings(&things, param);
1774 }
1775
1776 DoScaleTwoVertices(list, param);
1777 }
1778
1779
TransformObjects(transform_t & param)1780 void TransformObjects(transform_t& param)
1781 {
1782 // this is called by the MOUSE2 dynamic scaling code
1783
1784 SYS_ASSERT(edit.Selected->notempty());
1785
1786 BA_Begin();
1787 BA_MessageForSel("scaled", edit.Selected);
1788
1789 if (param.scale_x < 0)
1790 {
1791 param.scale_x = -param.scale_x;
1792 DoMirrorStuff(edit.Selected, false /* is_vert */, param.mid_x, param.mid_y);
1793 }
1794
1795 if (param.scale_y < 0)
1796 {
1797 param.scale_y = -param.scale_y;
1798 DoMirrorStuff(edit.Selected, true /* is_vert */, param.mid_x, param.mid_y);
1799 }
1800
1801 DoScaleTwoStuff(edit.Selected, param);
1802
1803 BA_End();
1804 }
1805
1806
DetermineOrigin(transform_t & param,double pos_x,double pos_y)1807 static void DetermineOrigin(transform_t& param, double pos_x, double pos_y)
1808 {
1809 if (pos_x == 0 && pos_y == 0)
1810 {
1811 Objs_CalcMiddle(edit.Selected, ¶m.mid_x, ¶m.mid_y);
1812 return;
1813 }
1814
1815 double lx, ly, hx, hy;
1816
1817 Objs_CalcBBox(edit.Selected, &lx, &ly, &hx, &hy);
1818
1819 if (pos_x < 0)
1820 param.mid_x = lx;
1821 else if (pos_x > 0)
1822 param.mid_x = hx;
1823 else
1824 param.mid_x = lx + (hx - lx) / 2;
1825
1826 if (pos_y < 0)
1827 param.mid_y = ly;
1828 else if (pos_y > 0)
1829 param.mid_y = hy;
1830 else
1831 param.mid_y = ly + (hy - ly) / 2;
1832 }
1833
1834
ScaleObjects3(double scale_x,double scale_y,double pos_x,double pos_y)1835 void ScaleObjects3(double scale_x, double scale_y, double pos_x, double pos_y)
1836 {
1837 SYS_ASSERT(scale_x > 0);
1838 SYS_ASSERT(scale_y > 0);
1839
1840 transform_t param;
1841
1842 param.Clear();
1843
1844 param.scale_x = scale_x;
1845 param.scale_y = scale_y;
1846
1847 DetermineOrigin(param, pos_x, pos_y);
1848
1849 BA_Begin();
1850 BA_MessageForSel("scaled", edit.Selected);
1851 {
1852 DoScaleTwoStuff(edit.Selected, param);
1853 }
1854 BA_End();
1855 }
1856
1857
DoScaleSectorHeights(selection_c * list,double scale_z,int pos_z)1858 static void DoScaleSectorHeights(selection_c *list, double scale_z, int pos_z)
1859 {
1860 SYS_ASSERT(! list->empty());
1861
1862 // determine Z range and origin
1863 int lz = +99999;
1864 int hz = -99999;
1865
1866 for (sel_iter_c it(list) ; !it.done() ; it.next())
1867 {
1868 const Sector * S = Sectors[*it];
1869
1870 lz = MIN(lz, S->floorh);
1871 hz = MAX(hz, S->ceilh);
1872 }
1873
1874 int mid_z;
1875
1876 if (pos_z < 0)
1877 mid_z = lz;
1878 else if (pos_z > 0)
1879 mid_z = hz;
1880 else
1881 mid_z = lz + (hz - lz) / 2;
1882
1883 // apply the scaling
1884
1885 for (sel_iter_c it(list) ; !it.done() ; it.next())
1886 {
1887 const Sector * S = Sectors[*it];
1888
1889 int new_f = mid_z + I_ROUND((S->floorh - mid_z) * scale_z);
1890 int new_c = mid_z + I_ROUND((S-> ceilh - mid_z) * scale_z);
1891
1892 BA_ChangeSEC(*it, Sector::F_FLOORH, new_f);
1893 BA_ChangeSEC(*it, Sector::F_CEILH, new_c);
1894 }
1895 }
1896
ScaleObjects4(double scale_x,double scale_y,double scale_z,double pos_x,double pos_y,double pos_z)1897 void ScaleObjects4(double scale_x, double scale_y, double scale_z,
1898 double pos_x, double pos_y, double pos_z)
1899 {
1900 SYS_ASSERT(edit.mode == OBJ_SECTORS);
1901
1902 transform_t param;
1903
1904 param.Clear();
1905
1906 param.scale_x = scale_x;
1907 param.scale_y = scale_y;
1908
1909 DetermineOrigin(param, pos_x, pos_y);
1910
1911 BA_Begin();
1912 BA_MessageForSel("scaled", edit.Selected);
1913 {
1914 DoScaleTwoStuff(edit.Selected, param);
1915 DoScaleSectorHeights(edit.Selected, scale_z, pos_z);
1916 }
1917 BA_End();
1918 }
1919
1920
RotateObjects3(double deg,double pos_x,double pos_y)1921 void RotateObjects3(double deg, double pos_x, double pos_y)
1922 {
1923 transform_t param;
1924
1925 param.Clear();
1926
1927 param.rotate = I_ROUND(deg * 65536.0 / 360.0);
1928
1929 DetermineOrigin(param, pos_x, pos_y);
1930
1931 BA_Begin();
1932 BA_MessageForSel("rotated", edit.Selected);
1933 {
1934 DoScaleTwoStuff(edit.Selected, param);
1935 }
1936 BA_End();
1937 }
1938
1939
SpotInUse(obj_type_e obj_type,int x,int y)1940 static bool SpotInUse(obj_type_e obj_type, int x, int y)
1941 {
1942 switch (obj_type)
1943 {
1944 case OBJ_THINGS:
1945 for (int n = 0 ; n < NumThings ; n++)
1946 if (I_ROUND(Things[n]->x()) == x && I_ROUND(Things[n]->y()) == y)
1947 return true;
1948 return false;
1949
1950 case OBJ_VERTICES:
1951 for (int n = 0 ; n < NumVertices ; n++)
1952 if (I_ROUND(Vertices[n]->x()) == x && I_ROUND(Vertices[n]->y()) == y)
1953 return true;
1954 return false;
1955
1956 default:
1957 BugError("IsSpotVacant: bad object type\n");
1958 return false;
1959 }
1960 }
1961
1962
DoEnlargeOrShrink(bool do_shrink)1963 static void DoEnlargeOrShrink(bool do_shrink)
1964 {
1965 // setup transform parameters...
1966 float mul = 2.0;
1967
1968 if (EXEC_Param[0][0])
1969 {
1970 mul = atof(EXEC_Param[0]);
1971
1972 if (mul < 0.02 || mul > 50)
1973 {
1974 Beep("bad factor: %s", EXEC_Param[0]);
1975 return;
1976 }
1977 }
1978
1979 if (do_shrink)
1980 mul = 1.0 / mul;
1981
1982 transform_t param;
1983
1984 param.Clear();
1985
1986 param.scale_x = mul;
1987 param.scale_y = mul;
1988
1989
1990 soh_type_e unselect = Selection_Or_Highlight();
1991 if (unselect == SOH_Empty)
1992 {
1993 Beep("No objects to %s", do_shrink ? "shrink" : "enlarge");
1994 return;
1995 }
1996
1997 // TODO: CONFIG ITEM (or FLAG)
1998 if ((true))
1999 {
2000 Objs_CalcMiddle(edit.Selected, ¶m.mid_x, ¶m.mid_y);
2001 }
2002 else
2003 {
2004 double lx, ly, hx, hy;
2005 Objs_CalcBBox(edit.Selected, &lx, &ly, &hx, &hy);
2006
2007 param.mid_x = lx + (hx - lx) / 2;
2008 param.mid_y = ly + (hy - ly) / 2;
2009 }
2010
2011 BA_Begin();
2012 BA_MessageForSel(do_shrink ? "shrunk" : "enlarged", edit.Selected);
2013
2014 DoScaleTwoStuff(edit.Selected, param);
2015
2016 BA_End();
2017
2018 if (unselect == SOH_Unselect)
2019 Selection_Clear(true /* nosave */);
2020 }
2021
2022
CMD_Enlarge()2023 void CMD_Enlarge()
2024 {
2025 DoEnlargeOrShrink(false /* do_shrink */);
2026 }
2027
CMD_Shrink()2028 void CMD_Shrink()
2029 {
2030 DoEnlargeOrShrink(true /* do_shrink */);
2031 }
2032
2033
Quantize_Things(selection_c * list)2034 static void Quantize_Things(selection_c *list)
2035 {
2036 // remember the things which we moved
2037 // (since we cannot modify the selection while we iterate over it)
2038 selection_c moved(list->what_type());
2039
2040 for (sel_iter_c it(list) ; !it.done() ; it.next())
2041 {
2042 const Thing * T = Things[*it];
2043
2044 if (grid.OnGrid(T->x(), T->y()))
2045 {
2046 moved.set(*it);
2047 continue;
2048 }
2049
2050 for (int pass = 0 ; pass < 4 ; pass++)
2051 {
2052 int new_x = grid.QuantSnapX(T->x(), pass & 1);
2053 int new_y = grid.QuantSnapY(T->y(), pass & 2);
2054
2055 if (! SpotInUse(OBJ_THINGS, new_x, new_y))
2056 {
2057 BA_ChangeTH(*it, Thing::F_X, MakeValidCoord(new_x));
2058 BA_ChangeTH(*it, Thing::F_Y, MakeValidCoord(new_y));
2059
2060 moved.set(*it);
2061 break;
2062 }
2063 }
2064 }
2065
2066 list->unmerge(moved);
2067
2068 if (! list->empty())
2069 Beep("Quantize: could not move %d things", list->count_obj());
2070 }
2071
2072
Quantize_Vertices(selection_c * list)2073 static void Quantize_Vertices(selection_c *list)
2074 {
2075 // first : do an analysis pass, remember vertices that are part
2076 // of a horizontal or vertical line (and both in the selection)
2077 // and limit the movement of those vertices to ensure the lines
2078 // stay horizontal or vertical.
2079
2080 enum
2081 {
2082 V_HORIZ = (1 << 0),
2083 V_VERT = (1 << 1),
2084 V_DIAG_NE = (1 << 2),
2085 V_DIAG_SE = (1 << 3)
2086 };
2087
2088 byte * vert_modes = new byte[NumVertices];
2089
2090 for (int n = 0 ; n < NumLineDefs ; n++)
2091 {
2092 const LineDef *L = LineDefs[n];
2093
2094 // require both vertices of the linedef to be in the selection
2095 if (! (list->get(L->start) && list->get(L->end)))
2096 continue;
2097
2098 // IDEA: make this a method of LineDef
2099 double x1 = L->Start()->x();
2100 double y1 = L->Start()->y();
2101 double x2 = L->End()->x();
2102 double y2 = L->End()->y();
2103
2104 if (L->IsHorizontal())
2105 {
2106 vert_modes[L->start] |= V_HORIZ;
2107 vert_modes[L->end] |= V_HORIZ;
2108 }
2109 else if (L->IsVertical())
2110 {
2111 vert_modes[L->start] |= V_VERT;
2112 vert_modes[L->end] |= V_VERT;
2113 }
2114 else if ((x1 < x2 && y1 < y2) || (x1 > x2 && y1 > y2))
2115 {
2116 vert_modes[L->start] |= V_DIAG_NE;
2117 vert_modes[L->end] |= V_DIAG_NE;
2118 }
2119 else
2120 {
2121 vert_modes[L->start] |= V_DIAG_SE;
2122 vert_modes[L->end] |= V_DIAG_SE;
2123 }
2124 }
2125
2126 // remember the vertices which we moved
2127 // (since we cannot modify the selection while we iterate over it)
2128 selection_c moved(list->what_type());
2129
2130 for (sel_iter_c it(list) ; !it.done() ; it.next())
2131 {
2132 const Vertex * V = Vertices[*it];
2133
2134 if (grid.OnGrid(V->x(), V->y()))
2135 {
2136 moved.set(*it);
2137 continue;
2138 }
2139
2140 byte mode = vert_modes[*it];
2141
2142 for (int pass = 0 ; pass < 4 ; pass++)
2143 {
2144 int x_dir, y_dir;
2145
2146 double new_x = grid.QuantSnapX(V->x(), pass & 1, &x_dir);
2147 double new_y = grid.QuantSnapY(V->y(), pass & 2, &y_dir);
2148
2149 // keep horizontal lines horizontal
2150 if ((mode & V_HORIZ) && (pass & 2))
2151 continue;
2152
2153 // keep vertical lines vertical
2154 if ((mode & V_VERT) && (pass & 1))
2155 continue;
2156
2157 // TODO: keep diagonal lines diagonal...
2158
2159 if (! SpotInUse(OBJ_VERTICES, new_x, new_y))
2160 {
2161 BA_ChangeVT(*it, Vertex::F_X, MakeValidCoord(new_x));
2162 BA_ChangeVT(*it, Vertex::F_Y, MakeValidCoord(new_y));
2163
2164 moved.set(*it);
2165 break;
2166 }
2167 }
2168 }
2169
2170 delete[] vert_modes;
2171
2172 list->unmerge(moved);
2173
2174 if (list->notempty())
2175 Beep("Quantize: could not move %d vertices", list->count_obj());
2176 }
2177
2178
CMD_Quantize()2179 void CMD_Quantize()
2180 {
2181 if (edit.Selected->empty())
2182 {
2183 if (edit.highlight.is_nil())
2184 {
2185 Beep("Nothing to quantize");
2186 return;
2187 }
2188
2189 Selection_Add(edit.highlight);
2190 }
2191
2192 BA_Begin();
2193 BA_MessageForSel("quantized", edit.Selected);
2194
2195 switch (edit.mode)
2196 {
2197 case OBJ_THINGS:
2198 Quantize_Things(edit.Selected);
2199 break;
2200
2201 case OBJ_VERTICES:
2202 Quantize_Vertices(edit.Selected);
2203 break;
2204
2205 // everything else merely quantizes vertices
2206 default:
2207 {
2208 selection_c verts(OBJ_VERTICES);
2209 ConvertSelection(edit.Selected, &verts);
2210
2211 Quantize_Vertices(&verts);
2212
2213 Selection_Clear();
2214 break;
2215 }
2216 }
2217
2218 BA_End();
2219
2220 edit.error_mode = true;
2221 }
2222
2223 //--- editor settings ---
2224 // vi:ts=4:sw=4:noexpandtab
2225