diff --git a/code/io/mouse.cpp b/code/io/mouse.cpp index 05859846e05..fae186bb653 100644 --- a/code/io/mouse.cpp +++ b/code/io/mouse.cpp @@ -54,6 +54,10 @@ int Mouse_dx = 0; int Mouse_dy = 0; int Mouse_dz = 0; +// Mouse wheel delta tracking movement not position. Resets every frame. +int Mouse_wheel_dx = 0; +int Mouse_wheel_dy = 0; + int Mouse_sensitivity = 4; static auto MouseSensitivityOption __UNUSED = options::OptionBuilder("Input.MouseSensitivity", @@ -523,6 +527,14 @@ void mouse_get_delta(int *dx, int *dy, int *dz) *dz = Mouse_dz; } +void mouse_get_wheel_delta(int* dx, int* dy) +{ + if (dx) + *dx = Mouse_wheel_dx; + if (dy) + *dy = Mouse_wheel_dy; +} + // Forces the actual windows cursor to be at (x,y). This may be independent of our tracked (x,y) mouse pos. void mouse_force_pos(int x, int y) { @@ -535,6 +547,7 @@ void mouse_force_pos(int x, int y) void mouse_reset_deltas() { Mouse_dx = Mouse_dy = Mouse_dz = 0; + Mouse_wheel_dx = Mouse_wheel_dy = 0; } void mouse_event(int x, int y, int dx, int dy) @@ -635,6 +648,9 @@ void mousewheel_motion(int x, int y, bool reversed) { Mouse_wheel_x += x; Mouse_wheel_y += y; + // Used for tracking the actual movement of the wheel, not just the current position + Mouse_wheel_dx += x; + Mouse_wheel_dy += y; // These nested if's should take care of all edge cases. // Since x and y's magnitudes can be larger than 1, it is possible to ignore the idle state diff --git a/code/io/mouse.h b/code/io/mouse.h index ed90334d9cf..156ea07f579 100644 --- a/code/io/mouse.h +++ b/code/io/mouse.h @@ -106,7 +106,8 @@ int mouse_down(const CC_bind& bind, bool must_be_wheel = false); int mouse_down(int btn, bool must_be_wheel = false); void mouse_reset_deltas(); -void mouse_get_delta(int *dx = NULL, int *dy = NULL, int *dz = NULL); +void mouse_get_delta(int* dx = nullptr, int* dy = nullptr, int* dz = nullptr); +void mouse_get_wheel_delta(int* dx = nullptr, int* dy = nullptr); void mouse_event(int x, int y, int dx, int dy); diff --git a/code/lab/dialogs/lab_ui.cpp b/code/lab/dialogs/lab_ui.cpp index e696391f3bb..435b37f3bcf 100644 --- a/code/lab/dialogs/lab_ui.cpp +++ b/code/lab/dialogs/lab_ui.cpp @@ -12,9 +12,45 @@ #include "weapon/weapon.h" #include "mission/missionload.h" #include "prop/prop.h" +#include "controlconfig/controlsconfig.h" using namespace ImGui; +namespace { +SCP_string get_binding_text(int action_id) +{ + const auto& action = Control_config[action_id]; + + if (!action.first.empty() && !action.second.empty()) { + return action.first.textify() + " or " + action.second.textify(); + } + + if (!action.first.empty()) { + return action.first.textify(); + } + + if (!action.second.empty()) { + return action.second.textify(); + } + + return "Unbound"; +} + +void controls_reference_entry(const char* label, const SCP_string& description) +{ + Bullet(); + SameLine(); + TextWrapped("%s: %s", label, description.c_str()); +} + +void controls_reference_entry(const char* label, const char* description) +{ + Bullet(); + SameLine(); + TextWrapped("%s: %s", label, description); +} +} // namespace + std::map> manual_animation_triggers = {}; std::map manual_animations = {}; @@ -269,6 +305,8 @@ void LabUi::build_options_menu() MenuItem("Object selector", nullptr, &show_object_selection_dialog); MenuItem("Background selector", nullptr, &show_background_selection_dialog); MenuItem("Object options", nullptr, &show_object_options_dialog); + MenuItem("Controls reference", nullptr, &show_controls_reference_dialog); + MenuItem("Reset View", nullptr, &reset_view); MenuItem("Close lab", "ESC", &close_lab); } } @@ -280,6 +318,11 @@ void LabUi::build_toolbar_entries() build_options_menu(); } + if (reset_view) { + getLabManager()->Renderer->resetView(); + reset_view = false; + } + if (close_lab) { getLabManager()->notify_close(); close_lab = false; @@ -330,9 +373,45 @@ void LabUi::create_ui() if (show_object_selection_dialog) show_object_selector(); + if (show_controls_reference_dialog) + show_controls_reference(); + rebuild_after_object_change = false; } +void LabUi::show_controls_reference() +{ + with_Window("Lab controls reference") + { + TextWrapped("Mouse controls"); + controls_reference_entry("LMB + drag", "Orient the displayed object."); + controls_reference_entry("RMB + drag", "Rotate the camera."); + controls_reference_entry("Shift + RMB + drag", "Pan the camera on the X/Y plane."); + controls_reference_entry("Mouse wheel", "Zoom the camera in or out."); + TextWrapped("Rotation axis limits and rotation speed apply only to object orientation (LMB), not " + "camera controls (RMB)."); + + Separator(); + TextWrapped("Keyboard shortcuts"); + controls_reference_entry("R", "Cycle object orientation (LMB) axis mode (Yaw, Pitch, Roll, or Both)."); + controls_reference_entry("S", "Cycle object orientation (LMB) speed."); + controls_reference_entry("V", "Reset camera view."); + controls_reference_entry("T / Y", "Cycle team color presets."); + controls_reference_entry("1-9", "Switch anti-aliasing presets."); + controls_reference_entry("M", "Export an environment map."); + controls_reference_entry("ESC", "Close the lab."); + + if (getLabManager()->CurrentMode == LabMode::Ship) { + Separator(); + TextWrapped("Ship-only controls (from current control bindings)"); + // These don't work in the new lab yet + //controls_reference_entry("Increase throttle by 5%", get_binding_text(PLUS_5_PERCENT_THROTTLE)); + //controls_reference_entry("Decrease throttle by 5%", get_binding_text(MINUS_5_PERCENT_THROTTLE)); + controls_reference_entry("Afterburner", get_binding_text(AFTERBURNER)); + } + } +} + const char* antialiasing_settings[] = { "None", "FXAA Low", @@ -604,7 +683,7 @@ void LabUi::show_render_options() getLabManager()->Renderer->setRenderFlag(LabRenderFlag::NoLighting, no_lighting); getLabManager()->Renderer->setRenderFlag(LabRenderFlag::ShowFullDetail, show_full_detail); getLabManager()->Renderer->setRenderFlag(LabRenderFlag::ShowThrusters, show_thrusters); - getLabManager()->Renderer->setRenderFlag(LabRenderFlag::ShowAfterburners, show_afterburners); + getLabManager()->Renderer->setRenderFlag(LabRenderFlag::ShowAfterburners, show_afterburners || getLabManager()->Lab_thrust_afterburn); getLabManager()->Renderer->setRenderFlag(LabRenderFlag::ShowWeapons, show_weapons); getLabManager()->Renderer->setRenderFlag(LabRenderFlag::ShowEmissiveLighting, show_emissive_lighting); getLabManager()->Renderer->setRenderFlag(LabRenderFlag::MoveSubsystems, animate_subsystems); diff --git a/code/lab/dialogs/lab_ui.h b/code/lab/dialogs/lab_ui.h index 299a705b4b7..ab5fc4a4157 100644 --- a/code/lab/dialogs/lab_ui.h +++ b/code/lab/dialogs/lab_ui.h @@ -37,6 +37,7 @@ class LabUi { static void build_background_list(); void show_render_options(); void show_object_options() const; + static void show_controls_reference(); void show_object_selector() const; void show_background_selector() const; void build_toolbar_entries(); @@ -88,6 +89,10 @@ class LabUi { bool show_object_selection_dialog = true; bool show_object_options_dialog = false; bool show_background_selection_dialog = true; + bool show_controls_reference_dialog = false; + + // used to track the "Reset View" function + bool reset_view = false; // used to track the "Close Lab" function bool close_lab = false; diff --git a/code/lab/manager/lab_manager.cpp b/code/lab/manager/lab_manager.cpp index b43ebc234db..7a8a68dca58 100644 --- a/code/lab/manager/lab_manager.cpp +++ b/code/lab/manager/lab_manager.cpp @@ -123,9 +123,13 @@ void LabManager::onFrame(float frametime) { int key = game_check_key(); - int dx, dy; + int dx, dy, dz; mouse_get_delta(&dx, &dy); - Renderer->getCurrentCamera()->handleInput(dx, dy, mouse_down(MOUSE_LEFT_BUTTON) != 0, mouse_down(MOUSE_RIGHT_BUTTON) != 0, key_get_shift_status()); + mouse_get_wheel_delta(nullptr, &dz); + if (dz != 0 && ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) { + dz = 0; + } + Renderer->getCurrentCamera()->handleInput(dx, dy, dz, mouse_down(MOUSE_LEFT_BUTTON) != 0, mouse_down(MOUSE_RIGHT_BUTTON) != 0, key_get_shift_status()); if (!Renderer->getCurrentCamera()->handlesObjectPlacement()) { if (mouse_down(MOUSE_LEFT_BUTTON)) { @@ -162,6 +166,12 @@ void LabManager::onFrame(float frametime) { } } + if (CurrentMode == LabMode::Ship) { + Lab_thrust_afterburn = check_control(AFTERBURNER) != 0; + } else { + Lab_thrust_afterburn = false; + } + if (key != 0) { // handle any key presses switch (key) { @@ -219,15 +229,15 @@ void LabManager::onFrame(float frametime) { default: // check for game-specific controls if (CurrentMode == LabMode::Ship) { - if (check_control(PLUS_5_PERCENT_THROTTLE, key)) + // These don't work because the lab is a lie and ships don't actually move + // Also the ships are AI and don't really respond to player input anyway so + // getting these working will be tricky + /*if (check_control(PLUS_5_PERCENT_THROTTLE, key)) Lab_thrust_len += 0.05f; else if (check_control(MINUS_5_PERCENT_THROTTLE, key)) Lab_thrust_len -= 0.05f; - CLAMP(Lab_thrust_len, 0.0f, 1.0f); - - if (check_control(AFTERBURNER, key)) - Lab_thrust_afterburn = !Lab_thrust_afterburn; + CLAMP(Lab_thrust_len, 0.0f, 1.0f);*/ } break; } diff --git a/code/lab/manager/lab_manager.h b/code/lab/manager/lab_manager.h index 2ff8758d054..7dfaf36b232 100644 --- a/code/lab/manager/lab_manager.h +++ b/code/lab/manager/lab_manager.h @@ -148,13 +148,13 @@ class LabManager { float RotationSpeedDivisor = 100.0f; bool AllowWeaponDestruction = false; bool ShowingTechModel = false; + bool Lab_thrust_afterburn = false; flagset Flags; gfx_options graphicsSettings; private: - float Lab_thrust_len = 0.0f; - bool Lab_thrust_afterburn = false; + //float Lab_thrust_len = 0.0f; // Unused bool Weapons_loaded = false; bool CloseThis = false; LabUi labUi; diff --git a/code/lab/renderer/lab_cameras.cpp b/code/lab/renderer/lab_cameras.cpp index 52ab6aaf285..519a3fe0574 100644 --- a/code/lab/renderer/lab_cameras.cpp +++ b/code/lab/renderer/lab_cameras.cpp @@ -1,5 +1,6 @@ #include "globalincs/pstypes.h" #include "io/key.h" +#include "io/mouse.h" #include "lab/renderer/lab_cameras.h" #include "lab/labv2_internal.h" @@ -8,18 +9,53 @@ LabCamera::~LabCamera() { cam_delete(FS_camera); } -void OrbitCamera::handleInput(int dx, int dy, bool, bool rmbDown, int modifierKeys) { - if (dx == 0 && dy == 0) +void OrbitCamera::handleInput(int dx, int dy, int dz, bool, bool rmbDown, int modifierKeys) { + if (dx == 0 && dy == 0 && dz == 0) return; + if (dz > 0) { + for (int i = 0; i < dz; ++i) { + distance *= 0.9f; + } + } else if (dz < 0) { + for (int i = 0; i < -dz; ++i) { + distance *= 1.1f; + } + } + CLAMP(distance, 1.0f, 10000000.0f); + if (rmbDown) { if (modifierKeys & KEY_SHIFTED) { - distance *= 1.0f + (dy / 200.0f); - CLAMP(distance, 1.0f, 10000000.0f); - } - else { - theta += dx / 100.0f; - phi += dy / 100.0f; + const float pan_factor = distance / 500.0f; + + vec3d camera_offset; + camera_offset.xyz.x = sinf(phi) * cosf(theta); + camera_offset.xyz.y = cosf(phi); + camera_offset.xyz.z = sinf(phi) * sinf(theta); + + vec3d view_forward; + vm_vec_copy_scale(&view_forward, &camera_offset, -1.0f); + + vec3d world_up = vmd_y_vector; + vec3d view_right; + vm_vec_cross(&view_right, &world_up, &view_forward); + + if (vm_vec_mag_squared(&view_right) <= 1e-6f) { + world_up = vmd_x_vector; + vm_vec_cross(&view_right, &world_up, &view_forward); + } + + vm_vec_normalize_safe(&view_right); + + vec3d view_up; + vm_vec_cross(&view_up, &view_forward, &view_right); + vm_vec_normalize_safe(&view_up); + + vm_vec_scale_add2(&pan_offset, &view_right, -dx * pan_factor); + vm_vec_scale_add2(&pan_offset, &view_up, dy * pan_factor); + } else { + theta -= dx / 100.0f; + phi -= dy / 100.0f; CLAMP(phi, 0.01f, PI - 0.01f); } @@ -28,11 +64,24 @@ void OrbitCamera::handleInput(int dx, int dy, bool, bool rmbDown, int modifierKe updateCamera(); } +void OrbitCamera::resetView() +{ + phi = DEFAULT_PHI; + theta = DEFAULT_THETA; + distance = DEFAULT_DISTANCE; + pan_offset = vmd_zero_vector; + + displayedObjectChanged(); +} + void OrbitCamera::displayedObjectChanged() { float distance_multiplier = 1.6f; if (getLabManager()->CurrentObject != -1) { object* obj = &Objects[getLabManager()->CurrentObject]; + + // Reset camera panning + pan_offset = vmd_zero_vector; // Ships and Missiles use the object radius to get a camera distance distance = obj->radius * distance_multiplier; @@ -75,10 +124,11 @@ void OrbitCamera::updateCamera() { vm_vec_copy_normalize(&forward, &obj->orient.vec.fvec); vm_vec_scale_add2(&target, &forward, wip->laser_length * 0.5f); } - - vm_vec_add2(&new_position, &target); } + vm_vec_add2(&target, &pan_offset); + vm_vec_add2(&new_position, &target); + cam->set_position(&new_position); // If these are the same then that's not great so do nothing and use the last facing value diff --git a/code/lab/renderer/lab_cameras.h b/code/lab/renderer/lab_cameras.h index 4aec57f697e..bc9589ecfb0 100644 --- a/code/lab/renderer/lab_cameras.h +++ b/code/lab/renderer/lab_cameras.h @@ -29,10 +29,11 @@ class LabCamera { /// /// Mouse delta on the x axis /// Mouse delta on the y axis + /// Mouse wheel delta /// State of the left mouse button /// State of the right mouse button /// State of the various modifier keys. See keys.h - virtual void handleInput(int dx, int dy, bool lmbDown, bool rmbDown, int modifierKeys) = 0; + virtual void handleInput(int dx, int dy, int dz, bool lmbDown, bool rmbDown, int modifierKeys) = 0; /// /// Called by the lab manager when the displayed object changes @@ -50,6 +51,9 @@ class LabCamera { /// /// virtual void updateCamera() = 0; + + /// Resets the camera orientation, pan, and zoom to default values + virtual void resetView() {} }; class OrbitCamera : public LabCamera { @@ -57,19 +61,22 @@ class OrbitCamera : public LabCamera { OrbitCamera() : LabCamera(cam_create("Lab orbit camera")) {} SCP_string getUsageInfo() override { - return "Hold RMB to rotate the Camera. Hold Shift + RMB to zoom in or out."; + return "Hold LMB to rotate the model. Hold RMB to rotate the camera. Hold Shift + RMB to pan on X/Y. Use mouse wheel to zoom in/out."; } SCP_string getOnFrameInfo() override { SCP_stringstream ss; ss.setf(std::ios::fixed); - ss << "Phi: " << phi << " Theta: " << theta << " Distance: " << distance; + ss << "Phi: " << phi << " Theta: " << theta << " Distance: " << distance << " Pan: (" << pan_offset.xyz.x << ", " + << pan_offset.xyz.y << ", " << pan_offset.xyz.z << ")"; return ss.str(); } - void handleInput(int dx, int dy, bool /*lmbDown*/, bool rmbDown, int modifierKeys) override; + void handleInput(int dx, int dy, int dz, bool /*lmbDown*/, bool rmbDown, int modifierKeys) override; + + void resetView() override; void displayedObjectChanged() override; @@ -78,7 +85,12 @@ class OrbitCamera : public LabCamera { void updateCamera() override; private: - float distance = 100.0f; - float phi = 1.24f; - float theta = 2.25f; -}; \ No newline at end of file + static constexpr float DEFAULT_DISTANCE = 100.0f; + static constexpr float DEFAULT_PHI = 1.24f; + static constexpr float DEFAULT_THETA = 2.25f; + + float distance = DEFAULT_DISTANCE; + float phi = DEFAULT_PHI; + float theta = DEFAULT_THETA; + vec3d pan_offset = vmd_zero_vector; +}; diff --git a/code/lab/renderer/lab_renderer.cpp b/code/lab/renderer/lab_renderer.cpp index 67ee4b58495..89f8f2c5e46 100644 --- a/code/lab/renderer/lab_renderer.cpp +++ b/code/lab/renderer/lab_renderer.cpp @@ -61,6 +61,12 @@ void LabRenderer::resetGraphicsSettings(gfx_options settings) { Gr_aa_mode = settings.aa_mode; } +void LabRenderer::resetView() +{ + getLabManager()->CurrentOrientation = vmd_identity_matrix; + labCamera->resetView(); +} + void LabRenderer::renderModel(float frametime) { GR_DEBUG_SCOPE("Lab Render Model"); @@ -252,13 +258,13 @@ SCP_string get_rot_mode_string(LabRotationMode rotmode) { switch (rotmode) { case LabRotationMode::Both: - return "Manual rotation mode: Pitch and Yaw"; + return "Pitch and Yaw"; case LabRotationMode::Pitch: - return "Manual rotation mode: Pitch"; + return "Pitch"; case LabRotationMode::Yaw: - return "Manual rotation mode: Yaw"; + return "Yaw"; case LabRotationMode::Roll: - return "Manual rotation mode: Roll"; + return "Roll"; default: return "HOW DID THIS HAPPEN? Ask a coder!"; } @@ -335,27 +341,24 @@ void LabRenderer::renderHud(float) { gr_printf_no_resize(gr_screen.center_offset_x + 2, gr_screen.center_offset_y + gr_screen.center_h - (gr_get_font_height() * 2) - 3, "AA Preset: %s", aa_mode); } - //Print current Team Color setting, if any - if (currentTeamColor != LAB_TEAM_COLOR_NONE) { - gr_printf_no_resize(gr_screen.center_offset_x + 2, - gr_screen.center_offset_y + gr_screen.center_h - (gr_get_font_height() * 3) - 3, - "Use T and Y to cycle through available Team Color settings. Current: %s", - currentTeamColor.c_str()); - } - - // Camera usage info + // Controls info gr_printf_no_resize(gr_screen.center_offset_x + 2, - gr_screen.center_offset_y + gr_screen.center_h - (gr_get_font_height() * 4) - 3, - "%s Use number keys to switch between AA presets. R to cycle model rotation " - "modes, S to cycle model rotation speeds, V to reset view, " - "M to export environment map.", labCamera->getUsageInfo().c_str()); + gr_screen.center_offset_y + gr_screen.center_h - (gr_get_font_height() * 7) - 3, + "Open Options -> Controls reference for the full object and camera controls list."); // Rotation mode - SCP_string text = get_rot_mode_string(getLabManager()->RotationMode); gr_printf_no_resize(gr_screen.center_offset_x + 2, gr_screen.center_offset_y + gr_screen.center_h - (gr_get_font_height() * 5) - 3, - "%s Rotation speed: %s", get_rot_mode_string(getLabManager()->RotationMode).c_str(), + "Model rotation axis limit: %s, Rotation speed: %s", get_rot_mode_string(getLabManager()->RotationMode).c_str(), get_rot_speed_string(getLabManager()->RotationSpeedDivisor).c_str()); + + // Print current Team Color setting, if any + if (currentTeamColor != LAB_TEAM_COLOR_NONE) { + gr_printf_no_resize(gr_screen.center_offset_x + 2, + gr_screen.center_offset_y + gr_screen.center_h - (gr_get_font_height() * 4) - 3, + "Current Team Color: %s", + currentTeamColor.c_str()); + } } void LabRenderer::useBackground(const SCP_string& mission_name) { diff --git a/code/lab/renderer/lab_renderer.h b/code/lab/renderer/lab_renderer.h index d8a2616a7df..5e5ac3407a7 100644 --- a/code/lab/renderer/lab_renderer.h +++ b/code/lab/renderer/lab_renderer.h @@ -168,7 +168,7 @@ class LabRenderer { return currentTeamColor; } - void resetView() {} + void resetView(); void setRenderFlag(LabRenderFlag flag, bool value) { renderFlags.set(flag, value); }