/* * Copyright (C) 2023 Christopher J. Howard * * This file is part of Antkeeper source code. * * Antkeeper source code is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Antkeeper source code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Antkeeper source code. If not, see . */ #include "game/controls.hpp" #include "game/components/spring-arm-component.hpp" #include "game/components/rigid-body-component.hpp" #include "game/components/scene-component.hpp" #include namespace { void handle_mouse_motion(::game& ctx, const input::mouse_moved_event& event) { if (ctx.active_camera_eid == entt::null) { return; } auto& spring_arm = ctx.entity_registry->get(ctx.active_camera_eid); // Rotate camera if (ctx.camera_mouse_look_action.is_active()) { // Adjust target pitch and yaw angles according to mouse motion auto target_angles = spring_arm.angles_spring.get_target_value(); target_angles.x() -= ctx.mouse_tilt_factor * static_cast(event.difference.y()); target_angles.y() -= ctx.mouse_pan_factor * static_cast(event.difference.x()); // Apply angular constraints target_angles = math::clamp(target_angles, spring_arm.min_angles, spring_arm.max_angles); // Update spring arm target angles spring_arm.angles_spring.set_target_value(target_angles); } // Zoom camera if (ctx.camera_mouse_zoom_action.is_active()) { // Adjust zoom factor spring_arm.zoom -= static_cast(event.difference.y()) / static_cast(ctx.window->get_viewport_size().y()); // Limit zoom factor spring_arm.zoom = std::min(std::max(spring_arm.zoom, 0.0), 1.0); } } void load_camera_preset(::game& ctx,std::uint8_t index) { } void save_camera_preset(::game& ctx,std::uint8_t index) { } void step_camera_zoom(::game& ctx, double scale) { if (ctx.active_camera_eid == entt::null) { return; } auto& spring_arm = ctx.entity_registry->get(ctx.active_camera_eid); auto target_angles = spring_arm.angles_spring.get_target_value(); // Modulate target pitch angle target_angles.x() += (spring_arm.max_angles.x() - spring_arm.min_angles.x()) / ctx.zoom_steps * scale; // Apply angular constraints target_angles = math::clamp(target_angles, spring_arm.min_angles, spring_arm.max_angles); // Update spring arm target angles spring_arm.angles_spring.set_target_value(target_angles); } void update_relative_mouse_mode(::game& ctx) { if (ctx.camera_mouse_look_action.is_active() || ctx.camera_mouse_drag_action.is_active() || ctx.camera_mouse_zoom_action.is_active()) { ctx.input_manager->set_relative_mouse_mode(true); } else { ctx.input_manager->set_relative_mouse_mode(false); } } void load_or_save_camera_preset(::game& ctx, std::uint8_t index) { if (ctx.camera_save_preset_action.is_active()) { save_camera_preset(ctx, index); } else { load_camera_preset(ctx, index); } } } void setup_camera_controls(::game& ctx) { // Camera mouse motion ctx.event_subscriptions.emplace_back ( ctx.input_manager->get_event_dispatcher().subscribe ( [&](const auto& event) { if (ctx.camera_action_map.is_enabled()) { handle_mouse_motion(ctx, event); } } ) ); // Camera mouse pick ctx.event_subscriptions.emplace_back ( ctx.camera_mouse_pick_action.get_activated_channel().subscribe ( [&](const auto& event) { } ) ); // Camera mouse look ctx.event_subscriptions.emplace_back ( ctx.camera_mouse_look_action.get_activated_channel().subscribe ( [&](const auto& event) { update_relative_mouse_mode(ctx); } ) ); ctx.event_subscriptions.emplace_back ( ctx.camera_mouse_look_action.get_deactivated_channel().subscribe ( [&](const auto& event) { update_relative_mouse_mode(ctx); } ) ); // Camera mouse drag ctx.event_subscriptions.emplace_back ( ctx.camera_mouse_drag_action.get_activated_channel().subscribe ( [&](const auto& event) { update_relative_mouse_mode(ctx); /* mouse_grip = ctx.toggle_mouse_grip ? !mouse_grip : true; if (mouse_grip) { const auto& mouse_position = (*ctx.input_manager->get_mice().begin())->get_position(); // Cast ray to plane const auto mouse_ray = get_mouse_ray(mouse_position); const auto intersection = geom::intersection(mouse_ray, mouse_grip_plane); if (intersection) { mouse_grip_point = mouse_ray.origin + mouse_ray.direction * (*intersection); } } ctx.input_manager->set_relative_mouse_mode(mouse_look || mouse_grip || mouse_zoom); */ } ) ); ctx.event_subscriptions.emplace_back ( ctx.camera_mouse_drag_action.get_deactivated_channel().subscribe ( [&](const auto& event) { update_relative_mouse_mode(ctx); } ) ); // Camera mouse zoom ctx.event_subscriptions.emplace_back ( ctx.camera_mouse_zoom_action.get_activated_channel().subscribe ( [&](const auto& event) { update_relative_mouse_mode(ctx); } ) ); ctx.event_subscriptions.emplace_back ( ctx.camera_mouse_zoom_action.get_deactivated_channel().subscribe ( [&](const auto& event) { update_relative_mouse_mode(ctx); } ) ); // Camera zoom in ctx.event_subscriptions.emplace_back ( ctx.camera_zoom_in_action.get_activated_channel().subscribe ( [&](const auto& event) { step_camera_zoom(ctx, static_cast(ctx.camera_zoom_in_action.get_input_value())); } ) ); // Camera zoom out ctx.event_subscriptions.emplace_back ( ctx.camera_zoom_out_action.get_activated_channel().subscribe ( [&](const auto& event) { step_camera_zoom(ctx, -static_cast(ctx.camera_zoom_out_action.get_input_value())); } ) ); // Camera orbit left ctx.event_subscriptions.emplace_back ( ctx.camera_orbit_left_action.get_active_channel().subscribe ( [&](const auto& event) { if (ctx.active_camera_eid == entt::null) { return; } auto& spring_arm = ctx.entity_registry->get(ctx.active_camera_eid); spring_arm.angular_velocities.y() = -ctx.gamepad_pan_factor * static_cast(event.input_value); } ) ); ctx.event_subscriptions.emplace_back ( ctx.camera_orbit_left_action.get_deactivated_channel().subscribe ( [&](const auto& event) { if (ctx.active_camera_eid == entt::null) { return; } auto& spring_arm = ctx.entity_registry->get(ctx.active_camera_eid); spring_arm.angular_velocities.y() = 0.0; } ) ); // Camera orbit right ctx.event_subscriptions.emplace_back ( ctx.camera_orbit_right_action.get_active_channel().subscribe ( [&](const auto& event) { if (ctx.active_camera_eid == entt::null) { return; } auto& spring_arm = ctx.entity_registry->get(ctx.active_camera_eid); spring_arm.angular_velocities.y() = ctx.gamepad_pan_factor * static_cast(event.input_value); } ) ); ctx.event_subscriptions.emplace_back ( ctx.camera_orbit_right_action.get_deactivated_channel().subscribe ( [&](const auto& event) { if (ctx.active_camera_eid == entt::null) { return; } auto& spring_arm = ctx.entity_registry->get(ctx.active_camera_eid); spring_arm.angular_velocities.y() = 0.0; } ) ); // Camera orbit up ctx.event_subscriptions.emplace_back ( ctx.camera_orbit_up_action.get_active_channel().subscribe ( [&](const auto& event) { if (ctx.active_camera_eid == entt::null) { return; } auto& spring_arm = ctx.entity_registry->get(ctx.active_camera_eid); spring_arm.angular_velocities.x() = ctx.gamepad_tilt_factor * static_cast(event.input_value); } ) ); ctx.event_subscriptions.emplace_back ( ctx.camera_orbit_up_action.get_deactivated_channel().subscribe ( [&](const auto& event) { if (ctx.active_camera_eid == entt::null) { return; } auto& spring_arm = ctx.entity_registry->get(ctx.active_camera_eid); spring_arm.angular_velocities.x() = 0.0; } ) ); // Camera orbit down ctx.event_subscriptions.emplace_back ( ctx.camera_orbit_down_action.get_active_channel().subscribe ( [&](const auto& event) { if (ctx.active_camera_eid == entt::null) { return; } auto& spring_arm = ctx.entity_registry->get(ctx.active_camera_eid); spring_arm.angular_velocities.x() = -ctx.gamepad_tilt_factor * static_cast(event.input_value); } ) ); ctx.event_subscriptions.emplace_back ( ctx.camera_orbit_down_action.get_deactivated_channel().subscribe ( [&](const auto& event) { if (ctx.active_camera_eid == entt::null) { return; } auto& spring_arm = ctx.entity_registry->get(ctx.active_camera_eid); spring_arm.angular_velocities.x() = 0.0; } ) ); // Camera look ahead ctx.event_subscriptions.emplace_back ( ctx.camera_look_ahead_action.get_activated_channel().subscribe ( [&](const auto& event) { if (ctx.active_camera_eid == entt::null || ctx.controlled_ant_eid == entt::null) { return; } auto& spring_arm = ctx.entity_registry->get(ctx.active_camera_eid); const auto& subject_rigid_body = *ctx.entity_registry->get(ctx.controlled_ant_eid).body; // Determine camera up direction const auto camera_up = math::fvec3(spring_arm.up_rotation * math::dvec3{0, 1, 0}); // Get spring arm target angles auto target_angles = spring_arm.angles_spring.get_target_value(); // Determine camera forward direction const auto camera_yaw_rotation = math::angle_axis(target_angles.y(), {0.0, 1.0, 0.0}); const auto camera_pitchless_orientation = math::normalize(spring_arm.up_rotation * camera_yaw_rotation); const auto camera_forward = math::fvec3(camera_pitchless_orientation * math::dvec3{0, 0, -1}); // Determine subject forward direction const auto subject_forward = subject_rigid_body.get_transform().rotation * math::fvec3{0, 0, 1}; // Find signed angle between the two forward directions about camera up axis const auto angular_difference = math::signed_angle(camera_forward, subject_forward, camera_up); // Add angular difference to spring arm target yaw angle target_angles.y() += angular_difference; // Update spring arm target angles spring_arm.angles_spring.set_target_value(target_angles); } ) ); // Camera presets ctx.event_subscriptions.emplace_back ( ctx.camera_preset_1_action.get_activated_channel().subscribe ( [&](const auto& event) {load_or_save_camera_preset(ctx, 0);} ) ); ctx.event_subscriptions.emplace_back ( ctx.camera_preset_2_action.get_activated_channel().subscribe ( [&](const auto& event) {load_or_save_camera_preset(ctx, 1);} ) ); ctx.event_subscriptions.emplace_back ( ctx.camera_preset_3_action.get_activated_channel().subscribe ( [&](const auto& event) {load_or_save_camera_preset(ctx, 2);} ) ); ctx.event_subscriptions.emplace_back ( ctx.camera_preset_4_action.get_activated_channel().subscribe ( [&](const auto& event) {load_or_save_camera_preset(ctx, 3);} ) ); ctx.event_subscriptions.emplace_back ( ctx.camera_preset_5_action.get_activated_channel().subscribe ( [&](const auto& event) {load_or_save_camera_preset(ctx, 4);} ) ); ctx.event_subscriptions.emplace_back ( ctx.camera_preset_6_action.get_activated_channel().subscribe ( [&](const auto& event) {load_or_save_camera_preset(ctx, 5);} ) ); ctx.event_subscriptions.emplace_back ( ctx.camera_preset_7_action.get_activated_channel().subscribe ( [&](const auto& event) {load_or_save_camera_preset(ctx, 6);} ) ); ctx.event_subscriptions.emplace_back ( ctx.camera_preset_8_action.get_activated_channel().subscribe ( [&](const auto& event) {load_or_save_camera_preset(ctx, 7);} ) ); ctx.event_subscriptions.emplace_back ( ctx.camera_preset_9_action.get_activated_channel().subscribe ( [&](const auto& event) {load_or_save_camera_preset(ctx, 8);} ) ); ctx.event_subscriptions.emplace_back ( ctx.camera_preset_10_action.get_activated_channel().subscribe ( [&](const auto& event) {load_or_save_camera_preset(ctx, 9);} ) ); } void enable_camera_controls(::game& ctx) { ctx.camera_action_map.enable(); } void disable_camera_controls(::game& ctx) { ctx.camera_action_map.disable(); ctx.camera_action_map.reset(); }