mirror of
https://github.com/RGBCube/serenity
synced 2025-10-17 03:52:26 +00:00

Also, add `Line::to_type<T>()` since that was missing. Calling to_type() with the same type as the existing object accomplishes nothing except wasting some cycles and making the code more verbose, and it is hard to spot. Nobody does this in the code currently (yay!) but I made this mistake repeatedly when doing my step-by-step CSS Pixels conversion, so let's make it easier to catch them.
175 lines
4.8 KiB
C++
175 lines
4.8 KiB
C++
/*
|
|
* Copyright (c) 2021, the SerenityOS developers.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <AK/Format.h>
|
|
#include <AK/Optional.h>
|
|
#include <AK/StdLibExtras.h>
|
|
#include <AK/String.h>
|
|
#include <LibGfx/Forward.h>
|
|
#include <LibGfx/Point.h>
|
|
#include <LibGfx/Rect.h>
|
|
#include <stdlib.h>
|
|
|
|
namespace Gfx {
|
|
|
|
template<typename T>
|
|
class Line {
|
|
public:
|
|
Line() = default;
|
|
|
|
Line(Point<T> a, Point<T> b)
|
|
: m_a(a)
|
|
, m_b(b)
|
|
{
|
|
}
|
|
|
|
template<typename U>
|
|
Line(U a, U b)
|
|
: m_a(a)
|
|
, m_b(b)
|
|
{
|
|
}
|
|
|
|
template<typename U>
|
|
explicit Line(Line<U> const& other)
|
|
: m_a(other.a())
|
|
, m_b(other.b())
|
|
{
|
|
}
|
|
|
|
bool intersects(Line const& other) const
|
|
{
|
|
return intersected(other).has_value();
|
|
}
|
|
|
|
Optional<Point<T>> intersected(Line const& other) const
|
|
{
|
|
auto cross_product = [](Point<T> const& p1, Point<T> 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<float>(num) / static_cast<float>(denom);
|
|
if (u < 0.0f || u > 1.0f) {
|
|
// Lines are not parallel and don't intersect
|
|
return {};
|
|
}
|
|
auto t = static_cast<float>(cross_product(delta_a, s)) / static_cast<float>(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<T> { m_a.x() + static_cast<T>(t * r.x()), m_a.y() + static_cast<T>(t * r.y()) };
|
|
}
|
|
|
|
float length() const
|
|
{
|
|
return m_a.distance_from(m_b);
|
|
}
|
|
|
|
Point<T> closest_to(Point<T> 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<float>(delta_a * delta_c + delta_b * delta_d) / static_cast<float>(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<T>(m_a.x() + param * delta_c), static_cast<T>(m_a.y() + param * delta_d) };
|
|
}
|
|
|
|
Line<T> shortest_line_to(Point<T> const& point) const
|
|
{
|
|
return { closest_to(point), point };
|
|
}
|
|
|
|
float distance_to(Point<T> const& point) const
|
|
{
|
|
return shortest_line_to(point).length();
|
|
}
|
|
|
|
Point<T> const& a() const { return m_a; }
|
|
Point<T> const& b() const { return m_b; }
|
|
|
|
void set_a(Point<T> const& a) { m_a = a; }
|
|
void set_b(Point<T> const& b) { m_b = b; }
|
|
|
|
template<typename U>
|
|
requires(!IsSame<T, U>)
|
|
[[nodiscard]] ALWAYS_INLINE constexpr Line<U> to_type() const
|
|
{
|
|
return Line<U>(*this);
|
|
}
|
|
|
|
String to_string() const;
|
|
|
|
private:
|
|
Point<T> m_a;
|
|
Point<T> m_b;
|
|
};
|
|
|
|
template<>
|
|
inline String IntLine::to_string() const
|
|
{
|
|
return String::formatted("[{},{} -> {},{}]", m_a.x(), m_a.y(), m_b.x(), m_b.y());
|
|
}
|
|
|
|
template<>
|
|
inline String FloatLine::to_string() const
|
|
{
|
|
return String::formatted("[{},{} -> {},{}]", m_a.x(), m_a.y(), m_b.x(), m_b.y());
|
|
}
|
|
|
|
}
|
|
|
|
namespace AK {
|
|
|
|
template<typename T>
|
|
struct Formatter<Gfx::Line<T>> : Formatter<FormatString> {
|
|
ErrorOr<void> format(FormatBuilder& builder, Gfx::Line<T> const& value)
|
|
{
|
|
return Formatter<FormatString>::format(builder, "[{},{} -> {},{}]"sv, value.a().x(), value.a().y(), value.b().x(), value.b().y());
|
|
}
|
|
};
|
|
|
|
}
|