diff --git a/src/Math/Geometry/Intersection.h b/src/Math/Geometry/Intersection.h index 35100ed86..35adfe578 100644 --- a/src/Math/Geometry/Intersection.h +++ b/src/Math/Geometry/Intersection.h @@ -37,6 +37,63 @@ class Intersection { public: Intersection() = delete; + /** + * @brief %Intersection of two line segments in 2D + * @param p Starting point of first line segment + * @param r Direction of first line segment + * @param q Starting point of second line segment + * @param s Direction of second line segment + * @return %Intersection point positions `t`, `u` on both lines, NaN if + * the lines are collinear or infinity if they are parallel. + * %Intersection point can be then computed with `p + t*r` or + * `q + u*s`. If `t` is in range @f$ [ 0 ; 1 ] @f$, the + * intersection is inside the line segment defined by `p` and + * `p + r`, if `u` is in range @f$ [ 0 ; 1 ] @f$, the intersection + * is inside the line segment defined by `q` and `q + s`. + * + * The two lines intersect if **t** and **u** exist such that: @f[ + * \boldsymbol p + t \boldsymbol r = \boldsymbol q + u \boldsymbol s + * @f] + * Crossing both sides with **s**, distributing the cross product and + * eliminating @f$ \boldsymbol s \times \boldsymbol s = 0 @f$, then + * solving for **t** and similarly for **u**: @f[ + * \begin{array}{rcl} + * (\boldsymbol p + t \boldsymbol r) \times s & = & (\boldsymbol q + u \boldsymbol s) \times s \\ + * t (\boldsymbol r \times s) & = & (\boldsymbol q - \boldsymbol p) \times s \\ + * t & = & \cfrac{(\boldsymbol q - \boldsymbol p) \times s}{\boldsymbol r \times \boldsymbol s} \\ + * u & = & \cfrac{(\boldsymbol q - \boldsymbol p) \times r}{\boldsymbol r \times \boldsymbol s} + * \end{array} + * @f] + * + * See also lineSegmentLine() which computes only **t**, which is + * useful if you don't need to test that the intersection lies inside + * line segment defined by `q` and `q + s`. + */ + template static std::pair lineSegmentLineSegment(const Vector2& p, const Vector2& r, const Vector2& q, const Vector2& s) { + const Vector2 qp = q - p; + const T rs = Vector2::cross(r, s); + return {Vector2::cross(qp, s)/rs, + Vector2::cross(qp, r)/rs}; + } + + /** + * @brief %Intersection of line segment and line in 2D + * @param p Starting point of first line segment + * @param r Direction of first line segment + * @param q Starting point of second line + * @param s Direction of second line + * @return %Intersection point position `t` on first line, NaN if the + * lines are collinear or infinity if they are parallel. + * %Intersection point can be then with `p + t*r`. If returned + * value is in range @f$ [ 0 ; 1 ] @f$, the intersection is inside + * the line segment defined by `p` and `p + r`. + * + * Unlike lineSegmentLineSegment() computes only **t**. + */ + template static T lineSegmentLine(const Vector2& p, const Vector2& r, const Vector2& q, const Vector2& s) { + return Vector2::cross(q - p, s)/Vector2::cross(r, s); + } + /** * @brief %Intersection of a plane and line * @param planePosition Plane position diff --git a/src/Math/Geometry/Test/IntersectionTest.cpp b/src/Math/Geometry/Test/IntersectionTest.cpp index 5d7581a3b..db23f96f8 100644 --- a/src/Math/Geometry/Test/IntersectionTest.cpp +++ b/src/Math/Geometry/Test/IntersectionTest.cpp @@ -34,12 +34,15 @@ class IntersectionTest: public Corrade::TestSuite::Tester { IntersectionTest(); void planeLine(); + void lineLine(); }; +typedef Math::Vector2 Vector2; typedef Math::Vector3 Vector3; IntersectionTest::IntersectionTest() { - addTests({&IntersectionTest::planeLine}); + addTests({&IntersectionTest::planeLine, + &IntersectionTest::lineLine}); } void IntersectionTest::planeLine() { @@ -63,6 +66,38 @@ void IntersectionTest::planeLine() { {1.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}), -std::numeric_limits::infinity()); } +void IntersectionTest::lineLine() { + const Vector2 p(-1.0f, -1.0f); + const Vector2 r(1.0, 2.0f); + + /* Inside both line segments */ + CORRADE_COMPARE(Intersection::lineSegmentLineSegment(p, r, + {0.0f, 0.0f}, {-1.0f, 0.0f}), std::make_pair(0.5f, 0.5f)); + CORRADE_COMPARE(Intersection::lineSegmentLine(p, r, + {0.0f, 0.0f}, {-1.0f, 0.0f}), 0.5); + + /* Outside both line segments */ + CORRADE_COMPARE(Intersection::lineSegmentLineSegment(p, r, + {0.0f, -2.0f}, {-1.0f, 0.0f}), std::make_pair(-0.5f, 1.5f)); + CORRADE_COMPARE(Intersection::lineSegmentLine(p, r, + {0.0f, -2.0f}, {-1.0f, 0.0f}), -0.5f); + + /* Collinear lines */ + const auto tu = Intersection::lineSegmentLineSegment(p, r, + {0.0f, 1.0f}, {-1.0f, -2.0f}); + CORRADE_COMPARE(tu.first, -std::numeric_limits::quiet_NaN()); + CORRADE_COMPARE(tu.second, -std::numeric_limits::quiet_NaN()); + CORRADE_COMPARE(Intersection::lineSegmentLine(p, r, + {0.0f, 1.0f}, {-1.0f, -2.0f}), -std::numeric_limits::quiet_NaN()); + + /* Parallel lines */ + CORRADE_COMPARE(Intersection::lineSegmentLineSegment(p, r, + {0.0f, 0.0f}, {1.0f, 2.0f}), std::make_pair(std::numeric_limits::infinity(), + std::numeric_limits::infinity())); + CORRADE_COMPARE(Intersection::lineSegmentLine(p, r, + {0.0f, 0.0f}, {1.0f, 2.0f}), std::numeric_limits::infinity()); +} + }}}} CORRADE_TEST_MAIN(Magnum::Math::Geometry::Test::IntersectionTest)