1 //------------------------------------------------------------------------
2 //  LINEDEF PATHS
3 //------------------------------------------------------------------------
4 //
5 //  Eureka DOOM Editor
6 //
7 //  Copyright (C) 2001-2016 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 "m_bitvec.h"
30 #include "e_main.h"
31 #include "e_objects.h"
32 #include "e_path.h"
33 #include "m_game.h"
34 #include "r_grid.h"
35 #include "r_render.h"
36 #include "w_rawdef.h"
37 
38 #include "ui_window.h"
39 #include "ui_misc.h"
40 
41 
42 typedef enum
43 {
44 	SLP_Normal = 0,
45 
46 	SLP_SameTex  = (1 << 1),  // require lines have same textures
47 	SLP_OneSided = (1 << 2),  // only handle one-sided lines
48 }
49 select_lines_in_path_flag_e;
50 
51 
MatchingTextures(int index1,int index2)52 static bool MatchingTextures(int index1, int index2)
53 {
54 	LineDef *L1 = LineDefs[index1];
55 	LineDef *L2 = LineDefs[index2];
56 
57 	// lines with no sidedefs only match each other
58 	if (! L1->Right() || ! L2->Right())
59 		return L1->Right() == L2->Right();
60 
61 	// determine texture to match from first line
62 	int texture = 0;
63 
64 	if (! L1->TwoSided())
65 	{
66 		texture = L1->Right()->mid_tex;
67 	}
68 	else
69 	{
70 		int f_diff = L1->Left()->SecRef()->floorh - L1->Right()->SecRef()->floorh;
71 		int c_diff = L1->Left()->SecRef()->ceilh  - L1->Right()->SecRef()->ceilh;
72 
73 		if (f_diff == 0 && c_diff != 0)
74 			texture = (c_diff > 0) ? L1->Left()->upper_tex : L1->Right()->upper_tex;
75 		else
76 			texture = (f_diff < 0) ? L1->Left()->lower_tex : L1->Right()->lower_tex;
77 	}
78 
79 	// match texture with other line
80 
81 	if (! L2->TwoSided())
82 	{
83 		return (L2->Right()->mid_tex == texture);
84 	}
85 	else
86 	{
87 		int f_diff = L2->Left()->SecRef()->floorh - L2->Right()->SecRef()->floorh;
88 		int c_diff = L2->Left()->SecRef()->ceilh  - L2->Right()->SecRef()->ceilh;
89 
90 		if (c_diff != 0)
91 			if (texture == ((c_diff > 0) ? L2->Left()->upper_tex : L2->Right()->upper_tex))
92 				return true;
93 
94 		if (f_diff != 0)
95 			if (texture == ((f_diff < 0) ? L2->Left()->lower_tex : L2->Right()->lower_tex))
96 				return true;
97 
98 		return false;
99 	}
100 }
101 
102 
OtherLineDef(int L,int V,int * L_other,int * V_other,int match,int start_L)103 bool OtherLineDef(int L, int V, int *L_other, int *V_other,
104                   int match, int start_L)
105 {
106 	*L_other = -1;
107 	*V_other = -1;
108 
109 	for (int n = 0 ; n < NumLineDefs ; n++)
110 	{
111 		if (n == L)
112 			continue;
113 
114 		if ((match & SLP_OneSided) && ! LineDefs[n]->OneSided())
115 			continue;
116 
117 		for (int k = 0 ; k < 2 ; k++)
118 		{
119 			int v1 = LineDefs[n]->start;
120 			int v2 = LineDefs[n]->end;
121 
122 			if (k == 1)
123 				std::swap(v1, v2);
124 
125 			if (v1 != V)
126 				continue;
127 
128 			if ((match & SLP_SameTex) && ! MatchingTextures(start_L, n))
129 				continue;
130 
131 			if (*L_other >= 0)  // There is a fork in the path. Stop here.
132 				return false;
133 
134 			*L_other = n;
135 			*V_other = v2;
136 		}
137 	}
138 
139 	return (*L_other >= 0);
140 }
141 
142 
143 //
144 // This routine looks for all linedefs other than 'L' which use
145 // the vertex 'V'.  If there are none or more than one, the search
146 // stops there and nothing else happens.  If there is exactly one,
147 // then it is added to the selection and we continue from the new
148 // linedef and vertex.
149 //
SelectLinesInHalfPath(int L,int V,selection_c & seen,int match)150 static void SelectLinesInHalfPath(int L, int V, selection_c& seen, int match)
151 {
152 	int start_L = L;
153 
154 	for (;;)
155 	{
156 		int L_other, V_other;
157 
158 		// does not exist or is forky
159 		if (! OtherLineDef(L, V, &L_other, &V_other, match, start_L))
160 			break;
161 
162 		// already seen?
163 		if (seen.get(L_other))
164 			break;
165 
166 		seen.set(L_other);
167 
168 		L = L_other;
169 		V = V_other;
170 	}
171 }
172 
173 
174 //
175 // select/unselect all linedefs in a non-forked path.
176 //
CMD_LIN_SelectPath(void)177 void CMD_LIN_SelectPath(void)
178 {
179 	// determine starting linedef
180 	if (edit.highlight.is_nil())
181 	{
182 		Beep("No highlighted line");
183 		return;
184 	}
185 
186 	bool fresh_sel = Exec_HasFlag("/fresh");
187 
188 	int match = 0;
189 
190 	if (Exec_HasFlag("/onesided")) match |= SLP_OneSided;
191 	if (Exec_HasFlag("/sametex"))  match |= SLP_SameTex;
192 
193 	int start_L = edit.highlight.num;
194 
195 	if ((match & SLP_OneSided) && ! LineDefs[start_L]->OneSided())
196 		return;
197 
198 	bool unset_them = false;
199 
200 	if (!fresh_sel && edit.Selected->get(start_L))
201 		unset_them = true;
202 
203 	selection_c seen(OBJ_LINEDEFS);
204 
205 	seen.set(start_L);
206 
207 	SelectLinesInHalfPath(start_L, LineDefs[start_L]->start, seen, match);
208 	SelectLinesInHalfPath(start_L, LineDefs[start_L]->end,   seen, match);
209 
210 	Editor_ClearErrorMode();
211 
212 	if (fresh_sel)
213 		Selection_Clear();
214 
215 	if (unset_them)
216 		edit.Selected->unmerge(seen);
217 	else
218 		edit.Selected->merge(seen);
219 
220 	 RedrawMap();
221 }
222 
223 
224 //------------------------------------------------------------------------
225 
226 #define PLAYER_STEP_H	24
227 
GrowContiguousSectors(selection_c & seen)228 static bool GrowContiguousSectors(selection_c &seen)
229 {
230 	// returns TRUE when some new sectors got added
231 
232 	bool changed = false;
233 
234 	bool can_walk    = Exec_HasFlag("/can_walk");
235 	bool allow_doors = Exec_HasFlag("/doors");
236 
237 	bool do_floor_h   = Exec_HasFlag("/floor_h");
238 	bool do_floor_tex = Exec_HasFlag("/floor_tex");
239 	bool do_ceil_h    = Exec_HasFlag("/ceil_h");
240 	bool do_ceil_tex  = Exec_HasFlag("/ceil_tex");
241 
242 	bool do_light   = Exec_HasFlag("/light");
243 	bool do_tag     = Exec_HasFlag("/tag");
244 	bool do_special = Exec_HasFlag("/special");
245 
246 	for (int n = 0 ; n < NumLineDefs ; n++)
247 	{
248 		LineDef *L = LineDefs[n];
249 
250 		if (! L->TwoSided())
251 			continue;
252 
253 		int sec1 = L->Right()->sector;
254 		int sec2 = L-> Left()->sector;
255 
256 		if (sec1 == sec2)
257 			continue;
258 
259 		Sector *S1 = Sectors[sec1];
260 		Sector *S2 = Sectors[sec2];
261 
262 		// skip closed doors
263 		if (! allow_doors && (S1->floorh >= S1->ceilh || S2->floorh >= S2->ceilh))
264 			continue;
265 
266 		if (can_walk)
267 		{
268 			if (L->flags & MLF_Blocking)
269 				continue;
270 
271 			// too big a step?
272 			if (abs(S1->floorh - S2->floorh) > PLAYER_STEP_H)
273 				continue;
274 
275 			// player wouldn't fit vertically?
276 			int f_max = MAX(S1->floorh, S2->floorh);
277 			int c_min = MIN(S1-> ceilh, S2-> ceilh);
278 
279 			if (c_min - f_max < Misc_info.player_h)
280 			{
281 				// ... but allow doors
282 				if (! (allow_doors && (S1->floorh == S1->ceilh || S2->floorh == S2->ceilh)))
283 					continue;
284 			}
285 		}
286 
287 		/* perform match */
288 
289 		if (do_floor_h && (S1->floorh != S2->floorh)) continue;
290 		if (do_ceil_h  && (S1->ceilh  != S2->ceilh))  continue;
291 
292 		if (do_floor_tex && (S1->floor_tex != S2->floor_tex)) continue;
293 		if (do_ceil_tex  && (S1->ceil_tex  != S2->ceil_tex))  continue;
294 
295 		if (do_light   && (S1->light != S2->light)) continue;
296 		if (do_tag     && (S1->tag   != S2->tag  )) continue;
297 		if (do_special && (S1->type  != S2->type))  continue;
298 
299 		// check if only one of the sectors is part of current set
300 		// (doing this _AFTER_ the matches since this can be a bit slow)
301 		bool got1 = seen.get(sec1);
302 		bool got2 = seen.get(sec2);
303 
304 		if (got1 == got2)
305 			continue;
306 
307 		seen.set(got1 ? sec2 : sec1);
308 
309 		changed = true;
310 	}
311 
312 	return changed;
313 }
314 
315 
316 //
317 // select/unselect a contiguous group of sectors.
318 //
CMD_SEC_SelectGroup(void)319 void CMD_SEC_SelectGroup(void)
320 {
321 	// determine starting sector
322 	if (edit.highlight.is_nil())
323 	{
324 		Beep("No highlighted sector");
325 		return;
326 	}
327 
328 	bool fresh_sel = Exec_HasFlag("/fresh");
329 
330 	int start_sec = edit.highlight.num;
331 
332 	bool unset_them = false;
333 
334 	if (!fresh_sel && edit.Selected->get(start_sec))
335 		unset_them = true;
336 
337 	selection_c seen(OBJ_SECTORS);
338 
339 	seen.set(start_sec);
340 
341 	while (GrowContiguousSectors(seen))
342 	{ }
343 
344 
345 	Editor_ClearErrorMode();
346 
347 	if (fresh_sel)
348 		Selection_Clear();
349 
350 	if (unset_them)
351 		edit.Selected->unmerge(seen);
352 	else
353 		edit.Selected->merge(seen);
354 
355 	 RedrawMap();
356 }
357 
358 
359 //------------------------------------------------------------------------
360 
361 
GoToSelection()362 void GoToSelection()
363 {
364 	if (edit.render3d)
365 		Render3D_Enable(false);
366 
367 	double x1, y1, x2, y2;
368 	Objs_CalcBBox(edit.Selected, &x1, &y1, &x2, &y2);
369 
370 	double mid_x = (x1 + x2) / 2;
371 	double mid_y = (y1 + y2) / 2;
372 
373 	grid.MoveTo(mid_x, mid_y);
374 
375 	// zoom out until selected objects fit on screen
376 	for (int loop = 0 ; loop < 30 ; loop++)
377 	{
378 		int eval = main_win->canvas->ApproxBoxSize(x1, y1, x2, y2);
379 
380 		if (eval <= 0)
381 			break;
382 
383 		grid.AdjustScale(-1);
384 	}
385 
386 	// zoom in when bbox is very small (say < 20% of window)
387 	for (int loop = 0 ; loop < 30 ; loop++)
388 	{
389 		if (grid.Scale >= 1.0)
390 			break;
391 
392 		int eval = main_win->canvas->ApproxBoxSize(x1, y1, x2, y2);
393 
394 		if (eval >= 0)
395 			break;
396 
397 		grid.AdjustScale(+1);
398 	}
399 
400 	RedrawMap();
401 }
402 
403 
GoToErrors()404 void GoToErrors()
405 {
406 	edit.error_mode = true;
407 
408 	GoToSelection();
409 }
410 
411 
412 //
413 // centre the map around the object and zoom in if necessary
414 //
GoToObject(const Objid & objid)415 void GoToObject(const Objid& objid)
416 {
417 	Selection_Clear();
418 
419 	edit.Selected->set(objid.num);
420 
421 	GoToSelection();
422 }
423 
424 
CMD_JumpToObject(void)425 void CMD_JumpToObject(void)
426 {
427 	int total = NumObjects(edit.mode);
428 
429 	if (total <= 0)
430 	{
431 		Beep("No objects!");
432 		return;
433 	}
434 
435 	UI_JumpToDialog *dialog = new UI_JumpToDialog(NameForObjectType(edit.mode), total - 1);
436 
437 	int num = dialog->Run();
438 
439 	delete dialog;
440 
441 	if (num < 0)	// cancelled
442 		return;
443 
444 	// this is guaranteed by the dialog
445 	SYS_ASSERT(num < total);
446 
447 	GoToObject(Objid(edit.mode, num));
448 }
449 
450 
CMD_NextObject()451 void CMD_NextObject()
452 {
453 	if (edit.Selected->count_obj() != 1)
454 	{
455 		Beep("Next: need a single object");
456 		return;
457 	}
458 
459 	int num = edit.Selected->find_first();
460 
461 	if (num >= NumObjects(edit.mode))
462 	{
463 		Beep("Next: no more objects");
464 		return;
465 	}
466 
467 	num += 1;
468 
469 	GoToObject(Objid(edit.mode, num));
470 }
471 
472 
CMD_PrevObject()473 void CMD_PrevObject()
474 {
475 	if (edit.Selected->count_obj() != 1)
476 	{
477 		Beep("Prev: need a single object");
478 		return;
479 	}
480 
481 	int num = edit.Selected->find_first();
482 
483 	if (num <= 0)
484 	{
485 		Beep("Prev: no more objects");
486 		return;
487 	}
488 
489 	num -= 1;
490 
491 	GoToObject(Objid(edit.mode, num));
492 }
493 
494 
CMD_PruneUnused(void)495 void CMD_PruneUnused(void)
496 {
497 	selection_c used_secs (OBJ_SECTORS);
498 	selection_c used_sides(OBJ_SIDEDEFS);
499 	selection_c used_verts(OBJ_VERTICES);
500 
501 	for (int i = 0 ; i < NumLineDefs ; i++)
502 	{
503 		const LineDef * L = LineDefs[i];
504 
505 		used_verts.set(L->start);
506 		used_verts.set(L->end);
507 
508 		if (L->left >= 0)
509 		{
510 			used_sides.set(L->left);
511 			used_secs.set(L->Left()->sector);
512 		}
513 
514 		if (L->right >= 0)
515 		{
516 			used_sides.set(L->right);
517 			used_secs.set(L->Right()->sector);
518 		}
519 	}
520 
521 	used_secs .frob_range(0, NumSectors -1, BOP_TOGGLE);
522 	used_sides.frob_range(0, NumSideDefs-1, BOP_TOGGLE);
523 	used_verts.frob_range(0, NumVertices-1, BOP_TOGGLE);
524 
525 	int num_secs  = used_secs .count_obj();
526 	int num_sides = used_sides.count_obj();
527 	int num_verts = used_verts.count_obj();
528 
529 	if (num_verts == 0 && num_sides == 0 && num_secs == 0)
530 	{
531 		Beep("Nothing to prune");
532 		return;
533 	}
534 
535 	BA_Begin();
536 	BA_Message("pruned %d objects", num_secs + num_sides + num_verts);
537 
538 	DeleteObjects(&used_sides);
539 	DeleteObjects(&used_secs);
540 	DeleteObjects(&used_verts);
541 
542 	BA_End();
543 }
544 
545 
546 //------------------------------------------------------------------------
547 
548 bool sound_propagation_invalid;
549 
550 static std::vector<byte> sound_prop_vec;
551 static std::vector<byte> sound_temp1_vec;
552 static std::vector<byte> sound_temp2_vec;
553 
554 static int sound_start_sec;
555 
556 
CalcPropagation(std::vector<byte> & vec,bool ignore_doors)557 static void CalcPropagation(std::vector<byte>& vec, bool ignore_doors)
558 {
559 	bool changes;
560 
561 	for (int k = 0 ; k < NumSectors ; k++)
562 		vec[k] = 0;
563 
564 	vec[sound_start_sec] = 2;
565 
566 	do
567 	{
568 		changes = false;
569 
570 		for (int n = 0 ; n < NumLineDefs ; n++)
571 		{
572 			const LineDef *L = LineDefs[n];
573 
574 			if (! L->TwoSided())
575 				continue;
576 
577 			int sec1 = L->WhatSector(SIDE_RIGHT);
578 			int sec2 = L->WhatSector(SIDE_LEFT);
579 
580 			SYS_ASSERT(sec1 >= 0);
581 			SYS_ASSERT(sec2 >= 0);
582 
583 			// check for doors
584 			if (!ignore_doors &&
585 				(MIN(Sectors[sec1]->ceilh,  Sectors[sec2]->ceilh) <=
586 				 MAX(Sectors[sec1]->floorh, Sectors[sec2]->floorh)))
587 			{
588 				continue;
589 			}
590 
591 			int val1 = vec[sec1];
592 			int val2 = vec[sec2];
593 
594 			int new_val = MAX(val1, val2);
595 
596 			if (L->flags & MLF_SoundBlock)
597 				new_val -= 1;
598 
599 			if (new_val > val1 || new_val > val2)
600 			{
601 				if (new_val > val1) vec[sec1] = new_val;
602 				if (new_val > val2) vec[sec2] = new_val;
603 
604 				changes = true;
605 			}
606 		}
607 
608 	} while (changes);
609 }
610 
611 
CalcFinalPropagation()612 static void CalcFinalPropagation()
613 {
614 	for (int s = 0 ; s < NumSectors ; s++)
615 	{
616 		int t1 = sound_temp1_vec[s];
617 		int t2 = sound_temp2_vec[s];
618 
619 		if (t1 != t2)
620 		{
621 			if (t1 == 0 || t2 == 0)
622 			{
623 				sound_prop_vec[s] = PGL_Maybe;
624 				continue;
625 			}
626 
627 			t1 = MIN(t1, t2);
628 		}
629 
630 		switch (t1)
631 		{
632 			case 0: sound_prop_vec[s] = PGL_Never;   break;
633 			case 1: sound_prop_vec[s] = PGL_Level_1; break;
634 			case 2: sound_prop_vec[s] = PGL_Level_2; break;
635 		}
636 	}
637 }
638 
639 
SoundPropagation(int start_sec)640 const byte * SoundPropagation(int start_sec)
641 {
642 	if ((int)sound_prop_vec.size() != NumSectors)
643 	{
644 		sound_prop_vec .resize(NumSectors);
645 		sound_temp1_vec.resize(NumSectors);
646 		sound_temp2_vec.resize(NumSectors);
647 
648 		sound_propagation_invalid = true;
649 	}
650 
651 	if (sound_propagation_invalid ||
652 		sound_start_sec != start_sec)
653 	{
654 		// cannot used cached data, recompute it
655 
656 		sound_start_sec = start_sec;
657 		sound_propagation_invalid = false;
658 
659 		CalcPropagation(sound_temp1_vec, false);
660 		CalcPropagation(sound_temp2_vec, true);
661 
662 		CalcFinalPropagation();
663 	}
664 
665 	return &sound_prop_vec[0];
666 }
667 
668 //--- editor settings ---
669 // vi:ts=4:sw=4:noexpandtab
670