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