mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 08:15:59 -10:00
P4: qtFRED: rotate around the centre of a multi-selection (#7843)
* 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 * 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. * 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. * 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. * 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. * 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. * Rename the option to Selection Midpoint, move it to qtFRED's Controls tab, and document it * Fix overlapping Appearance rows in qtFRED Preferences, realign a settings line The last commit renumbered the Viewport label size row of the Appearance group from 4 to 3, which is the Long list menu style row, so the label size pair fell out of the layout and its spin box drew over the group title. Put it back at row 4. --------- Co-authored-by: Dustinspace217 <[email protected]>
This commit is contained in:
1 parent
12bddb8366
commit
499b3fb38b
22 files changed
+254
-139
No files matched your search
@@ -752,3 +752,41 @@ void rotate_jump_nodes(const SCP_vector<int>& 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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
+19
-6
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
+21
-53
@@ -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; i<MAX_OBJECTS; i++)
|
||||
obj_flags[i] = 0;
|
||||
if (!get_object_bounds(just_marked != 0, ¢er, &radius))
|
||||
return;
|
||||
|
||||
if (just_marked)
|
||||
ptr = &Objects[cur_object_index];
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -268,6 +268,8 @@ protected:
|
||||
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();
|
||||
|
||||
+2
-1
@@ -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
|
||||
@@ -41,5 +41,13 @@ grid</strong> to restore the plane and center to their defaults.</p>
|
||||
viewport navigation. Hover over each entry for a description of what it does. The
|
||||
default keyboard bindings are listed on the
|
||||
<a href="Viewport.html">Viewport</a> page.</p>
|
||||
<ul>
|
||||
<li><strong>Mouse Orbit Camera</strong> - invert the horizontal or vertical
|
||||
direction of mouse orbit rotation.</li>
|
||||
<li><strong>Rotate Around Selection Midpoint</strong> - 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.</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
@@ -52,8 +52,11 @@ options vary depending on whether you clicked an object, a specific object type,
|
||||
empty space.</p>
|
||||
|
||||
<h2>Camera controls</h2>
|
||||
<p>The camera orbits around a pivot point in the scene. Mouse controls move the
|
||||
camera without changing any mission data:</p>
|
||||
<p>The camera orbits around a pivot point in the scene. With several objects
|
||||
marked, the pivot is the current object unless <strong>Rotate Around Selection
|
||||
Midpoint</strong> 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:</p>
|
||||
<ul>
|
||||
<li><strong>Middle-button drag</strong> - orbit (rotate) the camera around the
|
||||
pivot.</li>
|
||||
|
||||
@@ -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 _orbitSelectionMidpoint = false;
|
||||
|
||||
void orbitCameraApply();
|
||||
|
||||
@@ -81,6 +82,15 @@ public:
|
||||
bool getInvertOrbitY() const { return _invertOrbitY; }
|
||||
void setInvertOrbitY(bool invert) { _invertOrbitY = invert; }
|
||||
|
||||
bool getOrbitSelectionMidpoint() const { return _orbitSelectionMidpoint; }
|
||||
void setOrbitSelectionMidpoint(bool enabled)
|
||||
{
|
||||
if (_orbitSelectionMidpoint != enabled) {
|
||||
_orbitSelectionMidpoint = enabled;
|
||||
_orbitActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
void orbitCameraInitFromCurrentView(const vec3d* pivot, const matrix* grid_orient);
|
||||
void orbitCameraRotate(int dx, int dy);
|
||||
void orbitCameraPan(int dx, int dy);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <jumpnode/jumpnode.h>
|
||||
#include <mission/missionparse.h>
|
||||
#include <prop/prop.h>
|
||||
#include <missioneditor/common.h>
|
||||
#include <FredApplication.h>
|
||||
|
||||
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.setOrbitSelectionMidpoint(settings.value("camera_orbit_selection_midpoint", camera.getOrbitSelectionMidpoint()).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_midpoint", camera.getOrbitSelectionMidpoint());
|
||||
settings.endGroup();
|
||||
}
|
||||
void EditorViewport::needsUpdate() {
|
||||
@@ -498,10 +501,9 @@ 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);
|
||||
vec3d center = selectionMidpoint(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 +665,25 @@ void EditorViewport::level_object(matrix* orient) {
|
||||
vm_fix_matrix(orient);
|
||||
}
|
||||
|
||||
vec3d EditorViewport::selectionMidpoint(int cur_object_index) const
|
||||
{
|
||||
vec3d center;
|
||||
float radius;
|
||||
|
||||
if (camera.getOrbitSelectionMidpoint() && editor->getNumMarked() > 1 && get_object_bounds(true, ¢er, &radius)) {
|
||||
return center;
|
||||
}
|
||||
|
||||
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 selection
|
||||
pivot = selectionMidpoint(editor->currentObject);
|
||||
} else if (!The_grid) {
|
||||
// Pivot on the origin, if no grid
|
||||
pivot = ZERO_VECTOR;
|
||||
@@ -1745,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);
|
||||
if (!get_object_bounds(just_marked, ¢er, &radius))
|
||||
return;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -127,6 +127,8 @@ class EditorViewport {
|
||||
|
||||
vec3d orbitCameraGetPivot();
|
||||
|
||||
vec3d selectionMidpoint(int cur_object_index) const;
|
||||
|
||||
int object_check_collision(object* objp, vec3d* p0, vec3d* p1, vec3d* hitpos);
|
||||
|
||||
int select_object(int cx, int cy);
|
||||
|
||||
@@ -29,6 +29,7 @@ PreferencesDialogModel::PreferencesDialogModel(QObject* parent, EditorViewport*
|
||||
, _labelFontScale(viewport->view.Label_font_scale)
|
||||
, _invertOrbitX(viewport->camera.getInvertOrbitX())
|
||||
, _invertOrbitY(viewport->camera.getInvertOrbitY())
|
||||
, _orbitSelectionMidpoint(viewport->camera.getOrbitSelectionMidpoint())
|
||||
, _gridCenterX(static_cast<int>(viewport->The_grid->center.xyz.x))
|
||||
, _gridCenterY(static_cast<int>(viewport->The_grid->center.xyz.y))
|
||||
, _gridCenterZ(static_cast<int>(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.setOrbitSelectionMidpoint(_orbitSelectionMidpoint);
|
||||
|
||||
_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::getOrbitSelectionMidpoint() const { return _orbitSelectionMidpoint; }
|
||||
void PreferencesDialogModel::setOrbitSelectionMidpoint(bool value) { modify(_orbitSelectionMidpoint, value); }
|
||||
|
||||
void PreferencesDialogModel::resetControlDefaults() {
|
||||
auto& bindings = ControlBindings::instance();
|
||||
for (const auto& def : bindings.definitions()) {
|
||||
|
||||
@@ -79,6 +79,8 @@ public:
|
||||
void setInvertOrbitX(bool value);
|
||||
bool getInvertOrbitY() const;
|
||||
void setInvertOrbitY(bool value);
|
||||
bool getOrbitSelectionMidpoint() const;
|
||||
void setOrbitSelectionMidpoint(bool value);
|
||||
|
||||
// Grid
|
||||
int getGridCenterX() const;
|
||||
@@ -117,6 +119,7 @@ private:
|
||||
std::map<ControlAction, QKeySequence> _controlKeys;
|
||||
bool _invertOrbitX;
|
||||
bool _invertOrbitY;
|
||||
bool _orbitSelectionMidpoint;
|
||||
|
||||
// Grid
|
||||
int _gridCenterX;
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
#include <ui/dialogs/GlobalShipFlagsDialog.h>
|
||||
#include <ui/dialogs/VoiceActingManager.h>
|
||||
#include <globalincs/linklist.h>
|
||||
#include <missioneditor/common.h>
|
||||
#include <ui/dialogs/FictionViewerDialog.h>
|
||||
#include <ui/dialogs/CommandBriefingDialog.h>
|
||||
#include <ui/dialogs/DebriefingDialog.h>
|
||||
@@ -1461,28 +1462,10 @@ void FredView::onUpdateTransformBar() {
|
||||
if (valid && numMarked <= 1) {
|
||||
_transformRadiusLabel->setText(QString::number(static_cast<double>(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<double>(maxDist), 'f', 1));
|
||||
vec3d center;
|
||||
float radius;
|
||||
if (get_object_bounds(true, ¢er, &radius)) {
|
||||
_transformRadiusLabel->setText(QString::number(static_cast<double>(radius), 'f', 1));
|
||||
} else {
|
||||
_transformRadiusLabel->setText(tr("--"));
|
||||
}
|
||||
@@ -2879,7 +2862,7 @@ void FredView::on_actionOrbitSelected_triggered(bool enabled) {
|
||||
vec3d v, loc;
|
||||
matrix m;
|
||||
|
||||
loc = Objects[fred->currentObject].pos;
|
||||
loc = _viewport->selectionMidpoint(fred->currentObject);
|
||||
vm_vec_sub(&v, &loc, &_viewport->camera.view_pos);
|
||||
|
||||
if (v.xyz.x || v.xyz.y || v.xyz.z) {
|
||||
|
||||
@@ -150,6 +150,7 @@ void PreferencesDialog::updateUi() {
|
||||
// Controls
|
||||
ui->invertOrbitX->setChecked(_model->getInvertOrbitX());
|
||||
ui->invertOrbitY->setChecked(_model->getInvertOrbitY());
|
||||
ui->orbitSelectionMidpoint->setChecked(_model->getOrbitSelectionMidpoint());
|
||||
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_orbitSelectionMidpoint_toggled(bool checked) {
|
||||
_model->setOrbitSelectionMidpoint(checked);
|
||||
}
|
||||
|
||||
void PreferencesDialog::on_resetDefaultsButton_clicked() {
|
||||
_model->resetControlDefaults();
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ private slots:
|
||||
// Controls
|
||||
void on_invertOrbitX_toggled(bool checked);
|
||||
void on_invertOrbitY_toggled(bool checked);
|
||||
void on_orbitSelectionMidpoint_toggled(bool checked);
|
||||
void on_resetDefaultsButton_clicked();
|
||||
|
||||
private: // NOLINT(readability-redundant-access-specifiers)
|
||||
|
||||
@@ -569,6 +569,16 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="orbitSelectionMidpoint">
|
||||
<property name="toolTip">
|
||||
<string>When several objects are marked, rotate the view around the midpoint of the selection instead of the current object.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Rotate Around Selection Midpoint</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="controlsFormWidget" native="true"/>
|
||||
</item>
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "globalincs/linklist.h"
|
||||
#include "missioneditor/common.h"
|
||||
#include "object/object.h"
|
||||
|
||||
class ObjectBoundsTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
obj_init();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
obj_init();
|
||||
}
|
||||
|
||||
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_remove(&obj_free_list, objp);
|
||||
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);
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user