/* * 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 . */ #include "camera-system.hpp" #include "game/components/camera-subject-component.hpp" #include "game/components/transform-component.hpp" #include "animation/spring.hpp" #include "scene/camera.hpp" #include "math/math.hpp" #include #include using namespace ecs; camera_system::camera_system(entt::registry& registry): entity_system(registry), camera(nullptr), viewport{0, 0, 0, 0}, mouse_position{0, 0} { // Init azimuth spring constraint azimuth_spring.v = 0.0f; azimuth_spring.z = 1.0f; azimuth_spring.w = 2.0f * math::two_pi; // Init elevation spring constraint elevation_spring.v = 0.0f; elevation_spring.z = 1.0f; elevation_spring.w = 2.0f * math::two_pi; // Init focal distance spring constraint focal_distance_spring.v = 0.0f; focal_distance_spring.z = 1.0f; focal_distance_spring.w = 5.0f * math::two_pi; // Init fov spring constraint fov_spring.v = 0.0f; fov_spring.z = 1.0f; fov_spring.w = 5.0f * math::two_pi; } void camera_system::update(double t, double dt) { if (!camera) return; // Determine target focal point int subject_count = 0; float3 target_focal_point = {0, 0, 0}; registry.view().each( [&](auto entity, auto& subject, auto& transform) { target_focal_point += transform.transform.translation; ++subject_count; }); if (subject_count > 1) target_focal_point /= static_cast(subject_count); // Get source transform transform_type source_transform = camera->get_transform(); // Solve azimuth spring float2 xz_direction = math::normalize(math::swizzle<0, 2>(target_focal_point) - math::swizzle<0, 2>(source_transform.translation)); azimuth_spring.x0 = math::wrap_radians(std::atan2(-xz_direction.y, xz_direction.x) - math::half_pi); azimuth_spring.x1 = azimuth_spring.x0 + math::wrap_radians(azimuth_spring.x1 - azimuth_spring.x0); solve_spring_constraint(azimuth_spring, dt); // Sovle elevation spring elevation_spring.x0 = elevation; elevation_spring.x1 = elevation_spring.x0 + math::wrap_radians(elevation_spring.x1 - elevation_spring.x0); solve_spring_constraint(elevation_spring, dt); // Solve focal distance spring focal_distance_spring.x0 = math::length(source_transform.translation - target_focal_point); solve_spring_constraint(focal_distance_spring, dt); // Solve FOV spring fov_spring.x0 = camera->get_fov(); solve_spring_constraint(fov_spring, dt); // Determine camera rotation quaternion_type smooth_azimuth_rotation = math::angle_axis(azimuth_spring.x0, float3{0.0f, 1.0f, 0.0f}); quaternion_type smooth_elevation_rotation = math::angle_axis(elevation_spring.x0, float3{-1.0f, 0.0f, 0.0f}); quaternion_type smooth_rotation = math::normalize(smooth_azimuth_rotation * smooth_elevation_rotation); // Determine camera view point float3 smooth_view_point = target_focal_point + smooth_rotation * float3{0.0f, 0.0f, focal_distance_spring.x0}; // Update camera transform transform_type smooth_transform; smooth_transform.translation = smooth_view_point; smooth_transform.rotation = smooth_rotation; smooth_transform.scale = source_transform.scale; camera->set_transform(smooth_transform); // 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_spring.x0, 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); } void camera_system::zoom(float factor) { set_zoom(zoom_factor + factor); } void camera_system::set_camera(::camera* camera) { this->camera = camera; } 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}); azimuth_spring.x1 = azimuth; } 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}); elevation_spring.x1 = elevation; } void camera_system::set_zoom(float factor) { this->zoom_factor = std::max(0.0f, std::min(1.0f, factor)); update_focal_distance(); update_fov(); } void camera_system::set_focal_distance(float distance_near, float distance_far) { focal_distance_near = distance_near; focal_distance_far = distance_far; update_focal_distance(); } void camera_system::set_fov(float angle_near, float angle_far) { fov_near = angle_near; fov_far = angle_far; update_fov(); } 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)}); } void camera_system::update_focal_distance() { focal_distance = math::log_lerp(focal_distance_far, focal_distance_near, zoom_factor); focal_distance_spring.x1 = focal_distance; } void camera_system::update_fov() { fov = math::log_lerp(fov_far, fov_near, zoom_factor); fov_spring.x1 = fov; }