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, &param.mid_x, &param.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, &param.mid_x, &param.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