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.
+
+ - Mouse Orbit Camera - invert the horizontal or vertical
+ direction of mouse orbit rotation.
+ - Rotate Around Selection Midpoint - when several objects are
+ marked, look at and orbit around the midpoint of the selection's bounding
+ box instead of the current object. Off by default, which keeps the camera
+ on the current object as before.
+