From 6c26a613794c66a656004e87fd8d6c12903ddd82 Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Mon, 10 Oct 2022 04:22:54 +0800 Subject: [PATCH] Add picking support to nuptial flight state. Add more constraint types. Improve nuptial flight camera. --- src/game/ant/swarm.cpp | 13 ++ src/game/component/constraint/constraint.hpp | 1 + src/game/component/constraint/ease-to.hpp | 55 +++++ src/game/component/picking.hpp | 41 ++++ src/game/state/nuptial-flight.cpp | 224 ++++++++++++++++--- src/game/state/nuptial-flight.hpp | 10 + src/game/system/collision.cpp | 80 +++++++ src/game/system/collision.hpp | 24 +- src/game/system/constraint.cpp | 24 ++ src/game/system/constraint.hpp | 1 + src/math/vector-functions.hpp | 8 +- src/scene/camera.cpp | 14 +- src/scene/camera.hpp | 10 + 13 files changed, 471 insertions(+), 34 deletions(-) create mode 100644 src/game/component/constraint/ease-to.hpp create mode 100644 src/game/component/picking.hpp diff --git a/src/game/ant/swarm.cpp b/src/game/ant/swarm.cpp index 2bcdff4..e93c005 100644 --- a/src/game/ant/swarm.cpp +++ b/src/game/ant/swarm.cpp @@ -21,6 +21,7 @@ #include "game/component/transform.hpp" #include "game/component/steering.hpp" #include "game/component/model.hpp" +#include "game/component/picking.hpp" #include "resources/resource-manager.hpp" #include "config.hpp" #include @@ -68,6 +69,12 @@ entity::id create_swarm(game::context& ctx) transform.world = transform.local; transform.warp = true; + // Init picking component + game::component::picking picking; + picking.sphere = {float3{0, 0, 0}, 0.5f}; + std::uint32_t male_picking_flags = 0b01; + std::uint32_t queen_picking_flags = 0b10; + // Create swarm entity entity::id swarm_eid = ctx.entity_registry->create(); transform.local.translation = swarm_center; @@ -131,6 +138,9 @@ entity::id create_swarm(game::context& ctx) transform.local.scale = male_scale; transform.world = transform.local; ctx.entity_registry->emplace(alate_eid, transform); + + picking.flags = male_picking_flags; + ctx.entity_registry->emplace(alate_eid, picking); } else { @@ -140,6 +150,9 @@ entity::id create_swarm(game::context& ctx) transform.local.scale = queen_scale; transform.world = transform.local; ctx.entity_registry->emplace(alate_eid, transform); + + picking.flags = queen_picking_flags; + ctx.entity_registry->emplace(alate_eid, picking); } } diff --git a/src/game/component/constraint/constraint.hpp b/src/game/component/constraint/constraint.hpp index eab0416..15e167d 100644 --- a/src/game/component/constraint/constraint.hpp +++ b/src/game/component/constraint/constraint.hpp @@ -34,6 +34,7 @@ namespace constraint {} #include "game/component/constraint/copy-scale.hpp" #include "game/component/constraint/copy-transform.hpp" #include "game/component/constraint/copy-translation.hpp" +#include "game/component/constraint/ease-to.hpp" #include "game/component/constraint/pivot.hpp" #include "game/component/constraint/spring-rotation.hpp" #include "game/component/constraint/spring-to.hpp" diff --git a/src/game/component/constraint/ease-to.hpp b/src/game/component/constraint/ease-to.hpp new file mode 100644 index 0000000..c27519d --- /dev/null +++ b/src/game/component/constraint/ease-to.hpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 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_GAME_COMPONENT_CONSTRAINT_EASE_TO_HPP +#define ANTKEEPER_GAME_COMPONENT_CONSTRAINT_EASE_TO_HPP + +#include "entity/id.hpp" +#include "utility/fundamental-types.hpp" + +namespace game { +namespace component { +namespace constraint { + +/** + * Eases toward a target entity. + */ +struct ease_to +{ + /// Target entity ID. + entity::id target; + + /// Start position. + float3 start; + + /// Total duration of the ease. + float duration; + + /// Elapsed time since ease began. + float t; + + /// Pointer to the interpolation function. + float3 (*function)(const float3&, const float3&, float); +}; + +} // namespace constraint +} // namespace component +} // namespace game + +#endif // ANTKEEPER_GAME_COMPONENT_CONSTRAINT_EASE_TO_HPP diff --git a/src/game/component/picking.hpp b/src/game/component/picking.hpp new file mode 100644 index 0000000..347eddb --- /dev/null +++ b/src/game/component/picking.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 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_GAME_COMPONENT_PICKING_HPP +#define ANTKEEPER_GAME_COMPONENT_PICKING_HPP + +#include "geom/sphere.hpp" +#include + +namespace game { +namespace component { + +struct picking +{ + /// Picking sphere. + geom::sphere sphere; + + /// Picking flags. + std::uint32_t flags; +}; + +} // namespace component +} // namespace game + +#endif // ANTKEEPER_GAME_COMPONENT_PICKING_HPP diff --git a/src/game/state/nuptial-flight.cpp b/src/game/state/nuptial-flight.cpp index b8eb312..02d931e 100644 --- a/src/game/state/nuptial-flight.cpp +++ b/src/game/state/nuptial-flight.cpp @@ -24,6 +24,7 @@ #include "game/system/camera.hpp" #include "game/system/astronomy.hpp" #include "game/system/atmosphere.hpp" +#include "game/system/collision.hpp" #include "game/component/locomotion.hpp" #include "game/component/transform.hpp" #include "game/component/terrain.hpp" @@ -32,6 +33,7 @@ #include "game/component/constraint/constraint.hpp" #include "game/component/constraint-stack.hpp" #include "game/component/steering.hpp" +#include "game/component/picking.hpp" #include "game/controls.hpp" #include "entity/commands.hpp" #include "animation/screen-transition.hpp" @@ -49,6 +51,8 @@ #include "math/interpolation.hpp" #include "physics/light/exposure.hpp" #include "color/color.hpp" +#include "application.hpp" +#include "input/mouse.hpp" #include using namespace game::ant; @@ -61,6 +65,10 @@ nuptial_flight::nuptial_flight(game::context& ctx): { ctx.logger->push_task("Entering nuptial flight state"); + // Init selected picking flag + selected_picking_flag = std::uint32_t{1} << (sizeof(std::uint32_t) * 8 - 1); + selected_eid = entt::null; + // Disable UI color clear ctx.ui_clear_pass->set_cleared_buffers(false, true, false); @@ -105,23 +113,7 @@ nuptial_flight::nuptial_flight(game::context& ctx): ( [&](entity::id alate_eid, auto& transform, auto& steering) { - ctx.entity_registry->patch - ( - camera_rig_copy_translation_eid, - [&](auto& component) - { - component.target = alate_eid; - } - ); - - ctx.entity_registry->patch - ( - camera_rig_pivot_eid, - [&](auto& component) - { - component.target = alate_eid; - } - ); + select_entity(alate_eid); } ); @@ -139,6 +131,9 @@ nuptial_flight::~nuptial_flight() { ctx.logger->push_task("Exiting nuptial flight state"); + // Deselect selected entity + select_entity(entt::null); + destroy_camera_rig(); game::ant::destroy_swarm(ctx, swarm_eid); @@ -147,9 +142,40 @@ nuptial_flight::~nuptial_flight() void nuptial_flight::create_camera_rig() { + // Construct camera rig focus ease to constraint + component::constraint::ease_to camera_rig_focus_ease_to; + camera_rig_focus_ease_to.target = selected_eid; + camera_rig_focus_ease_to.start = {0, 0, 0}; + camera_rig_focus_ease_to.duration = 1.0f; + camera_rig_focus_ease_to.t = camera_rig_focus_ease_to.duration; + camera_rig_focus_ease_to.function = &ease::out_expo; + component::constraint_stack_node camera_rig_focus_ease_to_node; + camera_rig_focus_ease_to_node.active = true; + camera_rig_focus_ease_to_node.weight = 1.0f; + camera_rig_focus_ease_to_node.next = entt::null; + camera_rig_focus_ease_to_eid = ctx.entity_registry->create(); + ctx.entity_registry->emplace(camera_rig_focus_ease_to_eid, camera_rig_focus_ease_to); + ctx.entity_registry->emplace(camera_rig_focus_ease_to_eid, camera_rig_focus_ease_to_node); + + // Construct camera rig focus constraint stack + component::constraint_stack camera_rig_focus_constraint_stack; + camera_rig_focus_constraint_stack.priority = 1; + camera_rig_focus_constraint_stack.head = camera_rig_focus_ease_to_eid; + + // Construct camera rig focus transform component + component::transform camera_rig_focus_transform; + camera_rig_focus_transform.local = math::transform::identity; + camera_rig_focus_transform.world = camera_rig_focus_transform.local; + camera_rig_focus_transform.warp = true; + + // Construct camera rig focus entity + camera_rig_focus_eid = ctx.entity_registry->create(); + ctx.entity_registry->emplace(camera_rig_focus_eid, camera_rig_focus_transform); + ctx.entity_registry->emplace(camera_rig_focus_eid, camera_rig_focus_constraint_stack); + // Construct camera rig pivot constraint component::constraint::pivot camera_rig_pivot; - camera_rig_pivot.target = swarm_eid; + camera_rig_pivot.target = camera_rig_focus_eid; camera_rig_pivot.offset = {0, 0, 0}; component::constraint_stack_node camera_rig_pivot_node; camera_rig_pivot_node.active = true; @@ -161,7 +187,7 @@ void nuptial_flight::create_camera_rig() // Construct camera rig copy translation constraint component::constraint::copy_translation camera_rig_copy_translation; - camera_rig_copy_translation.target = swarm_eid; + camera_rig_copy_translation.target = camera_rig_focus_eid; camera_rig_copy_translation.copy_x = true; camera_rig_copy_translation.copy_y = true; camera_rig_copy_translation.copy_z = true; @@ -213,12 +239,12 @@ void nuptial_flight::create_camera_rig() ctx.entity_registry->emplace(camera_rig_spring_translation_eid, camera_rig_spring_translation); ctx.entity_registry->emplace(camera_rig_spring_translation_eid, camera_rig_spring_translation_node); - // Construct camera constraint stack + // Construct camera rig constraint stack component::constraint_stack camera_rig_constraint_stack; - camera_rig_constraint_stack.priority = 1; + camera_rig_constraint_stack.priority = 2; camera_rig_constraint_stack.head = camera_rig_spring_translation_eid; - // Construct camera transform component + // Construct camera rig transform component component::transform camera_rig_transform; camera_rig_transform.local = math::transform::identity; camera_rig_transform.world = camera_rig_transform.local; @@ -242,6 +268,10 @@ void nuptial_flight::destroy_camera_rig() ctx.entity_registry->destroy(camera_rig_spring_rotation_eid); ctx.entity_registry->destroy(camera_rig_copy_translation_eid); ctx.entity_registry->destroy(camera_rig_pivot_eid); + + ctx.entity_registry->destroy(camera_rig_focus_eid); + ctx.entity_registry->destroy(camera_rig_focus_ease_to_eid); + } void nuptial_flight::enable_controls() @@ -456,7 +486,7 @@ void nuptial_flight::enable_controls() ( [&](float value) { - ctx.entity_registry->patch + ctx.entity_registry->patch ( camera_rig_spring_translation_eid, [&](auto& component) @@ -472,7 +502,7 @@ void nuptial_flight::enable_controls() ( [&](float value) { - ctx.entity_registry->patch + ctx.entity_registry->patch ( camera_rig_spring_translation_eid, [&](auto& component) @@ -483,6 +513,72 @@ void nuptial_flight::enable_controls() } ); + // Mouse select control + ctx.controls["select_mouse"]->set_activated_callback + ( + [&]() + { + // Get window-space mouse coordinates + auto [mouse_x, mouse_y] = ctx.app->get_mouse()->get_current_position(); + + // Get window viewport dimensions + const auto viewport_dimensions = ctx.app->get_viewport_dimensions(); + + // Transform mouse coordinates from window space to NDC space + const float2 mouse_ndc = + { + static_cast(mouse_x) / static_cast(viewport_dimensions[0] - 1) * 2.0f - 1.0f, + (1.0f - static_cast(mouse_y) / static_cast(viewport_dimensions[1] - 1)) * 2.0f - 1.0f + }; + + // Get picking ray from camera + const geom::ray ray = ctx.surface_camera->pick(mouse_ndc); + + // Pick entity + entity::id picked_eid = ctx.collision_system->pick_nearest(ray, ~selected_picking_flag); + if (picked_eid != entt::null) + { + select_entity(picked_eid); + } + } + ); + + // Select forward control + ctx.controls["move_forward"]->set_activated_callback + ( + [&]() + { + select_nearest_entity({0.0f, 0.0f, -1.0f}); + } + ); + + // Select back control + ctx.controls["move_back"]->set_activated_callback + ( + [&]() + { + select_nearest_entity({0.0f, 0.0f, 1.0f}); + } + ); + + // Select right control + ctx.controls["move_right"]->set_activated_callback + ( + [&]() + { + select_nearest_entity({1.0f, 0.0f, 0.0f}); + } + ); + + // Select left control + ctx.controls["move_left"]->set_activated_callback + ( + [&]() + { + select_nearest_entity({-1.0f, 0.0f, 0.0f}); + } + ); + // Fast-forward ctx.controls["fast_forward"]->set_activated_callback ( @@ -632,10 +728,10 @@ void nuptial_flight::disable_controls() ctx.app->set_relative_mouse_mode(false); } - ctx.controls["move_forward"]->set_active_callback(nullptr); - ctx.controls["move_back"]->set_active_callback(nullptr); - ctx.controls["move_right"]->set_active_callback(nullptr); - ctx.controls["move_left"]->set_active_callback(nullptr); + ctx.controls["move_forward"]->set_activated_callback(nullptr); + ctx.controls["move_back"]->set_activated_callback(nullptr); + ctx.controls["move_right"]->set_activated_callback(nullptr); + ctx.controls["move_left"]->set_activated_callback(nullptr); ctx.controls["move_up"]->set_active_callback(nullptr); ctx.controls["move_down"]->set_active_callback(nullptr); ctx.controls["mouse_look"]->set_activated_callback(nullptr); @@ -648,6 +744,7 @@ void nuptial_flight::disable_controls() ctx.controls["look_up_mouse"]->set_active_callback(nullptr); ctx.controls["look_down_gamepad"]->set_active_callback(nullptr); ctx.controls["look_down_mouse"]->set_active_callback(nullptr); + ctx.controls["select_mouse"]->set_activated_callback(nullptr); ctx.controls["switch_pov"]->set_activated_callback(nullptr); ctx.controls["fast_forward"]->set_activated_callback(nullptr); ctx.controls["rewind"]->set_activated_callback(nullptr); @@ -656,5 +753,76 @@ void nuptial_flight::disable_controls() ctx.controls["decrease_exposure"]->set_activated_callback(nullptr); } +void nuptial_flight::select_entity(entity::id entity_id) +{ + if (entity_id != selected_eid) + { + if (ctx.entity_registry->valid(selected_eid) && ctx.entity_registry->all_of(selected_eid)) + { + // Unset selected bit on picking flags of previously selected entity + ctx.entity_registry->patch + ( + selected_eid, + [&](auto& component) + { + component.flags &= ~selected_picking_flag; + + } + ); + } + + selected_eid = entity_id; + + if (ctx.entity_registry->valid(selected_eid) && ctx.entity_registry->all_of(selected_eid)) + { + // Set selected bit on picking flags of current selected entity + ctx.entity_registry->patch + ( + selected_eid, + [&](auto& component) + { + component.flags |= selected_picking_flag; + } + ); + } + + // Update camera rig focus ease to target + ctx.entity_registry->patch + ( + camera_rig_focus_ease_to_eid, + [&](auto& component) + { + component.target = selected_eid; + component.t = 0.0f; + + const component::transform* transform = ctx.entity_registry->try_get(camera_rig_focus_eid); + if (transform) + component.start = transform->world.translation; + } + ); + } +} + +void nuptial_flight::select_nearest_entity(const float3& direction) +{ + if (!ctx.entity_registry->valid(selected_eid)) + return; + + const component::transform* selected_eid_transform = ctx.entity_registry->try_get(selected_eid); + if (!selected_eid_transform) + return; + + // Construct picking plane + const float3 picking_normal = math::normalize(ctx.surface_camera->get_rotation() * direction); + const float3 picking_origin = selected_eid_transform->world.translation; + + // Pick entity + entity::id picked_eid = ctx.collision_system->pick_nearest(picking_origin, picking_normal, ~selected_picking_flag); + if (picked_eid != entt::null) + { + select_entity(picked_eid); + } +} + } // namespace state } // namespace game diff --git a/src/game/state/nuptial-flight.hpp b/src/game/state/nuptial-flight.hpp index 45fbf72..2773663 100644 --- a/src/game/state/nuptial-flight.hpp +++ b/src/game/state/nuptial-flight.hpp @@ -22,6 +22,7 @@ #include "game/state/base.hpp" #include "entity/id.hpp" +#include "utility/fundamental-types.hpp" namespace game { namespace state { @@ -39,6 +40,12 @@ private: void enable_controls(); void disable_controls(); + void select_entity(entity::id entity_id); + void select_nearest_entity(const float3& direction); + + entity::id camera_rig_focus_eid; + entity::id camera_rig_focus_ease_to_eid; + entity::id camera_rig_eid; entity::id camera_rig_spring_translation_eid; entity::id camera_rig_spring_rotation_eid; @@ -47,6 +54,9 @@ private: entity::id swarm_eid; + std::uint32_t selected_picking_flag; + entity::id selected_eid; + bool mouse_look; }; diff --git a/src/game/system/collision.cpp b/src/game/system/collision.cpp index 4edccdf..7618f25 100644 --- a/src/game/system/collision.cpp +++ b/src/game/system/collision.cpp @@ -19,6 +19,10 @@ #include "collision.hpp" #include "game/component/transform.hpp" +#include "game/component/picking.hpp" +#include "geom/intersection.hpp" +#include "geom/plane.hpp" +#include namespace game { namespace system { @@ -38,6 +42,82 @@ void collision::update(double t, double dt) registry.on_destroy().disconnect<&collision::on_collision_destroy>(this); } +entity::id collision::pick_nearest(const geom::ray& ray, std::uint32_t flags) const +{ + entity::id nearest_eid = entt::null; + float nearest_distance = std::numeric_limits::infinity(); + + // For each entity with picking and transform components + registry.view().each + ( + [&](entity::id entity_id, const auto& picking, const auto& transform) + { + // Skip entity if picking flags don't match + if (!~(flags | picking.flags)) + return; + + // Transform picking sphere + const geom::sphere sphere = + { + transform.world * picking.sphere.center, + picking.sphere.radius * std::max(std::max(transform.world.scale[0], transform.world.scale[1]), transform.world.scale[2]) + }; + + // Test for intersection between ray and sphere + auto result = geom::ray_sphere_intersection(ray, sphere); + if (std::get<0>(result)) + { + if (std::get<1>(result) < nearest_distance) + { + nearest_eid = entity_id; + nearest_distance = std::get<1>(result); + } + } + } + ); + + return nearest_eid; +} + +entity::id collision::pick_nearest(const float3& origin, const float3& normal, std::uint32_t flags) const +{ + entity::id nearest_eid = entt::null; + float nearest_distance_squared = std::numeric_limits::infinity(); + + // Construct picking plane + const geom::plane picking_plane = geom::plane(normal, origin); + + // For each entity with picking and transform components + registry.view().each + ( + [&](entity::id entity_id, const auto& picking, const auto& transform) + { + // Skip entity if picking flags don't match + if (!~(flags | picking.flags)) + return; + + // Transform picking sphere center + float3 picking_sphere_center = transform.world * picking.sphere.center; + + // Skip entity if picking sphere center has negative distance from picking plane + if (picking_plane.signed_distance(picking_sphere_center) < 0.0f) + return; + + // Measure distance from picking plane origin to picking sphere center + const float distance_squared = math::distance_squared(picking_sphere_center, origin); + + // Check if entity is nearer than the current nearest entity + if (distance_squared < nearest_distance_squared) + { + nearest_eid = entity_id; + nearest_distance_squared = distance_squared; + } + } + ); + + return nearest_eid; +} + void collision::on_collision_construct(entity::registry& registry, entity::id entity_id) {} diff --git a/src/game/system/collision.hpp b/src/game/system/collision.hpp index 71c452a..5ec8487 100644 --- a/src/game/system/collision.hpp +++ b/src/game/system/collision.hpp @@ -23,18 +23,40 @@ #include "game/system/updatable.hpp" #include "entity/id.hpp" #include "game/component/collision.hpp" +#include "geom/ray.hpp" namespace game { namespace system { /** - * Maintains a spatially partitioned set of collision meshes. The set of collision meshes isnot owned by the collision system, so it can be accessed by other systems as well. + * Maintains a spatially partitioned set of collision meshes. */ class collision: public updatable { public: collision(entity::registry& registry); virtual void update(double t, double dt); + + /** + * Picks the nearest entity with the specified picking flags that intersects a ray. + * + * @param ray Picking ray. + * @param flags Picking flags. + * + * @return ID of the picked entity, or `entt::null` if no entity was picked. + */ + entity::id pick_nearest(const geom::ray& ray, std::uint32_t flags) const; + + /** + * Picks the nearest entity with the specified picking flags that has a non-negative distance from a plane. + * + * @param origin Origin of the picking plane. + * @param normal Picking plane normal direction. + * @param flags Picking flags. + * + * @return ID of the picked entity, or `entt::null` if no entity was picked. + */ + entity::id pick_nearest(const float3& origin, const float3& normal, std::uint32_t flags) const; private: void on_collision_construct(entity::registry& registry, entity::id entity_id); diff --git a/src/game/system/constraint.cpp b/src/game/system/constraint.cpp index 2b7c207..aab9966 100644 --- a/src/game/system/constraint.cpp +++ b/src/game/system/constraint.cpp @@ -107,6 +107,8 @@ void constraint::handle_constraint(component::transform& transform, entity::id c handle_spring_translation_constraint(transform, *constraint, dt); else if (auto constraint = registry.try_get(constraint_eid); constraint) handle_spring_rotation_constraint(transform, *constraint, dt); + else if (auto constraint = registry.try_get(constraint_eid); constraint) + handle_ease_to_constraint(transform, *constraint, dt); } void constraint::handle_child_of_constraint(component::transform& transform, const component::constraint::child_of& constraint) @@ -195,6 +197,28 @@ void constraint::handle_copy_translation_constraint(component::transform& transf } } +void constraint::handle_ease_to_constraint(component::transform& transform, component::constraint::ease_to& constraint, float dt) +{ + if (constraint.function && registry.valid(constraint.target)) + { + const component::transform* target_transform = registry.try_get(constraint.target); + if (target_transform) + { + if (constraint.t < constraint.duration) + { + const float a = constraint.t / constraint.duration; + transform.world.translation = constraint.function(constraint.start, target_transform->world.translation, a); + } + else + { + transform.world.translation = target_transform->world.translation; + } + + constraint.t += dt; + } + } +} + void constraint::handle_pivot_constraint(component::transform& transform, const component::constraint::pivot& constraint) { if (registry.valid(constraint.target)) diff --git a/src/game/system/constraint.hpp b/src/game/system/constraint.hpp index d631dbe..fb65da6 100644 --- a/src/game/system/constraint.hpp +++ b/src/game/system/constraint.hpp @@ -53,6 +53,7 @@ private: void handle_copy_scale_constraint(component::transform& transform, const component::constraint::copy_scale& constraint); void handle_copy_transform_constraint(component::transform& transform, const component::constraint::copy_transform& constraint); void handle_copy_translation_constraint(component::transform& transform, const component::constraint::copy_translation& constraint); + void handle_ease_to_constraint(component::transform& transform, component::constraint::ease_to& constraint, float dt); void handle_pivot_constraint(component::transform& transform, const component::constraint::pivot& constraint); void handle_spring_rotation_constraint(component::transform& transform, component::constraint::spring_rotation& constraint, float dt); void handle_spring_to_constraint(component::transform& transform, component::constraint::spring_to& constraint, float dt); diff --git a/src/math/vector-functions.hpp b/src/math/vector-functions.hpp index 2a541b0..8b52c6e 100644 --- a/src/math/vector-functions.hpp +++ b/src/math/vector-functions.hpp @@ -105,7 +105,7 @@ constexpr vector cross(const vector& x, const vector& y); * @return Distance between the two points. */ template -vector distance(const vector& p0, const vector& p1); +T distance(const vector& p0, const vector& p1); /** * Calculates the squared distance between two points. The squared distance can be calculated faster than the distance because a call to `std::sqrt` is saved. @@ -115,7 +115,7 @@ vector distance(const vector& p0, const vector& p1); * @return Squared distance between the two points. */ template -constexpr vector distance_squared(const vector& p0, const vector& p1); +constexpr T distance_squared(const vector& p0, const vector& p1); /** * Divides a vector by another vector. @@ -392,14 +392,14 @@ constexpr inline vector cross(const vector& x, const vector& y } template -inline vector distance(const vector& p0, const vector& p1) +inline T distance(const vector& p0, const vector& p1) { static_assert(std::is_floating_point::value); return length(sub(p0, p1)); } template -constexpr inline vector distance_squared(const vector& p0, const vector& p1) +constexpr inline T distance_squared(const vector& p0, const vector& p1) { static_assert(std::is_floating_point::value); return length_squared(sub(p0, p1)); diff --git a/src/scene/camera.cpp b/src/scene/camera.cpp index d503e14..05400aa 100644 --- a/src/scene/camera.cpp +++ b/src/scene/camera.cpp @@ -19,7 +19,6 @@ #include "scene/camera.hpp" #include "config.hpp" -#include "animation/ease.hpp" #include "math/constants.hpp" #include "math/interpolation.hpp" @@ -78,6 +77,19 @@ camera::camera(): exposure(0.0f, math::lerp) {} +geom::ray camera::pick(const float2& ndc) const +{ + const float4x4 inverse_view_projection = math::inverse(view_projection[1]); + + const float4 near = inverse_view_projection * float4{ndc[0], ndc[1], 1.0f, 1.0f}; + const float4 far = inverse_view_projection * float4{ndc[0], ndc[1], 0.0f, 1.0f}; + + const float3 origin = float3{near[0], near[1], near[2]} / near[3]; + const float3 direction = math::normalize(float3{far[0], far[1], far[2]} / far[3] - origin); + + return {origin, direction}; +} + float3 camera::project(const float3& object, const float4& viewport) const { float4 result = view_projection[1] * float4{object[0], object[1], object[2], 1.0f}; diff --git a/src/scene/camera.hpp b/src/scene/camera.hpp index 8174780..2afb008 100644 --- a/src/scene/camera.hpp +++ b/src/scene/camera.hpp @@ -22,6 +22,7 @@ #include "scene/object.hpp" #include "geom/view-frustum.hpp" +#include "geom/ray.hpp" #include "utility/fundamental-types.hpp" #include "render/compositor.hpp" @@ -36,6 +37,15 @@ public: typedef geom::view_frustum view_frustum_type; camera(); + + /** + * Constructs a picking ray from normalized device coordinates (NDC). + * + * @param ndc NDC coordinates. + * + * @return Picking ray. + */ + geom::ray pick(const float2& ndc) const; /** * Maps object coordinates to window coordinates.