MuseScore/src/engraving/infrastructure/draw/geometry.h

698 lines
20 KiB
C++

/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MU_GEOMETRY_H
#define MU_GEOMETRY_H
#include <vector>
#include <cmath>
#include <QtGlobal>
#include <QPair>
#include <QRectF>
#include <QSizeF>
#include <QPointF>
#ifndef NO_QT_SUPPORT
#include <QLineF>
#include <QPolygonF>
#endif
namespace mu {
inline bool isEqual(qreal a1, qreal a2) { return qFuzzyCompare(a1, a2); }
inline bool isEqual(int a1, int a2) { return a1 == a2; }
// ====================================
// Point
// ====================================
template<typename T>
class PointX
{
public:
inline PointX() = default;
inline PointX(T x, T y)
: m_x(x), m_y(y) {}
inline bool isNull() const { return isEqual(m_x, T()) && isEqual(m_y, T()); }
inline void setX(T x) { m_x = x; }
inline void setY(T y) { m_y = y; }
inline T x() const { return m_x; }
inline T y() const { return m_y; }
//! NOTE I don't like this methods, but now it a lot of using
inline T& rx() { return m_x; }
inline T& ry() { return m_y; }
inline bool operator==(const PointX<T>& p) const { return isEqual(p.m_x, m_x) && isEqual(p.m_y, m_y); }
inline bool operator!=(const PointX<T>& p) const { return !this->operator ==(p); }
inline PointX<T> operator-() const { return PointX<T>(-m_x, -m_y); }
inline PointX<T> operator-(const PointX<T>& p) const { return PointX<T>(m_x - p.m_x, m_y - p.m_y); }
inline PointX<T> operator+(const PointX<T>& p) const { return PointX<T>(m_x + p.m_x, m_y + p.m_y); }
inline PointX<T>& operator+=(const PointX<T>& p) { m_x += p.m_x; m_y += p.m_y; return *this; }
inline PointX<T>& operator-=(const PointX<T>& p) { m_x -= p.m_x; m_y -= p.m_y; return *this; }
inline PointX<T>& operator*=(T c) { m_x *= c; m_y *= c; return *this; }
inline PointX<T>& operator/=(T divisor) { m_x /= divisor; m_y /= divisor; return *this; }
inline T manhattanLength() const { return qAbs(m_x) + qAbs(m_y); }
static inline qreal dotProduct(const PointX<T>& p1, const PointX<T>& p2) { return p1.m_x * p2.m_x + p1.m_y * p2.m_y; }
inline void normalize()
{
// Need some extra precision if the length is very small.
double len = double(m_x) * double(m_x) + double(m_y) * double(m_y);
if (qFuzzyIsNull(len - 1.0f) || qFuzzyIsNull(len)) {
return;
}
len = std::sqrt(len);
m_x /= len;
m_y /= len;
}
//#ifndef NO_QT_SUPPORT
static PointX<T> fromQPointF(const QPointF& p) { return PointX<T>(p.x(), p.y()); }
inline QPointF toQPointF() const { return QPointF(m_x, m_y); }
inline QPoint toQPoint() const { return QPoint(m_x, m_y); }
inline PointX<T>& operator=(const QPointF& p) { m_x = p.x(); m_y = p.y(); return *this; }
//#endif
private:
//! NOTE We should not swap fields
//! We should not add new fields
//! If we really need to do this, then we need to change the implementation of QPainterProvider
T m_x = T();
T m_y = T();
};
template<typename T>
inline PointX<T> operator*(const PointX<T>& p, T c) { return PointX<T>(p.x() * c, p.y() * c); }
template<typename T>
inline PointX<T> operator*(T c, const PointX<T>& p) { return PointX<T>(p.x() * c, p.y() * c); }
template<typename T>
inline PointX<T> operator/(const PointX<T>& p, T c) { return PointX<T>(p.x() / c, p.y() / c); }
using PointF = PointX<qreal>;
using Point = PointX<int>;
#ifndef NO_QT_SUPPORT
inline QPointF operator+(const QPointF& p1, const PointF& p2) { return QPointF(p1.x() + p2.x(), p1.y() + p2.y()); }
#endif
// ====================================
// PairF
// ====================================
class PairF : public std::pair<qreal, qreal>
{
public:
PairF() = default;
PairF(qreal f, qreal s)
: std::pair<qreal, qreal>(f, s) {}
static PairF fromQPairF(const QPair<qreal, qreal>& v) { return PairF(v.first, v.second); }
QPair<qreal, qreal> toQPairF() const { return QPair<qreal, qreal>(first, second); }
};
// ====================================
// Line
// ====================================
template<typename T>
class LineX
{
public:
inline LineX() = default;
inline LineX(const PointX<T>& p1, const PointX<T>& p2)
: m_p1(p1), m_p2(p2) {}
inline LineX(T x1, T y1, T x2, T y2)
: m_p1(PointX<T>(x1, y1)), m_p2(PointX<T>(x2, y2)) {}
inline const PointX<T>& p1() const { return m_p1; }
inline const PointX<T>& p2() const { return m_p2; }
inline qreal x1() const { return m_p1.x(); }
inline qreal y1() const { return m_p1.y(); }
inline qreal x2() const { return m_p2.x(); }
inline qreal y2() const { return m_p2.y(); }
inline void setP1(const PointX<T>& p) { m_p1 = p; }
inline void setP2(const PointX<T>& p) { m_p2 = p; }
inline void setLine(T aX1, T aY1, T aX2, T aY2) { m_p1 = PointX<T>(aX1, aY1); m_p2 = PointX<T>(aX2, aY2); }
inline bool operator==(const LineX<T>& l) const { return m_p1 == l.m_p1 && m_p2 == l.m_p2; }
inline bool operator!=(const LineX<T>& l) const { return !this->operator ==(l); }
inline PointX<T> pointAt(T t) const { return PointX<T>(m_p1.x() + (m_p2.x() - m_p1.x()) * t, m_p1.y() + (m_p2.y() - m_p1.y()) * t); }
inline void translate(const PointX<T>& point) { m_p1 += point; m_p2 += point; }
inline LineX<T> translated(const PointX<T>& p) const { return LineX<T>(m_p1 + p, m_p2 + p); }
#ifndef NO_QT_SUPPORT
static LineX<T> fromQLineF(const QLineF& l) { return LineX<T>(l.x1(), l.y1(), l.x2(), l.y2()); }
inline QLineF toQLineF() const { return QLineF(m_p1.toQPointF(), m_p2.toQPointF()); }
#endif
private:
PointX<T> m_p1;
PointX<T> m_p2;
};
using LineF = LineX<qreal>;
using Line = LineX<int>;
// ====================================
// Size
// ====================================
template<typename T>
class SizeX
{
public:
inline SizeX() = default;
inline SizeX(T w, T h)
: m_w(w), m_h(h) {}
inline bool isNull() const { return isEqual(m_w, T()) && isEqual(m_h, T()); }
inline T width() const { return m_w; }
inline T height() const { return m_h; }
inline void setWidth(T w) { m_w = w; }
inline void setHeight(T h) { m_h = h; }
inline bool operator==(const SizeX<T>& s) const { return isEqual(s.m_w, m_w) && isEqual(s.m_h, m_h); }
inline bool operator!=(const SizeX<T>& s) const { return !this->operator ==(s); }
inline SizeX<T> transposed() const { return SizeX<T>(m_h, m_w); }
//#ifndef NO_QT_SUPPORT
static SizeX<T> fromQSizeF(const QSizeF& s) { return SizeX<T>(s.width(), s.height()); }
inline QSizeF toQSizeF() const { return QSizeF(m_w, m_h); }
//#endif
private:
T m_w = T();
T m_h = T();
};
template<typename T>
inline SizeX<T> operator*(const SizeX<T>& s, qreal c) { return SizeX<T>(s.width() * c, s.height() * c); }
template<typename T>
inline SizeX<T> operator*(qreal c, const SizeX<T>& s) { return SizeX<T>(s.width() * c, s.height() * c); }
template<typename T>
inline SizeX<T> operator/(const SizeX<T>& s, T c) { Q_ASSERT(!isEqual(c, T())); return SizeX<T>(s.width() / c, s.height() / c); }
using Size = SizeX<int>;
using SizeF = SizeX<qreal>;
class ScaleF : public SizeX<qreal>
{
public:
ScaleF() = default;
ScaleF(qreal w, qreal h)
: SizeX<qreal>(w, h) {}
#ifndef NO_QT_SUPPORT
static ScaleF fromQSizeF(const QSizeF& s) { return ScaleF(s.width(), s.height()); }
#endif
};
// ====================================
// Rect
// ====================================
template<typename T>
class RectX
{
public:
inline RectX() = default;
inline RectX(T x, T y, T w, T h)
: m_x(x), m_y(y), m_w(w), m_h(h) {}
inline RectX(const PointX<T>& topLeft, const PointX<T>& bottomRight)
: m_x(topLeft.x()), m_y(topLeft.y()), m_w(bottomRight.x() - topLeft.x()), m_h(bottomRight.y() - topLeft.y()) {}
inline RectX(const PointX<T>& atopLeft, const SizeX<T>& asize)
: m_x(atopLeft.x()), m_y(atopLeft.y()), m_w(asize.width()), m_h(asize.height()) {}
inline bool isNull() const { return isEqual(m_w, T()) && isEqual(m_h, T()); }
inline bool isEmpty() const { return m_w <= T() || m_h <= T(); }
inline bool isValid() const { return m_w > T() && m_h > T(); }
inline T x() const { return m_x; }
inline T y() const { return m_y; }
inline T width() const { return m_w; }
inline T height() const { return m_h; }
inline SizeX<T> size() const { return SizeX<T>(m_w, m_h); }
inline T left() const noexcept { return m_x; }
inline T top() const noexcept { return m_y; }
inline T right() const noexcept { return m_x + m_w; }
inline T bottom() const noexcept { return m_y + m_h; }
inline PointX<T> topLeft() const { return PointX<T>(m_x, m_y); }
inline PointX<T> bottomRight() const { return PointX<T>(m_x + m_w, m_y + m_h); }
inline PointX<T> topRight() const { return PointX<T>(m_x + m_w, m_y); }
inline PointX<T> bottomLeft() const { return PointX<T>(m_x, m_y + m_h); }
inline PointX<T> center() const { return PointX<T>(m_x + m_w / 2, m_y + m_h / 2); }
inline void setCoords(T xp1, T yp1, T xp2, T yp2) { m_x = xp1; m_y = yp1; m_w = xp2 - xp1; m_h = yp2 - yp1; }
inline void setRect(qreal ax, qreal ay, qreal aaw, qreal aah) { m_x = ax; m_y = ay; m_w = aaw; m_h = aah; }
inline void setHeight(T h) { m_h = h; }
inline void setWidth(T w) { m_w = w; }
inline void setSize(const SizeX<T>& s) { m_w = s.width(); m_h = s.height(); }
inline void setLeft(T pos) { T diff = pos - m_x; m_x += diff; m_w -= diff; }
inline void setRight(T pos) { m_w = pos - m_x; }
inline void setTop(T pos) { T diff = pos - m_y; m_y += diff; m_h -= diff; }
inline void setBottom(T pos) { m_h = pos - m_y; }
inline void setX(T x) { m_x = x; }
inline void setY(T y) { m_y = y; }
inline void setTopLeft(const PointX<T>& p) { setLeft(p.x()); setTop(p.y()); }
inline void setTopRight(const PointX<T>& p) { setRight(p.x()); setTop(p.y()); }
inline void setBottomLeft(const PointX<T>& p) { setLeft(p.x()); setBottom(p.y()); }
inline void setBottomRight(const PointX<T>& p) { setRight(p.x()); setBottom(p.y()); }
inline bool operator==(const RectX<T>& r) const
{
return isEqual(r.m_x, m_x) && isEqual(r.m_y, m_y) && isEqual(r.m_w, m_w) && isEqual(r.m_h, m_h);
}
inline bool operator!=(const RectX<T>& r) const { return !this->operator ==(r); }
inline void moveTo(qreal ax, qreal ay) { m_x = ax; m_y = ay; }
inline void moveTo(const PointX<T>& p) { m_x = p.x(); m_y = p.y(); }
inline void moveCenter(const PointX<T>& p) { m_x = p.x() - m_w / 2; m_y = p.y() - m_h / 2; }
inline void moveTop(qreal pos) { m_y = pos; }
inline void translate(T dx, T dy) { m_x += dx; m_y += dy; }
inline void translate(const PointX<T>& p) { m_x += p.x(); m_y += p.y(); }
inline RectX<T> translated(T dx, T dy) const { return RectX<T>(m_x + dx, m_y + dy, m_w, m_h); }
inline RectX<T> translated(const PointX<T>& p) const { return RectX<T>(m_x + p.x(), m_y + p.y(), m_w, m_h); }
inline void adjust(qreal xp1, qreal yp1, qreal xp2, qreal yp2) { m_x += xp1; m_y += yp1; m_w += xp2 - xp1; m_h += yp2 - yp1; }
inline RectX<T> adjusted(T xp1, T yp1, T xp2, T yp2) const { return RectX<T>(m_x + xp1, m_y + yp1, m_w + xp2 - xp1, m_h + yp2 - yp1); }
bool contains(const PointX<T>& p) const;
bool contains(const RectX<T>& r) const;
bool intersects(const RectX<T>& r) const;
RectX<T> united(const RectX<T>& r) const;
inline RectX<T>& unite(const RectX<T>& r) { *this = united(r); return *this; }
RectX<T> intersected(const RectX<T>& r) const;
inline RectX<T>& intersect(const RectX<T>& r) { *this = intersected(r); return *this; }
//! NOTE I don't like this operators, but now it a lot of using
inline RectX<T> operator|(const RectX<T>& r) const { return united(r); }
inline RectX<T> operator&(const RectX<T>& r) const { return intersected(r); }
inline RectX<T>& operator|=(const RectX<T>& r) { return unite(r); }
inline RectX<T>& operator&=(const RectX<T>& r) { return intersect(r); }
RectX<T> normalized() const;
//#ifndef NO_QT_SUPPORT
static RectX<T> fromQRectF(const QRectF& r) { return RectX<T>(r.x(), r.y(), r.width(), r.height()); }
inline QRectF toQRectF() const { return QRectF(m_x, m_y, m_w, m_h); }
inline QRect toQRect() const { return QRect(m_x, m_y, m_w, m_h); }
inline RectX<T>& operator=(const QRectF& r) { m_x = r.x(); m_y = r.y(); m_w = r.width(); m_h = r.height(); return *this; }
inline RectX<T> united(const QRectF& r) const { return united(RectX<T>(r)); }
//#endif
private:
T m_x = T();
T m_y = T();
T m_w = T();
T m_h = T();
};
using RectF = RectX<qreal>;
class Rect : public RectX<int>
{
public:
inline Rect()
: RectX<int>() {}
#ifndef NO_QT_SUPPORT
inline Rect(const QRect& r)
: RectX<int>(r.x(), r.y(), r.width(), r.height()) {}
#endif
inline Rect(int x, int y, int w, int h)
: RectX<int>(x, y, w, h) {}
inline RectF toRectF() const { return RectF(x(), y(), width(), height()); }
};
// ====================================
// Polygon
// ====================================
template<typename T>
class PolygonX : public std::vector<PointX<T> >
{
public:
inline PolygonX<T>() = default;
inline PolygonX<T>(const std::vector<PointX<T> >& v) : std::vector<PointX<T> >(v) {
}
inline PolygonX<T>(size_t size) : std::vector<PointX<T> >(size) {
}
inline PolygonX<T>& operator<<(const PointX<T>& p)
{
this->push_back(p);
return *this;
}
inline void translate(const PointX<T>& offset)
{
if (offset.isNull()) {
return;
}
PointX<T>* p = this->data();
size_t i = this->size();
while (i--) {
*p += offset;
++p;
}
}
inline PolygonX<T> translated(T x, T y) const { return translated(PointX<T>(x, y)); }
inline PolygonX<T> translated(const PointX<T>& offset) const
{
PolygonX<T> copy(*this);
copy.translate(offset);
return copy;
}
RectX<T> boundingRect() const
{
const PointX<T>* pd = this->data();
const PointX<T>* pe = pd + this->size();
if (pd == pe) {
return RectX<T>(0, 0, 0, 0);
}
T minx, maxx, miny, maxy;
minx = maxx = pd->x();
miny = maxy = pd->y();
++pd;
while (pd != pe) {
if (pd->x() < minx) {
minx = pd->x();
} else if (pd->x() > maxx) {
maxx = pd->x();
}
if (pd->y() < miny) {
miny = pd->y();
} else if (pd->y() > maxy) {
maxy = pd->y();
}
++pd;
}
return RectX<T>(minx, miny, maxx - minx, maxy - miny);
}
};
using PolygonF = PolygonX<qreal>;
using Polygon = PolygonX<int>;
// Implementation ==========================================
template<typename T>
RectX<T> RectX<T>::united(const RectX<T>& r) const
{
if (isNull()) {
return r;
}
if (r.isNull()) {
return *this;
}
T left = m_x;
T right = m_x;
if (m_w < 0) {
left += m_w;
} else {
right += m_w;
}
if (r.m_w < 0) {
left = qMin(left, r.m_x + r.m_w);
right = qMax(right, r.m_x);
} else {
left = qMin(left, r.m_x);
right = qMax(right, r.m_x + r.m_w);
}
qreal top = m_y;
qreal bottom = m_y;
if (m_h < 0) {
top += m_h;
} else {
bottom += m_h;
}
if (r.m_h < 0) {
top = qMin(top, r.m_y + r.m_h);
bottom = qMax(bottom, r.m_y);
} else {
top = qMin(top, r.m_y);
bottom = qMax(bottom, r.m_y + r.m_h);
}
return RectX<T>(left, top, right - left, bottom - top);
}
template<typename T>
RectX<T> RectX<T>::intersected(const RectX<T>& r) const
{
T l1 = m_x;
T r1 = m_x;
if (m_w < 0) {
l1 += m_w;
} else {
r1 += m_w;
}
if (l1 == r1) { // null rect
return RectX<T>();
}
T l2 = r.m_x;
T r2 = r.m_x;
if (r.m_w < 0) {
l2 += r.m_w;
} else {
r2 += r.m_w;
}
if (l2 == r2) { // null rect
return RectX<T>();
}
if (l1 >= r2 || l2 >= r1) {
return RectX<T>();
}
T t1 = m_y;
T b1 = m_y;
if (m_h < 0) {
t1 += m_h;
} else {
b1 += m_h;
}
if (t1 == b1) { // null rect
return RectX<T>();
}
T t2 = r.m_y;
T b2 = r.m_y;
if (r.m_h < 0) {
t2 += r.m_h;
} else {
b2 += r.m_h;
}
if (t2 == b2) { // null rect
return RectX<T>();
}
if (t1 >= b2 || t2 >= b1) {
return RectX<T>();
}
RectX<T> tmp;
tmp.m_x = qMax(l1, l2);
tmp.m_y = qMax(t1, t2);
tmp.m_w = qMin(r1, r2) - tmp.m_x;
tmp.m_h = qMin(b1, b2) - tmp.m_y;
return tmp;
}
template<typename T>
bool RectX<T>::intersects(const RectX<T>& r) const
{
T l1 = m_x;
T r1 = m_x;
if (m_w < 0) {
l1 += m_w;
} else {
r1 += m_w;
}
if (isEqual(l1, r1)) { // null rect
return false;
}
T l2 = r.m_x;
T r2 = r.m_x;
if (r.m_w < 0) {
l2 += r.m_w;
} else {
r2 += r.m_w;
}
if (isEqual(l2, r2)) { // null rect
return false;
}
if (l1 >= r2 || l2 >= r1) {
return false;
}
T t1 = m_y;
T b1 = m_y;
if (m_h < 0) {
t1 += m_h;
} else {
b1 += m_h;
}
if (isEqual(t1, b1)) { // null rect
return false;
}
T t2 = r.m_y;
T b2 = r.m_y;
if (r.m_h < 0) {
t2 += r.m_h;
} else {
b2 += r.m_h;
}
if (isEqual(t2, b2)) { // null rect
return false;
}
if (t1 >= b2 || t2 >= b1) {
return false;
}
return true;
}
template<typename T>
bool RectX<T>::contains(const PointX<T>& p) const
{
T l = m_x;
T r = m_x;
if (m_w < 0) {
l += m_w;
} else {
r += m_w;
}
if (isEqual(l, r)) { // null rect
return false;
}
if (p.x() < l || p.x() > r) {
return false;
}
T t = m_y;
T b = m_y;
if (m_h < 0) {
t += m_h;
} else {
b += m_h;
}
if (isEqual(t, b)) { // null rect
return false;
}
if (p.y() < t || p.y() > b) {
return false;
}
return true;
}
template<typename T>
bool RectX<T>::contains(const RectX<T>& r) const
{
T l1 = m_x;
T r1 = m_x;
if (m_w < 0) {
l1 += m_w;
} else {
r1 += m_w;
}
if (isEqual(l1, r1)) { // null rect
return false;
}
T l2 = r.m_x;
T r2 = r.m_x;
if (r.m_w < 0) {
l2 += r.m_w;
} else {
r2 += r.m_w;
}
if (isEqual(l2, r2)) { // null rect
return false;
}
if (l2 < l1 || r2 > r1) {
return false;
}
T t1 = m_y;
T b1 = m_y;
if (m_h < 0) {
t1 += m_h;
} else {
b1 += m_h;
}
if (isEqual(t1, b1)) { // null rect
return false;
}
T t2 = r.m_y;
T b2 = r.m_y;
if (r.m_h < 0) {
t2 += r.m_h;
} else {
b2 += r.m_h;
}
if (isEqual(t2, b2)) { // null rect
return false;
}
if (t2 < t1 || b2 > b1) {
return false;
}
return true;
}
template<typename T>
RectX<T> RectX<T>::normalized() const
{
RectX<T> r = *this;
if (r.m_w < 0) {
r.m_x += r.m_w;
r.m_w = -r.m_w;
}
if (r.m_h < 0) {
r.m_y += r.m_h;
r.m_h = -r.m_h;
}
return r;
}
}
#endif // MU_GEOMETRY_H