From e827990df9f24037e2fd9f25b1677f5e2989e5d2 Mon Sep 17 00:00:00 2001
From: Dustinspace217
Date: Sun, 11 Oct 2026 03:27:35 -0700
Subject: [PATCH 1/8] qtFRED: rotate around the centre of a multi-selection
Rotate Around Object (Alt+R) and the orbit pivot both used the current
object's position, so with several ships marked the camera still
circled whichever one happened to be current. Add
EditorViewport::selectionCenter(), which returns the centroid of the
marked objects when more than one is marked and the given object's
position otherwise, and use it at the three places that chose the
point: the look-at camera update, orbitCameraGetPivot(), and the Alt+R
handler that turns the camera toward the target when the mode is
switched on.
orbitCameraGetPivot() also serves middle-mouse orbit and wheel zoom in
normal mode, so those pivot on the selection centre as well. Every
marked type counts at full weight, including waypoints and jump nodes.
With a single object marked nothing changes.
Fixes #7724
---
qtfred/src/mission/EditorViewport.cpp | 44 +++++++++++++++++++++++----
qtfred/src/mission/EditorViewport.h | 5 +++
qtfred/src/ui/FredView.cpp | 3 +-
3 files changed, 45 insertions(+), 7 deletions(-)
diff --git a/qtfred/src/mission/EditorViewport.cpp b/qtfred/src/mission/EditorViewport.cpp
index dc4fe9d3c5c..d9c062503a7 100644
--- a/qtfred/src/mission/EditorViewport.cpp
+++ b/qtfred/src/mission/EditorViewport.cpp
@@ -498,10 +498,10 @@ void EditorViewport::game_do_frame(const int cur_object_index) {
}
if (camera.getLookatMode() && query_valid_object(cur_object_index)) {
- float dist;
-
- dist = vm_vec_dist(&camera.view_pos, &Objects[cur_object_index].pos);
- vm_vec_scale_add(&camera.view_pos, &Objects[cur_object_index].pos, &camera.view_orient.vec.fvec, -dist);
+ // keep the camera the same distance from the selection centre, looking at it
+ vec3d center = selectionCenter(cur_object_index);
+ float dist = vm_vec_dist(&camera.view_pos, ¢er);
+ vm_vec_scale_add(&camera.view_pos, ¢er, &camera.view_orient.vec.fvec, -dist);
}
switch (camera.getViewpoint()) {
@@ -663,13 +663,45 @@ void EditorViewport::level_object(matrix* orient) {
vm_fix_matrix(orient);
}
+vec3d EditorViewport::selectionCenter(int cur_object_index)
+{
+ // Average the positions of every marked object. Marked objects live on the global
+ // obj_used_list with the Marked flag (Editor::markObject), so walk that list rather
+ // than keeping a separate selection container in sync. Every marked type counts at
+ // full weight on purpose (ships, waypoints, jump nodes, starts, props): the pivot is
+ // the centre of what the user selected, not of the ships alone. With one object
+ // marked this reduces to that object's own position, the behaviour FRED has always had.
+ if (editor->getNumMarked() > 1) {
+ vec3d sum = ZERO_VECTOR;
+ int count = 0;
+
+ for (object* objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
+ if (!objp->flags[Object::Object_Flags::Marked]) {
+ continue;
+ }
+ vm_vec_add2(&sum, &objp->pos);
+ ++count;
+ }
+
+ // the three writers of the Marked flag keep numMarked in step with it; say so loudly if that ever changes
+ Assertion(count == editor->getNumMarked(), "Marked object count %d does not match numMarked %d", count, editor->getNumMarked());
+
+ if (count > 0) {
+ vm_vec_scale(&sum, 1.0f / i2fl(count));
+ return sum;
+ }
+ }
+
+ return Objects[cur_object_index].pos;
+}
+
vec3d EditorViewport::orbitCameraGetPivot()
{
vec3d pivot;
if (query_valid_object(editor->currentObject)) {
- // Pivot on current object
- pivot = Objects[editor->currentObject].pos;
+ // Pivot on the current object, or on the centre of a multi-selection
+ pivot = selectionCenter(editor->currentObject);
} else if (!The_grid) {
// Pivot on the origin, if no grid
pivot = ZERO_VECTOR;
diff --git a/qtfred/src/mission/EditorViewport.h b/qtfred/src/mission/EditorViewport.h
index e533cd815a5..241f0fd9347 100644
--- a/qtfred/src/mission/EditorViewport.h
+++ b/qtfred/src/mission/EditorViewport.h
@@ -127,6 +127,11 @@ class EditorViewport {
vec3d orbitCameraGetPivot();
+ // The point the camera orbits or looks at: the centroid of the marked objects when more
+ // than one is marked, otherwise the position of the given object (#7724). Callers have
+ // already checked that cur_object_index is a valid object.
+ vec3d selectionCenter(int cur_object_index);
+
int object_check_collision(object* objp, vec3d* p0, vec3d* p1, vec3d* hitpos);
int select_object(int cx, int cy);
diff --git a/qtfred/src/ui/FredView.cpp b/qtfred/src/ui/FredView.cpp
index 5e776cd5142..70f813fe086 100644
--- a/qtfred/src/ui/FredView.cpp
+++ b/qtfred/src/ui/FredView.cpp
@@ -2879,7 +2879,8 @@ void FredView::on_actionOrbitSelected_triggered(bool enabled) {
vec3d v, loc;
matrix m;
- loc = Objects[fred->currentObject].pos;
+ // turn the camera toward the selection centre (one object, or the centroid of several)
+ loc = _viewport->selectionCenter(fred->currentObject);
vm_vec_sub(&v, &loc, &_viewport->camera.view_pos);
if (v.xyz.x || v.xyz.y || v.xyz.z) {
From 683e3ba761ce5ce054daf1048cb23061be4fa9ef Mon Sep 17 00:00:00 2001
From: Dustinspace217
Date: Sun, 11 Oct 2026 14:48:20 -0700
Subject: [PATCH 2/8] Make EditorViewport::selectionCenter const
It only reads the marked-object list and the editor's marked count;
clang-tidy (readability-make-member-function-const) flagged it in CI.
---
qtfred/src/mission/EditorViewport.cpp | 2 +-
qtfred/src/mission/EditorViewport.h | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/qtfred/src/mission/EditorViewport.cpp b/qtfred/src/mission/EditorViewport.cpp
index d9c062503a7..3317cdc57ac 100644
--- a/qtfred/src/mission/EditorViewport.cpp
+++ b/qtfred/src/mission/EditorViewport.cpp
@@ -663,7 +663,7 @@ void EditorViewport::level_object(matrix* orient) {
vm_fix_matrix(orient);
}
-vec3d EditorViewport::selectionCenter(int cur_object_index)
+vec3d EditorViewport::selectionCenter(int cur_object_index) const
{
// Average the positions of every marked object. Marked objects live on the global
// obj_used_list with the Marked flag (Editor::markObject), so walk that list rather
diff --git a/qtfred/src/mission/EditorViewport.h b/qtfred/src/mission/EditorViewport.h
index 241f0fd9347..4f08dbf6512 100644
--- a/qtfred/src/mission/EditorViewport.h
+++ b/qtfred/src/mission/EditorViewport.h
@@ -130,7 +130,7 @@ class EditorViewport {
// The point the camera orbits or looks at: the centroid of the marked objects when more
// than one is marked, otherwise the position of the given object (#7724). Callers have
// already checked that cur_object_index is a valid object.
- vec3d selectionCenter(int cur_object_index);
+ vec3d selectionCenter(int cur_object_index) const;
int object_check_collision(object* objp, vec3d* p0, vec3d* p1, vec3d* hitpos);
From 0acf8cf77834f3f9432a8bb74cd227f5348facaf Mon Sep 17 00:00:00 2001
From: Dustinspace217
Date: Sun, 11 Oct 2026 21:16:02 -0700
Subject: [PATCH 3/8] Mirror the multi-selection orbit pivot to FRED, trim
comments
FRED gets the same selection_center() as qtFRED's selectionCenter(),
used at the same three places: the look-at camera update, the orbit
pivot, and the Rotate Around Object toggle. Also removes the qtFRED
comments flagged in review.
---
fred2/fredrender.cpp | 36 ++++++++++++++++++++++-----
fred2/fredrender.h | 2 ++
fred2/fredview.cpp | 2 +-
qtfred/src/mission/EditorViewport.cpp | 10 +-------
qtfred/src/mission/EditorViewport.h | 3 ---
qtfred/src/ui/FredView.cpp | 1 -
6 files changed, 34 insertions(+), 20 deletions(-)
diff --git a/fred2/fredrender.cpp b/fred2/fredrender.cpp
index 861eea8d7a1..ba34fdb978e 100644
--- a/fred2/fredrender.cpp
+++ b/fred2/fredrender.cpp
@@ -1055,10 +1055,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_center();
+ float dist = vm_vec_dist(&view_pos, ¢er);
+ vm_vec_scale_add(&view_pos, ¢er, &view_orient.vec.fvec, -dist);
}
switch (viewpoint) {
@@ -1288,6 +1287,31 @@ void move_mouse(int btn, int mdx, int mdy) {
}
}
+vec3d selection_center()
+{
+ if (Marked > 1) {
+ vec3d sum = ZERO_VECTOR;
+ int count = 0;
+
+ for (object *objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
+ if (!objp->flags[Object::Object_Flags::Marked]) {
+ continue;
+ }
+ vm_vec_add2(&sum, &objp->pos);
+ ++count;
+ }
+
+ Assertion(count == Marked, "Marked object count %d does not match Marked count %d", count, Marked);
+
+ if (count > 0) {
+ vm_vec_scale(&sum, 1.0f / i2fl(count));
+ return sum;
+ }
+ }
+
+ return Objects[cur_object_index].pos;
+}
+
// ---------- Orbit camera functions ----------
vec3d orbit_camera_get_pivot()
@@ -1295,8 +1319,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_center();
} 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..bf341df5e82 100644
--- a/fred2/fredrender.h
+++ b/fred2/fredrender.h
@@ -105,6 +105,8 @@ void verticalize_controlled();
*/
int select_object(int cx, int cy);
+vec3d selection_center();
+
// 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..5d6f8992246 100644
--- a/fred2/fredview.cpp
+++ b/fred2/fredview.cpp
@@ -4502,7 +4502,7 @@ void CFREDView::OnLookatObj()
vec3d v, loc;
matrix m;
- loc = Objects[cur_object_index].pos;
+ loc = selection_center();
vm_vec_sub(&v, &loc, &view_pos);
if (v.xyz.x || v.xyz.y || v.xyz.z) {
diff --git a/qtfred/src/mission/EditorViewport.cpp b/qtfred/src/mission/EditorViewport.cpp
index 3317cdc57ac..1986fcd8c0c 100644
--- a/qtfred/src/mission/EditorViewport.cpp
+++ b/qtfred/src/mission/EditorViewport.cpp
@@ -498,7 +498,6 @@ void EditorViewport::game_do_frame(const int cur_object_index) {
}
if (camera.getLookatMode() && query_valid_object(cur_object_index)) {
- // keep the camera the same distance from the selection centre, looking at it
vec3d center = selectionCenter(cur_object_index);
float dist = vm_vec_dist(&camera.view_pos, ¢er);
vm_vec_scale_add(&camera.view_pos, ¢er, &camera.view_orient.vec.fvec, -dist);
@@ -665,12 +664,6 @@ void EditorViewport::level_object(matrix* orient) {
vec3d EditorViewport::selectionCenter(int cur_object_index) const
{
- // Average the positions of every marked object. Marked objects live on the global
- // obj_used_list with the Marked flag (Editor::markObject), so walk that list rather
- // than keeping a separate selection container in sync. Every marked type counts at
- // full weight on purpose (ships, waypoints, jump nodes, starts, props): the pivot is
- // the centre of what the user selected, not of the ships alone. With one object
- // marked this reduces to that object's own position, the behaviour FRED has always had.
if (editor->getNumMarked() > 1) {
vec3d sum = ZERO_VECTOR;
int count = 0;
@@ -683,7 +676,6 @@ vec3d EditorViewport::selectionCenter(int cur_object_index) const
++count;
}
- // the three writers of the Marked flag keep numMarked in step with it; say so loudly if that ever changes
Assertion(count == editor->getNumMarked(), "Marked object count %d does not match numMarked %d", count, editor->getNumMarked());
if (count > 0) {
@@ -700,7 +692,7 @@ vec3d EditorViewport::orbitCameraGetPivot()
vec3d pivot;
if (query_valid_object(editor->currentObject)) {
- // Pivot on the current object, or on the centre of a multi-selection
+ // Pivot on the selection
pivot = selectionCenter(editor->currentObject);
} else if (!The_grid) {
// Pivot on the origin, if no grid
diff --git a/qtfred/src/mission/EditorViewport.h b/qtfred/src/mission/EditorViewport.h
index 4f08dbf6512..0a18c1a6a90 100644
--- a/qtfred/src/mission/EditorViewport.h
+++ b/qtfred/src/mission/EditorViewport.h
@@ -127,9 +127,6 @@ class EditorViewport {
vec3d orbitCameraGetPivot();
- // The point the camera orbits or looks at: the centroid of the marked objects when more
- // than one is marked, otherwise the position of the given object (#7724). Callers have
- // already checked that cur_object_index is a valid object.
vec3d selectionCenter(int cur_object_index) const;
int object_check_collision(object* objp, vec3d* p0, vec3d* p1, vec3d* hitpos);
diff --git a/qtfred/src/ui/FredView.cpp b/qtfred/src/ui/FredView.cpp
index 70f813fe086..bdd018fee2f 100644
--- a/qtfred/src/ui/FredView.cpp
+++ b/qtfred/src/ui/FredView.cpp
@@ -2879,7 +2879,6 @@ void FredView::on_actionOrbitSelected_triggered(bool enabled) {
vec3d v, loc;
matrix m;
- // turn the camera toward the selection centre (one object, or the centroid of several)
loc = _viewport->selectionCenter(fred->currentObject);
vm_vec_sub(&v, &loc, &_viewport->camera.view_pos);
From 205062c54c61187fd2fedbbbfea223dd64630b5e Mon Sep 17 00:00:00 2001
From: Dustinspace217
Date: Mon, 12 Oct 2026 02:22:47 -0700
Subject: [PATCH 4/8] Make the selection-center pivot optional and share object
bounds
Look-at and orbit use the current object again unless the new option is
on (qtFRED: Preferences > Controls; FRED: View menu), and the selection
center is now the bounding-box midpoint. view_universe in both editors,
the pivot, and qtFRED's selection radius all use get_object_bounds() in
missioneditor/common.
---
code/missioneditor/common.cpp | 38 +++++++++
code/missioneditor/common.h | 3 +
fred2/fred.cpp | 2 +
fred2/fred.rc | 2 +
fred2/fredrender.cpp | 23 ++----
fred2/fredrender.h | 1 +
fred2/fredview.cpp | 71 +++++------------
fred2/fredview.h | 2 +
fred2/resource.h | 3 +-
qtfred/src/mission/CameraController.h | 4 +
qtfred/src/mission/EditorViewport.cpp | 77 +++---------------
.../dialogs/PreferencesDialogModel.cpp | 5 ++
.../mission/dialogs/PreferencesDialogModel.h | 3 +
qtfred/src/ui/FredView.cpp | 27 ++-----
qtfred/src/ui/dialogs/PreferencesDialog.cpp | 5 ++
qtfred/src/ui/dialogs/PreferencesDialog.h | 1 +
qtfred/ui/PreferencesDialog.ui | 10 +++
test/src/missioneditor/test_object_bounds.cpp | 78 +++++++++++++++++++
test/src/source_groups.cmake | 4 +
19 files changed, 202 insertions(+), 157 deletions(-)
create mode 100644 test/src/missioneditor/test_object_bounds.cpp
diff --git a/code/missioneditor/common.cpp b/code/missioneditor/common.cpp
index 9aeec23257c..80b89cadae8 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 (object* objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
+ 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 (object* objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
+ 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..0a655f99d3c 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_center = GetProfileInt("Preferences", "Orbit selection center", 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 center", Orbit_selection_center ? 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..8cb55ee6bbe 100644
--- a/fred2/fred.rc
+++ b/fred2/fred.rc
@@ -301,6 +301,7 @@ BEGIN
MENUITEM "Show Outlines\tShift+Alt+O", 32980
MENUITEM "Draw Outlines on Selected Ships", ID_VIEW_OUTLINES_ON_SELECTED, CHECKED
MENUITEM "Draw Outline at Warpin Position", ID_VIEW_OUTLINE_AT_WARPIN, CHECKED
+ MENUITEM "Look At and Orbit Around Selection Center", ID_VIEW_ORBIT_SELECTION_CENTER
MENUITEM "Show Ship Info\tShift+Alt+I", 32823, CHECKED, HELP
MENUITEM "Show Coordinates\tShift+Alt+C", 32915
MENUITEM "Show Grid Positions\tShift+Alt+P", 32914
@@ -542,6 +543,7 @@ BEGIN
MENUITEM "Show Outlines\tShift+Alt+O", 32980
MENUITEM "Draw Outlines on Selected Ships", ID_VIEW_OUTLINES_ON_SELECTED, CHECKED
MENUITEM "Draw Outline at Warpin Position", ID_VIEW_OUTLINE_AT_WARPIN, CHECKED
+ MENUITEM "Look At and Orbit Around Selection Center", ID_VIEW_ORBIT_SELECTION_CENTER
MENUITEM "Show Ship Info\tShift+Alt+I", 32823, CHECKED, HELP
MENUITEM "Show Coordinates\tShift+Alt+C", 32915
MENUITEM "Show Grid Positions\tShift+Alt+P", 32914
diff --git a/fred2/fredrender.cpp b/fred2/fredrender.cpp
index ba34fdb978e..b5a5ed75602 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_center = false;
int Outline_lod = 1;
bool Always_save_display_names = false;
bool Error_checker_checks_potential_issues = true;
@@ -1289,24 +1291,11 @@ void move_mouse(int btn, int mdx, int mdy) {
vec3d selection_center()
{
- if (Marked > 1) {
- vec3d sum = ZERO_VECTOR;
- int count = 0;
+ vec3d center;
+ float radius;
- for (object *objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
- if (!objp->flags[Object::Object_Flags::Marked]) {
- continue;
- }
- vm_vec_add2(&sum, &objp->pos);
- ++count;
- }
-
- Assertion(count == Marked, "Marked object count %d does not match Marked count %d", count, Marked);
-
- if (count > 0) {
- vm_vec_scale(&sum, 1.0f / i2fl(count));
- return sum;
- }
+ if (Orbit_selection_center && Marked > 1 && get_object_bounds(true, ¢er, &radius)) {
+ return center;
}
return Objects[cur_object_index].pos;
diff --git a/fred2/fredrender.h b/fred2/fredrender.h
index bf341df5e82..5e6d9a1162b 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_center;
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.
diff --git a/fred2/fredview.cpp b/fred2/fredview.cpp
index 5d6f8992246..952d8f06835 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_VIEW_ORBIT_SELECTION_CENTER, OnViewOrbitSelectionCenter)
+ ON_UPDATE_COMMAND_UI(ID_VIEW_ORBIT_SELECTION_CENTER, OnUpdateViewOrbitSelectionCenter)
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,18 @@ void CFREDView::OnUpdateViewOutlineAtWarpin(CCmdUI* pCmdUI)
pCmdUI->SetCheck(Draw_outline_at_warpin_position);
}
+void CFREDView::OnViewOrbitSelectionCenter()
+{
+ Orbit_selection_center = !Orbit_selection_center;
+ theApp.write_ini_file();
+ Update_window = 1;
+}
+
+void CFREDView::OnUpdateViewOrbitSelectionCenter(CCmdUI* pCmdUI)
+{
+ pCmdUI->SetCheck(Orbit_selection_center);
+}
+
void CFREDView::OnAlwaysSaveDisplayNames()
{
Always_save_display_names = !Always_save_display_names;
diff --git a/fred2/fredview.h b/fred2/fredview.h
index d396a8a6b53..8e655a2b8ca 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 OnViewOrbitSelectionCenter();
+ afx_msg void OnUpdateViewOrbitSelectionCenter(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..eb139465489 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_VIEW_ORBIT_SELECTION_CENTER 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/src/mission/CameraController.h b/qtfred/src/mission/CameraController.h
index dc2efb20644..040c6116706 100644
--- a/qtfred/src/mission/CameraController.h
+++ b/qtfred/src/mission/CameraController.h
@@ -32,6 +32,7 @@ class CameraController {
// Default to inverted, matching the engine's F3 lab and POF Tools.
bool _invertOrbitX = true;
bool _invertOrbitY = true;
+ bool _orbitSelectionCenter = false;
void orbitCameraApply();
@@ -81,6 +82,9 @@ class CameraController {
bool getInvertOrbitY() const { return _invertOrbitY; }
void setInvertOrbitY(bool invert) { _invertOrbitY = invert; }
+ bool getOrbitSelectionCenter() const { return _orbitSelectionCenter; }
+ void setOrbitSelectionCenter(bool enabled) { _orbitSelectionCenter = enabled; }
+
void orbitCameraInitFromCurrentView(const vec3d* pivot, const matrix* grid_orient);
void orbitCameraRotate(int dx, int dy);
void orbitCameraPan(int dx, int dy);
diff --git a/qtfred/src/mission/EditorViewport.cpp b/qtfred/src/mission/EditorViewport.cpp
index 1986fcd8c0c..25cc2b40ed4 100644
--- a/qtfred/src/mission/EditorViewport.cpp
+++ b/qtfred/src/mission/EditorViewport.cpp
@@ -15,6 +15,7 @@
#include
#include
#include
+#include
#include
namespace {
@@ -180,6 +181,7 @@ void EditorViewport::loadSettings() {
view.Label_font_scale = settings.value("view_label_font_scale", view.Label_font_scale).toFloat();
camera.setInvertOrbitX(settings.value("camera_invert_orbit_x", camera.getInvertOrbitX()).toBool());
camera.setInvertOrbitY(settings.value("camera_invert_orbit_y", camera.getInvertOrbitY()).toBool());
+ camera.setOrbitSelectionCenter(settings.value("camera_orbit_selection_center", camera.getOrbitSelectionCenter()).toBool());
settings.endGroup();
}
@@ -229,6 +231,7 @@ void EditorViewport::saveSettings() const {
settings.setValue("view_label_font_scale", view.Label_font_scale);
settings.setValue("camera_invert_orbit_x", camera.getInvertOrbitX());
settings.setValue("camera_invert_orbit_y", camera.getInvertOrbitY());
+ settings.setValue("camera_orbit_selection_center", camera.getOrbitSelectionCenter());
settings.endGroup();
}
void EditorViewport::needsUpdate() {
@@ -664,24 +667,11 @@ void EditorViewport::level_object(matrix* orient) {
vec3d EditorViewport::selectionCenter(int cur_object_index) const
{
- if (editor->getNumMarked() > 1) {
- vec3d sum = ZERO_VECTOR;
- int count = 0;
+ vec3d center;
+ float radius;
- for (object* objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
- if (!objp->flags[Object::Object_Flags::Marked]) {
- continue;
- }
- vm_vec_add2(&sum, &objp->pos);
- ++count;
- }
-
- Assertion(count == editor->getNumMarked(), "Marked object count %d does not match numMarked %d", count, editor->getNumMarked());
-
- if (count > 0) {
- vm_vec_scale(&sum, 1.0f / i2fl(count));
- return sum;
- }
+ if (camera.getOrbitSelectionCenter() && editor->getNumMarked() > 1 && get_object_bounds(true, ¢er, &radius)) {
+ return center;
}
return Objects[cur_object_index].pos;
@@ -1769,58 +1759,15 @@ void EditorViewport::cancel_drag() {
needsUpdate();
}
void EditorViewport::view_universe(bool just_marked) {
- int max = 0;
- float dist, largest = 20.0f;
- vec3d center, p1, p2;
+ float dist, radius;
+ vec3d center;
vertex v;
object *ptr;
- if (just_marked)
- ptr = &Objects[editor->currentObject];
- else
- ptr = GET_FIRST(&obj_used_list);
-
- p1.xyz.x = p2.xyz.x = ptr->pos.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;
-
- if (OBJ_INDEX(ptr) > max)
- max = OBJ_INDEX(ptr);
- }
-
- ptr = GET_NEXT(ptr);
- }
+ if (!get_object_bounds(just_marked, ¢er, &radius))
+ return;
- dist = fl_sqrt(largest) + 1.0f;
+ dist = std::max(radius, fl_sqrt(20.0f)) + 1.0f;
vm_vec_scale_add(&camera.view_pos, ¢er, &camera.view_orient.vec.fvec, -dist);
g3_set_view_matrix(&camera.view_pos, &camera.view_orient, 0.5f);
diff --git a/qtfred/src/mission/dialogs/PreferencesDialogModel.cpp b/qtfred/src/mission/dialogs/PreferencesDialogModel.cpp
index d396a5badb3..d78f8a11697 100644
--- a/qtfred/src/mission/dialogs/PreferencesDialogModel.cpp
+++ b/qtfred/src/mission/dialogs/PreferencesDialogModel.cpp
@@ -29,6 +29,7 @@ PreferencesDialogModel::PreferencesDialogModel(QObject* parent, EditorViewport*
, _labelFontScale(viewport->view.Label_font_scale)
, _invertOrbitX(viewport->camera.getInvertOrbitX())
, _invertOrbitY(viewport->camera.getInvertOrbitY())
+ , _orbitSelectionCenter(viewport->camera.getOrbitSelectionCenter())
, _gridCenterX(static_cast(viewport->The_grid->center.xyz.x))
, _gridCenterY(static_cast(viewport->The_grid->center.xyz.y))
, _gridCenterZ(static_cast(viewport->The_grid->center.xyz.z))
@@ -72,6 +73,7 @@ bool PreferencesDialogModel::apply() {
_viewport->view.Label_font_scale = _labelFontScale;
_viewport->camera.setInvertOrbitX(_invertOrbitX);
_viewport->camera.setInvertOrbitY(_invertOrbitY);
+ _viewport->camera.setOrbitSelectionCenter(_orbitSelectionCenter);
_viewport->saveSettings();
if (themeModeChanged) {
@@ -203,6 +205,9 @@ void PreferencesDialogModel::setInvertOrbitX(bool value) { modify(_invertOrbitX,
bool PreferencesDialogModel::getInvertOrbitY() const { return _invertOrbitY; }
void PreferencesDialogModel::setInvertOrbitY(bool value) { modify(_invertOrbitY, value); }
+bool PreferencesDialogModel::getOrbitSelectionCenter() const { return _orbitSelectionCenter; }
+void PreferencesDialogModel::setOrbitSelectionCenter(bool value) { modify(_orbitSelectionCenter, value); }
+
void PreferencesDialogModel::resetControlDefaults() {
auto& bindings = ControlBindings::instance();
for (const auto& def : bindings.definitions()) {
diff --git a/qtfred/src/mission/dialogs/PreferencesDialogModel.h b/qtfred/src/mission/dialogs/PreferencesDialogModel.h
index e8a71869e6a..a66f3fd82c8 100644
--- a/qtfred/src/mission/dialogs/PreferencesDialogModel.h
+++ b/qtfred/src/mission/dialogs/PreferencesDialogModel.h
@@ -79,6 +79,8 @@ class PreferencesDialogModel : public AbstractDialogModel {
void setInvertOrbitX(bool value);
bool getInvertOrbitY() const;
void setInvertOrbitY(bool value);
+ bool getOrbitSelectionCenter() const;
+ void setOrbitSelectionCenter(bool value);
// Grid
int getGridCenterX() const;
@@ -117,6 +119,7 @@ class PreferencesDialogModel : public AbstractDialogModel {
std::map _controlKeys;
bool _invertOrbitX;
bool _invertOrbitY;
+ bool _orbitSelectionCenter;
// Grid
int _gridCenterX;
diff --git a/qtfred/src/ui/FredView.cpp b/qtfred/src/ui/FredView.cpp
index bdd018fee2f..fd22c57297c 100644
--- a/qtfred/src/ui/FredView.cpp
+++ b/qtfred/src/ui/FredView.cpp
@@ -51,6 +51,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -1461,28 +1462,10 @@ void FredView::onUpdateTransformBar() {
if (valid && numMarked <= 1) {
_transformRadiusLabel->setText(QString::number(static_cast(Objects[curObj].radius), 'f', 1));
} else if (numMarked > 1 && obj_used_list.next != nullptr) {
- // Bounding radius of the selection: distance from centroid to the farthest object.
- float cx = 0.0f, cy = 0.0f, cz = 0.0f;
- int cnt = 0;
- for (object* p = GET_FIRST(&obj_used_list); p != END_OF_LIST(&obj_used_list); p = GET_NEXT(p)) {
- if (!p->flags[Object::Object_Flags::Marked]) continue;
- cx += p->pos.xyz.x;
- cy += p->pos.xyz.y;
- cz += p->pos.xyz.z;
- ++cnt;
- }
- if (cnt > 0) {
- cx /= cnt; cy /= cnt; cz /= cnt;
- float maxDist = 0.0f;
- for (object* p = GET_FIRST(&obj_used_list); p != END_OF_LIST(&obj_used_list); p = GET_NEXT(p)) {
- if (!p->flags[Object::Object_Flags::Marked]) continue;
- float dx = p->pos.xyz.x - cx;
- float dy = p->pos.xyz.y - cy;
- float dz = p->pos.xyz.z - cz;
- float d = sqrtf(dx*dx + dy*dy + dz*dz);
- if (d > maxDist) maxDist = d;
- }
- _transformRadiusLabel->setText(QString::number(static_cast(maxDist), 'f', 1));
+ vec3d center;
+ float radius;
+ if (get_object_bounds(true, ¢er, &radius)) {
+ _transformRadiusLabel->setText(QString::number(static_cast(radius), 'f', 1));
} else {
_transformRadiusLabel->setText(tr("--"));
}
diff --git a/qtfred/src/ui/dialogs/PreferencesDialog.cpp b/qtfred/src/ui/dialogs/PreferencesDialog.cpp
index 488bb58980b..69ef7e6364d 100644
--- a/qtfred/src/ui/dialogs/PreferencesDialog.cpp
+++ b/qtfred/src/ui/dialogs/PreferencesDialog.cpp
@@ -150,6 +150,7 @@ void PreferencesDialog::updateUi() {
// Controls
ui->invertOrbitX->setChecked(_model->getInvertOrbitX());
ui->invertOrbitY->setChecked(_model->getInvertOrbitY());
+ ui->orbitSelectionCenter->setChecked(_model->getOrbitSelectionCenter());
for (const auto& entry : _controlEditors) {
entry.second->setKeySequence(_model->getControlKey(entry.first));
}
@@ -269,6 +270,10 @@ void PreferencesDialog::on_invertOrbitY_toggled(bool checked) {
_model->setInvertOrbitY(checked);
}
+void PreferencesDialog::on_orbitSelectionCenter_toggled(bool checked) {
+ _model->setOrbitSelectionCenter(checked);
+}
+
void PreferencesDialog::on_resetDefaultsButton_clicked() {
_model->resetControlDefaults();
}
diff --git a/qtfred/src/ui/dialogs/PreferencesDialog.h b/qtfred/src/ui/dialogs/PreferencesDialog.h
index d625ecc4892..3cdfab6d4f8 100644
--- a/qtfred/src/ui/dialogs/PreferencesDialog.h
+++ b/qtfred/src/ui/dialogs/PreferencesDialog.h
@@ -56,6 +56,7 @@ private slots:
// Controls
void on_invertOrbitX_toggled(bool checked);
void on_invertOrbitY_toggled(bool checked);
+ void on_orbitSelectionCenter_toggled(bool checked);
void on_resetDefaultsButton_clicked();
private: // NOLINT(readability-redundant-access-specifiers)
diff --git a/qtfred/ui/PreferencesDialog.ui b/qtfred/ui/PreferencesDialog.ui
index c239fc9732b..3725f1be4e7 100644
--- a/qtfred/ui/PreferencesDialog.ui
+++ b/qtfred/ui/PreferencesDialog.ui
@@ -566,6 +566,16 @@
+ -
+
+
+ When several objects are selected, look at and orbit around the center of the selection instead of the current object.
+
+
+ Look at and orbit around the center of a multi-selection
+
+
+
diff --git a/test/src/missioneditor/test_object_bounds.cpp b/test/src/missioneditor/test_object_bounds.cpp
new file mode 100644
index 00000000000..99baeaadb80
--- /dev/null
+++ b/test/src/missioneditor/test_object_bounds.cpp
@@ -0,0 +1,78 @@
+#include
+
+#include "globalincs/linklist.h"
+#include "missioneditor/common.h"
+#include "object/object.h"
+
+class ObjectBoundsTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ list_init(&obj_used_list);
+ }
+
+ void TearDown() override {
+ for (int i = 0; i < _count; ++i) {
+ Objects[i].clear();
+ }
+ list_init(&obj_used_list);
+ }
+
+ void add(float x, bool marked) {
+ object* objp = &Objects[_count++];
+ objp->clear();
+ objp->pos = vm_vec_new(x, 0.0f, 0.0f);
+ if (marked) {
+ objp->flags.set(Object::Object_Flags::Marked);
+ }
+ list_append(&obj_used_list, objp);
+ }
+
+ int _count = 0;
+};
+
+TEST_F(ObjectBoundsTest, no_objects) {
+ vec3d center = vm_vec_new(1.0f, 2.0f, 3.0f);
+ float radius = 4.0f;
+
+ EXPECT_FALSE(get_object_bounds(false, ¢er, &radius));
+ EXPECT_EQ(center.xyz.x, 1.0f);
+ EXPECT_EQ(radius, 4.0f);
+}
+
+TEST_F(ObjectBoundsTest, box_midpoint_not_average) {
+ add(0.0f, false);
+ add(1.0f, false);
+ add(10.0f, false);
+ vec3d center;
+ float radius;
+
+ ASSERT_TRUE(get_object_bounds(false, ¢er, &radius));
+ EXPECT_FLOAT_EQ(center.xyz.x, 5.0f);
+ EXPECT_FLOAT_EQ(radius, 5.0f);
+}
+
+TEST_F(ObjectBoundsTest, marked_only) {
+ add(0.0f, true);
+ add(10.0f, true);
+ add(100.0f, false);
+ vec3d center;
+ float radius;
+
+ ASSERT_TRUE(get_object_bounds(true, ¢er, &radius));
+ EXPECT_FLOAT_EQ(center.xyz.x, 5.0f);
+ EXPECT_FLOAT_EQ(radius, 5.0f);
+
+ ASSERT_TRUE(get_object_bounds(false, ¢er, &radius));
+ EXPECT_FLOAT_EQ(center.xyz.x, 50.0f);
+ EXPECT_FLOAT_EQ(radius, 50.0f);
+}
+
+TEST_F(ObjectBoundsTest, nothing_marked) {
+ add(5.0f, false);
+ vec3d center = vm_vec_new(1.0f, 2.0f, 3.0f);
+ float radius = 4.0f;
+
+ EXPECT_FALSE(get_object_bounds(true, ¢er, &radius));
+ EXPECT_EQ(center.xyz.x, 1.0f);
+ EXPECT_EQ(radius, 4.0f);
+}
diff --git a/test/src/source_groups.cmake b/test/src/source_groups.cmake
index 1c268dda86e..67f671b99ee 100644
--- a/test/src/source_groups.cmake
+++ b/test/src/source_groups.cmake
@@ -41,6 +41,10 @@ add_file_folder("menuui"
menuui/test_intel_parse.cpp
)
+add_file_folder("missioneditor"
+ missioneditor/test_object_bounds.cpp
+)
+
add_file_folder("mod"
mod/test_mod_table.cpp
)
From 51317335769086d40aa8437c2647cd896a6d8517 Mon Sep 17 00:00:00 2001
From: Dustinspace217
Date: Mon, 12 Oct 2026 02:22:47 -0700
Subject: [PATCH 5/8] Reset the cached orbit pivot when the selection-center
option changes
Wheel zoom reuses the orbit pivot until the camera moves, so without this
the option only took effect for scrolling after the next camera move.
---
fred2/fredview.cpp | 1 +
qtfred/src/mission/CameraController.h | 6 +++++-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/fred2/fredview.cpp b/fred2/fredview.cpp
index 952d8f06835..3239011f961 100644
--- a/fred2/fredview.cpp
+++ b/fred2/fredview.cpp
@@ -4067,6 +4067,7 @@ void CFREDView::OnUpdateViewOutlineAtWarpin(CCmdUI* pCmdUI)
void CFREDView::OnViewOrbitSelectionCenter()
{
Orbit_selection_center = !Orbit_selection_center;
+ Orbit_active = false;
theApp.write_ini_file();
Update_window = 1;
}
diff --git a/qtfred/src/mission/CameraController.h b/qtfred/src/mission/CameraController.h
index 040c6116706..da8fc4f74af 100644
--- a/qtfred/src/mission/CameraController.h
+++ b/qtfred/src/mission/CameraController.h
@@ -83,7 +83,11 @@ class CameraController {
void setInvertOrbitY(bool invert) { _invertOrbitY = invert; }
bool getOrbitSelectionCenter() const { return _orbitSelectionCenter; }
- void setOrbitSelectionCenter(bool enabled) { _orbitSelectionCenter = enabled; }
+ void setOrbitSelectionCenter(bool enabled)
+ {
+ _orbitSelectionCenter = enabled;
+ _orbitActive = false;
+ }
void orbitCameraInitFromCurrentView(const vec3d* pivot, const matrix* grid_orient);
void orbitCameraRotate(int dx, int dy);
From 7a11dd97828dfc660a2184f6838bb8ced0dc5dab Mon Sep 17 00:00:00 2001
From: Dustinspace217
Date: Wed, 14 Oct 2026 00:30:51 -0700
Subject: [PATCH 6/8] Address review: move and rename the selection-centroid
option
- FRED: the option moves from the View menu to Settings (and Misc in the
classic layout), under Move Ships When Undocking, with hover help, and is
renamed Rotate Around Selection Centroid.
- qtFRED: the checkbox moves next to Move Ships When Undocking in the
preferences dialog, with the same name.
- setOrbitSelectionCenter only resets the orbit when the value changes.
- get_object_bounds iterates with list_range.
- The bounds test resets the object system with obj_init and takes objects
off the free list, so it leaves a valid state for other tests.
---
code/missioneditor/common.cpp | 4 +--
fred2/fred.rc | 6 +++--
fred2/fredview.cpp | 8 +++---
fred2/fredview.h | 4 +--
fred2/resource.h | 2 +-
qtfred/src/mission/CameraController.h | 6 +++--
qtfred/ui/PreferencesDialog.ui | 26 +++++++++----------
test/src/missioneditor/test_object_bounds.cpp | 8 +++---
8 files changed, 33 insertions(+), 31 deletions(-)
diff --git a/code/missioneditor/common.cpp b/code/missioneditor/common.cpp
index 80b89cadae8..8642c9a941a 100644
--- a/code/missioneditor/common.cpp
+++ b/code/missioneditor/common.cpp
@@ -758,7 +758,7 @@ bool get_object_bounds(bool marked_only, vec3d* center, float* radius)
vec3d mins = ZERO_VECTOR, maxs = ZERO_VECTOR;
bool found = false;
- for (object* objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
+ for (auto* objp : list_range(&obj_used_list)) {
if (marked_only && !objp->flags[Object::Object_Flags::Marked]) {
continue;
}
@@ -781,7 +781,7 @@ bool get_object_bounds(bool marked_only, vec3d* center, float* radius)
vm_vec_avg(center, &mins, &maxs);
*radius = 0.0f;
- for (object* objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
+ for (auto* objp : list_range(&obj_used_list)) {
if (marked_only && !objp->flags[Object::Object_Flags::Marked]) {
continue;
}
diff --git a/fred2/fred.rc b/fred2/fred.rc
index 8cb55ee6bbe..7080de813c6 100644
--- a/fred2/fred.rc
+++ b/fred2/fred.rc
@@ -301,7 +301,6 @@ BEGIN
MENUITEM "Show Outlines\tShift+Alt+O", 32980
MENUITEM "Draw Outlines on Selected Ships", ID_VIEW_OUTLINES_ON_SELECTED, CHECKED
MENUITEM "Draw Outline at Warpin Position", ID_VIEW_OUTLINE_AT_WARPIN, CHECKED
- MENUITEM "Look At and Orbit Around Selection Center", ID_VIEW_ORBIT_SELECTION_CENTER
MENUITEM "Show Ship Info\tShift+Alt+I", 32823, CHECKED, HELP
MENUITEM "Show Coordinates\tShift+Alt+C", 32915
MENUITEM "Show Grid Positions\tShift+Alt+P", 32914
@@ -437,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 Centroid", ID_MISC_ROTATE_SELECTION_CENTROID
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
@@ -543,7 +543,6 @@ BEGIN
MENUITEM "Show Outlines\tShift+Alt+O", 32980
MENUITEM "Draw Outlines on Selected Ships", ID_VIEW_OUTLINES_ON_SELECTED, CHECKED
MENUITEM "Draw Outline at Warpin Position", ID_VIEW_OUTLINE_AT_WARPIN, CHECKED
- MENUITEM "Look At and Orbit Around Selection Center", ID_VIEW_ORBIT_SELECTION_CENTER
MENUITEM "Show Ship Info\tShift+Alt+I", 32823, CHECKED, HELP
MENUITEM "Show Coordinates\tShift+Alt+C", 32915
MENUITEM "Show Grid Positions\tShift+Alt+P", 32914
@@ -666,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 Centroid", ID_MISC_ROTATE_SELECTION_CENTROID
MENUITEM "Mission Statistics\tCtrl+Shift+D", 33067
MENUITEM "Music Player", ID_MUSIC_PLAYER
MENUITEM SEPARATOR
@@ -3893,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_CENTROID
+ "When several objects are marked, rotate the view around the centroid 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/fredview.cpp b/fred2/fredview.cpp
index 3239011f961..4644042c03d 100644
--- a/fred2/fredview.cpp
+++ b/fred2/fredview.cpp
@@ -308,8 +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_VIEW_ORBIT_SELECTION_CENTER, OnViewOrbitSelectionCenter)
- ON_UPDATE_COMMAND_UI(ID_VIEW_ORBIT_SELECTION_CENTER, OnUpdateViewOrbitSelectionCenter)
+ ON_COMMAND(ID_MISC_ROTATE_SELECTION_CENTROID, OnMiscRotateSelectionCentroid)
+ ON_UPDATE_COMMAND_UI(ID_MISC_ROTATE_SELECTION_CENTROID, OnUpdateMiscRotateSelectionCentroid)
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)
@@ -4064,7 +4064,7 @@ void CFREDView::OnUpdateViewOutlineAtWarpin(CCmdUI* pCmdUI)
pCmdUI->SetCheck(Draw_outline_at_warpin_position);
}
-void CFREDView::OnViewOrbitSelectionCenter()
+void CFREDView::OnMiscRotateSelectionCentroid()
{
Orbit_selection_center = !Orbit_selection_center;
Orbit_active = false;
@@ -4072,7 +4072,7 @@ void CFREDView::OnViewOrbitSelectionCenter()
Update_window = 1;
}
-void CFREDView::OnUpdateViewOrbitSelectionCenter(CCmdUI* pCmdUI)
+void CFREDView::OnUpdateMiscRotateSelectionCentroid(CCmdUI* pCmdUI)
{
pCmdUI->SetCheck(Orbit_selection_center);
}
diff --git a/fred2/fredview.h b/fred2/fredview.h
index 8e655a2b8ca..0fc53a530e7 100644
--- a/fred2/fredview.h
+++ b/fred2/fredview.h
@@ -268,8 +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 OnViewOrbitSelectionCenter();
- afx_msg void OnUpdateViewOrbitSelectionCenter(CCmdUI* pCmdUI);
+ afx_msg void OnMiscRotateSelectionCentroid();
+ afx_msg void OnUpdateMiscRotateSelectionCentroid(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 eb139465489..3bb0e12ce85 100644
--- a/fred2/resource.h
+++ b/fred2/resource.h
@@ -1626,7 +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_VIEW_ORBIT_SELECTION_CENTER 33124
+#define ID_MISC_ROTATE_SELECTION_CENTROID 33124
#define ID_INDICATOR_MODE 59142
#define ID_INDICATOR_LEFT 59143
#define ID_INDICATOR_RIGHT 59144
diff --git a/qtfred/src/mission/CameraController.h b/qtfred/src/mission/CameraController.h
index da8fc4f74af..97259d94100 100644
--- a/qtfred/src/mission/CameraController.h
+++ b/qtfred/src/mission/CameraController.h
@@ -85,8 +85,10 @@ class CameraController {
bool getOrbitSelectionCenter() const { return _orbitSelectionCenter; }
void setOrbitSelectionCenter(bool enabled)
{
- _orbitSelectionCenter = enabled;
- _orbitActive = false;
+ if (_orbitSelectionCenter != enabled) {
+ _orbitSelectionCenter = enabled;
+ _orbitActive = false;
+ }
}
void orbitCameraInitFromCurrentView(const vec3d* pivot, const matrix* grid_orient);
diff --git a/qtfred/ui/PreferencesDialog.ui b/qtfred/ui/PreferencesDialog.ui
index 3725f1be4e7..8e6b072b4eb 100644
--- a/qtfred/ui/PreferencesDialog.ui
+++ b/qtfred/ui/PreferencesDialog.ui
@@ -241,6 +241,16 @@
-
+
+
+ When several objects are marked, rotate the view around the centroid of the selection instead of the current object.
+
+
+ Rotate Around Selection Centroid
+
+
+
+ -
Always write each object's $Display Name to the mission file, even when it matches the object's name.
@@ -250,7 +260,7 @@
- -
+
-
Keep a .bak backup copy of the previous mission file each time you save.
@@ -260,14 +270,14 @@
- -
+
-
Number every Nth argument in SEXP trees (0 = disabled):
- -
+
-
0
@@ -566,16 +576,6 @@
- -
-
-
- When several objects are selected, look at and orbit around the center of the selection instead of the current object.
-
-
- Look at and orbit around the center of a multi-selection
-
-
-
diff --git a/test/src/missioneditor/test_object_bounds.cpp b/test/src/missioneditor/test_object_bounds.cpp
index 99baeaadb80..7d7c8fd44a0 100644
--- a/test/src/missioneditor/test_object_bounds.cpp
+++ b/test/src/missioneditor/test_object_bounds.cpp
@@ -7,14 +7,11 @@
class ObjectBoundsTest : public ::testing::Test {
protected:
void SetUp() override {
- list_init(&obj_used_list);
+ obj_init();
}
void TearDown() override {
- for (int i = 0; i < _count; ++i) {
- Objects[i].clear();
- }
- list_init(&obj_used_list);
+ obj_init();
}
void add(float x, bool marked) {
@@ -24,6 +21,7 @@ class ObjectBoundsTest : public ::testing::Test {
if (marked) {
objp->flags.set(Object::Object_Flags::Marked);
}
+ list_remove(&obj_free_list, objp);
list_append(&obj_used_list, objp);
}
From 10bd191ab7f8888dcc7821146204d08f055e3fa5 Mon Sep 17 00:00:00 2001
From: Dustinspace217
Date: Fri, 16 Oct 2026 08:30:07 -0700
Subject: [PATCH 7/8] Rename the option to Selection Midpoint, move it to
qtFRED's Controls tab, and document it
---
fred2/fred.cpp | 4 +--
fred2/fred.rc | 8 ++---
fred2/fredrender.cpp | 10 +++----
fred2/fredrender.h | 4 +--
fred2/fredview.cpp | 14 ++++-----
fred2/fredview.h | 4 +--
fred2/resource.h | 2 +-
.../doc/general/PreferencesDialog.html | 8 +++++
qtfred/help-src/doc/general/Viewport.html | 7 +++--
qtfred/src/mission/CameraController.h | 10 +++----
qtfred/src/mission/EditorViewport.cpp | 12 ++++----
qtfred/src/mission/EditorViewport.h | 2 +-
.../dialogs/PreferencesDialogModel.cpp | 8 ++---
.../mission/dialogs/PreferencesDialogModel.h | 6 ++--
qtfred/src/ui/FredView.cpp | 2 +-
qtfred/src/ui/dialogs/PreferencesDialog.cpp | 6 ++--
qtfred/src/ui/dialogs/PreferencesDialog.h | 2 +-
qtfred/ui/PreferencesDialog.ui | 30 +++++++++----------
18 files changed, 75 insertions(+), 64 deletions(-)
diff --git a/fred2/fred.cpp b/fred2/fred.cpp
index 0a655f99d3c..76154b761c1 100644
--- a/fred2/fred.cpp
+++ b/fred2/fred.cpp
@@ -234,7 +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_center = GetProfileInt("Preferences", "Orbit selection center", 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;
@@ -540,7 +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 center", Orbit_selection_center ? 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 7080de813c6..f22142069b3 100644
--- a/fred2/fred.rc
+++ b/fred2/fred.rc
@@ -436,7 +436,7 @@ BEGIN
MENUITEM "Adjust Grid", 33032
MENUITEM "Point Ships Using UVec", 33101
MENUITEM "Move Ships When Undocking", 33098, CHECKED
- MENUITEM "Rotate Around Selection Centroid", ID_MISC_ROTATE_SELECTION_CENTROID
+ 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
@@ -665,7 +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 Centroid", ID_MISC_ROTATE_SELECTION_CENTROID
+ 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
@@ -3893,8 +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_CENTROID
- "When several objects are marked, rotate the view around the centroid of the selection instead of the current object"
+ 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 b5a5ed75602..419ea29995e 100644
--- a/fred2/fredrender.cpp
+++ b/fred2/fredrender.cpp
@@ -116,7 +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_center = false;
+bool Orbit_selection_midpoint = false;
int Outline_lod = 1;
bool Always_save_display_names = false;
bool Error_checker_checks_potential_issues = true;
@@ -1057,7 +1057,7 @@ void game_do_frame() {
}
if (Lookat_mode && query_valid_object()) {
- vec3d center = selection_center();
+ vec3d center = selection_midpoint();
float dist = vm_vec_dist(&view_pos, ¢er);
vm_vec_scale_add(&view_pos, ¢er, &view_orient.vec.fvec, -dist);
}
@@ -1289,12 +1289,12 @@ void move_mouse(int btn, int mdx, int mdy) {
}
}
-vec3d selection_center()
+vec3d selection_midpoint()
{
vec3d center;
float radius;
- if (Orbit_selection_center && Marked > 1 && get_object_bounds(true, ¢er, &radius)) {
+ if (Orbit_selection_midpoint && Marked > 1 && get_object_bounds(true, ¢er, &radius)) {
return center;
}
@@ -1309,7 +1309,7 @@ vec3d orbit_camera_get_pivot()
if (query_valid_object()) {
// Pivot on the selection
- pivot = selection_center();
+ 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 5e6d9a1162b..bddb090a25f 100644
--- a/fred2/fredrender.h
+++ b/fred2/fredrender.h
@@ -22,7 +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_center;
+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.
@@ -106,7 +106,7 @@ void verticalize_controlled();
*/
int select_object(int cx, int cy);
-vec3d selection_center();
+vec3d selection_midpoint();
// Orbit camera functions
vec3d orbit_camera_get_pivot();
diff --git a/fred2/fredview.cpp b/fred2/fredview.cpp
index 4644042c03d..7478e410eed 100644
--- a/fred2/fredview.cpp
+++ b/fred2/fredview.cpp
@@ -308,8 +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_CENTROID, OnMiscRotateSelectionCentroid)
- ON_UPDATE_COMMAND_UI(ID_MISC_ROTATE_SELECTION_CENTROID, OnUpdateMiscRotateSelectionCentroid)
+ 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)
@@ -4064,17 +4064,17 @@ void CFREDView::OnUpdateViewOutlineAtWarpin(CCmdUI* pCmdUI)
pCmdUI->SetCheck(Draw_outline_at_warpin_position);
}
-void CFREDView::OnMiscRotateSelectionCentroid()
+void CFREDView::OnMiscRotateSelectionMidpoint()
{
- Orbit_selection_center = !Orbit_selection_center;
+ Orbit_selection_midpoint = !Orbit_selection_midpoint;
Orbit_active = false;
theApp.write_ini_file();
Update_window = 1;
}
-void CFREDView::OnUpdateMiscRotateSelectionCentroid(CCmdUI* pCmdUI)
+void CFREDView::OnUpdateMiscRotateSelectionMidpoint(CCmdUI* pCmdUI)
{
- pCmdUI->SetCheck(Orbit_selection_center);
+ pCmdUI->SetCheck(Orbit_selection_midpoint);
}
void CFREDView::OnAlwaysSaveDisplayNames()
@@ -4470,7 +4470,7 @@ void CFREDView::OnLookatObj()
vec3d v, loc;
matrix m;
- loc = selection_center();
+ 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 0fc53a530e7..10ed8fa5941 100644
--- a/fred2/fredview.h
+++ b/fred2/fredview.h
@@ -268,8 +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 OnMiscRotateSelectionCentroid();
- afx_msg void OnUpdateMiscRotateSelectionCentroid(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 3bb0e12ce85..6c6ef735a72 100644
--- a/fred2/resource.h
+++ b/fred2/resource.h
@@ -1626,7 +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_CENTROID 33124
+#define ID_MISC_ROTATE_SELECTION_MIDPOINT 33124
#define ID_INDICATOR_MODE 59142
#define ID_INDICATOR_LEFT 59143
#define ID_INDICATOR_RIGHT 59144
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.
+