diff --git a/sources/enduro2d/high/systems/input_system.cpp b/sources/enduro2d/high/systems/input_system.cpp index f1e6e649..9e953d75 100644 --- a/sources/enduro2d/high/systems/input_system.cpp +++ b/sources/enduro2d/high/systems/input_system.cpp @@ -6,9 +6,209 @@ #include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define PNPOLY_IMPLEMENTATION +#include <3rdparty/pnpoly.h/pnpoly.h> + namespace { using namespace e2d; + + struct world_space_rect_collider { + using src_collider_t = rect_collider; + std::array points{}; + }; + + struct world_space_circle_collider { + using src_collider_t = circle_collider; + std::array points{}; + }; + + struct world_space_polygon_collider { + using src_collider_t = polygon_collider; + vector points{}; + }; + + void update_world_space_collider( + world_space_rect_collider& dst, + const rect_collider& src, + const m4f& local_to_world) + { + const v2f& of = src.offset(); + const v2f& hs = src.size() * 0.5f; + + const v2f p1{of.x - hs.x, of.y - hs.y}; + const v2f p2{of.x + hs.x, of.y - hs.y}; + const v2f p3{of.x + hs.x, of.y + hs.y}; + const v2f p4{of.x - hs.x, of.y + hs.y}; + + dst.points[0] = v3f(v4f(p1, 0.f, 1.f) * local_to_world); + dst.points[1] = v3f(v4f(p2, 0.f, 1.f) * local_to_world); + dst.points[2] = v3f(v4f(p3, 0.f, 1.f) * local_to_world); + dst.points[3] = v3f(v4f(p4, 0.f, 1.f) * local_to_world); + } + + void update_world_space_collider( + world_space_circle_collider& dst, + const circle_collider& src, + const m4f& local_to_world) + { + const v2f& of = src.offset(); + for ( std::size_t i = 0, e = dst.points.size(); i < e; ++i ) { + const radf a = + math::two_pi() / + math::numeric_cast(e) * + math::numeric_cast(i); + const v2f p = + of + + v2f(math::cos(a), math::sin(a)) * + src.radius(); + dst.points[i] = v3f(v4f(p, 0.f, 1.f) * local_to_world); + } + } + + void update_world_space_collider( + world_space_polygon_collider& dst, + const polygon_collider& src, + const m4f& local_to_world) + { + const vector& src_points = src.points(); + + dst.points.clear(); + if ( dst.points.capacity() < src_points.size() ) { + dst.points.reserve(math::max(dst.points.capacity() * 2u, src_points.size())); + } + + const v2f& of = src.offset(); + for ( std::size_t i = 0, e = src_points.size(); i < e; ++i ) { + const v2f p = of + src_points[i]; + dst.points.push_back(v3f(v4f(p, 0.f, 1.f) * local_to_world)); + } + } + + template < typename WorldSpaceCollider > + void update_world_space_colliders(ecs::registry& owner) { + using dst_collider_t = WorldSpaceCollider; + using src_collider_t = typename WorldSpaceCollider::src_collider_t; + owner.for_joined_components([]( + ecs::entity e, + const touchable&, + const src_collider_t& src, + const actor& a) + { + update_world_space_collider( + e.ensure_component(), + src, + a.node() ? a.node()->world_matrix() : m4f::identity()); + }, !ecs::exists_any, disabled>()); + } +} + +namespace +{ + using namespace e2d; + + bool is_collider_under_mouse( + const world_space_rect_collider& c, + const v2f& mouse_p, + const m4f& camera_vp, + const b2f& camera_viewport) noexcept + { + std::array< + v2f, + std::tuple_size_v + > points; + + std::transform(c.points.begin(), c.points.end(), points.begin(), [ + &camera_vp, + &camera_viewport + ](const v3f& point) noexcept { + return v2f(math::project(point, camera_vp, camera_viewport).first); + }); + + return !!pnpoly_aos( + math::numeric_cast(points.size()), + points.data()->data(), + mouse_p.x, mouse_p.y); + } + + bool is_collider_under_mouse( + const world_space_circle_collider& c, + const v2f& mouse_p, + const m4f& camera_vp, + const b2f& camera_viewport) noexcept + { + std::array< + v2f, + std::tuple_size_v + > points; + + std::transform(c.points.begin(), c.points.end(), points.begin(), [ + &camera_vp, + &camera_viewport + ](const v3f& point) noexcept { + return v2f(math::project(point, camera_vp, camera_viewport).first); + }); + + return !!pnpoly_aos( + math::numeric_cast(points.size()), + points.data()->data(), + mouse_p.x, mouse_p.y); + } + + bool is_collider_under_mouse( + const world_space_polygon_collider& c, + const v2f& mouse_p, + const m4f& camera_vp, + const b2f& camera_viewport) noexcept + { + static thread_local std::vector points; + points.clear(); + + if ( points.capacity() < c.points.size() ) { + points.reserve(math::max(points.capacity() * 2u, c.points.size())); + } + + std::transform(c.points.begin(), c.points.end(), std::back_inserter(points), [ + &camera_vp, + &camera_viewport + ](const v3f& point) noexcept { + return v2f(math::project(point, camera_vp, camera_viewport).first); + }); + + return !!pnpoly_aos( + math::numeric_cast(points.size()), + points.data()->data(), + mouse_p.x, mouse_p.y); + } + + template < typename WorldSpaceCollider > + void update_colliders_under_mouse( + ecs::registry& owner, + const v2f& mouse_p, + const m4f& camera_vp, + const b2f& camera_viewport) + { + using world_space_collider_t = WorldSpaceCollider; + owner.for_joined_components([ + &mouse_p, + &camera_vp, + &camera_viewport + ](ecs::entity e, const touchable&, const world_space_collider_t& c){ + if ( is_collider_under_mouse(c, mouse_p, camera_vp, camera_viewport) ) { + e.ensure_component(); + } + }, !ecs::exists_any, disabled>()); + } } namespace e2d @@ -19,12 +219,67 @@ namespace e2d class input_system::internal_state final : private noncopyable { public: - internal_state() = default; + internal_state(input& i, window& w) + : input_(i) + , window_(w) {} ~internal_state() noexcept = default; void process_update(ecs::registry& owner) { - E2D_UNUSED(owner); + owner.remove_all_components(); + owner.remove_all_components(); + + update_world_space_colliders(owner); + update_world_space_colliders(owner); + update_world_space_colliders(owner); + + owner.for_joined_components([this, &owner]( + const ecs::const_entity&, + const camera::input&, + const camera& camera) + { + const v2u& target_size = camera.target() + ? camera.target()->size() + : window_.framebuffer_size(); + + const v2f& mouse_p = math::normalized_to_point( + b2f(target_size.cast_to()), + math::point_to_normalized( + b2f(window_.real_size().cast_to()), + input_.mouse().cursor_pos())); + + const m4f& camera_vp = + camera.view() * camera.projection(); + + const b2f& camera_viewport = b2f( + camera.viewport().position * target_size.cast_to(), + camera.viewport().size * target_size.cast_to()); + + if ( !math::inside(camera_viewport, mouse_p) ) { + return; + } + + update_colliders_under_mouse( + owner, + mouse_p, + camera_vp, + camera_viewport); + + update_colliders_under_mouse( + owner, + mouse_p, + camera_vp, + camera_viewport); + + update_colliders_under_mouse( + owner, + mouse_p, + camera_vp, + camera_viewport); + }, !ecs::exists>()); } + private: + input& input_; + window& window_; }; // @@ -32,7 +287,7 @@ namespace e2d // input_system::input_system() - : state_(new internal_state()) {} + : state_(new internal_state(the(), the())) {} input_system::~input_system() noexcept = default; void input_system::process(