diff --git a/code/missioneditor/common.cpp b/code/missioneditor/common.cpp index 9aeec23257c..8642c9a941a 100644 --- a/code/missioneditor/common.cpp +++ b/code/missioneditor/common.cpp @@ -752,3 +752,41 @@ void rotate_jump_nodes(const SCP_vector& slots, int from_pos, int to_pos) Jump_nodes[slots[j]] = std::move(Jump_nodes[slots[j + step]]); Jump_nodes[slots[to_pos]] = std::move(moving); } + +bool get_object_bounds(bool marked_only, vec3d* center, float* radius) +{ + vec3d mins = ZERO_VECTOR, maxs = ZERO_VECTOR; + bool found = false; + + for (auto* objp : list_range(&obj_used_list)) { + if (marked_only && !objp->flags[Object::Object_Flags::Marked]) { + continue; + } + if (!found) { + mins = maxs = objp->pos; + found = true; + continue; + } + mins.xyz.x = std::min(mins.xyz.x, objp->pos.xyz.x); + mins.xyz.y = std::min(mins.xyz.y, objp->pos.xyz.y); + mins.xyz.z = std::min(mins.xyz.z, objp->pos.xyz.z); + maxs.xyz.x = std::max(maxs.xyz.x, objp->pos.xyz.x); + maxs.xyz.y = std::max(maxs.xyz.y, objp->pos.xyz.y); + maxs.xyz.z = std::max(maxs.xyz.z, objp->pos.xyz.z); + } + + if (!found) { + return false; + } + + vm_vec_avg(center, &mins, &maxs); + *radius = 0.0f; + for (auto* objp : list_range(&obj_used_list)) { + if (marked_only && !objp->flags[Object::Object_Flags::Marked]) { + continue; + } + *radius = std::max(*radius, vm_vec_dist(center, &objp->pos)); + } + + return true; +} diff --git a/code/missioneditor/common.h b/code/missioneditor/common.h index 1c3e47e9946..698f4d88b68 100644 --- a/code/missioneditor/common.h +++ b/code/missioneditor/common.h @@ -77,6 +77,9 @@ SCP_string check_name_conflict(const char *entity_type, const char *name, int ex // system is still up, since models own GPU buffers and cursors own bitmaps. void editor_free_engine_data(); +// Bounding-box midpoint of all (or only marked) objects and the farthest distance from it; false if there are none. +bool get_object_bounds(bool marked_only, vec3d* center, float* radius); + // Resolve a mission filename to its index in Campaign.missions[], lazy-loading the mission's goal/event name lists from disk if the // FRED_LOAD_PENDING flag is still set. Returns -1 if the mission isn't in the campaign. int load_and_find_campaign_mission(const char *mission_filename); diff --git a/fred2/fred.cpp b/fred2/fred.cpp index 48f6d1fce62..76154b761c1 100644 --- a/fred2/fred.cpp +++ b/fred2/fred.cpp @@ -234,6 +234,7 @@ BOOL CFREDApp::InitInstance() { Draw_outlines_on_selected_ships = GetProfileInt("Preferences", "Draw outlines on selected ships", 1) != 0; Point_using_uvec = GetProfileInt("Preferences", "Point using uvec", Point_using_uvec); Draw_outline_at_warpin_position = GetProfileInt("Preferences", "Draw outline at warpin position", 0) != 0; + Orbit_selection_midpoint = GetProfileInt("Preferences", "Orbit selection midpoint", 0) != 0; Outline_lod = GetProfileInt("Preferences", "Outline LOD", 1); Fred_label_font_scale = GetProfileInt("Preferences", "Label font scale percent", 100) / 100.0f; Always_save_display_names = GetProfileInt("Preferences", "Always save display names", 0) != 0; @@ -539,6 +540,7 @@ void CFREDApp::write_ini_file(int degree) { WriteProfileInt("Preferences", "Draw outlines on selected ships", Draw_outlines_on_selected_ships ? 1 : 0); WriteProfileInt("Preferences", "Point using uvec", Point_using_uvec); WriteProfileInt("Preferences", "Draw outline at warpin position", Draw_outline_at_warpin_position ? 1 : 0); + WriteProfileInt("Preferences", "Orbit selection midpoint", Orbit_selection_midpoint ? 1 : 0); WriteProfileInt("Preferences", "Outline LOD", Outline_lod); WriteProfileInt("Preferences", "Label font scale percent", fl2ir(Fred_label_font_scale * 100.0f)); WriteProfileInt("Preferences", "Always save display names", Always_save_display_names ? 1 : 0); diff --git a/fred2/fred.rc b/fred2/fred.rc index d69261b8939..f22142069b3 100644 --- a/fred2/fred.rc +++ b/fred2/fred.rc @@ -436,6 +436,7 @@ BEGIN MENUITEM "Adjust Grid", 33032 MENUITEM "Point Ships Using UVec", 33101 MENUITEM "Move Ships When Undocking", 33098, CHECKED + MENUITEM "Rotate Around Selection Midpoint", ID_MISC_ROTATE_SELECTION_MIDPOINT MENUITEM SEPARATOR MENUITEM "Always save Display Names", ID_ALWAYS_SAVE_DISPLAY_NAMES MENUITEM "Error checker checks for potential issues", ID_ERROR_CHECKER_CHECKS_POTENTIAL_ISSUES, CHECKED @@ -664,6 +665,7 @@ BEGIN MENUITEM "Calculate Relative Coordinates", 33030 MENUITEM "Point Ships Using UVec", 33101 MENUITEM "Move Ships When Undocking", 33098, CHECKED + MENUITEM "Rotate Around Selection Midpoint", ID_MISC_ROTATE_SELECTION_MIDPOINT MENUITEM "Mission Statistics\tCtrl+Shift+D", 33067 MENUITEM "Music Player", ID_MUSIC_PLAYER MENUITEM SEPARATOR @@ -3891,6 +3893,8 @@ STRINGTABLE BEGIN ID_MISC_MOVESHIPSWHENUNDOCKING "When setting initially docked ships to initially undocked, move the smaller ship away from the dockpoint by one ship length" + ID_MISC_ROTATE_SELECTION_MIDPOINT + "When several objects are marked, rotate the view around the midpoint of the selection instead of the current object" ID_EDIT_CLONEMARKEDOBJECTS "Clone every marked object in-place, like Ctrl-drag without the dragging" ID_EDIT_LOCK_MARKED_OBJECTS diff --git a/fred2/fredrender.cpp b/fred2/fredrender.cpp index 861eea8d7a1..419ea29995e 100644 --- a/fred2/fredrender.cpp +++ b/fred2/fredrender.cpp @@ -43,6 +43,7 @@ #include "model/model.h" #include "model/model.h" #include "mod_table/mod_table.h" +#include "missioneditor/common.h" #include "object/object.h" #include "physics/physics.h" #include "prop/prop.h" @@ -115,6 +116,7 @@ int Show_horizon = 0; int Show_outlines = 0; bool Draw_outlines_on_selected_ships = true; bool Draw_outline_at_warpin_position = false; +bool Orbit_selection_midpoint = false; int Outline_lod = 1; bool Always_save_display_names = false; bool Error_checker_checks_potential_issues = true; @@ -1055,10 +1057,9 @@ void game_do_frame() { } if (Lookat_mode && query_valid_object()) { - float dist; - - dist = vm_vec_dist(&view_pos, &Objects[cur_object_index].pos); - vm_vec_scale_add(&view_pos, &Objects[cur_object_index].pos, &view_orient.vec.fvec, -dist); + vec3d center = selection_midpoint(); + float dist = vm_vec_dist(&view_pos, ¢er); + vm_vec_scale_add(&view_pos, ¢er, &view_orient.vec.fvec, -dist); } switch (viewpoint) { @@ -1288,6 +1289,18 @@ void move_mouse(int btn, int mdx, int mdy) { } } +vec3d selection_midpoint() +{ + vec3d center; + float radius; + + if (Orbit_selection_midpoint && Marked > 1 && get_object_bounds(true, ¢er, &radius)) { + return center; + } + + return Objects[cur_object_index].pos; +} + // ---------- Orbit camera functions ---------- vec3d orbit_camera_get_pivot() @@ -1295,8 +1308,8 @@ vec3d orbit_camera_get_pivot() vec3d pivot; if (query_valid_object()) { - // Pivot on current object - pivot = Objects[cur_object_index].pos; + // Pivot on the selection + pivot = selection_midpoint(); } else if (!The_grid) { // Pivot on the origin, if no grid pivot = ZERO_VECTOR; diff --git a/fred2/fredrender.h b/fred2/fredrender.h index 3c6fdf45d27..bddb090a25f 100644 --- a/fred2/fredrender.h +++ b/fred2/fredrender.h @@ -22,6 +22,7 @@ extern int Show_coordinates; //!< Bool. If nonzero, draw the coordinates extern int Show_outlines; //!< Bool. If nonzero, draw each object's mesh. If models are shown, highlight them in white. extern bool Draw_outlines_on_selected_ships; // If a ship is selected, draw mesh lines extern bool Draw_outline_at_warpin_position; // Project an outline at the place where the ship will arrive after warping in +extern bool Orbit_selection_midpoint; extern int Outline_lod; // The LOD to use for wireframe outlines (0 = highest detail, default 1) extern float Fred_label_font_scale; // Multiplier applied to viewport text labels extern bool Always_save_display_names; // When saving a mission, always write display names to the mission file even if the display name is not set. @@ -105,6 +106,8 @@ void verticalize_controlled(); */ int select_object(int cx, int cy); +vec3d selection_midpoint(); + // Orbit camera functions vec3d orbit_camera_get_pivot(); void orbit_camera_init_from_current_view(const vec3d *pivot, const matrix *grid_orient); diff --git a/fred2/fredview.cpp b/fred2/fredview.cpp index fd582cef489..7478e410eed 100644 --- a/fred2/fredview.cpp +++ b/fred2/fredview.cpp @@ -308,6 +308,8 @@ BEGIN_MESSAGE_MAP(CFREDView, CView) ON_UPDATE_COMMAND_UI(ID_VIEW_OUTLINES_ON_SELECTED, OnUpdateViewOutlinesOnSelected) ON_COMMAND(ID_VIEW_OUTLINE_AT_WARPIN, OnViewOutlineAtWarpin) ON_UPDATE_COMMAND_UI(ID_VIEW_OUTLINE_AT_WARPIN, OnUpdateViewOutlineAtWarpin) + ON_COMMAND(ID_MISC_ROTATE_SELECTION_MIDPOINT, OnMiscRotateSelectionMidpoint) + ON_UPDATE_COMMAND_UI(ID_MISC_ROTATE_SELECTION_MIDPOINT, OnUpdateMiscRotateSelectionMidpoint) ON_COMMAND(ID_ALWAYS_SAVE_DISPLAY_NAMES, OnAlwaysSaveDisplayNames) ON_UPDATE_COMMAND_UI(ID_ALWAYS_SAVE_DISPLAY_NAMES, OnUpdateAlwaysSaveDisplayNames) ON_COMMAND(ID_ERROR_CHECKER_CHECKS_POTENTIAL_ISSUES, OnErrorCheckerChecksPotentialIssues) @@ -2305,62 +2307,15 @@ void CFREDView::OnSelectList() // position camera to view all objects on the screen at once. Doesn't change orientation. void view_universe(int just_marked) { - int i, max = 0, obj_flags[MAX_OBJECTS]; - float dist, largest = 20.0f; - vec3d center, p1, p2; // center of all the objects collectively + float dist, radius; + vec3d center; vertex v; object *ptr; - for (i=0; ipos.xyz.x; - p1.xyz.y = p2.xyz.y = ptr->pos.xyz.y; - p1.xyz.z = p2.xyz.z = ptr->pos.xyz.z; - - ptr = GET_FIRST(&obj_used_list); - while (ptr != END_OF_LIST(&obj_used_list)) { - if (!just_marked || (ptr->flags[Object::Object_Flags::Marked])) { - center = ptr->pos; - if (center.xyz.x < p1.xyz.x) - p1.xyz.x = center.xyz.x; - if (center.xyz.x > p2.xyz.x) - p2.xyz.x = center.xyz.x; - if (center.xyz.y < p1.xyz.y) - p1.xyz.y = center.xyz.y; - if (center.xyz.y > p2.xyz.y) - p2.xyz.y = center.xyz.y; - if (center.xyz.z < p1.xyz.z) - p1.xyz.z = center.xyz.z; - if (center.xyz.z > p2.xyz.z) - p2.xyz.z = center.xyz.z; - } - - ptr = GET_NEXT(ptr); - } - - vm_vec_avg(¢er, &p1, &p2); - ptr = GET_FIRST(&obj_used_list); - while (ptr != END_OF_LIST(&obj_used_list)) { - if (!just_marked || (ptr->flags[Object::Object_Flags::Marked])) { - dist = vm_vec_dist_squared(¢er, &ptr->pos); - if (dist > largest) - largest = dist; - - obj_flags[OBJ_INDEX(ptr)] = 1; // flag object as needing on-screen check - if (OBJ_INDEX(ptr) > max) - max = OBJ_INDEX(ptr); - } - - ptr = GET_NEXT(ptr); - } + if (!get_object_bounds(just_marked != 0, ¢er, &radius)) + return; - dist = fl_sqrt(largest) + 1.0f; + dist = std::max(radius, fl_sqrt(20.0f)) + 1.0f; vm_vec_scale_add(&view_pos, ¢er, &view_orient.vec.fvec, -dist); g3_set_view_matrix(&view_pos, &view_orient, 0.5f); @@ -4109,6 +4064,19 @@ void CFREDView::OnUpdateViewOutlineAtWarpin(CCmdUI* pCmdUI) pCmdUI->SetCheck(Draw_outline_at_warpin_position); } +void CFREDView::OnMiscRotateSelectionMidpoint() +{ + Orbit_selection_midpoint = !Orbit_selection_midpoint; + Orbit_active = false; + theApp.write_ini_file(); + Update_window = 1; +} + +void CFREDView::OnUpdateMiscRotateSelectionMidpoint(CCmdUI* pCmdUI) +{ + pCmdUI->SetCheck(Orbit_selection_midpoint); +} + void CFREDView::OnAlwaysSaveDisplayNames() { Always_save_display_names = !Always_save_display_names; @@ -4502,7 +4470,7 @@ void CFREDView::OnLookatObj() vec3d v, loc; matrix m; - loc = Objects[cur_object_index].pos; + loc = selection_midpoint(); vm_vec_sub(&v, &loc, &view_pos); if (v.xyz.x || v.xyz.y || v.xyz.z) { diff --git a/fred2/fredview.h b/fred2/fredview.h index d396a8a6b53..10ed8fa5941 100644 --- a/fred2/fredview.h +++ b/fred2/fredview.h @@ -268,6 +268,8 @@ class CFREDView : public CView afx_msg void OnUpdateViewOutlinesOnSelected(CCmdUI* pCmdUI); afx_msg void OnViewOutlineAtWarpin(); afx_msg void OnUpdateViewOutlineAtWarpin(CCmdUI* pCmdUI); + afx_msg void OnMiscRotateSelectionMidpoint(); + afx_msg void OnUpdateMiscRotateSelectionMidpoint(CCmdUI* pCmdUI); afx_msg void OnAlwaysSaveDisplayNames(); afx_msg void OnUpdateAlwaysSaveDisplayNames(CCmdUI* pCmdUI); afx_msg void OnErrorCheckerChecksPotentialIssues(); diff --git a/fred2/resource.h b/fred2/resource.h index f03b894bf21..6c6ef735a72 100644 --- a/fred2/resource.h +++ b/fred2/resource.h @@ -1626,6 +1626,7 @@ #define ID_LABEL_FONT_SCALE_300 33121 #define ID_LABEL_FONT_SCALE_350 33122 #define ID_LABEL_FONT_SCALE_400 33123 +#define ID_MISC_ROTATE_SELECTION_MIDPOINT 33124 #define ID_INDICATOR_MODE 59142 #define ID_INDICATOR_LEFT 59143 #define ID_INDICATOR_RIGHT 59144 @@ -1639,7 +1640,7 @@ #define _APS_3D_CONTROLS 1 #define _APS_NEXT_RESOURCE_VALUE 340 #define _APS_NEXT_CONTROL_VALUE 1747 -#define _APS_NEXT_COMMAND_VALUE 33124 +#define _APS_NEXT_COMMAND_VALUE 33125 #define _APS_NEXT_SYMED_VALUE 105 #endif #endif diff --git a/qtfred/help-src/doc/general/PreferencesDialog.html b/qtfred/help-src/doc/general/PreferencesDialog.html index 8a8ca2e3e84..94a5b507389 100644 --- a/qtfred/help-src/doc/general/PreferencesDialog.html +++ b/qtfred/help-src/doc/general/PreferencesDialog.html @@ -41,5 +41,13 @@

Controls

viewport navigation. Hover over each entry for a description of what it does. The default keyboard bindings are listed on the Viewport page.

+ diff --git a/qtfred/help-src/doc/general/Viewport.html b/qtfred/help-src/doc/general/Viewport.html index 4adee4a2705..8eb6ccb4ef0 100644 --- a/qtfred/help-src/doc/general/Viewport.html +++ b/qtfred/help-src/doc/general/Viewport.html @@ -52,8 +52,11 @@

Context menu

empty space.

Camera controls

-

The camera orbits around a pivot point in the scene. Mouse controls move the -camera without changing any mission data:

+

The camera orbits around a pivot point in the scene. With several objects +marked, the pivot is the current object unless Rotate Around Selection +Midpoint is enabled in Preferences › Controls, which uses the +midpoint of the selection's bounding box instead. Mouse controls move the camera +without changing any mission data: