valentina/src/libs/vwidgets/vpiecegrainline.cpp
2024-05-06 14:01:29 +03:00

465 lines
17 KiB
C++

/************************************************************************
**
** @file vgrainline.cpp
** @author Roman Telezhynskyi <dismine(at)gmail.com>
** @date 27 4, 2023
**
** @brief
** @copyright
** This source code is part of the Valentina project, a pattern making
** program, whose allow create and modeling patterns of clothing.
** Copyright (C) 2023 Valentina project
** <https://gitlab.com/smart-pattern/valentina> All Rights Reserved.
**
** Valentina 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.
**
** Valentina 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 Valentina. If not, see <http://www.gnu.org/licenses/>.
**
*************************************************************************/
#include "vpiecegrainline.h"
#include "../vgeometry/vabstractcurve.h"
#include "compatibility.h"
#include "qmath.h"
#include "vpiecegrainline_p.h"
#include <QPolygonF>
#include <QTransform>
#include <algorithm>
namespace
{
constexpr qreal arrowAngle = M_PI / 9;
constexpr int arrowLength = 15;
} // namespace
// VPieceGrainlinePrivate
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainlinePrivate::MainLine(const QPointF &p1, qreal length, qreal angle) -> QLineF
{
QPointF const pt2(p1.x() + length * cos(angle), p1.y() - length * sin(angle));
return {p1, pt2};
}
// VPieceGrainline
//---------------------------------------------------------------------------------------------------------------------
QT_WARNING_PUSH
QT_WARNING_DISABLE_GCC("-Wnoexcept")
VPieceGrainline::VPieceGrainline()
: d(new VPieceGrainlinePrivate)
{
}
QT_WARNING_POP
//---------------------------------------------------------------------------------------------------------------------
VPieceGrainline::~VPieceGrainline() = default;
//---------------------------------------------------------------------------------------------------------------------
VPieceGrainline::VPieceGrainline(const QLineF &mainLine, GrainlineArrowDirection arrowType)
: d(new VPieceGrainlinePrivate(mainLine, arrowType))
{
}
//---------------------------------------------------------------------------------------------------------------------
VPieceGrainline::VPieceGrainline(const QPointF &p1, qreal length, qreal angle, GrainlineArrowDirection arrowType)
: d(new VPieceGrainlinePrivate(VPieceGrainlinePrivate::MainLine(p1, length, angle), arrowType))
{
}
//---------------------------------------------------------------------------------------------------------------------
COPY_CONSTRUCTOR_IMPL(VPieceGrainline)
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::operator=(const VPieceGrainline &grainline) -> VPieceGrainline &
{
if (&grainline == this)
{
return *this;
}
d = grainline.d;
return *this;
}
//---------------------------------------------------------------------------------------------------------------------
VPieceGrainline::VPieceGrainline(VPieceGrainline &&grainline) noexcept
: d(std::move(grainline.d))
{
}
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::operator=(VPieceGrainline &&grainline) noexcept -> VPieceGrainline &
{
std::swap(d, grainline.d);
return *this;
}
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::GetMainLine() const -> QLineF
{
return d->m_mainLine;
}
//---------------------------------------------------------------------------------------------------------------------
void VPieceGrainline::SetMainLine(const QLineF &mainLine)
{
d->m_mainLine = mainLine;
}
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::GetArrowType() const -> GrainlineArrowDirection
{
return d->m_arrowType;
}
//---------------------------------------------------------------------------------------------------------------------
void VPieceGrainline::SetArrowType(GrainlineArrowDirection arrowType)
{
d->m_arrowType = arrowType;
}
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::IsEnabled() const -> bool
{
return d->m_enabled;
}
//---------------------------------------------------------------------------------------------------------------------
void VPieceGrainline::SetEnabled(bool enabled)
{
d->m_enabled = enabled;
}
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::IsVisible() const -> bool
{
return d->m_visible;
}
//---------------------------------------------------------------------------------------------------------------------
void VPieceGrainline::SetVisible(bool visible)
{
d->m_visible = visible;
}
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::SecondaryLine() const -> QLineF
{
const QLineF mainLine = GetMainLine();
const QPointF center = mainLine.center();
QTransform t;
t.translate(center.x(), center.y());
t.rotate(90);
t.translate(-center.x(), -center.y());
return t.map(mainLine);
}
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::IsFourWays() const -> bool
{
return d->m_arrowType == GrainlineArrowDirection::fourWays ||
d->m_arrowType == GrainlineArrowDirection::twoWaysUpLeft ||
d->m_arrowType == GrainlineArrowDirection::twoWaysUpRight ||
d->m_arrowType == GrainlineArrowDirection::twoWaysDownLeft ||
d->m_arrowType == GrainlineArrowDirection::twoWaysDownRight ||
d->m_arrowType == GrainlineArrowDirection::threeWaysUpDownLeft ||
d->m_arrowType == GrainlineArrowDirection::threeWaysUpDownRight ||
d->m_arrowType == GrainlineArrowDirection::threeWaysUpLeftRight ||
d->m_arrowType == GrainlineArrowDirection::threeWaysDownLeftRight;
}
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::IsArrowUpEnabled() const -> bool
{
return d->m_arrowType == GrainlineArrowDirection::oneWayUp ||
d->m_arrowType == GrainlineArrowDirection::twoWaysUpDown ||
d->m_arrowType == GrainlineArrowDirection::twoWaysUpLeft ||
d->m_arrowType == GrainlineArrowDirection::twoWaysUpRight ||
d->m_arrowType == GrainlineArrowDirection::threeWaysUpDownLeft ||
d->m_arrowType == GrainlineArrowDirection::threeWaysUpDownRight ||
d->m_arrowType == GrainlineArrowDirection::threeWaysUpLeftRight ||
d->m_arrowType == GrainlineArrowDirection::fourWays;
}
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::IsArrowDownEnabled() const -> bool
{
return d->m_arrowType == GrainlineArrowDirection::oneWayDown ||
d->m_arrowType == GrainlineArrowDirection::twoWaysUpDown ||
d->m_arrowType == GrainlineArrowDirection::twoWaysDownLeft ||
d->m_arrowType == GrainlineArrowDirection::twoWaysDownRight ||
d->m_arrowType == GrainlineArrowDirection::threeWaysUpDownLeft ||
d->m_arrowType == GrainlineArrowDirection::threeWaysUpDownRight ||
d->m_arrowType == GrainlineArrowDirection::threeWaysDownLeftRight ||
d->m_arrowType == GrainlineArrowDirection::fourWays;
}
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::IsArrowLeftEnabled() const -> bool
{
return d->m_arrowType == GrainlineArrowDirection::fourWays ||
d->m_arrowType == GrainlineArrowDirection::twoWaysUpLeft ||
d->m_arrowType == GrainlineArrowDirection::twoWaysDownLeft ||
d->m_arrowType == GrainlineArrowDirection::threeWaysUpDownLeft ||
d->m_arrowType == GrainlineArrowDirection::threeWaysUpLeftRight ||
d->m_arrowType == GrainlineArrowDirection::threeWaysDownLeftRight;
}
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::IsArrowRightEnabled() const -> bool
{
return d->m_arrowType == GrainlineArrowDirection::fourWays ||
d->m_arrowType == GrainlineArrowDirection::twoWaysUpRight ||
d->m_arrowType == GrainlineArrowDirection::twoWaysDownRight ||
d->m_arrowType == GrainlineArrowDirection::threeWaysUpDownRight ||
d->m_arrowType == GrainlineArrowDirection::threeWaysUpLeftRight ||
d->m_arrowType == GrainlineArrowDirection::threeWaysDownLeftRight;
}
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::ArrowUp() const -> QPolygonF
{
const QLineF mainLine = GetMainLine();
const qreal rotation = M_PI + qDegreesToRadians(mainLine.angle());
const QPointF pt = mainLine.p2();
return {
{pt,
QPointF(pt.x() + arrowLength * cos(rotation + arrowAngle), pt.y() - arrowLength * sin(rotation + arrowAngle)),
QPointF(pt.x() + arrowLength * cos(rotation - arrowAngle), pt.y() - arrowLength * sin(rotation - arrowAngle)),
pt}};
}
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::ArrowDown() const -> QPolygonF
{
const QLineF mainLine = GetMainLine();
const qreal rotation = qDegreesToRadians(mainLine.angle());
const QPointF pt = mainLine.p1();
return {
{pt,
QPointF(pt.x() + arrowLength * cos(rotation + arrowAngle), pt.y() - arrowLength * sin(rotation + arrowAngle)),
QPointF(pt.x() + arrowLength * cos(rotation - arrowAngle), pt.y() - arrowLength * sin(rotation - arrowAngle)),
pt}};
}
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::ArrowLeft() const -> QPolygonF
{
const qreal rotation = 3 * M_PI / 2 + qDegreesToRadians(GetMainLine().angle());
const QPointF pt = SecondaryLine().p1();
return {
{pt,
QPointF(pt.x() + arrowLength * cos(rotation - arrowAngle), pt.y() - arrowLength * sin(rotation - arrowAngle)),
QPointF(pt.x() + arrowLength * cos(rotation + arrowAngle), pt.y() - arrowLength * sin(rotation + arrowAngle)),
pt}};
}
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::ArrowRight() const -> QPolygonF
{
const qreal rotation = M_PI / 2 + qDegreesToRadians(GetMainLine().angle());
const QPointF pt = SecondaryLine().p2();
return {
{pt,
QPointF(pt.x() + arrowLength * cos(rotation + arrowAngle), pt.y() - arrowLength * sin(rotation + arrowAngle)),
QPointF(pt.x() + arrowLength * cos(rotation - arrowAngle), pt.y() - arrowLength * sin(rotation - arrowAngle)),
pt}};
}
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::Shape() const -> GrainlineShape
{
const QLineF mainLine = GetMainLine();
if (mainLine.isNull())
{
return {};
}
// main arrow
QVector<QPointF> arrow1;
if (IsArrowDownEnabled())
{
arrow1 << ArrowDown();
}
else
{
arrow1 << mainLine.p1();
}
if (IsArrowUpEnabled())
{
arrow1 << ArrowUp();
}
else
{
arrow1 << mainLine.p2();
}
if (IsFourWays())
{
// secondary arrow
QVector<QPointF> arrow2;
const QLineF secondaryLine = SecondaryLine();
if (IsArrowLeftEnabled())
{
arrow2 << ArrowLeft();
}
else
{
arrow2 << secondaryLine.p1();
}
if (IsArrowRightEnabled())
{
arrow2 << ArrowRight();
}
else
{
arrow2 << secondaryLine.p2();
}
return {arrow1, arrow2};
}
return {arrow1};
}
//---------------------------------------------------------------------------------------------------------------------
/**
* @brief VPieceGrainline::IsContained checks, if all ends of the grainline, starting at pt, are contained in
* parent widget.
* @param boundingRect bounding rect of piece
* @param dX horizontal translation needed to put the arrow inside parent item
* @param dY vertical translation needed to put the arrow inside parent item
* @return true, if all ends of the grainline, starting at pt, are contained in the bounding rect of piece and
* false otherwise.
*/
auto VPieceGrainline::IsContained(const QRectF &boundingRect, qreal &dX, qreal &dY) const -> bool
{
dX = 0;
dY = 0;
const QLineF mainLine = GetMainLine();
QVector<QPointF> apt = {mainLine.p1(), mainLine.p2()};
if (IsFourWays())
{
const QLineF secondaryLine = SecondaryLine();
apt.append(secondaryLine.p1());
apt.append(secondaryLine.p2());
}
// single point differences
qreal dPtX;
qreal dPtY;
bool bInside = true;
for (auto item : apt)
{
dPtX = 0;
dPtY = 0;
if (boundingRect.contains(item))
{
continue;
}
if (item.x() < boundingRect.left())
{
dPtX = boundingRect.left() - item.x();
}
else if (item.x() > boundingRect.right())
{
dPtX = boundingRect.right() - item.x();
}
if (item.y() < boundingRect.top())
{
dPtY = boundingRect.top() - item.y();
}
else if (item.y() > boundingRect.bottom())
{
dPtY = boundingRect.bottom() - item.y();
}
if (fabs(dPtX) > fabs(dX))
{
dX = dPtX;
}
if (fabs(dPtY) > fabs(dY))
{
dY = dPtY;
}
bInside = false;
}
return bInside;
}
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::IsPositionValid(const QVector<QPointF> &contourPoints) const -> bool
{
QVector<QLineF> grainLine;
QLineF const mainLine = GetMainLine();
if (IsFourWays())
{
grainLine = {mainLine, SecondaryLine()};
}
grainLine = {mainLine};
for (auto line : qAsConst(grainLine))
{
QVector<QPointF> const points = VAbstractCurve::CurveIntersectLine(contourPoints, line);
for (const auto &point : points)
{
if (not VFuzzyComparePoints(line.p1(), point) && not VFuzzyComparePoints(line.p2(), point))
{
return false;
}
}
}
QPainterPath grainLinePath;
for (auto line : qAsConst(grainLine))
{
grainLinePath.addPath(VGObject::PainterPath(QVector<QPointF>{line.p1(), line.p2()}));
}
const QPainterPath contourPath = VGObject::PainterPath(contourPoints);
return contourPath.contains(grainLinePath);
}
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::IsShapeValid() const -> bool
{
GrainlineShape const shape = Shape();
return std::all_of(shape.cbegin(), shape.cend(), [](const auto &subShape) { return not subShape.isEmpty(); });
}
// Friend functions
//---------------------------------------------------------------------------------------------------------------------
auto operator<<(QDataStream &dataStream, const VPieceGrainline &grainline) -> QDataStream &
{
dataStream << *grainline.d;
return dataStream;
}
//---------------------------------------------------------------------------------------------------------------------
auto operator>>(QDataStream &dataStream, VPieceGrainline &grainline) -> QDataStream &
{
dataStream >> *grainline.d;
return dataStream;
}