💿🐜 Antkeeper source code https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

518 lines
13 KiB

/*
* 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 <http://www.gnu.org/licenses/>.
*/
#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 <engine/debug/log.hpp>
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<spring_arm_component>(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<double>(event.difference.y());
target_angles.y() -= ctx.mouse_pan_factor * static_cast<double>(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<double>(event.difference.y()) / static_cast<double>(ctx.window->get_viewport_size().y());
// Limit zoom factor
spring_arm.zoom = std::min<double>(std::max<double>(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<spring_arm_component>(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<input::mouse_moved_event>
(
[&](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<double>(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<double>(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<spring_arm_component>(ctx.active_camera_eid);
spring_arm.angular_velocities.y() = -ctx.gamepad_pan_factor * static_cast<double>(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<spring_arm_component>(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<spring_arm_component>(ctx.active_camera_eid);
spring_arm.angular_velocities.y() = ctx.gamepad_pan_factor * static_cast<double>(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<spring_arm_component>(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<spring_arm_component>(ctx.active_camera_eid);
spring_arm.angular_velocities.x() = ctx.gamepad_tilt_factor * static_cast<double>(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<spring_arm_component>(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<spring_arm_component>(ctx.active_camera_eid);
spring_arm.angular_velocities.x() = -ctx.gamepad_tilt_factor * static_cast<double>(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<spring_arm_component>(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<spring_arm_component>(ctx.active_camera_eid);
const auto& subject_rigid_body = *ctx.entity_registry->get<rigid_body_component>(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();
}