1 //------------------------------------------------------------------------
2 // 3D RENDERING
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 #include "main.h"
23
24 #include <map>
25 #include <algorithm>
26
27 #ifndef NO_OPENGL
28 #include "FL/gl.h"
29 #endif
30
31 #include "e_basis.h"
32 #include "e_cutpaste.h"
33 #include "e_hover.h"
34 #include "e_linedef.h"
35 #include "e_sector.h"
36 #include "e_main.h"
37 #include "m_game.h"
38 #include "m_events.h"
39 #include "r_render.h"
40 #include "r_subdiv.h"
41 #include "ui_window.h"
42
43
44 // config items
45 rgb_color_t transparent_col = RGB_MAKE(0, 255, 255);
46
47 bool render_high_detail = false;
48 bool render_lock_gravity = false;
49 bool render_missing_bright = true;
50 bool render_unknown_bright = true;
51
52 int render_far_clip = 32768;
53
54 // in original DOOM pixels were 20% taller than wide, giving 0.83
55 // as the pixel aspect ratio.
56 int render_pixel_aspect = 83; // 100 * width / height
57
58
59 namespace thing_sec_cache
60 {
61 int invalid_low, invalid_high;
62
ResetRange()63 void ResetRange()
64 {
65 invalid_low = 999999999;
66 invalid_high = -1;
67 }
68
InvalidateThing(int th)69 void InvalidateThing(int th)
70 {
71 invalid_low = MIN(invalid_low, th);
72 invalid_high = MAX(invalid_high, th);
73 }
74
InvalidateAll()75 void InvalidateAll()
76 {
77 invalid_low = 0;
78 invalid_high = NumThings - 1;
79 }
80
81 void Update();
82 };
83
84
85 Render_View_t r_view;
86
87
Render_View_t()88 Render_View_t::Render_View_t() :
89 p_type(0), px(), py(),
90 x(), y(), z(),
91 angle(), Sin(), Cos(),
92 screen_w(), screen_h(), screen(NULL),
93 texturing(false), sprites(false), lighting(false),
94 gravity(true),
95 thing_sectors(),
96 mouse_x(-1), mouse_y(-1)
97 { }
98
~Render_View_t()99 Render_View_t::~Render_View_t()
100 { }
101
SetAngle(float new_ang)102 void Render_View_t::SetAngle(float new_ang)
103 {
104 angle = new_ang;
105
106 if (angle >= 2*M_PI)
107 angle -= 2*M_PI;
108 else if (angle < 0)
109 angle += 2*M_PI;
110
111 Sin = sin(angle);
112 Cos = cos(angle);
113 }
114
FindGroundZ()115 void Render_View_t::FindGroundZ()
116 {
117 // test a grid of points on the player's bounding box, and
118 // use the maximum floor of all contacted sectors.
119
120 double max_floor = -9e9;
121 bool hit_something = false;
122
123 for (int dx = -2 ; dx <= 2 ; dx++)
124 for (int dy = -2 ; dy <= 2 ; dy++)
125 {
126 double test_x = x + dx * 8;
127 double test_y = y + dy * 8;
128
129 Objid o;
130
131 GetNearObject(o, OBJ_SECTORS, test_x, test_y);
132
133 if (o.num >= 0)
134 {
135 double z = Sectors[o.num]->floorh;
136 {
137 sector_3dfloors_c *ex = Subdiv_3DFloorsForSector(o.num);
138 if (ex->f_plane.sloped)
139 z = ex->f_plane.SlopeZ(test_x, test_y);
140 }
141
142 max_floor = MAX(max_floor, z);
143 hit_something = true;
144 }
145 }
146
147 if (hit_something)
148 z = max_floor + Misc_info.view_height;
149 }
150
CalcAspect()151 void Render_View_t::CalcAspect()
152 {
153 aspect_sw = screen_w; // things break if these are different
154
155 aspect_sh = screen_w / (render_pixel_aspect / 100.0);
156 }
157
DistToViewPlane(double map_x,double map_y)158 double Render_View_t::DistToViewPlane(double map_x, double map_y)
159 {
160 map_x -= r_view.x;
161 map_y -= r_view.y;
162
163 return map_x * r_view.Cos + map_y * r_view.Sin;
164 }
165
UpdateScreen(int ow,int oh)166 void Render_View_t::UpdateScreen(int ow, int oh)
167 {
168 // in low detail mode, setup size so that expansion always covers
169 // our window (i.e. we draw a bit more than we need).
170
171 int new_sw = render_high_detail ? ow : (ow + 1) / 2;
172 int new_sh = render_high_detail ? oh : (oh + 1) / 2;
173
174 if (!screen || screen_w != new_sw || screen_h != new_sh)
175 {
176 screen_w = new_sw;
177 screen_h = new_sh;
178
179 if (screen)
180 {
181 delete[] screen;
182 screen = NULL;
183 }
184 }
185
186 // we don't need a screen buffer when using OpenGL
187 #ifdef NO_OPENGL
188 if (!screen)
189 screen = new img_pixel_t [screen_w * screen_h];
190 #endif
191
192 CalcAspect();
193 }
194
PrepareToRender(int ow,int oh)195 void Render_View_t::PrepareToRender(int ow, int oh)
196 {
197 thing_sec_cache::Update();
198
199 UpdateScreen(ow, oh);
200
201 if (gravity)
202 FindGroundZ();
203 }
204
205
FindPlayer(int typenum)206 static Thing *FindPlayer(int typenum)
207 {
208 // need to search backwards (to handle Voodoo dolls properly)
209
210 for (int i = NumThings-1 ; i >= 0 ; i--)
211 if (Things[i]->type == typenum)
212 return Things[i];
213
214 return NULL; // not found
215 }
216
217
218 //------------------------------------------------------------------------
219
220 namespace thing_sec_cache
221 {
Update()222 void Update()
223 {
224 // guarantee that thing_sectors has the correct size.
225 // [ prevent a potential crash ]
226 if (NumThings != (int)r_view.thing_sectors.size())
227 {
228 r_view.thing_sectors.resize(NumThings);
229 thing_sec_cache::InvalidateAll();
230 }
231
232 // nothing changed?
233 if (invalid_low > invalid_high)
234 return;
235
236 for (int i = invalid_low ; i <= invalid_high ; i++)
237 {
238 Objid obj;
239 GetNearObject(obj, OBJ_SECTORS, Things[i]->x(), Things[i]->y());
240
241 r_view.thing_sectors[i] = obj.num;
242 }
243
244 thing_sec_cache::ResetRange();
245 }
246 }
247
Render3D_NotifyBegin()248 void Render3D_NotifyBegin()
249 {
250 thing_sec_cache::ResetRange();
251 }
252
Render3D_NotifyInsert(obj_type_e type,int objnum)253 void Render3D_NotifyInsert(obj_type_e type, int objnum)
254 {
255 if (type == OBJ_THINGS)
256 thing_sec_cache::InvalidateThing(objnum);
257 }
258
Render3D_NotifyDelete(obj_type_e type,int objnum)259 void Render3D_NotifyDelete(obj_type_e type, int objnum)
260 {
261 if (type == OBJ_THINGS || type == OBJ_SECTORS)
262 thing_sec_cache::InvalidateAll();
263 }
264
Render3D_NotifyChange(obj_type_e type,int objnum,int field)265 void Render3D_NotifyChange(obj_type_e type, int objnum, int field)
266 {
267 if (type == OBJ_THINGS &&
268 (field == Thing::F_X || field == Thing::F_Y))
269 {
270 thing_sec_cache::InvalidateThing(objnum);
271 }
272 }
273
Render3D_NotifyEnd()274 void Render3D_NotifyEnd()
275 {
276 thing_sec_cache::Update();
277 }
278
279
280 //------------------------------------------------------------------------
281
282 class save_obj_field_c
283 {
284 public:
285 int obj; // object number (SaveBucket::type is the type)
286 int field; // e.g. Thing::F_X
287 int value; // the saved value
288
289 public:
save_obj_field_c(int _obj,int _field,int _value)290 save_obj_field_c(int _obj, int _field, int _value) :
291 obj(_obj), field(_field), value(_value)
292 { }
293
~save_obj_field_c()294 ~save_obj_field_c()
295 { }
296 };
297
298
299 class SaveBucket_c
300 {
301 private:
302 obj_type_e type;
303
304 std::vector< save_obj_field_c > fields;
305
306 public:
SaveBucket_c(obj_type_e _type)307 SaveBucket_c(obj_type_e _type) : type(_type), fields()
308 { }
309
~SaveBucket_c()310 ~SaveBucket_c()
311 { }
312
Clear()313 void Clear()
314 {
315 fields.clear();
316 }
317
Clear(obj_type_e _type)318 void Clear(obj_type_e _type)
319 {
320 type = _type;
321 fields.clear();
322 }
323
Save(int obj,int field)324 void Save(int obj, int field)
325 {
326 // is it already saved?
327 for (size_t i = 0 ; i < fields.size() ; i++)
328 if (fields[i].obj == obj && fields[i].field == field)
329 return;
330
331 int value = RawGet(obj, field);
332
333 fields.push_back(save_obj_field_c(obj, field, value));
334 }
335
RestoreAll()336 void RestoreAll()
337 {
338 for (size_t i = 0 ; i < fields.size() ; i++)
339 {
340 RawSet(fields[i].obj, fields[i].field, fields[i].value);
341 }
342 }
343
ApplyTemp(int field,int delta)344 void ApplyTemp(int field, int delta)
345 {
346 for (size_t i = 0 ; i < fields.size() ; i++)
347 {
348 if (fields[i].field == field)
349 RawSet(fields[i].obj, field, fields[i].value + delta);
350 }
351 }
352
ApplyToBasis(int field,int delta)353 void ApplyToBasis(int field, int delta)
354 {
355 for (size_t i = 0 ; i < fields.size() ; i++)
356 {
357 if (fields[i].field == field)
358 {
359 BA_Change(type, fields[i].obj, (byte)field, fields[i].value + delta);
360 }
361 }
362 }
363
364 private:
RawObjPointer(int objnum)365 int * RawObjPointer(int objnum)
366 {
367 switch (type)
368 {
369 case OBJ_THINGS:
370 return (int*) Things[objnum];
371
372 case OBJ_VERTICES:
373 return (int*) Vertices[objnum];
374
375 case OBJ_SECTORS:
376 return (int*) Sectors[objnum];
377
378 case OBJ_SIDEDEFS:
379 return (int*) SideDefs[objnum];
380
381 case OBJ_LINEDEFS:
382 return (int*) LineDefs[objnum];
383
384 default:
385 BugError("SaveBucket with bad mode\n");
386 return NULL; /* NOT REACHED */
387 }
388 }
389
RawGet(int objnum,int field)390 int RawGet(int objnum, int field)
391 {
392 int *ptr = RawObjPointer(objnum) + field;
393 return *ptr;
394 }
395
RawSet(int objnum,int field,int value)396 void RawSet(int objnum, int field, int value)
397 {
398 int *ptr = RawObjPointer(objnum) + field;
399 *ptr = value;
400 }
401 };
402
403
AdjustOfs_UpdateBBox(int ld_num)404 static void AdjustOfs_UpdateBBox(int ld_num)
405 {
406 const LineDef *L = LineDefs[ld_num];
407
408 float lx1 = L->Start()->x();
409 float ly1 = L->Start()->y();
410 float lx2 = L->End()->x();
411 float ly2 = L->End()->y();
412
413 if (lx1 > lx2) std::swap(lx1, lx2);
414 if (ly1 > ly2) std::swap(ly1, ly2);
415
416 edit.adjust_bbox.x1 = MIN(edit.adjust_bbox.x1, lx1);
417 edit.adjust_bbox.y1 = MIN(edit.adjust_bbox.y1, ly1);
418 edit.adjust_bbox.x2 = MAX(edit.adjust_bbox.x2, lx2);
419 edit.adjust_bbox.y2 = MAX(edit.adjust_bbox.y2, ly2);
420 }
421
AdjustOfs_CalcDistFactor(float & dx_factor,float & dy_factor)422 void AdjustOfs_CalcDistFactor(float& dx_factor, float& dy_factor)
423 {
424 // this computes how far to move the offsets for each screen pixel
425 // the mouse moves. we want it to appear as though each texture
426 // is being dragged by the mouse, e.g. if you click on the middle
427 // of a switch, that switch follows the mouse pointer around.
428 // such an effect can only be approximate though.
429
430 float dx = (r_view.x < edit.adjust_bbox.x1) ? (edit.adjust_bbox.x1 - r_view.x) :
431 (r_view.x > edit.adjust_bbox.x2) ? (r_view.x - edit.adjust_bbox.x2) : 0;
432
433 float dy = (r_view.y < edit.adjust_bbox.y1) ? (edit.adjust_bbox.y1 - r_view.y) :
434 (r_view.y > edit.adjust_bbox.y2) ? (r_view.y - edit.adjust_bbox.y2) : 0;
435
436 float dist = hypot(dx, dy);
437
438 dist = CLAMP(20, dist, 1000);
439
440 dx_factor = dist / r_view.aspect_sw;
441 dy_factor = dist / r_view.aspect_sh;
442 }
443
AdjustOfs_Add(int ld_num,int part)444 static void AdjustOfs_Add(int ld_num, int part)
445 {
446 if (! edit.adjust_bucket)
447 return;
448
449 const LineDef *L = LineDefs[ld_num];
450
451 // ignore invalid sides (sanity check)
452 int sd_num = (part & PART_LF_ALL) ? L->left : L->right;
453 if (sd_num < 0)
454 return;
455
456 // TODO : UDMF ports can allow full control over each part
457
458 edit.adjust_bucket->Save(sd_num, SideDef::F_X_OFFSET);
459 edit.adjust_bucket->Save(sd_num, SideDef::F_Y_OFFSET);
460 }
461
AdjustOfs_Begin()462 static void AdjustOfs_Begin()
463 {
464 if (edit.adjust_bucket)
465 delete edit.adjust_bucket;
466
467 edit.adjust_bucket = new SaveBucket_c(OBJ_SIDEDEFS);
468 edit.adjust_lax = Exec_HasFlag("/LAX");
469
470 int total_lines = 0;
471
472 // we will compute the bbox of selected lines
473 edit.adjust_bbox.x1 = edit.adjust_bbox.y1 = +9e9;
474 edit.adjust_bbox.x2 = edit.adjust_bbox.y2 = -9e9;
475
476 // find the sidedefs to adjust
477 if (! edit.Selected->empty())
478 {
479 for (sel_iter_c it(edit.Selected) ; !it.done() ; it.next())
480 {
481 int ld_num = *it;
482 byte parts = edit.Selected->get_ext(ld_num);
483
484 // handle "simply selected" linedefs
485 if (parts <= 1)
486 {
487 parts |= PART_RT_LOWER | PART_RT_UPPER;
488 parts |= PART_LF_LOWER | PART_LF_UPPER;
489 }
490
491 total_lines++;
492 AdjustOfs_UpdateBBox(ld_num);
493
494 if (parts & PART_RT_LOWER) AdjustOfs_Add(ld_num, PART_RT_LOWER);
495 if (parts & PART_RT_UPPER) AdjustOfs_Add(ld_num, PART_RT_UPPER);
496 if (parts & PART_RT_RAIL) AdjustOfs_Add(ld_num, PART_RT_RAIL);
497
498 if (parts & PART_LF_LOWER) AdjustOfs_Add(ld_num, PART_LF_LOWER);
499 if (parts & PART_LF_UPPER) AdjustOfs_Add(ld_num, PART_LF_UPPER);
500 if (parts & PART_LF_RAIL) AdjustOfs_Add(ld_num, PART_LF_RAIL);
501 }
502 }
503 else if (edit.highlight.valid())
504 {
505 int ld_num = edit.highlight.num;
506 byte parts = edit.highlight.parts;
507
508 if (parts >= 2)
509 {
510 AdjustOfs_Add(ld_num, parts);
511 AdjustOfs_UpdateBBox(ld_num);
512 total_lines++;
513 }
514 }
515
516 if (total_lines == 0)
517 {
518 Beep("nothing to adjust");
519 return;
520 }
521
522 edit.adjust_dx = 0;
523 edit.adjust_dy = 0;
524
525 edit.adjust_lax = Exec_HasFlag("/LAX");
526
527 Editor_SetAction(ACT_ADJUST_OFS);
528 }
529
AdjustOfs_Finish()530 static void AdjustOfs_Finish()
531 {
532 if (! edit.adjust_bucket)
533 {
534 Editor_ClearAction();
535 return;
536 }
537
538 int dx = I_ROUND(edit.adjust_dx);
539 int dy = I_ROUND(edit.adjust_dy);
540
541 if (dx || dy)
542 {
543 BA_Begin();
544 BA_Message("adjusted offsets");
545
546 edit.adjust_bucket->ApplyToBasis(SideDef::F_X_OFFSET, dx);
547 edit.adjust_bucket->ApplyToBasis(SideDef::F_Y_OFFSET, dy);
548
549 BA_End();
550 }
551
552 delete edit.adjust_bucket;
553 edit.adjust_bucket = NULL;
554
555 Editor_ClearAction();
556 }
557
AdjustOfs_Delta(int dx,int dy)558 static void AdjustOfs_Delta(int dx, int dy)
559 {
560 if (! edit.adjust_bucket)
561 return;
562
563 if (dx == 0 && dy == 0)
564 return;
565
566 bool force_one_dir = true;
567
568 if (force_one_dir)
569 {
570 if (abs(dx) >= abs(dy))
571 dy = 0;
572 else
573 dx = 0;
574 }
575
576 keycode_t mod = edit.adjust_lax ? M_ReadLaxModifiers() : 0;
577
578 float factor = (mod & MOD_SHIFT) ? 0.5 : 2.0;
579
580 if (!render_high_detail)
581 factor = factor * 0.5;
582
583 float dx_factor, dy_factor;
584 AdjustOfs_CalcDistFactor(dx_factor, dy_factor);
585
586 edit.adjust_dx -= dx * factor * dx_factor;
587 edit.adjust_dy -= dy * factor * dy_factor;
588
589 RedrawMap();
590 }
591
592
AdjustOfs_RenderAnte()593 void AdjustOfs_RenderAnte()
594 {
595 if (edit.action == ACT_ADJUST_OFS && edit.adjust_bucket)
596 {
597 int dx = I_ROUND(edit.adjust_dx);
598 int dy = I_ROUND(edit.adjust_dy);
599
600 // change it temporarily (just for the render)
601 edit.adjust_bucket->ApplyTemp(SideDef::F_X_OFFSET, dx);
602 edit.adjust_bucket->ApplyTemp(SideDef::F_Y_OFFSET, dy);
603 }
604 }
605
AdjustOfs_RenderPost()606 void AdjustOfs_RenderPost()
607 {
608 if (edit.action == ACT_ADJUST_OFS && edit.adjust_bucket)
609 {
610 edit.adjust_bucket->RestoreAll();
611 }
612 }
613
614
615 //------------------------------------------------------------------------
616
617 static Thing *player;
618
619 extern void CheckBeginDrag();
620
621
Render3D_Draw(int ox,int oy,int ow,int oh)622 void Render3D_Draw(int ox, int oy, int ow, int oh)
623 {
624 r_view.PrepareToRender(ow, oh);
625
626 AdjustOfs_RenderAnte();
627
628 #ifdef NO_OPENGL
629 SW_RenderWorld(ox, oy, ow, oh);
630 #else
631 RGL_RenderWorld(ox, oy, ow, oh);
632 #endif
633
634 AdjustOfs_RenderPost();
635 }
636
637
Render3D_Query(Objid & hl,int sx,int sy)638 bool Render3D_Query(Objid& hl, int sx, int sy)
639 {
640 int ow = main_win->canvas->w();
641 int oh = main_win->canvas->h();
642
643 #ifdef NO_OPENGL
644 // in OpenGL mode, UI_Canvas is a window and that means the
645 // event X/Y values are relative to *it* and not the main window.
646 // hence the following is only needed in software mode.
647 int ox = main_win->canvas->x();
648 int oy = main_win->canvas->y();
649
650 sx -= ox;
651 sy -= oy;
652 #endif
653
654 // force high detail mode for OpenGL, so we don't lose
655 // precision when performing the query.
656 #ifndef NO_OPENGL
657 render_high_detail = true;
658 #endif
659
660 hl.clear();
661
662 if (! edit.pointer_in_window)
663 return false;
664
665 r_view.PrepareToRender(ow, oh);
666
667 return SW_QueryPoint(hl, sx, sy);
668 }
669
670
Render3D_Setup()671 void Render3D_Setup()
672 {
673 thing_sec_cache::InvalidateAll();
674 r_view.thing_sectors.resize(0);
675
676 if (! r_view.p_type)
677 {
678 r_view.p_type = THING_PLAYER1;
679 r_view.px = 99999;
680 }
681
682 player = FindPlayer(r_view.p_type);
683
684 if (! player)
685 {
686 if (r_view.p_type != THING_DEATHMATCH)
687 r_view.p_type = THING_DEATHMATCH;
688
689 player = FindPlayer(r_view.p_type);
690 }
691
692 if (player && !(r_view.px == player->x() && r_view.py == player->y()))
693 {
694 // if player moved, re-create view parameters
695
696 r_view.x = r_view.px = player->x();
697 r_view.y = r_view.py = player->y();
698
699 r_view.FindGroundZ();
700
701 r_view.SetAngle(player->angle * M_PI / 180.0);
702 }
703 else
704 {
705 r_view.x = 0;
706 r_view.y = 0;
707 r_view.z = 64;
708
709 r_view.SetAngle(0);
710 }
711
712 r_view.screen_w = -1;
713 r_view.screen_h = -1;
714
715 r_view.texturing = true;
716 r_view.sprites = true;
717 r_view.lighting = true;
718 }
719
720
Render3D_Enable(bool _enable)721 void Render3D_Enable(bool _enable)
722 {
723 Editor_ClearAction();
724
725 edit.render3d = _enable;
726
727 edit.highlight.clear();
728 edit.clicked.clear();
729 edit.dragged.clear();
730
731 // give keyboard focus to the appropriate large widget
732 Fl::focus(main_win->canvas);
733
734 main_win->scroll->UpdateRenderMode();
735 main_win->info_bar->UpdateSecRend();
736
737 if (edit.render3d)
738 {
739 main_win->info_bar->SetMouse(r_view.x, r_view.y);
740
741 // TODO: ideally query this, like code in PointerPos
742 r_view.mouse_x = r_view.mouse_y = -1;
743 }
744 else
745 {
746 main_win->canvas->PointerPos();
747 main_win->info_bar->SetMouse(edit.map_x, edit.map_y);
748 }
749
750 RedrawMap();
751 }
752
753
Render3D_ScrollMap(int dx,int dy,keycode_t mod)754 void Render3D_ScrollMap(int dx, int dy, keycode_t mod)
755 {
756 // we separate the movement into either turning or moving up/down
757 // (never both at the same time : CONFIG IT THOUGH).
758
759 bool force_one_dir = true;
760
761 if (force_one_dir)
762 {
763 if (abs(dx) >= abs(dy))
764 dy = 0;
765 else
766 dx = 0;
767 }
768
769 bool is_strafe = (mod & MOD_ALT) ? true : false;
770
771 float mod_factor = 1.0;
772 if (mod & MOD_SHIFT) mod_factor = 0.4;
773 if (mod & MOD_COMMAND) mod_factor = 2.5;
774
775 float speed = edit.panning_speed * mod_factor;
776
777 if (is_strafe)
778 {
779 r_view.x += r_view.Sin * dx * mod_factor;
780 r_view.y -= r_view.Cos * dx * mod_factor;
781 }
782 else // turn camera
783 {
784 double d_ang = dx * speed * M_PI / 480.0;
785
786 r_view.SetAngle(r_view.angle - d_ang);
787 }
788
789 dy = -dy; //TODO CONFIG ITEM
790
791 if (is_strafe)
792 {
793 r_view.x += r_view.Cos * dy * mod_factor;
794 r_view.y += r_view.Sin * dy * mod_factor;
795 }
796 else if (! (render_lock_gravity && r_view.gravity))
797 {
798 r_view.z += dy * speed * 0.75;
799
800 r_view.gravity = false;
801 }
802
803 main_win->info_bar->SetMouse(r_view.x, r_view.y);
804 RedrawMap();
805 }
806
807
DragSectors_Update()808 static void DragSectors_Update()
809 {
810 float ow = main_win->canvas->w();
811 float x_slope = 100.0 / render_pixel_aspect;
812
813 float factor = CLAMP(20, edit.drag_point_dist, 1000) / (ow * x_slope * 0.5);
814 float map_dz = -edit.drag_screen_dy * factor;
815
816 float step = 8.0; // TODO config item
817
818 if (map_dz > step*0.25)
819 edit.drag_sector_dz = step * (int)ceil(map_dz / step);
820 else if (map_dz < step*-0.25)
821 edit.drag_sector_dz = step * (int)floor(map_dz / step);
822 else
823 edit.drag_sector_dz = 0;
824 }
825
Render3D_DragSectors()826 void Render3D_DragSectors()
827 {
828 int dz = I_ROUND(edit.drag_sector_dz);
829 if (dz == 0)
830 return;
831
832 BA_Begin();
833
834 if (dz > 0)
835 BA_Message("raised sectors");
836 else
837 BA_Message("lowered sectors");
838
839 if (edit.dragged.valid())
840 {
841 int parts = edit.dragged.parts;
842
843 SEC_SafeRaiseLower(edit.dragged.num, parts, dz);
844 }
845 else
846 {
847 for (sel_iter_c it(edit.Selected) ; !it.done() ; it.next())
848 {
849 int parts = edit.Selected->get_ext(*it);
850 parts &= ~1;
851
852 SEC_SafeRaiseLower(*it, parts, dz);
853 }
854 }
855
856 BA_End();
857 }
858
859
DragThings_Update()860 static void DragThings_Update()
861 {
862 float ow = main_win->canvas->w();
863 // float oh = main_win->canvas->h();
864
865 float x_slope = 100.0 / render_pixel_aspect;
866 // float y_slope = (float)oh / (float)ow;
867
868 float dist = CLAMP(20, edit.drag_point_dist, 1000);
869
870 float x_factor = dist / (ow * 0.5);
871 float y_factor = dist / (ow * x_slope * 0.5);
872
873 if (edit.drag_thing_up_down)
874 {
875 // vertical positioning in Hexen and UDMF formats
876 float map_dz = -edit.drag_screen_dy * y_factor;
877
878 // final result is in drag_cur_x/y/z
879 edit.drag_cur_x = edit.drag_start_x;
880 edit.drag_cur_y = edit.drag_start_y;
881 edit.drag_cur_z = edit.drag_start_z + map_dz;
882 return;
883 }
884
885 /* move thing around XY plane */
886
887 edit.drag_cur_z = edit.drag_start_z;
888
889 // vectors for view camera
890 double fwd_vx = r_view.Cos;
891 double fwd_vy = r_view.Sin;
892
893 double side_vx = fwd_vy;
894 double side_vy = -fwd_vx;
895
896 double dx = edit.drag_screen_dx * x_factor;
897 double dy = -edit.drag_screen_dy * y_factor * 2.0;
898
899 // this usually won't happen, but is a reasonable fallback...
900 if (edit.drag_thing_num < 0)
901 {
902 edit.drag_cur_x = edit.drag_start_x + dx * side_vx + dy * fwd_vx;
903 edit.drag_cur_y = edit.drag_start_y + dx * side_vy + dy * fwd_vy;
904 return;
905 }
906
907 // old code for depth calculation, works well in certain cases
908 // but very poorly in other cases.
909 #if 0
910 int sy1 = edit.click_screen_y;
911 int sy2 = sy1 + edit.drag_screen_dy;
912
913 if (sy1 >= oh/2 && sy2 >= oh/2)
914 {
915 double d1 = (edit.drag_thing_floorh - r_view.z) / (oh - sy1*2.0);
916 double d2 = (edit.drag_thing_floorh - r_view.z) / (oh - sy2*2.0);
917
918 d1 = d1 * ow;
919 d2 = d2 * ow;
920
921 dy = (d2 - d1) * 0.5;
922 }
923 #endif
924
925 const Thing *T = Things[edit.drag_thing_num];
926
927 float old_x = T->x();
928 float old_y = T->y();
929
930 float new_x = old_x + dx * side_vx;
931 float new_y = old_y + dx * side_vy;
932
933 // recompute forward/back vector
934 fwd_vx = new_x - r_view.x;
935 fwd_vy = new_y - r_view.y;
936
937 double fwd_len = hypot(fwd_vx, fwd_vy);
938 if (fwd_len < 1)
939 fwd_len = 1;
940
941 new_x = new_x + dy * fwd_vx / fwd_len;
942 new_y = new_y + dy * fwd_vy / fwd_len;
943
944 // handle a change in floor height
945 Objid old_sec;
946 GetNearObject(old_sec, OBJ_SECTORS, old_x, old_y);
947
948 Objid new_sec;
949 GetNearObject(new_sec, OBJ_SECTORS, new_x, new_y);
950
951 if (old_sec.valid() && new_sec.valid())
952 {
953 float old_z = Sectors[old_sec.num]->floorh;
954 float new_z = Sectors[new_sec.num]->floorh;
955
956 // intent here is to show proper position, NOT raise/lower things.
957 // [ perhaps add a new variable? ]
958 edit.drag_cur_z += (new_z - old_z);
959 }
960
961 edit.drag_cur_x = edit.drag_start_x + new_x - old_x;
962 edit.drag_cur_y = edit.drag_start_y + new_y - old_y;
963 }
964
Render3D_DragThings()965 void Render3D_DragThings()
966 {
967 double dx = edit.drag_cur_x - edit.drag_start_x;
968 double dy = edit.drag_cur_y - edit.drag_start_y;
969 double dz = edit.drag_cur_z - edit.drag_start_z;
970
971 // for movement in XY plane, ensure we don't raise/lower things
972 if (! edit.drag_thing_up_down)
973 dz = 0.0;
974
975 if (edit.dragged.valid())
976 {
977 selection_c sel(OBJ_THINGS);
978 sel.set(edit.dragged.num);
979
980 MoveObjects(&sel, dx, dy, dz);
981 }
982 else
983 {
984 MoveObjects(edit.Selected, dx, dy, dz);
985 }
986
987 RedrawMap();
988 }
989
990
Render3D_MouseMotion(int x,int y,keycode_t mod,int dx,int dy)991 void Render3D_MouseMotion(int x, int y, keycode_t mod, int dx, int dy)
992 {
993 edit.pointer_in_window = true;
994
995 // save position for Render3D_UpdateHighlight
996 r_view.mouse_x = x;
997 r_view.mouse_y = y;
998
999 if (edit.is_panning)
1000 {
1001 Editor_ScrollMap(0, dx, dy, mod);
1002 return;
1003 }
1004 else if (edit.action == ACT_ADJUST_OFS)
1005 {
1006 AdjustOfs_Delta(dx, dy);
1007 return;
1008 }
1009
1010 if (edit.action == ACT_CLICK)
1011 {
1012 CheckBeginDrag();
1013 }
1014 else if (edit.action == ACT_DRAG)
1015 {
1016 // get the latest map_x/y/z coordinates
1017 Objid unused_hl;
1018 Render3D_Query(unused_hl, x, y);
1019
1020 edit.drag_screen_dx = x - edit.click_screen_x;
1021 edit.drag_screen_dy = y - edit.click_screen_y;
1022
1023 edit.drag_cur_x = edit.map_x;
1024 edit.drag_cur_y = edit.map_y;
1025
1026 if (edit.mode == OBJ_SECTORS)
1027 DragSectors_Update();
1028
1029 if (edit.mode == OBJ_THINGS)
1030 DragThings_Update();
1031
1032 main_win->canvas->redraw();
1033 main_win->status_bar->redraw();
1034 return;
1035 }
1036
1037 UpdateHighlight();
1038 }
1039
1040
Render3D_UpdateHighlight()1041 void Render3D_UpdateHighlight()
1042 {
1043 edit.highlight.clear();
1044 edit.split_line.clear();
1045
1046 if (edit.pointer_in_window && r_view.mouse_x >= 0 &&
1047 edit.action != ACT_DRAG)
1048 {
1049 Objid current_hl;
1050
1051 // this also updates edit.map_x/y/z
1052 Render3D_Query(current_hl, r_view.mouse_x, r_view.mouse_y);
1053
1054 if (current_hl.type == edit.mode)
1055 edit.highlight = current_hl;
1056 }
1057
1058 main_win->canvas->UpdateHighlight();
1059 main_win->canvas->redraw();
1060
1061 main_win->status_bar->redraw();
1062 }
1063
1064
Render3D_Navigate()1065 void Render3D_Navigate()
1066 {
1067 float delay_ms = Nav_TimeDiff();
1068
1069 delay_ms = delay_ms / 1000.0;
1070
1071 keycode_t mod = 0;
1072
1073 if (edit.nav_lax)
1074 mod = M_ReadLaxModifiers();
1075
1076 float mod_factor = 1.0;
1077 if (mod & MOD_SHIFT) mod_factor = 0.5;
1078 if (mod & MOD_COMMAND) mod_factor = 2.0;
1079
1080 if (edit.nav_fwd || edit.nav_back || edit.nav_right || edit.nav_left)
1081 {
1082 float fwd = edit.nav_fwd - edit.nav_back;
1083 float right = edit.nav_right - edit.nav_left;
1084
1085 float dx = r_view.Cos * fwd + r_view.Sin * right;
1086 float dy = r_view.Sin * fwd - r_view.Cos * right;
1087
1088 dx = dx * mod_factor * mod_factor;
1089 dy = dy * mod_factor * mod_factor;
1090
1091 r_view.x += dx * delay_ms;
1092 r_view.y += dy * delay_ms;
1093 }
1094
1095 if (edit.nav_up || edit.nav_down)
1096 {
1097 float dz = (edit.nav_up - edit.nav_down);
1098
1099 r_view.z += dz * mod_factor * delay_ms;
1100 }
1101
1102 if (edit.nav_turn_L || edit.nav_turn_R)
1103 {
1104 float dang = (edit.nav_turn_L - edit.nav_turn_R);
1105
1106 dang = dang * mod_factor * delay_ms;
1107 dang = CLAMP(-90, dang, 90);
1108
1109 r_view.SetAngle(r_view.angle + dang);
1110 }
1111
1112 main_win->info_bar->SetMouse(r_view.x, r_view.y);
1113 RedrawMap();
1114 }
1115
1116
1117 // returns -1 if nothing in selection or highlight, -2 if multiple
1118 // things are selected and they have different types.
GrabSelectedThing()1119 static int GrabSelectedThing()
1120 {
1121 int result = -1;
1122
1123 if (edit.Selected->empty())
1124 {
1125 if (edit.highlight.is_nil())
1126 {
1127 Beep("no things for copy/cut type");
1128 return -1;
1129 }
1130
1131 result = Things[edit.highlight.num]->type;
1132 }
1133 else
1134 {
1135 for (sel_iter_c it(edit.Selected) ; !it.done() ; it.next())
1136 {
1137 const Thing *T = Things[*it];
1138 if (result >= 0 && T->type != result)
1139 {
1140 Beep("multiple thing types");
1141 return -2;
1142 }
1143
1144 result = T->type;
1145 }
1146 }
1147
1148 Status_Set("copied type %d", result);
1149
1150 return result;
1151 }
1152
1153
StoreSelectedThing(int new_type)1154 static void StoreSelectedThing(int new_type)
1155 {
1156 // this code is similar to code in UI_Thing::type_callback(),
1157 // but here we must handle a highlighted object.
1158
1159 soh_type_e unselect = Selection_Or_Highlight();
1160 if (unselect == SOH_Empty)
1161 {
1162 Beep("no things for paste type");
1163 return;
1164 }
1165
1166 BA_Begin();
1167 BA_MessageForSel("pasted type of", edit.Selected);
1168
1169 for (sel_iter_c it(edit.Selected) ; !it.done() ; it.next())
1170 {
1171 BA_ChangeTH(*it, Thing::F_TYPE, new_type);
1172 }
1173
1174 BA_End();
1175
1176 if (unselect == SOH_Unselect)
1177 Selection_Clear(true /* nosave */);
1178
1179 Status_Set("pasted type %d", new_type);
1180 }
1181
1182
SEC_GrabFlat(const Sector * S,int part)1183 static int SEC_GrabFlat(const Sector *S, int part)
1184 {
1185 if (part & PART_CEIL)
1186 return S->ceil_tex;
1187
1188 if (part & PART_FLOOR)
1189 return S->floor_tex;
1190
1191 return S->floor_tex;
1192 }
1193
1194
1195 // returns -1 if nothing in selection or highlight, -2 if multiple
1196 // sectors are selected and they have different flats.
GrabSelectedFlat()1197 static int GrabSelectedFlat()
1198 {
1199 int result = -1;
1200
1201 if (edit.Selected->empty())
1202 {
1203 if (edit.highlight.is_nil())
1204 {
1205 Beep("no sectors for copy/cut flat");
1206 return -1;
1207 }
1208
1209 const Sector *S = Sectors[edit.highlight.num];
1210
1211 result = SEC_GrabFlat(S, edit.highlight.parts);
1212 }
1213 else
1214 {
1215 for (sel_iter_c it(edit.Selected) ; !it.done() ; it.next())
1216 {
1217 const Sector *S = Sectors[*it];
1218 byte parts = edit.Selected->get_ext(*it);
1219
1220 int tex = SEC_GrabFlat(S, parts & ~1);
1221
1222 if (result >= 0 && tex != result)
1223 {
1224 Beep("multiple flats present");
1225 return -2;
1226 }
1227
1228 result = tex;
1229 }
1230 }
1231
1232 if (result >= 0)
1233 Status_Set("copied %s", BA_GetString(result));
1234
1235 return result;
1236 }
1237
1238
StoreSelectedFlat(int new_tex)1239 static void StoreSelectedFlat(int new_tex)
1240 {
1241 soh_type_e unselect = Selection_Or_Highlight();
1242 if (unselect == SOH_Empty)
1243 {
1244 Beep("no sectors for paste flat");
1245 return;
1246 }
1247
1248 BA_Begin();
1249 BA_MessageForSel("pasted flat to", edit.Selected);
1250
1251 for (sel_iter_c it(edit.Selected) ; !it.done() ; it.next())
1252 {
1253 byte parts = edit.Selected->get_ext(*it);
1254
1255 if (parts == 1 || (parts & PART_FLOOR))
1256 BA_ChangeSEC(*it, Sector::F_FLOOR_TEX, new_tex);
1257
1258 if (parts == 1 || (parts & PART_CEIL))
1259 BA_ChangeSEC(*it, Sector::F_CEIL_TEX, new_tex);
1260 }
1261
1262 BA_End();
1263
1264 if (unselect == SOH_Unselect)
1265 Selection_Clear(true /* nosave */);
1266
1267 Status_Set("pasted %s", BA_GetString(new_tex));
1268 }
1269
1270
StoreDefaultedFlats()1271 static void StoreDefaultedFlats()
1272 {
1273 soh_type_e unselect = Selection_Or_Highlight();
1274 if (unselect == SOH_Empty)
1275 {
1276 Beep("no sectors for default");
1277 return;
1278 }
1279
1280 int floor_tex = BA_InternaliseString(default_floor_tex);
1281 int ceil_tex = BA_InternaliseString(default_ceil_tex);
1282
1283 BA_Begin();
1284 BA_MessageForSel("defaulted flat in", edit.Selected);
1285
1286 for (sel_iter_c it(edit.Selected) ; !it.done() ; it.next())
1287 {
1288 byte parts = edit.Selected->get_ext(*it);
1289
1290 if (parts == 1 || (parts & PART_FLOOR))
1291 BA_ChangeSEC(*it, Sector::F_FLOOR_TEX, floor_tex);
1292
1293 if (parts == 1 || (parts & PART_CEIL))
1294 BA_ChangeSEC(*it, Sector::F_CEIL_TEX, ceil_tex);
1295 }
1296
1297 BA_End();
1298
1299 if (unselect == SOH_Unselect)
1300 Selection_Clear(true /* nosave */);
1301
1302 Status_Set("defaulted flats");
1303 }
1304
1305
LD_GrabTex(const LineDef * L,int part)1306 static int LD_GrabTex(const LineDef *L, int part)
1307 {
1308 if (L->NoSided())
1309 return BA_InternaliseString(default_wall_tex);
1310
1311 if (L->OneSided())
1312 return L->Right()->mid_tex;
1313
1314 if (part & PART_RT_LOWER) return L->Right()->lower_tex;
1315 if (part & PART_RT_UPPER) return L->Right()->upper_tex;
1316
1317 if (part & PART_LF_LOWER) return L->Left()->lower_tex;
1318 if (part & PART_LF_UPPER) return L->Left()->upper_tex;
1319
1320 if (part & PART_RT_RAIL) return L->Right()->mid_tex;
1321 if (part & PART_LF_RAIL) return L->Left() ->mid_tex;
1322
1323 // pick something reasonable for a simply selected line
1324 if (L->Left()->SecRef()->floorh > L->Right()->SecRef()->floorh)
1325 return L->Right()->lower_tex;
1326
1327 if (L->Left()->SecRef()->ceilh < L->Right()->SecRef()->ceilh)
1328 return L->Right()->upper_tex;
1329
1330 if (L->Left()->SecRef()->floorh < L->Right()->SecRef()->floorh)
1331 return L->Left()->lower_tex;
1332
1333 if (L->Left()->SecRef()->ceilh > L->Right()->SecRef()->ceilh)
1334 return L->Left()->upper_tex;
1335
1336 // emergency fallback
1337 return L->Right()->lower_tex;
1338 }
1339
1340
1341 // returns -1 if nothing in selection or highlight, -2 if multiple
1342 // linedefs are selected and they have different textures.
GrabSelectedTexture()1343 static int GrabSelectedTexture()
1344 {
1345 int result = -1;
1346
1347 if (edit.Selected->empty())
1348 {
1349 if (edit.highlight.is_nil())
1350 {
1351 Beep("no linedefs for copy/cut tex");
1352 return -1;
1353 }
1354
1355 const LineDef *L = LineDefs[edit.highlight.num];
1356
1357 result = LD_GrabTex(L, edit.highlight.parts);
1358 }
1359 else
1360 {
1361 for (sel_iter_c it(edit.Selected) ; !it.done() ; it.next())
1362 {
1363 const LineDef *L = LineDefs[*it];
1364 byte parts = edit.Selected->get_ext(*it);
1365
1366 int tex = LD_GrabTex(L, parts & ~1);
1367
1368 if (result >= 0 && tex != result)
1369 {
1370 Beep("multiple textures present");
1371 return -2;
1372 }
1373
1374 result = tex;
1375 }
1376 }
1377
1378 if (result >= 0)
1379 Status_Set("copied %s", BA_GetString(result));
1380
1381 return result;
1382 }
1383
1384
StoreSelectedTexture(int new_tex)1385 static void StoreSelectedTexture(int new_tex)
1386 {
1387 soh_type_e unselect = Selection_Or_Highlight();
1388 if (unselect == SOH_Empty)
1389 {
1390 Beep("no linedefs for paste tex");
1391 return;
1392 }
1393
1394 BA_Begin();
1395 BA_MessageForSel("pasted tex to", edit.Selected);
1396
1397 for (sel_iter_c it(edit.Selected) ; !it.done() ; it.next())
1398 {
1399 const LineDef *L = LineDefs[*it];
1400 byte parts = edit.Selected->get_ext(*it);
1401
1402 if (L->NoSided())
1403 continue;
1404
1405 if (L->OneSided())
1406 {
1407 BA_ChangeSD(L->right, SideDef::F_MID_TEX, new_tex);
1408 continue;
1409 }
1410
1411 /* right side */
1412 if (parts == 1 || (parts & PART_RT_LOWER))
1413 BA_ChangeSD(L->right, SideDef::F_LOWER_TEX, new_tex);
1414
1415 if (parts == 1 || (parts & PART_RT_UPPER))
1416 BA_ChangeSD(L->right, SideDef::F_UPPER_TEX, new_tex);
1417
1418 if (parts & PART_RT_RAIL)
1419 BA_ChangeSD(L->right, SideDef::F_MID_TEX, new_tex);
1420
1421 /* left side */
1422 if (parts == 1 || (parts & PART_LF_LOWER))
1423 BA_ChangeSD(L->left, SideDef::F_LOWER_TEX, new_tex);
1424
1425 if (parts == 1 || (parts & PART_LF_UPPER))
1426 BA_ChangeSD(L->left, SideDef::F_UPPER_TEX, new_tex);
1427
1428 if (parts & PART_LF_RAIL)
1429 BA_ChangeSD(L->left, SideDef::F_MID_TEX, new_tex);
1430 }
1431
1432 BA_End();
1433
1434 if (unselect == SOH_Unselect)
1435 Selection_Clear(true /* nosave */);
1436
1437 Status_Set("pasted %s", BA_GetString(new_tex));
1438 }
1439
1440
Render3D_CB_Copy()1441 void Render3D_CB_Copy()
1442 {
1443 int num;
1444
1445 switch (edit.mode)
1446 {
1447 case OBJ_THINGS:
1448 num = GrabSelectedThing();
1449 if (num >= 0)
1450 Texboard_SetThing(num);
1451 break;
1452
1453 case OBJ_SECTORS:
1454 num = GrabSelectedFlat();
1455 if (num >= 0)
1456 Texboard_SetFlat(BA_GetString(num));
1457 break;
1458
1459 case OBJ_LINEDEFS:
1460 num = GrabSelectedTexture();
1461 if (num >= 0)
1462 Texboard_SetTex(BA_GetString(num));
1463 break;
1464
1465 default:
1466 break;
1467 }
1468 }
1469
1470
Render3D_CB_Paste()1471 void Render3D_CB_Paste()
1472 {
1473 switch (edit.mode)
1474 {
1475 case OBJ_THINGS:
1476 StoreSelectedThing(Texboard_GetThing());
1477 break;
1478
1479 case OBJ_SECTORS:
1480 StoreSelectedFlat(Texboard_GetFlatNum());
1481 break;
1482
1483 case OBJ_LINEDEFS:
1484 StoreSelectedTexture(Texboard_GetTexNum());
1485 break;
1486
1487 default:
1488 break;
1489 }
1490 }
1491
1492
Render3D_CB_Cut()1493 void Render3D_CB_Cut()
1494 {
1495 // this is repurposed to set the default texture/thing
1496
1497 switch (edit.mode)
1498 {
1499 case OBJ_THINGS:
1500 StoreSelectedThing(default_thing);
1501 break;
1502
1503 case OBJ_SECTORS:
1504 StoreDefaultedFlats();
1505 break;
1506
1507 case OBJ_LINEDEFS:
1508 StoreSelectedTexture(BA_InternaliseString(default_wall_tex));
1509 break;
1510
1511 default:
1512 break;
1513 }
1514 }
1515
1516
Render3D_SetCameraPos(double new_x,double new_y)1517 void Render3D_SetCameraPos(double new_x, double new_y)
1518 {
1519 r_view.x = new_x;
1520 r_view.y = new_y;
1521
1522 r_view.FindGroundZ();
1523 }
1524
1525
Render3D_GetCameraPos(double * x,double * y,float * angle)1526 void Render3D_GetCameraPos(double *x, double *y, float *angle)
1527 {
1528 *x = r_view.x;
1529 *y = r_view.y;
1530
1531 // convert angle from radians to degrees
1532 *angle = r_view.angle * 180.0 / M_PI;
1533 }
1534
1535
Render3D_ParseUser(const char ** tokens,int num_tok)1536 bool Render3D_ParseUser(const char ** tokens, int num_tok)
1537 {
1538 if (strcmp(tokens[0], "camera") == 0 && num_tok >= 5)
1539 {
1540 r_view.x = atof(tokens[1]);
1541 r_view.y = atof(tokens[2]);
1542 r_view.z = atof(tokens[3]);
1543
1544 r_view.SetAngle(atof(tokens[4]));
1545 return true;
1546 }
1547
1548 if (strcmp(tokens[0], "r_modes") == 0 && num_tok >= 4)
1549 {
1550 r_view.texturing = atoi(tokens[1]) ? true : false;
1551 r_view.sprites = atoi(tokens[2]) ? true : false;
1552 r_view.lighting = atoi(tokens[3]) ? true : false;
1553
1554 return true;
1555 }
1556
1557 if (strcmp(tokens[0], "r_gravity") == 0 && num_tok >= 2)
1558 {
1559 r_view.gravity = atoi(tokens[1]) ? true : false;
1560 return true;
1561 }
1562
1563 if (strcmp(tokens[0], "low_detail") == 0 && num_tok >= 2)
1564 {
1565 // ignored for compatibility
1566 return true;
1567 }
1568
1569 if (strcmp(tokens[0], "gamma") == 0 && num_tok >= 2)
1570 {
1571 usegamma = MAX(0, atoi(tokens[1])) % 5;
1572
1573 W_UpdateGamma();
1574 return true;
1575 }
1576
1577 return false;
1578 }
1579
1580
Render3D_WriteUser(FILE * fp)1581 void Render3D_WriteUser(FILE *fp)
1582 {
1583 fprintf(fp, "camera %1.2f %1.2f %1.2f %1.2f\n",
1584 r_view.x, r_view.y, r_view.z, r_view.angle);
1585
1586 fprintf(fp, "r_modes %d %d %d\n",
1587 r_view.texturing ? 1 : 0,
1588 r_view.sprites ? 1 : 0,
1589 r_view.lighting ? 1 : 0);
1590
1591 fprintf(fp, "r_gravity %d\n",
1592 r_view.gravity ? 1 : 0);
1593
1594 fprintf(fp, "gamma %d\n",
1595 usegamma);
1596 }
1597
1598
1599 //------------------------------------------------------------------------
1600 // COMMAND FUNCTIONS
1601 //------------------------------------------------------------------------
1602
R3D_Forward()1603 void R3D_Forward()
1604 {
1605 float dist = atof(EXEC_Param[0]);
1606
1607 r_view.x += r_view.Cos * dist;
1608 r_view.y += r_view.Sin * dist;
1609
1610 main_win->info_bar->SetMouse(r_view.x, r_view.y);
1611 RedrawMap();
1612 }
1613
R3D_Backward()1614 void R3D_Backward()
1615 {
1616 float dist = atof(EXEC_Param[0]);
1617
1618 r_view.x -= r_view.Cos * dist;
1619 r_view.y -= r_view.Sin * dist;
1620
1621 main_win->info_bar->SetMouse(r_view.x, r_view.y);
1622 RedrawMap();
1623 }
1624
R3D_Left()1625 void R3D_Left()
1626 {
1627 float dist = atof(EXEC_Param[0]);
1628
1629 r_view.x -= r_view.Sin * dist;
1630 r_view.y += r_view.Cos * dist;
1631
1632 main_win->info_bar->SetMouse(r_view.x, r_view.y);
1633 RedrawMap();
1634 }
1635
R3D_Right()1636 void R3D_Right()
1637 {
1638 float dist = atof(EXEC_Param[0]);
1639
1640 r_view.x += r_view.Sin * dist;
1641 r_view.y -= r_view.Cos * dist;
1642
1643 main_win->info_bar->SetMouse(r_view.x, r_view.y);
1644 RedrawMap();
1645 }
1646
R3D_Up()1647 void R3D_Up()
1648 {
1649 if (r_view.gravity && render_lock_gravity)
1650 {
1651 Beep("Gravity is on");
1652 return;
1653 }
1654
1655 r_view.gravity = false;
1656
1657 float dist = atof(EXEC_Param[0]);
1658
1659 r_view.z += dist;
1660
1661 RedrawMap();
1662 }
1663
R3D_Down()1664 void R3D_Down()
1665 {
1666 if (r_view.gravity && render_lock_gravity)
1667 {
1668 Beep("Gravity is on");
1669 return;
1670 }
1671
1672 r_view.gravity = false;
1673
1674 float dist = atof(EXEC_Param[0]);
1675
1676 r_view.z -= dist;
1677
1678 RedrawMap();
1679 }
1680
1681
R3D_Turn()1682 void R3D_Turn()
1683 {
1684 float angle = atof(EXEC_Param[0]);
1685
1686 // convert to radians
1687 angle = angle * M_PI / 180.0;
1688
1689 r_view.SetAngle(r_view.angle + angle);
1690
1691 RedrawMap();
1692 }
1693
1694
R3D_DropToFloor()1695 void R3D_DropToFloor()
1696 {
1697 r_view.FindGroundZ();
1698
1699 RedrawMap();
1700 }
1701
1702
R3D_NAV_Forward_release()1703 static void R3D_NAV_Forward_release()
1704 {
1705 edit.nav_fwd = 0;
1706 }
1707
R3D_NAV_Forward()1708 void R3D_NAV_Forward()
1709 {
1710 if (! EXEC_CurKey)
1711 return;
1712
1713 if (! edit.is_navigating)
1714 Editor_ClearNav();
1715
1716 edit.nav_fwd = atof(EXEC_Param[0]);
1717
1718 Nav_SetKey(EXEC_CurKey, &R3D_NAV_Forward_release);
1719 }
1720
1721
R3D_NAV_Back_release(void)1722 static void R3D_NAV_Back_release(void)
1723 {
1724 edit.nav_back = 0;
1725 }
1726
R3D_NAV_Back()1727 void R3D_NAV_Back()
1728 {
1729 if (! EXEC_CurKey)
1730 return;
1731
1732 if (! edit.is_navigating)
1733 Editor_ClearNav();
1734
1735 edit.nav_back = atof(EXEC_Param[0]);
1736
1737 Nav_SetKey(EXEC_CurKey, &R3D_NAV_Back_release);
1738 }
1739
1740
R3D_NAV_Right_release(void)1741 static void R3D_NAV_Right_release(void)
1742 {
1743 edit.nav_right = 0;
1744 }
1745
R3D_NAV_Right()1746 void R3D_NAV_Right()
1747 {
1748 if (! EXEC_CurKey)
1749 return;
1750
1751 if (! edit.is_navigating)
1752 Editor_ClearNav();
1753
1754 edit.nav_right = atof(EXEC_Param[0]);
1755
1756 Nav_SetKey(EXEC_CurKey, &R3D_NAV_Right_release);
1757 }
1758
1759
R3D_NAV_Left_release(void)1760 static void R3D_NAV_Left_release(void)
1761 {
1762 edit.nav_left = 0;
1763 }
1764
R3D_NAV_Left()1765 void R3D_NAV_Left()
1766 {
1767 if (! EXEC_CurKey)
1768 return;
1769
1770 if (! edit.is_navigating)
1771 Editor_ClearNav();
1772
1773 edit.nav_left = atof(EXEC_Param[0]);
1774
1775 Nav_SetKey(EXEC_CurKey, &R3D_NAV_Left_release);
1776 }
1777
1778
R3D_NAV_Up_release(void)1779 static void R3D_NAV_Up_release(void)
1780 {
1781 edit.nav_up = 0;
1782 }
1783
R3D_NAV_Up()1784 void R3D_NAV_Up()
1785 {
1786 if (! EXEC_CurKey)
1787 return;
1788
1789 if (r_view.gravity && render_lock_gravity)
1790 {
1791 Beep("Gravity is on");
1792 return;
1793 }
1794
1795 r_view.gravity = false;
1796
1797 if (! edit.is_navigating)
1798 Editor_ClearNav();
1799
1800 edit.nav_up = atof(EXEC_Param[0]);
1801
1802 Nav_SetKey(EXEC_CurKey, &R3D_NAV_Up_release);
1803 }
1804
1805
R3D_NAV_Down_release(void)1806 static void R3D_NAV_Down_release(void)
1807 {
1808 edit.nav_down = 0;
1809 }
1810
R3D_NAV_Down()1811 void R3D_NAV_Down()
1812 {
1813 if (! EXEC_CurKey)
1814 return;
1815
1816 if (r_view.gravity && render_lock_gravity)
1817 {
1818 Beep("Gravity is on");
1819 return;
1820 }
1821
1822 r_view.gravity = false;
1823
1824 if (! edit.is_navigating)
1825 Editor_ClearNav();
1826
1827 edit.nav_down = atof(EXEC_Param[0]);
1828
1829 Nav_SetKey(EXEC_CurKey, &R3D_NAV_Down_release);
1830 }
1831
1832
R3D_NAV_TurnLeft_release(void)1833 static void R3D_NAV_TurnLeft_release(void)
1834 {
1835 edit.nav_turn_L = 0;
1836 }
1837
R3D_NAV_TurnLeft()1838 void R3D_NAV_TurnLeft()
1839 {
1840 if (! EXEC_CurKey)
1841 return;
1842
1843 if (! edit.is_navigating)
1844 Editor_ClearNav();
1845
1846 float turn = atof(EXEC_Param[0]);
1847
1848 // convert to radians
1849 edit.nav_turn_L = turn * M_PI / 180.0;
1850
1851 Nav_SetKey(EXEC_CurKey, &R3D_NAV_TurnLeft_release);
1852 }
1853
1854
R3D_NAV_TurnRight_release(void)1855 static void R3D_NAV_TurnRight_release(void)
1856 {
1857 edit.nav_turn_R = 0;
1858 }
1859
R3D_NAV_TurnRight()1860 void R3D_NAV_TurnRight()
1861 {
1862 if (! EXEC_CurKey)
1863 return;
1864
1865 if (! edit.is_navigating)
1866 Editor_ClearNav();
1867
1868 float turn = atof(EXEC_Param[0]);
1869
1870 // convert to radians
1871 edit.nav_turn_R = turn * M_PI / 180.0;
1872
1873 Nav_SetKey(EXEC_CurKey, &R3D_NAV_TurnRight_release);
1874 }
1875
1876
ACT_AdjustOfs_release(void)1877 static void ACT_AdjustOfs_release(void)
1878 {
1879 // check if cancelled or overridden
1880 if (edit.action != ACT_ADJUST_OFS)
1881 return;
1882
1883 AdjustOfs_Finish();
1884 }
1885
R3D_ACT_AdjustOfs()1886 void R3D_ACT_AdjustOfs()
1887 {
1888 if (! EXEC_CurKey)
1889 return;
1890
1891 if (! Nav_ActionKey(EXEC_CurKey, &ACT_AdjustOfs_release))
1892 return;
1893
1894 if (edit.mode != OBJ_LINEDEFS)
1895 {
1896 Beep("not in linedef mode");
1897 return;
1898 }
1899
1900 AdjustOfs_Begin();
1901 }
1902
1903
R3D_Set()1904 void R3D_Set()
1905 {
1906 const char *var_name = EXEC_Param[0];
1907 const char *value = EXEC_Param[1];
1908
1909 if (! var_name[0])
1910 {
1911 Beep("3D_Set: missing var name");
1912 return;
1913 }
1914
1915 if (! value[0])
1916 {
1917 Beep("3D_Set: missing value");
1918 return;
1919 }
1920
1921 int int_val = atoi(value);
1922 bool bool_val = (int_val > 0);
1923
1924
1925 if (y_stricmp(var_name, "tex") == 0)
1926 {
1927 r_view.texturing = bool_val;
1928 }
1929 else if (y_stricmp(var_name, "obj") == 0)
1930 {
1931 r_view.sprites = bool_val;
1932 }
1933 else if (y_stricmp(var_name, "light") == 0)
1934 {
1935 r_view.lighting = bool_val;
1936 }
1937 else if (y_stricmp(var_name, "grav") == 0)
1938 {
1939 r_view.gravity = bool_val;
1940 }
1941 else
1942 {
1943 Beep("3D_Set: unknown var: %s", var_name);
1944 return;
1945 }
1946
1947 RedrawMap();
1948 }
1949
1950
R3D_Toggle()1951 void R3D_Toggle()
1952 {
1953 const char *var_name = EXEC_Param[0];
1954
1955 if (! var_name[0])
1956 {
1957 Beep("3D_Toggle: missing var name");
1958 return;
1959 }
1960
1961 if (y_stricmp(var_name, "tex") == 0)
1962 {
1963 r_view.texturing = ! r_view.texturing;
1964 }
1965 else if (y_stricmp(var_name, "obj") == 0)
1966 {
1967 r_view.sprites = ! r_view.sprites;
1968 }
1969 else if (y_stricmp(var_name, "light") == 0)
1970 {
1971 r_view.lighting = ! r_view.lighting;
1972 }
1973 else if (y_stricmp(var_name, "grav") == 0)
1974 {
1975 r_view.gravity = ! r_view.gravity;
1976 }
1977 else
1978 {
1979 Beep("3D_Toggle: unknown var: %s", var_name);
1980 return;
1981 }
1982
1983 RedrawMap();
1984 }
1985
1986
R3D_WHEEL_Move()1987 void R3D_WHEEL_Move()
1988 {
1989 float dx = Fl::event_dx();
1990 float dy = Fl::event_dy();
1991
1992 dy = 0 - dy;
1993
1994 float speed = atof(EXEC_Param[0]);
1995
1996 if (Exec_HasFlag("/LAX"))
1997 {
1998 keycode_t mod = Fl::event_state() & MOD_ALL_MASK;
1999
2000 if (mod == MOD_SHIFT)
2001 speed /= 4.0;
2002 else if (mod == MOD_COMMAND)
2003 speed *= 4.0;
2004 }
2005
2006 r_view.x += speed * (r_view.Cos * dy + r_view.Sin * dx);
2007 r_view.y += speed * (r_view.Sin * dy - r_view.Cos * dx);
2008
2009 main_win->info_bar->SetMouse(r_view.x, r_view.y);
2010 RedrawMap();
2011 }
2012
2013
2014 //------------------------------------------------------------------------
2015
2016 extern void CMD_ACT_Click();
2017
2018
2019 static editor_command_t render_commands[] =
2020 {
2021 { "3D_Set", NULL,
2022 &R3D_Set,
2023 /* flags */ NULL,
2024 /* keywords */ "tex obj light grav"
2025 },
2026
2027 { "3D_Toggle", NULL,
2028 &R3D_Toggle,
2029 /* flags */ NULL,
2030 /* keywords */ "tex obj light grav"
2031 },
2032
2033 { "3D_Forward", NULL,
2034 &R3D_Forward
2035 },
2036
2037 { "3D_Backward", NULL,
2038 &R3D_Backward
2039 },
2040
2041 { "3D_Left", NULL,
2042 &R3D_Left
2043 },
2044
2045 { "3D_Right", NULL,
2046 &R3D_Right
2047 },
2048
2049 { "3D_Up", NULL,
2050 &R3D_Up
2051 },
2052
2053 { "3D_Down", NULL,
2054 &R3D_Down
2055 },
2056
2057 { "3D_Turn", NULL,
2058 &R3D_Turn
2059 },
2060
2061 { "3D_DropToFloor", NULL,
2062 &R3D_DropToFloor
2063 },
2064
2065 { "3D_ACT_AdjustOfs", NULL,
2066 &R3D_ACT_AdjustOfs
2067 },
2068
2069 { "3D_WHEEL_Move", NULL,
2070 &R3D_WHEEL_Move
2071 },
2072
2073 { "3D_NAV_Forward", NULL,
2074 &R3D_NAV_Forward
2075 },
2076
2077 { "3D_NAV_Back", NULL,
2078 &R3D_NAV_Back
2079 },
2080
2081 { "3D_NAV_Right", NULL,
2082 &R3D_NAV_Right
2083 },
2084
2085 { "3D_NAV_Left", NULL,
2086 &R3D_NAV_Left
2087 },
2088
2089 { "3D_NAV_Up", NULL,
2090 &R3D_NAV_Up
2091 },
2092
2093 { "3D_NAV_Down", NULL,
2094 &R3D_NAV_Down
2095 },
2096
2097 { "3D_NAV_TurnLeft", NULL,
2098 &R3D_NAV_TurnLeft
2099 },
2100
2101 { "3D_NAV_TurnRight", NULL,
2102 &R3D_NAV_TurnRight
2103 },
2104
2105 // backwards compatibility.
2106 // [ we cannot remap this in FindEditorCommand, it fails the context check ]
2107 { "3D_Align", NULL,
2108 &CMD_LIN_Align,
2109 /* flags */ "/x /y /right /clear"
2110 },
2111
2112 // end of command list
2113 { NULL, NULL, 0, NULL }
2114 };
2115
2116
Render3D_RegisterCommands()2117 void Render3D_RegisterCommands()
2118 {
2119 M_RegisterCommandList(render_commands);
2120 }
2121
2122
2123 //--- editor settings ---
2124 // vi:ts=4:sw=4:noexpandtab
2125