/* * Copyright (c) 2021, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include namespace Gfx { template class Line { public: Line() = default; Line(Point a, Point b) : m_a(a) , m_b(b) { } template Line(U a, U b) : m_a(a) , m_b(b) { } template explicit Line(Line const& other) : m_a(other.a()) , m_b(other.b()) { } bool intersects(Line const& other) const { return intersected(other).has_value(); } Optional> intersected(Line const& other) const { auto cross_product = [](Point const& p1, Point const& p2) { return p1.x() * p2.y() - p1.y() * p2.x(); }; auto r = m_b - m_a; auto s = other.m_b - other.m_a; auto delta_a = other.m_a - m_a; auto num = cross_product(delta_a, r); auto denom = cross_product(r, s); if (denom == 0) { if (num == 0) { // Lines are collinear, check if line ends are touching if (m_a == other.m_a || m_a == other.m_b) return m_a; if (m_b == other.m_a || m_b == other.m_b) return m_b; // Check if they're overlapping if (!(m_b.x() - m_a.x() < 0 && m_b.x() - other.m_a.x() < 0 && other.m_b.x() - m_a.x() && other.m_b.x() - other.m_a.x())) { // Overlapping // TODO find center point? } if (!(m_b.y() - m_a.y() < 0 && m_b.y() - other.m_a.y() < 0 && other.m_b.y() - m_a.y() && other.m_b.y() - other.m_a.y())) { // Overlapping // TODO find center point? } return {}; } else { // Lines are parallel and not intersecting return {}; } } auto u = static_cast(num) / static_cast(denom); if (u < 0.0f || u > 1.0f) { // Lines are not parallel and don't intersect return {}; } auto t = static_cast(cross_product(delta_a, s)) / static_cast(denom); if (t < 0.0f || t > 1.0f) { // Lines are not parallel and don't intersect return {}; } // TODO: round if we're dealing with int return Point { m_a.x() + static_cast(t * r.x()), m_a.y() + static_cast(t * r.y()) }; } float length() const { return m_a.distance_from(m_b); } Point closest_to(Point const& point) const { if (m_a == m_b) return m_a; auto delta_a = point.x() - m_a.x(); auto delta_b = point.y() - m_a.y(); auto delta_c = m_b.x() - m_a.x(); auto delta_d = m_b.y() - m_a.y(); auto len_sq = delta_c * delta_c + delta_d * delta_d; float param = -1.0; if (len_sq != 0) param = static_cast(delta_a * delta_c + delta_b * delta_d) / static_cast(len_sq); if (param < 0) return m_a; if (param > 1) return m_b; // TODO: round if we're dealing with int return { static_cast(m_a.x() + param * delta_c), static_cast(m_a.y() + param * delta_d) }; } Line shortest_line_to(Point const& point) const { return { closest_to(point), point }; } float distance_to(Point const& point) const { return shortest_line_to(point).length(); } Point const& a() const { return m_a; } Point const& b() const { return m_b; } Line rotated(float radians) { Gfx::AffineTransform rotation_transform; rotation_transform.rotate_radians(radians); Line line = *this; line.set_a(line.a().transformed(rotation_transform)); line.set_b(line.b().transformed(rotation_transform)); return line; } void set_a(Point const& a) { m_a = a; } void set_b(Point const& b) { m_b = b; } Line scaled(T sx, T sy) const { Line line = *this; line.set_a(line.a().scaled(sx, sy)); line.set_b(line.b().scaled(sx, sy)); return line; } Line translated(Point const& delta) const { Line line = *this; line.set_a(line.a().translated(delta)); line.set_b(line.b().translated(delta)); return line; } template requires(!IsSame) [[nodiscard]] ALWAYS_INLINE constexpr Line to_type() const { return Line(*this); } ByteString to_byte_string() const; private: Point m_a; Point m_b; }; template<> inline ByteString IntLine::to_byte_string() const { return ByteString::formatted("[{},{} -> {},{}]", m_a.x(), m_a.y(), m_b.x(), m_b.y()); } template<> inline ByteString FloatLine::to_byte_string() const { return ByteString::formatted("[{},{} -> {},{}]", m_a.x(), m_a.y(), m_b.x(), m_b.y()); } } namespace AK { template struct Formatter> : Formatter { ErrorOr format(FormatBuilder& builder, Gfx::Line const& value) { return Formatter::format(builder, "[{},{} -> {},{}]"sv, value.a().x(), value.a().y(), value.b().x(), value.b().y()); } }; }