From 7c8b2ddfbcbc4d01c0da075cb8f125c48fe07dd3 Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Tue, 18 Aug 2020 03:43:06 -0700 Subject: [PATCH] Add angle interpolation functions, switch from using slerp to nlerp in scene object transform interpolation, and start rewriting the camera and control systems --- src/game/bootloader.cpp | 24 ++- .../components/camera-subject-component.hpp | 33 +++ src/game/game-context.hpp | 2 + src/game/states/play-state.cpp | 26 +-- src/game/systems/camera-system.cpp | 188 ++++++++++++------ src/game/systems/camera-system.hpp | 42 +++- src/game/systems/control-system.hpp | 3 + src/math/angles.hpp | 31 +++ src/math/interpolation.hpp | 20 ++ src/math/quaternion-functions.hpp | 32 ++- src/scene/scene-object.cpp | 2 +- 11 files changed, 309 insertions(+), 94 deletions(-) create mode 100644 src/game/components/camera-subject-component.hpp diff --git a/src/game/bootloader.cpp b/src/game/bootloader.cpp index 8e4daf5..f5a6dd6 100644 --- a/src/game/bootloader.cpp +++ b/src/game/bootloader.cpp @@ -777,7 +777,6 @@ void setup_systems(game_context* ctx) // Setup camera system ctx->camera_system = new camera_system(*ctx->ecs_registry); - ctx->camera_system->set_orbit_cam(ctx->orbit_cam); ctx->camera_system->set_viewport(viewport); // Setup subterrain system @@ -874,8 +873,6 @@ void setup_controls(game_context* ctx) } ); - - // Create screenshot control ctx->screenshot_control = new control(); ctx->screenshot_control->set_activated_callback @@ -887,6 +884,18 @@ void setup_controls(game_context* ctx) } ); + // Create rotation controls + ctx->rotate_ccw_control = new control(); + ctx->rotate_ccw_control->set_activated_callback + ( + std::bind(&camera_system::rotate, ctx->camera_system, math::radians(-45.0f)) + ); + ctx->rotate_cw_control = new control(); + ctx->rotate_cw_control->set_activated_callback + ( + std::bind(&camera_system::rotate, ctx->camera_system, math::radians(45.0f)) + ); + // Create menu back control ctx->menu_back_control = new control(); ctx->menu_back_control->set_activated_callback @@ -901,6 +910,8 @@ void setup_controls(game_context* ctx) ctx->application_controls = new control_set(); ctx->application_controls->add_control(ctx->toggle_fullscreen_control); ctx->application_controls->add_control(ctx->screenshot_control); + ctx->application_controls->add_control(ctx->rotate_ccw_control); + ctx->application_controls->add_control(ctx->rotate_cw_control); // Create menu control set ctx->menu_controls = new control_set(); @@ -915,6 +926,8 @@ void setup_controls(game_context* ctx) // Application control mappings ctx->input_event_router->add_mapping(key_mapping(ctx->toggle_fullscreen_control, nullptr, scancode::f11)); ctx->input_event_router->add_mapping(key_mapping(ctx->screenshot_control, nullptr, scancode::f12)); + ctx->input_event_router->add_mapping(key_mapping(ctx->rotate_ccw_control, nullptr, scancode::q)); + ctx->input_event_router->add_mapping(key_mapping(ctx->rotate_cw_control, nullptr, scancode::e)); // Add menu control mappings ctx->input_event_router->add_mapping(key_mapping(ctx->menu_back_control, nullptr, scancode::escape)); @@ -985,6 +998,7 @@ void setup_controls(game_context* ctx) event_dispatcher->subscribe(ctx->control_system); event_dispatcher->subscribe(ctx->camera_system); + event_dispatcher->subscribe(ctx->camera_system); event_dispatcher->subscribe(ctx->tool_system); } @@ -1018,6 +1032,7 @@ void setup_callbacks(game_context* ctx) ctx->timeline->advance(dt); + //ctx->control_system->update(t, dt); ctx->terrain_system->update(t, dt); ctx->vegetation_system->update(t, dt); ctx->placement_system->update(t, dt); @@ -1025,10 +1040,9 @@ void setup_callbacks(game_context* ctx) ctx->subterrain_system->update(t, dt); ctx->collision_system->update(t, dt); ctx->samara_system->update(t, dt); - ctx->camera_system->update(t, dt); ctx->behavior_system->update(t, dt); ctx->locomotion_system->update(t, dt); - ctx->control_system->update(t, dt); + ctx->camera_system->update(t, dt); ctx->tool_system->update(t, dt); ctx->constraint_system->update(t, dt); diff --git a/src/game/components/camera-subject-component.hpp b/src/game/components/camera-subject-component.hpp new file mode 100644 index 0000000..4436605 --- /dev/null +++ b/src/game/components/camera-subject-component.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 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 . + */ + +#ifndef ANTKEEPER_ECS_CAMERA_SUBJECT_COMPONENT_HPP +#define ANTKEEPER_ECS_CAMERA_SUBJECT_COMPONENT_HPP + +namespace ecs { + +struct camera_subject_component +{ + +}; + +} // namespace ecs + +#endif // ANTKEEPER_ECS_CAMERA_SUBJECT_COMPONENT_HPP + diff --git a/src/game/game-context.hpp b/src/game/game-context.hpp index 2532b47..9a8ac91 100644 --- a/src/game/game-context.hpp +++ b/src/game/game-context.hpp @@ -194,6 +194,8 @@ struct game_context control* menu_back_control; control* menu_select_control; control* screenshot_control; + control* rotate_ccw_control; + control* rotate_cw_control; control* toggle_fullscreen_control; // Entities diff --git a/src/game/states/play-state.cpp b/src/game/states/play-state.cpp index 7cecf25..476277a 100644 --- a/src/game/states/play-state.cpp +++ b/src/game/states/play-state.cpp @@ -36,7 +36,6 @@ #include "game/states/game-states.hpp" #include "math/math.hpp" #include "nest.hpp" -#include "orbit-cam.hpp" #include "renderer/material.hpp" #include "renderer/model.hpp" #include "renderer/passes/sky-pass.hpp" @@ -44,6 +43,7 @@ #include "scene/model-instance.hpp" #include "scene/scene.hpp" #include "game/systems/control-system.hpp" +#include "game/systems/camera-system.hpp" #include "utility/fundamental-types.hpp" void play_state_enter(game_context* ctx) @@ -152,20 +152,15 @@ void play_state_enter(game_context* ctx) ecs_registry.get(grass_entity_2).transform.rotation = math::angle_axis(math::radians(120.0f), float3{0, 1, 0}); */ - // Setup overworld camera - camera* camera = ctx->overworld_camera; - orbit_cam* orbit_cam = ctx->orbit_cam; - orbit_cam->attach(camera); - orbit_cam->set_target_focal_point({0, 0, 0}); - orbit_cam->set_target_focal_distance(15.0f); - orbit_cam->set_target_elevation(math::radians(25.0f)); - orbit_cam->set_target_azimuth(0.0f); - orbit_cam->set_focal_point(orbit_cam->get_target_focal_point()); - orbit_cam->set_focal_distance(orbit_cam->get_target_focal_distance()); - orbit_cam->set_elevation(orbit_cam->get_target_elevation()); - orbit_cam->set_azimuth(orbit_cam->get_target_azimuth()); - - + // Setup camera + ctx->camera_system->set_camera(ctx->overworld_camera); + ctx->camera_system->set_azimuth(0.0f); + ctx->camera_system->set_elevation(math::radians(45.0f)); + ctx->camera_system->set_zoom(0.0f); + ctx->camera_system->set_focal_distance(2.0f, 200.0f); + ctx->camera_system->set_fov(math::radians(80.0f), math::radians(35.0f)); + ctx->camera_system->set_clip_near(1.0f, 5.0f); + ctx->camera_system->set_clip_far(100.0f, 2000.0f); // Create forceps tool @@ -243,7 +238,6 @@ void play_state_enter(game_context* ctx) control_system* control_system = ctx->control_system; control_system->update(0.0, 0.0); control_system->set_nest(nest); - orbit_cam->update(0.0f); // Start fade in ctx->fade_transition->transition(1.0f, true, ease::in_quad); diff --git a/src/game/systems/camera-system.cpp b/src/game/systems/camera-system.cpp index 2537ef6..dc0e3d2 100644 --- a/src/game/systems/camera-system.cpp +++ b/src/game/systems/camera-system.cpp @@ -18,96 +18,111 @@ */ #include "camera-system.hpp" -#include "game/components/collision-component.hpp" -#include "game/components/tool-component.hpp" +#include "game/components/camera-subject-component.hpp" #include "game/components/transform-component.hpp" #include "scene/camera.hpp" -#include "orbit-cam.hpp" -#include "geometry/mesh.hpp" -#include "geometry/intersection.hpp" #include "math/math.hpp" +#include +#include using namespace ecs; camera_system::camera_system(entt::registry& registry): entity_system(registry), - orbit_cam(nullptr), + camera(nullptr), viewport{0, 0, 0, 0}, mouse_position{0, 0} {} void camera_system::update(double t, double dt) { - if (!orbit_cam) - return; - - const camera* camera = orbit_cam->get_camera(); if (!camera) return; - /* - registry.view().each( - [&](auto entity, auto& transform, auto& tool) + // Determine focal point + int subject_count = 0; + float3 focal_point = {0, 0, 0}; + registry.view().each( + [&](auto entity, auto& subject, auto& transform) { - if (!tool.active) - return; + focal_point += transform.transform.translation; + ++subject_count; + }); + if (subject_count > 1) + focal_point /= static_cast(subject_count); + + // Determine focal distance + float focal_distance = math::log_lerp(focal_distance_far, focal_distance_near, zoom_factor); + + // Determine view point + quaternion_type rotation = math::normalize(azimuth_rotation * elevation_rotation); + float3 view_point = focal_point + rotation * float3{0.0f, 0.0f, focal_distance}; + + // Update camera transform + transform_type source_transform = camera->get_transform(); + transform_type target_transform = math::identity_transform; + target_transform.translation = view_point; + target_transform.rotation = rotation; + + + float2 xz_direction = math::normalize(math::swizzle<0, 2>(focal_point) - math::swizzle<0, 2>(source_transform.translation)); + float source_azimuth = math::wrap_radians(std::atan2(-xz_direction.y, xz_direction.x) - math::half_pi); + float source_elevation = elevation; + + std::cout << "azimuth: " << math::degrees(azimuth) << "\n"; + std::cout << "source azimuth: " << math::degrees(source_azimuth) << "\n"; + + float smooth_factor = 0.1f; + float smooth_azimuth = math::lerp_angle(source_azimuth, azimuth, smooth_factor); + float smooth_elevation = math::lerp_angle(source_elevation, elevation, smooth_factor); + quaternion_type smooth_azimuth_rotation = math::angle_axis(smooth_azimuth, float3{0.0f, 1.0f, 0.0f}); + quaternion_type smooth_elevation_rotation = math::angle_axis(smooth_elevation, float3{-1.0f, 0.0f, 0.0f}); + quaternion_type smooth_rotation = math::normalize(smooth_azimuth_rotation * smooth_elevation_rotation); + + float3 smooth_view_point = focal_point + smooth_rotation * float3{0.0f, 0.0f, focal_distance}; - float3 screen_position = camera->project(transform.transform.translation, viewport); - std::cout << screen_position << std::endl; + transform_type smooth_transform; + smooth_transform.translation = smooth_view_point; + //smooth_transform.translation = math::lerp(source_transform.translation, target_transform.translation, smooth_factor); + //smooth_transform.rotation = math::slerp(source_transform.rotation, target_transform.rotation, smooth_factor); + smooth_transform.rotation = smooth_rotation; + smooth_transform.scale = math::lerp(source_transform.scale, target_transform.scale, smooth_factor); + camera->set_transform(smooth_transform); - // Project tool position onto viewport - }); - */ - // Cast a ray straight down from infinity at the focal point's lateral coordinates. - // std::numeric_limits::infinity() actually doesn't work here - float3 focal_point = orbit_cam->get_target_focal_point(); - float3 pick_origin = float3{focal_point.x, focal_point.y + 500.0f, focal_point.z}; - float3 pick_direction = float3{0, -1, 0}; - ray picking_ray = {pick_origin, pick_direction}; - - float a = std::numeric_limits::infinity(); - bool intersection = false; - float3 pick; - - registry.view().each( - [&](auto entity, auto& transform, auto& collision) - { - math::transform inverse_transform = math::inverse(transform.transform); - float3 origin = inverse_transform * pick_origin; - float3 direction = math::normalize(math::conjugate(transform.transform.rotation) * pick_direction); - ray transformed_ray = {origin, direction}; - - // Broad phase AABB test - auto aabb_result = ray_aabb_intersection(transformed_ray, collision.bounds); - if (!std::get<0>(aabb_result)) - { - return; - } - - // Narrow phase mesh test - auto mesh_result = ray_mesh_intersection(transformed_ray, *collision.mesh); - if (std::get<0>(mesh_result)) - { - intersection = true; - if (std::get<1>(mesh_result) < a) - { - a = std::get<1>(mesh_result); - pick = picking_ray.extrapolate(a); - } - } - }); + // Determine FOV + float fov = math::log_lerp(fov_far, fov_near, zoom_factor); + + // Determine aspect ratio + float aspect_ratio = viewport[2] / viewport[3]; + + // Determine clipping planes + float clip_near = math::log_lerp(near_clip_far, near_clip_near, zoom_factor); + float clip_far = math::log_lerp(far_clip_far, far_clip_near, zoom_factor); + + // Update camera projection + camera->set_perspective(fov, aspect_ratio, clip_near, clip_far); +} +void camera_system::rotate(float angle) +{ + set_azimuth(azimuth + angle); +} + +void camera_system::tilt(float angle) +{ + set_elevation(elevation + angle); + +} - if (intersection) - { - //orbit_cam->set_target_focal_point(pick); - } +void camera_system::zoom(float factor) +{ + set_zoom(std::max(0.0f, std::min(1.0f, zoom_factor + factor))); } -void camera_system::set_orbit_cam(::orbit_cam* orbit_cam) +void camera_system::set_camera(::camera* camera) { - this->orbit_cam = orbit_cam; + this->camera = camera; } void camera_system::set_viewport(const float4& viewport) @@ -115,9 +130,54 @@ void camera_system::set_viewport(const float4& viewport) this->viewport = viewport; } +void camera_system::set_azimuth(float angle) +{ + azimuth = math::wrap_radians(angle); + azimuth_rotation = math::angle_axis(azimuth, float3{0.0f, 1.0f, 0.0f}); +} + +void camera_system::set_elevation(float angle) +{ + elevation = math::wrap_radians(angle); + elevation_rotation = math::angle_axis(elevation, float3{-1.0f, 0.0f, 0.0f}); +} + +void camera_system::set_zoom(float factor) +{ + this->zoom_factor = factor; +} + +void camera_system::set_focal_distance(float distance_near, float distance_far) +{ + focal_distance_near = distance_near; + focal_distance_far = distance_far; +} + +void camera_system::set_fov(float angle_near, float angle_far) +{ + fov_near = angle_near; + fov_far = angle_far; +} + +void camera_system::set_clip_near(float distance_near, float distance_far) +{ + near_clip_near = distance_near; + near_clip_far = distance_far; +} + +void camera_system::set_clip_far(float distance_near, float distance_far) +{ + far_clip_near = distance_near; + far_clip_far = distance_far; +} + void camera_system::handle_event(const mouse_moved_event& event) { mouse_position[0] = event.x; mouse_position[1] = event.y; } +void camera_system::handle_event(const window_resized_event& event) +{ + set_viewport({0.0f, 0.0f, static_cast(event.w), static_cast(event.h)}); +} diff --git a/src/game/systems/camera-system.hpp b/src/game/systems/camera-system.hpp index a4eb018..a467f0a 100644 --- a/src/game/systems/camera-system.hpp +++ b/src/game/systems/camera-system.hpp @@ -23,28 +23,62 @@ #include "entity-system.hpp" #include "event/event-handler.hpp" #include "event/input-events.hpp" +#include "event/window-events.hpp" #include "utility/fundamental-types.hpp" +#include "math/quaternion-type.hpp" +#include "math/transform-type.hpp" class camera; class orbit_cam; class camera_system: public entity_system, - public event_handler + public event_handler, + public event_handler { public: + typedef math::quaternion quaternion_type; + typedef math::transform transform_type; + camera_system(entt::registry& registry); virtual void update(double t, double dt); - - void set_orbit_cam(orbit_cam* orbit_cam); + + void rotate(float angle); + void tilt(float angle); + void zoom(float factor); + + void set_camera(::camera* camera); void set_viewport(const float4& viewport); + void set_azimuth(float angle); + void set_elevation(float angle); + void set_zoom(float factor); + void set_focal_distance(float distance_near, float distance_far); + void set_fov(float angle_near, float angle_far); + void set_clip_near(float distance_near, float distance_far); + void set_clip_far(float distance_near, float distance_far); private: virtual void handle_event(const mouse_moved_event& event); + virtual void handle_event(const window_resized_event& event); - orbit_cam* orbit_cam; + camera* camera; float4 viewport; float2 mouse_position; + + float azimuth; + float elevation; + float zoom_factor; + float focal_distance_near; + float focal_distance_far; + float fov_near; + float fov_far; + float near_clip_near; + float near_clip_far; + float far_clip_near; + float far_clip_far; + + quaternion_type elevation_rotation; + quaternion_type azimuth_rotation; }; #endif // ANTKEEPER_CAMERA_SYSTEM_HPP diff --git a/src/game/systems/control-system.hpp b/src/game/systems/control-system.hpp index 28fb8cb..05beffa 100644 --- a/src/game/systems/control-system.hpp +++ b/src/game/systems/control-system.hpp @@ -40,6 +40,9 @@ public: control_system(entt::registry& registry); virtual void update(double t, double dt); + + void set_invert_mouse_x(bool invert); + void set_invert_mouse_y(bool invert); void set_orbit_cam(orbit_cam* orbit_cam); void set_nest(::nest* nest); diff --git a/src/math/angles.hpp b/src/math/angles.hpp index 42d5953..7825460 100644 --- a/src/math/angles.hpp +++ b/src/math/angles.hpp @@ -21,6 +21,7 @@ #define ANTKEEPER_MATH_ANGLES_HPP #include "math/constants.hpp" +#include namespace math { @@ -45,6 +46,24 @@ T degrees(T radians); template T radians(T degrees); +/** + * Wraps an angle on [-180, 180]. + * + * @param degrees Angle in degrees. + * @return Wrapped angle. + */ +template +T wrap_degrees(T degrees); + +/** + * Wraps an angle on [-pi, pi]. + * + * @param radians Angle in radians. + * @return Wrapped angle. + */ +template +T wrap_radians(T radians); + template inline T degrees(T radians) { @@ -57,6 +76,18 @@ inline T radians(T degrees) return degrees * pi / T(180); } +template +inline T wrap_degrees(T degrees) +{ + return std::remainder(degrees, T(360)); +} + +template +inline T wrap_radians(T radians) +{ + return std::remainder(radians, two_pi); +} + /// @} } // namespace math diff --git a/src/math/interpolation.hpp b/src/math/interpolation.hpp index 1f2e4c2..76c6fbc 100644 --- a/src/math/interpolation.hpp +++ b/src/math/interpolation.hpp @@ -20,6 +20,7 @@ #ifndef ANTKEEPER_MATH_INTERPOLATION_HPP #define ANTKEEPER_MATH_INTERPOLATION_HPP +#include "math/constants.hpp" #include "math/matrix-type.hpp" #include "math/quaternion-type.hpp" #include "math/transform-type.hpp" @@ -46,6 +47,18 @@ namespace math { template T lerp(const T& x, const T& y, S a); +/** + * Linearly interpolates between two angles, @p x and @p y. + * + * @tparam T Value type. + * @param x Start angle, in radians. + * @param y End angle, in radians. + * @param a Interpolation ratio. + * @return Interpolated angle, in radians. + */ +template +T lerp_angle(T x, T y, T a); + /** * Logarithmically interpolates between @p x and @p y. * @@ -63,6 +76,13 @@ inline T lerp(const T& x, const T& y, S a) return (y - x) * a + x; } +template +T lerp_angle(T x, T y, T a) +{ + T shortest_angle = std::remainder((y - x), two_pi); + return std::remainder(x + shortest_angle * a, two_pi); +} + template inline T log_lerp(const T& x, const T& y, S a) { diff --git a/src/math/quaternion-functions.hpp b/src/math/quaternion-functions.hpp index 8b96f3f..41565f2 100644 --- a/src/math/quaternion-functions.hpp +++ b/src/math/quaternion-functions.hpp @@ -154,6 +154,17 @@ vector mul(const quaternion& q, const vector& v); template quaternion negate(const quaternion& x); +/** + * Performs normalized linear interpolation between two quaternions. + * + * @param x First quaternion. + * @param y Second quaternion. + * @param a Interpolation factor. + * @return Interpolated quaternion. + */ +template +quaternion nlerp(const quaternion& x, const quaternion& y, T a); + /** * Normalizes a quaternion. */ @@ -251,10 +262,10 @@ inline quaternion lerp(const quaternion& x, const quaternion& y, T a) { return { - x.w * (T(1) - a) + y.w * a, - x.x * (T(1) - a) + y.x * a, - x.y * (T(1) - a) + y.y * a, - x.z * (T(1) - a) + y.z * a + (y.w - x.w) * a + x.w, + (y.x - x.x) * a + x.x, + (y.y - x.y) * a + x.y, + (y.z - x.z) * a + x.z }; } @@ -328,6 +339,19 @@ inline quaternion negate(const quaternion& x) return {-x.w, -x.x, -x.y, -x.z}; } +template +quaternion nlerp(const quaternion& x, const quaternion& y, T a) +{ + if (dot(x, y) < T(0)) + { + return normalize(add(mul(x, T(1) - a), mul(y, -a))); + } + else + { + return normalize(add(mul(x, T(1) - a), mul(y, a))); + } +} + template inline quaternion normalize(const quaternion& x) { diff --git a/src/scene/scene-object.cpp b/src/scene/scene-object.cpp index f596350..66fc847 100644 --- a/src/scene/scene-object.cpp +++ b/src/scene/scene-object.cpp @@ -25,7 +25,7 @@ typename scene_object_base::transform_type scene_object_base::interpolate_transf return { math::lerp(x.translation, y.translation, a), - math::slerp(x.rotation, y.rotation, a), + math::nlerp(x.rotation, y.rotation, a), math::lerp(x.scale, y.scale, a), }; }