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(¢er, &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(¢er, &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, ¢er, &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, ¢er, &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, ¢er, &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