1 //
2 //
3 
4 #include <globalincs/linklist.h>
5 #include <object/object.h>
6 #include <render/3d.h>
7 #include <ship/ship.h>
8 #include <io/key.h>
9 
10 #include "object.h"
11 
12 #include "EditorViewport.h"
13 #include <math/fvi.h>
14 #include <jumpnode/jumpnode.h>
15 #include <FredApplication.h>
16 
17 namespace {
18 
19 const fix MAX_FRAMETIME = (F1_0 / 4); // Frametime gets saturated at this.
20 const fix MIN_FRAMETIME = (F1_0 / 120);
21 
22 const float REDUCER = 100.0f;
23 
process_movement_keys(int key,vec3d * mvec,angles * angs)24 void process_movement_keys(int key, vec3d* mvec, angles* angs) {
25 	int raw_key;
26 
27 	mvec->xyz.x = 0.0f;
28 	mvec->xyz.y = 0.0f;
29 	mvec->xyz.z = 0.0f;
30 	angs->p = 0.0f;
31 	angs->b = 0.0f;
32 	angs->h = 0.0f;
33 
34 	raw_key = key & 0xff;
35 
36 	switch (raw_key) {
37 	case KEY_PAD1:
38 		mvec->xyz.x += -1.0f;
39 		break;
40 	case KEY_PAD3:
41 		mvec->xyz.x += +1.0f;
42 		break;
43 	case KEY_PADPLUS:
44 		mvec->xyz.y += -1.0f;
45 		break;
46 	case KEY_PADMINUS:
47 		mvec->xyz.y += +1.0f;
48 		break;
49 	case KEY_A:
50 		mvec->xyz.z += +1.0f;
51 		break;
52 	case KEY_Z:
53 		mvec->xyz.z += -1.0f;
54 		break;
55 	case KEY_PAD4:
56 		angs->h += -0.1f;
57 		break;
58 	case KEY_PAD6:
59 		angs->h += +0.1f;
60 		break;
61 	case KEY_PAD8:
62 		angs->p += -0.1f;
63 		break;
64 	case KEY_PAD2:
65 		angs->p += +0.1f;
66 		break;
67 	case KEY_PAD7:
68 		angs->b += -0.1f;
69 		break;
70 	case KEY_PAD9:
71 		angs->b += +0.1f;
72 		break;
73 	}
74 
75 	if (key & KEY_SHIFTED) {
76 		vm_vec_scale(mvec, 5.0f);
77 		angs->p *= 5.0f;
78 		angs->b *= 5.0f;
79 		angs->h *= 5.0f;
80 	}
81 }
align_vector_to_axis(vec3d * v)82 void align_vector_to_axis(vec3d* v) {
83 	float x, y, z;
84 
85 	x = v->xyz.x;
86 	if (x < 0) {
87 		x = -x;
88 	}
89 
90 	y = v->xyz.y;
91 	if (y < 0) {
92 		y = -y;
93 	}
94 
95 	z = v->xyz.z;
96 	if (z < 0) {
97 		z = -z;
98 	}
99 
100 	if ((x > y) && (x > z)) { // x axis
101 		if (v->xyz.x < 0) // negative x
102 			vm_vec_make(v, -1.0f, 0.0f, 0.0f);
103 		else // positive x
104 			vm_vec_make(v, 1.0f, 0.0f, 0.0f);
105 	} else if (y > z) { // y axis
106 		if (v->xyz.y < 0) // negative y
107 			vm_vec_make(v, 0.0f, -1.0f, 0.0f);
108 		else // positive y
109 			vm_vec_make(v, 0.0f, 1.0f, 0.0f);
110 	} else { // z axis
111 		if (v->xyz.z < 0) // negative z
112 			vm_vec_make(v, 0.0f, 0.0f, -1.0f);
113 		else // positive z
114 			vm_vec_make(v, 0.0f, 0.0f, 1.0f);
115 	}
116 }
verticalize_object(matrix * orient)117 void verticalize_object(matrix* orient) {
118 	align_vector_to_axis(&orient->vec.fvec);
119 	align_vector_to_axis(&orient->vec.uvec);
120 	align_vector_to_axis(&orient->vec.rvec);
121 	vm_fix_matrix(orient); // just in case something odd occurs.
122 }
123 
124 }
125 
126 namespace fso {
127 namespace fred {
128 
EditorViewport(Editor * in_editor,std::unique_ptr<FredRenderer> && in_renderer)129 EditorViewport::EditorViewport(Editor* in_editor, std::unique_ptr<FredRenderer>&& in_renderer) :
130 	_renderer(std::move(in_renderer)), editor(in_editor) {
131 	renderer = _renderer.get();
132 
133 	_renderer->setViewport(this);
134 
135 	vm_vec_make(&Constraint, 1.0f, 0.0f, 1.0f);
136 	vm_vec_make(&Anticonstraint, 0.0f, 1.0f, 0.0f);
137 	resetView();
138 
139 	memset(&saved_cam_orient, 0, sizeof(saved_cam_orient));
140 
141 	fredApp->runAfterInit([this]() { initialSetup(); });
142 }
needsUpdate()143 void EditorViewport::needsUpdate() {
144 	_renderer->scheduleUpdate();
145 }
resetViewPhysics()146 void EditorViewport::resetViewPhysics() {
147 	physics_init(&view_physics);
148 	view_physics.max_vel.xyz.x *= physics_speed / 3.0f;
149 	view_physics.max_vel.xyz.y *= physics_speed / 3.0f;
150 	view_physics.max_vel.xyz.z *= physics_speed / 3.0f;
151 	view_physics.max_rear_vel *= physics_speed / 3.0f;
152 	view_physics.max_rotvel.xyz.x *= physics_rot / 30.0f;
153 	view_physics.max_rotvel.xyz.y *= physics_rot / 30.0f;
154 	view_physics.max_rotvel.xyz.z *= physics_rot / 30.0f;
155 	view_physics.flags |= PF_ACCELERATES | PF_SLIDE_ENABLED;
156 }
select_objects(const Marking_box & box)157 void EditorViewport::select_objects(const Marking_box& box) {
158 	int x, y, valid, icon_mode = 0;
159 	vertex v;
160 	object* ptr;
161 
162 	// Copy this so we can modify it
163 	auto marking_box = box;
164 
165 	if (marking_box.x1 > marking_box.x2) {
166 		x = marking_box.x1;
167 		marking_box.x1 = marking_box.x2;
168 		marking_box.x2 = x;
169 	}
170 
171 	if (marking_box.y1 > marking_box.y2) {
172 		y = marking_box.y1;
173 		marking_box.y1 = marking_box.y2;
174 		marking_box.y2 = y;
175 	}
176 
177 	ptr = GET_FIRST(&obj_used_list);
178 	while (ptr != END_OF_LIST(&obj_used_list)) {
179 		valid = 1;
180 		if (ptr->flags[Object::Object_Flags::Hidden]) {
181 			valid = 0;
182 		}
183 
184 		Assert(ptr->type != OBJ_NONE);
185 		switch (ptr->type) {
186 		case OBJ_WAYPOINT:
187 			if (!Show_waypoints) {
188 				valid = 0;
189 			}
190 			break;
191 
192 		case OBJ_START:
193 			if (!view.Show_starts || !view.Show_ships) {
194 				valid = 0;
195 			}
196 			break;
197 
198 		case OBJ_SHIP:
199 			if (!view.Show_ships) {
200 				valid = 0;
201 			}
202 
203 			if (!view.Show_iff[Ships[ptr->instance].team]) {
204 				valid = 0;
205 			}
206 
207 			break;
208 		}
209 
210 		g3_rotate_vertex(&v, &ptr->pos);
211 		if (!(v.codes & CC_BEHIND) && valid) {
212 			if (!(g3_project_vertex(&v) & PF_OVERFLOW)) {
213 				x = (int) v.screen.xyw.x;
214 				y = (int) v.screen.xyw.y;
215 
216 				if (x >= marking_box.x1 && x <= marking_box.x2 && y >= marking_box.y1 && y <= marking_box.y2) {
217 					if (ptr->flags[Object::Object_Flags::Marked]) {
218 						editor->unmarkObject(OBJ_INDEX(ptr));
219 					} else {
220 						editor->markObject(OBJ_INDEX(ptr));
221 					}
222 
223 					if (ptr->type == OBJ_POINT) {
224 						icon_mode = 1;
225 					}
226 				}
227 			}
228 		}
229 
230 		ptr = GET_NEXT(ptr);
231 	}
232 
233 	if (icon_mode) {
234 		ptr = GET_FIRST(&obj_used_list);
235 		while (ptr != END_OF_LIST(&obj_used_list)) {
236 			if ((ptr->flags[Object::Object_Flags::Marked]) && (ptr->type != OBJ_POINT)) {
237 				editor->unmarkObject(OBJ_INDEX(ptr));
238 			}
239 
240 			ptr = GET_NEXT(ptr);
241 		}
242 	}
243 
244 	needsUpdate();
245 }
246 
resetView()247 void EditorViewport::resetView() {
248 	my_pos = vmd_zero_vector;
249 	my_pos.xyz.z = -5.0f;
250 	vec3d f, u, r;
251 
252 	physics_init(&view_physics);
253 	view_physics.max_vel.xyz.z = 5.0f; //forward/backward
254 	view_physics.max_rotvel.xyz.x = 1.5f; //pitch
255 	memset(&view_controls, 0, sizeof(control_info));
256 
257 	vm_vec_make(&view_pos, 0.0f, 150.0f, -200.0f);
258 	vm_vec_make(&f, 0.0f, -0.5f, 0.866025404f); // 30 degree angle
259 	vm_vec_make(&u, 0.0f, 0.866025404f, 0.5f);
260 	vm_vec_make(&r, 1.0f, 0.0f, 0.0f);
261 	vm_vector_2_matrix(&view_orient, &f, &u, &r);
262 
263 	The_grid = create_default_grid();
264 	maybe_create_new_grid(The_grid, &view_pos, &view_orient, 1);
265 	//	vm_set_identity(&view_orient);
266 }
267 
268 
move_mouse(int btn,int mdx,int mdy)269 void EditorViewport::move_mouse(int btn, int mdx, int mdy) {
270 	int dx, dy;
271 
272 	dx = mdx - last_x;
273 	dy = mdy - last_y;
274 	last_x = mdx;
275 	last_y = mdy;
276 
277 	if (btn & 1) {
278 		matrix tempm, mousem;
279 
280 		if (dx || dy) {
281 			vm_trackball(dx, dy, &mousem);
282 			vm_matrix_x_matrix(&tempm, &trackball_orient, &mousem);
283 			trackball_orient = tempm;
284 			view_orient = trackball_orient;
285 		}
286 	}
287 
288 	if (btn & 2) {
289 		my_pos.xyz.z += (float) dy;
290 	}
291 }
292 
293 ///////////////////////////////////////////////////
process_system_keys(int key)294 void EditorViewport::process_system_keys(int key) {
295 	//	mprintf(("Key = %d\n", key));
296 	switch (key) {
297 	case KEY_LAPOSTRO:
298 		///! \todo cycle through axis-constraints for rotations.
299 		//CFREDView::GetView()->cycle_constraint();
300 		break;
301 
302 	case KEY_R: // for some stupid reason, an accelerator for 'R' doesn't work.
303 		///! \todo Change editing mode to 'move and rotate'.
304 		//Editing_mode = 2;
305 		break;
306 
307 	case KEY_SPACEBAR:
308 		Selection_lock = !Selection_lock;
309 		break;
310 
311 	case KEY_ESC:
312 		///! \todo Cancel drag.
313 		//if (button_down)
314 		//	cancel_drag();
315 
316 		break;
317 	}
318 }
319 
process_controls(vec3d * pos,matrix * orient,float frametime,int key,int mode)320 void EditorViewport::process_controls(vec3d* pos, matrix* orient, float frametime, int key, int mode) {
321 	if (Flying_controls_mode) {
322 		grid_read_camera_controls(&view_controls, frametime);
323 
324 		if (key_get_shift_status()) {
325 			memset(&view_controls, 0, sizeof(control_info));
326 		}
327 
328 		if ((fabs(view_controls.pitch) > (frametime / 100)) || (fabs(view_controls.vertical) > (frametime / 100))
329 			|| (fabs(view_controls.heading) > (frametime / 100)) || (fabs(view_controls.sideways) > (frametime / 100))
330 			|| (fabs(view_controls.bank) > (frametime / 100)) || (fabs(view_controls.forward) > (frametime / 100))) {
331 			needsUpdate();
332 		}
333 
334 		//view_physics.flags |= (PF_ACCELERATES | PF_SLIDE_ENABLED);
335 		physics_read_flying_controls(orient, &view_physics, &view_controls, frametime);
336 		if (mode) {
337 			physics_sim_editor(pos, orient, &view_physics, frametime);
338 		} else {
339 			physics_sim(pos, orient, &view_physics, frametime);
340 		}
341 	} else {
342 		vec3d movement_vec, rel_movement_vec;
343 		angles rotangs;
344 		matrix newmat, rotmat;
345 
346 		process_movement_keys(key, &movement_vec, &rotangs);
347 		vm_vec_rotate(&rel_movement_vec, &movement_vec, &The_grid->gmatrix);
348 		vm_vec_add2(pos, &rel_movement_vec);
349 
350 		vm_angles_2_matrix(&rotmat, &rotangs);
351 		if (rotangs.h && view.Universal_heading) {
352 			vm_transpose(orient);
353 		}
354 		vm_matrix_x_matrix(&newmat, orient, &rotmat);
355 		*orient = newmat;
356 		if (rotangs.h && view.Universal_heading) {
357 			vm_transpose(orient);
358 		}
359 	}
360 }
361 
362 /**
363 * @brief Increments mission time
364 *
365 * @details This only increments the mission time if the time difference is greater than the minimum frametime to avoid
366 * excessive computation
367 *
368 * @return @c true if the mission time was incremented, @c false otherwise.
369 */
inc_mission_time()370 bool EditorViewport::inc_mission_time() {
371 	fix thistime = timer_get_fixed_seconds();
372 	fix time_diff; // This holds the computed time difference since the last time this function was called
373 	if (!lasttime) {
374 		time_diff = F1_0 / 30;
375 	} else {
376 		time_diff = thistime - lasttime;
377 	}
378 
379 	if (time_diff > MAX_FRAMETIME) {
380 		time_diff = MAX_FRAMETIME;
381 	} else if (time_diff < MIN_FRAMETIME) {
382 		return false;
383 	}
384 
385 	Frametime = time_diff;
386 	Missiontime += Frametime;
387 	lasttime = thistime;
388 
389 	return true;
390 }
391 
game_do_frame(const int cur_object_index)392 void EditorViewport::game_do_frame(const int cur_object_index) {
393 	int key, cmode;
394 	vec3d viewer_position, control_pos;
395 	object* objp;
396 	matrix control_orient;
397 
398 	if (!inc_mission_time()) {
399 		// Don't do anything if the mission time wasn't incremented
400 		return;
401 	}
402 
403 	viewer_position = my_orient.vec.fvec;
404 	vm_vec_scale(&viewer_position, my_pos.xyz.z);
405 
406 	if ((viewpoint == 1) && !query_valid_object(view_obj)) {
407 		viewpoint = 0;
408 	}
409 
410 	key = key_inkey();
411 	process_system_keys(key);
412 	cmode = Control_mode;
413 	if ((viewpoint == 1) && !cmode) {
414 		cmode = 2;
415 	}
416 
417 	control_pos = Last_control_pos;
418 	control_orient = Last_control_orient;
419 
420 	//	if ((key & KEY_MASK) == key)  // unmodified
421 	switch (cmode) {
422 	case 0: //	Control the viewer's location and orientation
423 		process_controls(&view_pos, &view_orient, f2fl(Frametime), key, 1);
424 		control_pos = view_pos;
425 		control_orient = view_orient;
426 		break;
427 
428 	case 2: // Control viewpoint object
429 		process_controls(&Objects[view_obj].pos, &Objects[view_obj].orient, f2fl(Frametime), key);
430 		object_moved(&Objects[view_obj]);
431 		control_pos = Objects[view_obj].pos;
432 		control_orient = Objects[view_obj].orient;
433 		break;
434 
435 	case 1: //	Control the current object's location and orientation
436 		if (query_valid_object(cur_object_index)) {
437 			vec3d delta_pos, leader_old_pos;
438 			matrix leader_orient, leader_transpose, tmp;
439 			object* leader;
440 
441 			leader = &Objects[cur_object_index];
442 			leader_old_pos = leader->pos; // save original position
443 			leader_orient = leader->orient; // save original orientation
444 			vm_copy_transpose(&leader_transpose, &leader_orient);
445 
446 			process_controls(&leader->pos, &leader->orient, f2fl(Frametime), key);
447 			vm_vec_sub(&delta_pos, &leader->pos, &leader_old_pos); // get position change
448 			control_pos = leader->pos;
449 			control_orient = leader->orient;
450 
451 			objp = GET_FIRST(&obj_used_list);
452 			while (objp != END_OF_LIST(&obj_used_list)) {
453 				Assert(objp->type != OBJ_NONE);
454 				if ((objp->flags[Object::Object_Flags::Marked]) && (cur_object_index != OBJ_INDEX(objp))) {
455 					if (Group_rotate) {
456 						matrix rot_trans;
457 						vec3d tmpv1, tmpv2;
458 
459 						// change rotation matrix to rotate in opposite direction.  This rotation
460 						// matrix is what the leader ship has rotated by.
461 						vm_copy_transpose(&rot_trans, &view_physics.last_rotmat);
462 
463 						// get point relative to our point of rotation (make POR the origin).  Since
464 						// only the leader has been moved yet, and not the objects, we have to use
465 						// the old leader's position.
466 						vm_vec_sub(&tmpv1, &objp->pos, &leader_old_pos);
467 
468 						// convert point from real-world coordinates to leader's relative coordinate
469 						// system (z=forward vec, y=up vec, x=right vec
470 						vm_vec_rotate(&tmpv2, &tmpv1, &leader_orient);
471 
472 						// now rotate the point by the transpose from above.
473 						vm_vec_rotate(&tmpv1, &tmpv2, &rot_trans);
474 
475 						// convert point back into real-world coordinates
476 						vm_vec_rotate(&tmpv2, &tmpv1, &leader_transpose);
477 
478 						// and move origin back to real-world origin.  Object is now at its correct
479 						// position.  Note we used the leader's new position, instead of old position.
480 						vm_vec_add(&objp->pos, &leader->pos, &tmpv2);
481 
482 						// Now fix the object's orientation to what it should be.
483 						vm_matrix_x_matrix(&tmp, &objp->orient, &view_physics.last_rotmat);
484 						vm_orthogonalize_matrix(&tmp); // safety check
485 						objp->orient = tmp;
486 					} else {
487 						vm_vec_add2(&objp->pos, &delta_pos);
488 						vm_matrix_x_matrix(&tmp, &objp->orient, &view_physics.last_rotmat);
489 						objp->orient = tmp;
490 					}
491 				}
492 
493 				objp = GET_NEXT(objp);
494 			}
495 
496 			objp = GET_FIRST(&obj_used_list);
497 			while (objp != END_OF_LIST(&obj_used_list)) {
498 				if (objp->flags[Object::Object_Flags::Marked]) {
499 					object_moved(objp);
500 				}
501 
502 				objp = GET_NEXT(objp);
503 			}
504 
505 			// Notify the editor that the mission has changed
506 			editor->missionChanged();
507 		}
508 
509 		break;
510 
511 	default:
512 		Assert(0);
513 	}
514 
515 	if (Lookat_mode && query_valid_object(cur_object_index)) {
516 		float dist;
517 
518 		dist = vm_vec_dist(&view_pos, &Objects[cur_object_index].pos);
519 		vm_vec_scale_add(&view_pos, &Objects[cur_object_index].pos, &view_orient.vec.fvec, -dist);
520 	}
521 
522 	switch (viewpoint) {
523 	case 0:
524 		eye_pos = view_pos;
525 		eye_orient = view_orient;
526 		break;
527 
528 	case 1:
529 		eye_pos = Objects[view_obj].pos;
530 		eye_orient = Objects[view_obj].orient;
531 		break;
532 
533 	default:
534 		Assert(0);
535 	}
536 
537 	maybe_create_new_grid(The_grid, &eye_pos, &eye_orient);
538 
539 	if (Cursor_over != Last_cursor_over) {
540 		Last_cursor_over = Cursor_over;
541 		needsUpdate();
542 	}
543 
544 	// redraw screen if controlled object moved or rotated
545 	if (vm_vec_cmp(&control_pos, &Last_control_pos) || vm_matrix_cmp(&control_orient, &Last_control_orient)) {
546 		needsUpdate();
547 		Last_control_pos = control_pos;
548 		Last_control_orient = control_orient;
549 	}
550 
551 	// redraw screen if current viewpoint moved or rotated
552 	if (vm_vec_cmp(&eye_pos, &Last_eye_pos) || vm_matrix_cmp(&eye_orient, &Last_eye_orient)) {
553 		needsUpdate();
554 		Last_eye_pos = eye_pos;
555 		Last_eye_orient = eye_orient;
556 	}
557 }
558 
level_controlled()559 void EditorViewport::level_controlled() {
560 	int cmode, count = 0;
561 	object* objp;
562 
563 	cmode = Control_mode;
564 	if ((viewpoint == 1) && !cmode) {
565 		cmode = 2;
566 	}
567 
568 	switch (cmode) {
569 	case 0: //	Control the viewer's location and orientation
570 		level_object(&view_orient);
571 		break;
572 
573 	case 2: // Control viewpoint object
574 		level_object(&Objects[view_obj].orient);
575 		object_moved(&Objects[view_obj]);
576 		///! \todo Notify.
577 		editor->missionChanged();
578 		//FREDDoc_ptr->autosave("level object");
579 		break;
580 
581 	case 1: //	Control the current object's location and orientation
582 		objp = GET_FIRST(&obj_used_list);
583 		while (objp != END_OF_LIST(&obj_used_list)) {
584 			if (objp->flags[Object::Object_Flags::Marked]) {
585 				level_object(&objp->orient);
586 			}
587 
588 			objp = GET_NEXT(objp);
589 		}
590 
591 		objp = GET_FIRST(&obj_used_list);
592 		while (objp != END_OF_LIST(&obj_used_list)) {
593 			if (objp->flags[Object::Object_Flags::Marked]) {
594 				object_moved(objp);
595 				count++;
596 			}
597 
598 			objp = GET_NEXT(objp);
599 		}
600 
601 		///! \todo Notify.
602 		if (count) {
603 			/*
604 			if (count > 1)
605 			FREDDoc_ptr->autosave("level objects");
606 			else
607 			FREDDoc_ptr->autosave("level object");
608 			*/
609 
610 			editor->missionChanged();
611 		}
612 
613 		break;
614 	}
615 
616 	return;
617 }
618 
verticalize_controlled()619 void EditorViewport::verticalize_controlled() {
620 	int cmode, count = 0;
621 	object* objp;
622 
623 	cmode = Control_mode;
624 	if ((viewpoint == 1) && !cmode) {
625 		cmode = 2;
626 	}
627 
628 	switch (cmode) {
629 	case 0: //	Control the viewer's location and orientation
630 		verticalize_object(&view_orient);
631 		break;
632 
633 	case 2: // Control viewpoint object
634 		verticalize_object(&Objects[view_obj].orient);
635 		object_moved(&Objects[view_obj]);
636 		///! \todo notify.
637 		//FREDDoc_ptr->autosave("align object");
638 		editor->missionChanged();
639 		break;
640 
641 	case 1: //	Control the current object's location and orientation
642 		objp = GET_FIRST(&obj_used_list);
643 		while (objp != END_OF_LIST(&obj_used_list)) {
644 			if (objp->flags[Object::Object_Flags::Marked]) {
645 				verticalize_object(&objp->orient);
646 			}
647 
648 			objp = GET_NEXT(objp);
649 		}
650 
651 		objp = GET_FIRST(&obj_used_list);
652 		while (objp != END_OF_LIST(&obj_used_list)) {
653 			if (objp->flags[Object::Object_Flags::Marked]) {
654 				object_moved(objp);
655 				count++;
656 			}
657 
658 			objp = GET_NEXT(objp);
659 		}
660 
661 		///! \todo Notify.
662 		if (count) {
663 			/*
664 			if (count > 1)
665 			FREDDoc_ptr->autosave("align objects");
666 			else
667 			FREDDoc_ptr->autosave("align object");
668 			*/
669 
670 			editor->missionChanged();
671 		}
672 
673 		break;
674 	}
675 
676 	return;
677 }
678 
level_object(matrix * orient)679 void EditorViewport::level_object(matrix* orient) {
680 	vec3d u;
681 
682 	u = orient->vec.uvec = The_grid->gmatrix.vec.uvec;
683 	if (u.xyz.x) // y-z plane
684 	{
685 		orient->vec.fvec.xyz.x = orient->vec.rvec.xyz.x = 0.0f;
686 	} else if (u.xyz.y) { // x-z plane
687 		orient->vec.fvec.xyz.y = orient->vec.rvec.xyz.y = 0.0f;
688 	} else if (u.xyz.z) { // x-y plane
689 		orient->vec.fvec.xyz.z = orient->vec.rvec.xyz.z = 0.0f;
690 	}
691 
692 	vm_fix_matrix(orient);
693 }
694 
object_check_collision(object * objp,vec3d * p0,vec3d * p1,vec3d * hitpos)695 int EditorViewport::object_check_collision(object* objp, vec3d* p0, vec3d* p1, vec3d* hitpos) {
696 	mc_info mc;
697 	mc_info_init(&mc);
698 
699 	if ((objp->type == OBJ_NONE) || (objp->type == OBJ_POINT)) {
700 		return 0;
701 	}
702 
703 	if ((objp->type == OBJ_WAYPOINT) && !view.Show_waypoints) {
704 		return 0;
705 	}
706 
707 	if ((objp->type == OBJ_START) && !view.Show_starts) {
708 		return 0;
709 	}
710 
711 	if ((objp->type == OBJ_SHIP) || (objp->type == OBJ_START)) {
712 		if (!view.Show_ships) {
713 			return 0;
714 		}
715 
716 		if (!view.Show_iff[Ships[objp->instance].team]) {
717 			return 0;
718 		}
719 	}
720 
721 	if (objp->flags[Object::Object_Flags::Hidden]) {
722 		return 0;
723 	}
724 
725 	if ((view.Show_ship_models || view.Show_outlines) && (objp->type == OBJ_SHIP)) {
726 		mc.model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num; // Fill in the model to check
727 	} else if ((view.Show_ship_models || view.Show_outlines) && (objp->type == OBJ_START)) {
728 		mc.model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num; // Fill in the model to check
729 	} else {
730 		return fvi_ray_sphere(hitpos, p0, p1, &objp->pos, (objp->radius > 0.1f) ? objp->radius : LOLLIPOP_SIZE);
731 	}
732 
733 	mc.model_instance_num = -1;
734 	mc.orient = &objp->orient; // The object's orient
735 	mc.pos = &objp->pos; // The object's position
736 	mc.p0 = p0; // Point 1 of ray to check
737 	mc.p1 = p1; // Point 2 of ray to check
738 	mc.flags = MC_CHECK_MODEL | MC_CHECK_RAY; // flags
739 	model_collide(&mc);
740 	*hitpos = mc.hit_point_world;
741 	if (mc.num_hits < 1) {
742 		// check shield
743 		mc.orient = &objp->orient; // The object's orient
744 		mc.pos = &objp->pos; // The object's position
745 		mc.p0 = p0; // Point 1 of ray to check
746 		mc.p1 = p1; // Point 2 of ray to check
747 		mc.flags = MC_CHECK_SHIELD; // flags
748 		model_collide(&mc);
749 		*hitpos = mc.hit_point_world;
750 	}
751 
752 	return mc.num_hits;
753 }
754 
select_object(int cx,int cy)755 int EditorViewport::select_object(int cx, int cy) {
756 	int best = -1;
757 	double dist, best_dist = 9e99;
758 	vec3d p0, p1, v, hitpos;
759 	vertex vt;
760 
761 	///! \fixme Briefing!
762 #if 0
763     if (Briefing_dialog) {
764         best = Briefing_dialog->check_mouse_hit(cx, cy);
765         if (best >= 0)
766         {
767             if (Selection_lock && !(Objects[best].flags & OF_MARKED))
768             {
769                 return -1;
770             }
771             return best;
772         }
773     }
774 #endif
775 
776 	/*	gr_reset_clip();
777 	g3_start_frame(0); ////////////////
778 	g3_set_view_matrix(&eye_pos, &eye_orient, 0.5f);*/
779 
780 	//	Get 3d vector specified by mouse cursor location.
781 	g3_point_to_vec(&v, cx, cy);
782 
783 	//	g3_end_frame();
784 	if (!v.xyz.x && !v.xyz.y && !v.xyz.z) { // zero vector {
785 		return -1;
786 	}
787 
788 	p0 = view_pos;
789 	vm_vec_scale_add(&p1, &p0, &v, 100.0f);
790 
791 	for (auto objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
792 		if (object_check_collision(objp, &p0, &p1, &hitpos)) {
793 			hitpos.xyz.x = objp->pos.xyz.x - view_pos.xyz.x;
794 			hitpos.xyz.y = objp->pos.xyz.y - view_pos.xyz.y;
795 			hitpos.xyz.z = objp->pos.xyz.z - view_pos.xyz.z;
796 			dist = hitpos.xyz.x * hitpos.xyz.x + hitpos.xyz.y * hitpos.xyz.y + hitpos.xyz.z * hitpos.xyz.z;
797 			if (dist < best_dist) {
798 				best = OBJ_INDEX(objp);
799 				best_dist = dist;
800 			}
801 		}
802 	}
803 
804 	if (best >= 0) {
805 		if (Selection_lock && !(Objects[best].flags[Object::Object_Flags::Marked])) {
806 			return -1;
807 		}
808 		return best;
809 	}
810 
811 	for (auto objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
812 		g3_rotate_vertex(&vt, &objp->pos);
813 		if (!(vt.codes & CC_BEHIND)) {
814 			if (!(g3_project_vertex(&vt) & PF_OVERFLOW)) {
815 				hitpos.xyz.x = vt.screen.xyw.x - cx;
816 				hitpos.xyz.y = vt.screen.xyw.y - cy;
817 				dist = hitpos.xyz.x * hitpos.xyz.x + hitpos.xyz.y * hitpos.xyz.y;
818 				if ((dist < 8) && (dist < best_dist)) {
819 					best = OBJ_INDEX(objp);
820 					best_dist = dist;
821 				}
822 			}
823 		}
824 	}
825 
826 	if (Selection_lock && !(Objects[best].flags[Object::Object_Flags::Marked])) {
827 		return -1;
828 	}
829 
830 	return best;
831 }
832 
drag_rotate_save_backup()833 void EditorViewport::drag_rotate_save_backup() {
834 	object* objp;
835 
836 	/*
837 	if (Cur_bitmap != -1)
838 		bitmap_matrix_backup = Starfield_bitmaps[Cur_bitmap].m;
839 		*/
840 
841 	objp = GET_FIRST(&obj_used_list);
842 	while (objp != END_OF_LIST(&obj_used_list)) {
843 		Assert(objp->type != OBJ_NONE);
844 		if (objp->flags[Object::Object_Flags::Marked]) {
845 			rotation_backup[OBJ_INDEX(objp)].pos = objp->pos;
846 			rotation_backup[OBJ_INDEX(objp)].orient = objp->orient;
847 		}
848 
849 		objp = GET_NEXT(objp);
850 	}
851 }
852 
create_object_on_grid(int x,int y,int waypoint_instance)853 int EditorViewport::create_object_on_grid(int x, int y, int waypoint_instance) {
854 	int obj = -1;
855 	float rval;
856 	vec3d dir, pos;
857 
858 	g3_point_to_vec_delayed(&dir, x, y);
859 
860 	rval = fvi_ray_plane(&pos, &The_grid->center, &The_grid->gmatrix.vec.uvec, &view_pos, &dir, 0.0f);
861 
862 	if (rval >= 0.0f) {
863 		editor->unmark_all();
864 		obj = create_object(&pos, waypoint_instance);
865 		if (obj >= 0) {
866 			editor->markObject(obj);
867 
868 			// TODO: Add autosave here
869 			// FREDDoc_ptr->autosave("object create");
870 
871 		} else if (obj == -1) {
872 			dialogProvider->showButtonDialog(DialogType::Error, "Error", "Maximum ship limit reached.  Can't add any more ships.", { DialogButton::Ok });
873 		}
874 	}
875 
876 	return obj;
877 }
create_object(vec3d * pos,int waypoint_instance)878 int EditorViewport::create_object(vec3d* pos, int waypoint_instance) {
879 
880 	int obj, n;
881 
882 	if (cur_model_index == editor->Id_select_type_waypoint) {
883 		obj = editor->create_waypoint(pos, waypoint_instance);
884 	} else if (cur_model_index == editor->Id_select_type_jump_node) {
885 		CJumpNode jnp(pos);
886 		obj = jnp.GetSCPObjectNumber();
887 		Jump_nodes.push_back(std::move(jnp));
888 	} else if(Ship_info[cur_model_index].flags[Ship::Info_Flags::No_fred]){
889 		obj = -1;
890 	} else {  // creating a ship
891 		obj = editor->create_ship(NULL, pos, cur_model_index);
892 		if (obj == -1)
893 			return -1;
894 
895 		n = Objects[obj].instance;
896 		Ships[n].arrival_cue = alloc_sexp("true", SEXP_ATOM, SEXP_ATOM_OPERATOR, -1, -1);
897 		Ships[n].departure_cue = alloc_sexp("false", SEXP_ATOM, SEXP_ATOM_OPERATOR, -1, -1);
898 		Ships[n].cargo1 = 0;
899 	}
900 
901 	if (obj < 0)
902 		return obj;
903 
904 	obj_merge_created_list();
905 
906 	needsUpdate();
907 	return obj;
908 }
initialSetup()909 void EditorViewport::initialSetup() {
910 	cur_model_index = get_default_player_ship_index();
911 }
912 
duplicate_marked_objects()913 int EditorViewport::duplicate_marked_objects()
914 {
915 	int z, cobj, flag;
916 	object *objp, *ptr;
917 
918 	cobj = Duped_wing = -1;
919 	flag = 0;
920 
921 	int duping_waypoint_list = -1;
922 
923 	objp = GET_FIRST(&obj_used_list);
924 	while (objp != END_OF_LIST(&obj_used_list))	{
925 		Assert(objp->type != OBJ_NONE);
926 		if (objp->flags[Object::Object_Flags::Marked]) {
927 			if ((objp->type == OBJ_SHIP) || (objp->type == OBJ_START)) {
928 				z = Ships[objp->instance].wingnum;
929 				if (!flag)
930 					Duped_wing = z;
931 				else if (Duped_wing != z)
932 					Duped_wing = -1;
933 
934 			} else {
935 				Duped_wing = -1;
936 			}
937 
938 			// make sure we dup as many waypoint lists as we have
939 			if (objp->type == OBJ_WAYPOINT) {
940 				int this_list = calc_waypoint_list_index(objp->instance);
941 				if (duping_waypoint_list != this_list) {
942 					editor->dup_object(nullptr);  // reset waypoint list
943 					duping_waypoint_list = this_list;
944 				}
945 			}
946 
947 			flag = 1;
948 			z = editor->dup_object(objp);
949 			if (z == -1) {
950 				cobj = -1;
951 				break;
952 			}
953 
954 			if (editor->currentObject == OBJ_INDEX(objp) )
955 				cobj = z;
956 		}
957 
958 		objp = GET_NEXT(objp);
959 	}
960 
961 	obj_merge_created_list();
962 
963 	// I think this code is to catch the case where an object wasn't created for whatever reason;
964 	// in this case just delete the remaining objects we just created
965 	if (cobj == -1) {
966 		objp = GET_FIRST(&obj_used_list);
967 		while (objp != END_OF_LIST(&obj_used_list))	{
968 			ptr = GET_NEXT(objp);
969 			if (objp->flags [Object::Object_Flags::Temp_marked])
970 				editor->delete_object(OBJ_INDEX(objp));
971 
972 			objp = ptr;
973 		}
974 
975 		button_down = false;
976 		return -1;
977 	}
978 
979 	editor->unmark_all();
980 
981 	objp = GET_FIRST(&obj_used_list);
982 	while (objp != END_OF_LIST(&obj_used_list))	{
983 		if (objp->flags [Object::Object_Flags::Temp_marked]) {
984 			objp->flags.remove(Object::Object_Flags::Temp_marked);
985 			editor->markObject(OBJ_INDEX(objp));
986 		}
987 
988 		objp = GET_NEXT(objp);
989 	}
990 
991 	editor->selectObject(cobj);
992 	return 0;
993 }
994 
995 //	If cur_object_index references a valid object, drag it from its current
996 //	location to the new cursor location specified by "point".
997 //	It is dragged relative to the main grid.  Its y coordinate is not changed.
998 //	Return value: 0/1 = didn't/did move object all the way to goal.
drag_objects(int x,int y)999 int EditorViewport::drag_objects(int x, int y)
1000 {
1001 	int rval = 1;
1002 	float r;
1003 	float	distance_moved = 0.0f;
1004 	vec3d cursor_dir, int_pnt;
1005 	vec3d movement_vector;
1006 	vec3d obj;
1007 	vec3d vec1, vec2;
1008 	object *objp;
1009 	// starfield_bitmaps *bmp;
1010 
1011 	/*
1012 	if (Bg_bitmap_dialog) {
1013 		if (Cur_bitmap < 0)
1014 			return -1;
1015 
1016 		bmp = &Starfield_bitmaps[Cur_bitmap];
1017 		if (Single_axis_constraint && Constraint.z) {
1018 			bmp->dist *= 1.0f + mouse_dx / -800.0f;
1019 			calculate_bitmap_points(bmp, 0.0f);
1020 
1021 		} else {
1022 			g3_point_to_vec_delayed(&bmp->m.fvec, marking_box.x2, marking_box.y2);
1023 			vm_orthogonalize_matrix(&bmp->m);
1024 			calculate_bitmap_points(bmp, 0.0f);
1025 		}
1026 		return rval;
1027 	}
1028 	*/
1029 
1030 	// Do not move ships that we are currently centered around (Lookat_mode). The vector math will start going haywire and return NAN
1031 	if (!query_valid_object(editor->currentObject) || Lookat_mode)
1032 		return -1;
1033 
1034 	if (Dup_drag == 1
1035 		//&& (Briefing_dialog) TODO
1036 		) {
1037 		Dup_drag = 0;
1038 	}
1039 
1040 	if (Dup_drag == 1) {
1041 		if (duplicate_marked_objects() < 0)
1042 			return -1;
1043 
1044 		if (Duped_wing != -1)
1045 			Dup_drag = DUP_DRAG_OF_WING;  // indication for later that we duped objects in a wing
1046 		else
1047 			Dup_drag = 0;
1048 
1049 		drag_rotate_save_backup();
1050 
1051 		editor->missionChanged();
1052 	}
1053 
1054 	objp = &Objects[editor->currentObject];
1055 	Assert(objp->type != OBJ_NONE);
1056 	obj = int_pnt = objp->pos;
1057 
1058 	//	Get 3d vector specified by mouse cursor location.
1059 	g3_point_to_vec_delayed(&cursor_dir, x, y);
1060 	if (Single_axis_constraint)	{
1061 //		if (fvi_ray_plane(&int_pnt, &obj, &view_orient.fvec, &view_pos, &cursor_dir, 0.0f) >= 0.0f )	{
1062 //			vm_vec_add(&p1, &obj, &Constraint);
1063 //			find_nearest_point_on_line(&nearest_point, &obj, &p1, &int_pnt);
1064 //			int_pnt = nearest_point;
1065 //			distance_moved = vm_vec_dist(&obj, &int_pnt);
1066 //		}
1067 
1068 		vec3d tmpAnticonstraint = Anticonstraint;
1069 		vec3d tmpObject = obj;
1070 
1071 		tmpAnticonstraint.xyz.x = 0.0f;
1072 		r = fvi_ray_plane(&int_pnt, &tmpObject, &tmpAnticonstraint, &view_pos, &cursor_dir, 0.0f);
1073 
1074 		//	If intersected behind viewer, don't move.  Too confusing, not what user wants.
1075 		vm_vec_sub(&vec1, &int_pnt, &view_pos);
1076 		vm_vec_sub(&vec2, &obj, &view_pos);
1077 		if ((r>=0.0f) && (vm_vec_dot(&vec1, &vec2) >= 0.0f))	{
1078 			vec3d tmp1;
1079 			vm_vec_sub( &tmp1, &int_pnt, &obj );
1080 			tmp1.xyz.x *= Constraint.xyz.x;
1081 			tmp1.xyz.y *= Constraint.xyz.y;
1082 			tmp1.xyz.z *= Constraint.xyz.z;
1083 			vm_vec_add( &int_pnt, &obj, &tmp1 );
1084 
1085 			distance_moved = vm_vec_dist(&obj, &int_pnt);
1086 		}
1087 
1088 
1089 	} else {  // Move in x-z plane, defined by grid.  Preserve height.
1090 		r = fvi_ray_plane(&int_pnt, &obj, &Anticonstraint, &view_pos, &cursor_dir, 0.0f);
1091 
1092 		//	If intersected behind viewer, don't move.  Too confusing, not what user wants.
1093 		vm_vec_sub(&vec1, &int_pnt, &view_pos);
1094 		vm_vec_sub(&vec2, &obj, &view_pos);
1095 		if ((r>=0.0f) && (vm_vec_dot(&vec1, &vec2) >= 0.0f))
1096 			distance_moved = vm_vec_dist(&obj, &int_pnt);
1097 	}
1098 
1099 	//	If moved too far, then move max distance along vector.
1100 	vm_vec_sub(&movement_vector, &int_pnt, &obj);
1101 /*	if (distance_moved > MAX_MOVE_DISTANCE)	{
1102 		vm_vec_normalize(&movement_vector);
1103 		vm_vec_scale(&movement_vector, MAX_MOVE_DISTANCE);
1104 	} */
1105 
1106 	if (distance_moved) {
1107 		objp = GET_FIRST(&obj_used_list);
1108 		while (objp != END_OF_LIST(&obj_used_list))	{
1109 			Assert(objp->type != OBJ_NONE);
1110 			if (objp->flags[Object::Object_Flags::Marked]) {
1111 				vm_vec_add(&objp->pos, &objp->pos, &movement_vector);
1112 				if (objp->type == OBJ_WAYPOINT) {
1113 					waypoint *wpt = find_waypoint_with_instance(objp->instance);
1114 					Assert(wpt != NULL);
1115 					wpt->set_pos(&objp->pos);
1116 				}
1117 			}
1118 
1119 			objp = GET_NEXT(objp);
1120 		}
1121 
1122 		objp = GET_FIRST(&obj_used_list);
1123 		while (objp != END_OF_LIST(&obj_used_list)) {
1124 			if (objp->flags[Object::Object_Flags::Marked])
1125 				object_moved(objp);
1126 
1127 			objp = GET_NEXT(objp);
1128 		}
1129 	}
1130 
1131 	/*
1132 	TODO: Implement brieding dialog
1133 	if (Briefing_dialog)
1134 		Briefing_dialog->update_positions();
1135 	 */
1136 
1137 	editor->missionChanged();
1138 	return rval;
1139 }
drag_rotate_objects(int mouse_dx,int mouse_dy)1140 int EditorViewport::drag_rotate_objects(int mouse_dx, int mouse_dy) {
1141 	int rval = 1;
1142 	vec3d int_pnt, obj;
1143 	angles a;
1144 	matrix leader_orient, leader_transpose, tmp, newmat, rotmat;
1145 	object *leader, *objp;
1146 	// starfield_bitmaps *bmp;
1147 
1148 	needsUpdate();
1149 	/*
1150     if (Bg_bitmap_dialog) {
1151         if (Cur_bitmap < 0)
1152             return -1;
1153 
1154         bmp = &Starfield_bitmaps[Cur_bitmap];
1155         calculate_bitmap_points(bmp, mouse_dx / -300.0f);
1156         return rval;
1157     }
1158     */
1159 
1160 	if (!query_valid_object(editor->currentObject)){
1161 		return -1;
1162 	}
1163 
1164 	objp = &Objects[editor->currentObject];
1165 	Assert(objp->type != OBJ_NONE);
1166 	obj = int_pnt = objp->pos;
1167 
1168 	memset(&a, 0, sizeof(angles));
1169 	if (Single_axis_constraint) {
1170 		if (Constraint.xyz.x)
1171 			a.p = mouse_dy / REDUCER;
1172 		else if (Constraint.xyz.y)
1173 			a.h = mouse_dx / REDUCER;
1174 		else if (Constraint.xyz.z)
1175 			a.b = -mouse_dx / REDUCER;
1176 
1177 	} else {
1178 		if (!Constraint.xyz.x) {				// yz
1179 			a.b = -mouse_dx / REDUCER;
1180 			a.h = mouse_dy / REDUCER;
1181 		} else if (!Constraint.xyz.y) {	// xz
1182 			a.p = mouse_dy / REDUCER;
1183 			a.b = -mouse_dx / REDUCER;
1184 		} else if (!Constraint.xyz.z) {	// xy
1185 			a.p = mouse_dy / REDUCER;
1186 			a.h = mouse_dx / REDUCER;
1187 		}
1188 	}
1189 
1190 	leader = &Objects[editor->currentObject];
1191 	leader_orient = leader->orient;			// save original orientation
1192 	vm_copy_transpose(&leader_transpose, &leader_orient);
1193 
1194 	vm_angles_2_matrix(&rotmat, &a);
1195 	vm_matrix_x_matrix(&newmat, &leader->orient, &rotmat);
1196 	leader->orient = newmat;
1197 
1198 	objp = GET_FIRST(&obj_used_list);
1199 	while (objp != END_OF_LIST(&obj_used_list))			{
1200 		Assert(objp->type != OBJ_NONE);
1201 		if ((objp->flags[Object::Object_Flags::Marked]) && (editor->currentObject != OBJ_INDEX(objp) )) {
1202 			if (Group_rotate) {
1203 				matrix rot_trans;
1204 				vec3d tmpv1, tmpv2;
1205 
1206 				// change rotation matrix to rotate in opposite direction.  This rotation
1207 				// matrix is what the leader ship has rotated by.
1208 				vm_copy_transpose(&rot_trans, &rotmat);
1209 
1210 				// get point relative to our point of rotation (make POR the origin).
1211 				vm_vec_sub(&tmpv1, &objp->pos, &leader->pos);
1212 
1213 				// convert point from real-world coordinates to leader's relative coordinate
1214 				// system (z=forward vec, y=up vec, x=right vec
1215 				vm_vec_rotate(&tmpv2, &tmpv1, &leader_orient);
1216 
1217 				// now rotate the point by the transpose from above.
1218 				vm_vec_rotate(&tmpv1, &tmpv2, &rot_trans);
1219 
1220 				// convert point back into real-world coordinates
1221 				vm_vec_rotate(&tmpv2, &tmpv1, &leader_transpose);
1222 
1223 				// and move origin back to real-world origin.  Object is now at its correct
1224 				// position.
1225 				vm_vec_add(&objp->pos, &leader->pos, &tmpv2);
1226 
1227 				// Now fix the object's orientation to what it should be.
1228 				vm_matrix_x_matrix(&tmp, &objp->orient, &rotmat);
1229 				vm_orthogonalize_matrix(&tmp);  // safety check
1230 				objp->orient = tmp;
1231 
1232 			} else {
1233 				vm_matrix_x_matrix(&tmp, &objp->orient, &rotmat);
1234 				objp->orient = tmp;
1235 			}
1236 		}
1237 
1238 		objp = GET_NEXT(objp);
1239 	}
1240 
1241 	objp = GET_FIRST(&obj_used_list);
1242 	while (objp != END_OF_LIST(&obj_used_list)) {
1243 		if (objp->flags[Object::Object_Flags::Marked])
1244 			object_moved(objp);
1245 
1246 		objp = GET_NEXT(objp);
1247 	}
1248 
1249 	editor->missionChanged();
1250 	return rval;
1251 }
view_universe(bool just_marked)1252 void EditorViewport::view_universe(bool just_marked) {
1253 	int max = 0;
1254 	float dist, largest = 20.0f;
1255 	vec3d center, p1, p2;		// center of all the objects collectively
1256 	vertex v;
1257 	object *ptr;
1258 
1259 	if (just_marked)
1260 		ptr = &Objects[editor->currentObject];
1261 	else
1262 		ptr = GET_FIRST(&obj_used_list);
1263 
1264 	p1.xyz.x = p2.xyz.x = ptr->pos.xyz.x;
1265 	p1.xyz.y = p2.xyz.y = ptr->pos.xyz.y;
1266 	p1.xyz.z = p2.xyz.z = ptr->pos.xyz.z;
1267 
1268 	ptr = GET_FIRST(&obj_used_list);
1269 	while (ptr != END_OF_LIST(&obj_used_list)) {
1270 		if (!just_marked || (ptr->flags[Object::Object_Flags::Marked])) {
1271 			center = ptr->pos;
1272 			if (center.xyz.x < p1.xyz.x)
1273 				p1.xyz.x = center.xyz.x;
1274 			if (center.xyz.x > p2.xyz.x)
1275 				p2.xyz.x = center.xyz.x;
1276 			if (center.xyz.y < p1.xyz.y)
1277 				p1.xyz.y = center.xyz.y;
1278 			if (center.xyz.y > p2.xyz.y)
1279 				p2.xyz.y = center.xyz.y;
1280 			if (center.xyz.z < p1.xyz.z)
1281 				p1.xyz.z = center.xyz.z;
1282 			if (center.xyz.z > p2.xyz.z)
1283 				p2.xyz.z = center.xyz.z;
1284 		}
1285 
1286 		ptr = GET_NEXT(ptr);
1287 	}
1288 
1289 	vm_vec_avg(&center, &p1, &p2);
1290 	ptr = GET_FIRST(&obj_used_list);
1291 	while (ptr != END_OF_LIST(&obj_used_list)) {
1292 		if (!just_marked || (ptr->flags[Object::Object_Flags::Marked])) {
1293 			dist = vm_vec_dist_squared(&center, &ptr->pos);
1294 			if (dist > largest)
1295 				largest = dist;
1296 
1297 			if (OBJ_INDEX(ptr) > max)
1298 				max = OBJ_INDEX(ptr);
1299 		}
1300 
1301 		ptr = GET_NEXT(ptr);
1302 	}
1303 
1304 	dist = fl_sqrt(largest) + 1.0f;
1305 	vm_vec_scale_add(&view_pos, &center, &view_orient.vec.fvec, -dist);
1306 	g3_set_view_matrix(&view_pos, &view_orient, 0.5f);
1307 
1308 	ptr = GET_FIRST(&obj_used_list);
1309 	while (ptr != END_OF_LIST(&obj_used_list)) {
1310 		if (!just_marked || (ptr->flags[Object::Object_Flags::Marked])) {
1311 			g3_rotate_vertex(&v, &ptr->pos);
1312 			Assert(!(v.codes & CC_BEHIND));
1313 			if (g3_project_vertex(&v) & PF_OVERFLOW)
1314 				Int3();
1315 
1316 			while (v.codes & CC_OFF) {  // is point off screen?
1317 				dist += 5.0f;  // zoom out a little and check again.
1318 				vm_vec_scale_add(&view_pos, &center, &view_orient.vec.fvec, -dist);
1319 				g3_set_view_matrix(&view_pos, &view_orient, 0.5f);
1320 				g3_rotate_vertex(&v, &ptr->pos);
1321 				if (g3_project_vertex(&v) & PF_OVERFLOW)
1322 					Int3();
1323 			}
1324 		}
1325 
1326 		ptr = GET_NEXT(ptr);
1327 	}
1328 
1329 	dist *= 1.1f;
1330 	vm_vec_scale_add(&view_pos, &center, &view_orient.vec.fvec, -dist);
1331 	g3_set_view_matrix(&view_pos, &view_orient, 0.5f);
1332 
1333 	needsUpdate();
1334 }
view_object(int obj_num)1335 void EditorViewport::view_object(int obj_num) {
1336 	vm_vec_scale_add(&view_pos, &Objects[obj_num].pos, &view_orient.vec.fvec, Objects[obj_num].radius * -3.0f);
1337 
1338 	needsUpdate();
1339 }
1340 
1341 }
1342 }
1343