15.1.155. tablet_qt/graphics/linesegment.cpp

/*
    Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
    Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

    This file is part of CamCOPS.

    CamCOPS 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.

    CamCOPS 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 CamCOPS. If not, see <https://www.gnu.org/licenses/>.
*/

// #define DEBUG_TRANSFORM

#include "linesegment.h"
#include <QDebug>
#include <QTransform>
#include <QtMath>
#include "maths/mathfunc.h"
using mathfunc::sgn;
using mathfunc::rangesOverlap;


LineSegment::LineSegment(const QPointF& from, const QPointF& to) :
    from(from),
    to(to)
{
    // http://stackoverflow.com/questions/385305/efficient-maths-algorithm-to-calculate-intersections
    const qreal x0 = from.x();
    const qreal x1 = to.x();
    const qreal y0 = from.y();
    const qreal y1 = to.y();

    // Stash normalized coordinates:
    xlow = x0;
    xhigh = x1;
    if (x0 > x1) {
        std::swap(xlow, xhigh);
    }
    ylow = y0;
    yhigh = y1;
    if (y0 > y1) {
        std::swap(ylow, yhigh);
    }

    // Stash equation parameters:
    xm = (x0 + x1) / 2;
    ym = (y0 + y1) / 2;
    a = y1 - y0;
    b = x0 - x1;
}


qreal LineSegment::c(const qreal x, const qreal y) const
{
    // The line has equation a * (x - xm) + b * (y - ym) = c = 0
    return a * (x - xm) + b * (y - ym);
}


qreal LineSegment::c(const QPointF& pt) const
{
    return c(pt.x(), pt.y());
}


int LineSegment::side(const QPointF& pt) const
{
    return sgn(c(pt));
}


bool LineSegment::isPoint() const
{
    return from == to;
}


bool LineSegment::xRangesOverlap(const LineSegment& other) const
{
    return rangesOverlap(xlow, xhigh, other.xlow, other.xhigh);
}


bool LineSegment::yRangesOverlap(const LineSegment& other) const
{
    return rangesOverlap(ylow, yhigh, other.ylow, other.yhigh);
}


bool LineSegment::intersects(const LineSegment& other) const
{
    if (isPoint() || other.isPoint()) {
        return false;
    }
    // Don't use QRectF::intersects(); that returns false when using a
    // rectangle without width (even if it has height) or vice versa.
    if (!xRangesOverlap(other) || !yRangesOverlap(other)) {
        return false;
    }
    // http://stackoverflow.com/questions/385305/efficient-maths-algorithm-to-calculate-intersections
    // See also: http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
    if (sgn(c(other.from)) == sgn(c(other.to))) {
        // Both other.from and other.to are on the same side of our
        // line, and therefore there can be no intersection.
        return false;
    }
    if (sgn(other.c(from)) == sgn(other.c(to))) {
        // Both from and to are on the same side of the other
        // line, and therefore there can be no intersection.
        return false;
    }
    // There must be an intersection.
    return true;
}


bool LineSegment::pointOn(const QPointF& point) const
{
    const qreal x = point.x();
    const qreal y = point.y();
    if (x < xlow || x > xhigh || y < ylow || y > yhigh) {
        return false;
    }
    return qFuzzyIsNull(c(point));
}


qreal LineSegment::angleRad() const
{
    const qreal dx = to.x() - from.x();
    const qreal dy = to.y() - from.y();
    return qAtan2(dy, dx);
}


QRectF LineSegment::rect() const
{
    const QRectF r(from, to);
    return r.normalized();
}


bool LineSegment::pointInPerpendicularArea(const QPointF& point) const
{
    const qreal angle = angleRad();
    QTransform tr;
    tr = tr.rotateRadians(angle);  // rotate ANTICLOCKWISE by angle
    // The direction should now be in the positive x direction.
    const QRectF rotated_rect = tr.mapRect(rect());
    const QPointF rotated_point = tr.map(point);
#ifdef DEBUG_TRANSFORM
    qDebug() << "pointInPerpendicularArea:"
             << "point" << point
             << "angle" << angle
             << "in degrees" << qRadiansToDegrees(angle)
             << "rotated_rect" << rotated_rect
             << "rotated_point" << rotated_point;
#endif
    const qreal x = rotated_point.x();
    return x >= rotated_rect.left() && x <= rotated_rect.right();
}