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