/**************************************************************************** Copyright (C) 2010-2012 AMVALOR, France. All rights reserved. Contact : micha.krebs@gmail.com Developers : Michaƫl KREBS (AMVALOR) This file is part of AMKgl Computree 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. Computree 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 Amkgl. If not, see . *****************************************************************************/ #include "polygonforpicking.h" #include #include #include #include #include "actions/tools/math.h" #define MIN_DISTANCE 5 namespace AMKgl { PolygonForPicking::PolygonForPicking() { m_moveWhat = MoveNone; m_pointToMoveIndex = -1; m_polygonClosed = false; } void PolygonForPicking::clear() { m_polygon.clear(); m_pointToMoveIndex = -1; m_moveWhat = MoveNone; m_polygonClosed = false; emit polygonChanged(); redrawOverlay(); } bool PolygonForPicking::isPolygonClosed() const { return m_polygonClosed; } bool PolygonForPicking::closePolygon() { if(!isPolygonClosed() && (m_polygon.size() > 2)) { m_polygonClosed = true; reorderPolygonInClockWiseOrder(); emit polygonChanged(); redrawOverlay(); } return isPolygonClosed(); } bool PolygonForPicking::removeLastPoint() { if(m_polygon.isEmpty()) return false; m_pointToMoveIndex = -1; m_moveWhat = MoveNone; m_polygonClosed = false; m_polygon.removeLast(); m_originPolygon = m_polygon; emit polygonChanged(); redrawOverlay(); return true; } void PolygonForPicking::setPolygon(const QPolygon &p) { m_pointToMoveIndex = -1; m_moveWhat = MoveNone; m_polygon = p; m_originPolygon = m_polygon; m_polygonClosed = false; closePolygon(); emit polygonChanged(); redrawOverlay(); } const QPolygon& PolygonForPicking::getPolygon() const { return m_polygon; } bool PolygonForPicking::mousePressEvent(QMouseEvent *e) { if((e->button() == Qt::LeftButton) && (m_polygon.size() > 1)) { double distance; Math::getClosestPolygonPointToPoint(m_polygon, m_mousePos, distance, &m_pointToMoveIndex); if((m_pointToMoveIndex != -1) && (distance < MIN_DISTANCE)) { m_moveWhat = MovePointOfPolygon; m_originPolygon = m_polygon; } else if(isMousePosInPolygon()) { m_moveWhat = MovePolygon; m_originPolygon = m_polygon; } } if(m_moveWhat != MoveNone) m_originPointMove = e->pos(); return true; } bool PolygonForPicking::mouseMoveEvent(QMouseEvent *e) { Q_UNUSED(e) m_mousePos = e->pos(); if(m_moveWhat == MovePointOfPolygon) { if(currentPointCanBeMoved()) { m_polygon[m_pointToMoveIndex] = m_mousePos; emit polygonChanged(); } } else if(m_moveWhat == MovePolygon) { m_polygon = m_originPolygon; m_polygon.translate(e->pos() - m_originPointMove); emit polygonChanged(); } redrawOverlay(); return true; } bool PolygonForPicking::mouseReleaseEvent(QMouseEvent *e) { if((e->button() == Qt::LeftButton) && (m_moveWhat == MoveNone)) { if(newPointCanBeAdded()) { int index = getWhereToInsertPoint(); m_polygon.insert(index, e->pos()); emit polygonChanged(); redrawOverlay(); } } m_moveWhat = MoveNone; return true; } bool PolygonForPicking::mouseDoubleClickEvent(QMouseEvent *e) { Q_UNUSED(e) return false; } bool PolygonForPicking::wheelEvent(QWheelEvent *e) { Q_UNUSED(e) return true; } bool PolygonForPicking::keyPressEvent(QKeyEvent *e) { Q_UNUSED(e) return false; } bool PolygonForPicking::keyReleaseEvent(QKeyEvent *e) { Q_UNUSED(e) return false; } void PolygonForPicking::drawOverlay(QPainter &painter) { painter.save(); // draw just lines of the polygon painter.setPen(createPenToUse(Qt::blue)); painter.setBrush(QColor(0, 0, 255, 50)); drawPolygon(m_polygon, painter); // draw the preview drawPreview(painter); /*painter.save(); drawInterestPoint(painter, info); painter.restore();*/ painter.restore(); } void PolygonForPicking::drawPolygon(const QPolygon &pol, QPainter &painter) { if(m_polygonClosed) { painter.drawPolygon(pol); } else { QVectorIterator it(pol); if(it.hasNext()) { QPoint lastP = it.next(); while(it.hasNext()) { const QPoint &p = it.next(); painter.drawLine(lastP, p); lastP = p; } } } } void PolygonForPicking::drawInterestPoint(QPainter &painter) { if(m_polygon.size() > 1) { int index = getWhereToInsertPoint(); if(index >= m_polygon.size()) --index; painter.setPen(createPenToUse(Qt::red)); painter.setBrush(Qt::red); painter.drawEllipse(m_polygon.at(index), 2, 2); --index; if(index < 0) index = m_polygon.size()-1; painter.drawEllipse(m_polygon.at(index), 2, 2); painter.setPen(createPenToUse(Qt::white)); int i = 0; foreach (const QPoint &p, m_polygon) { painter.drawText(p, QString().setNum(i++)); } } } void PolygonForPicking::drawPreview(QPainter &painter) { bool drawFilled = false; int nPoint = m_polygon.size(); if(nPoint > 1) { double distance; int index; QPoint p = Math::getClosestPolygonPointToPoint(m_polygon, m_mousePos, distance, &index); if(distance < MIN_DISTANCE) { painter.setPen(createPenToUse(Qt::yellow)); painter.setBrush(Qt::yellow); painter.drawEllipse(p, 5, 5); painter.setPen(createPenToUse(Qt::red)); painter.setBrush(Qt::red); painter.drawEllipse(p, 1, 1); } else if(!m_polygonClosed) { drawFilled = true; } else if(isMousePosInPolygon()){ painter.setPen(createPenToUse(Qt::yellow)); painter.setBrush(QBrush()); drawPolygon(m_polygon, painter); } } else if (nPoint != 0) { drawFilled = !m_polygonClosed; } if(drawFilled) { if(newPointCanBeAdded()) { painter.setPen(createPenToUse(Qt::yellow)); painter.setBrush(Qt::yellow); painter.drawLine(m_polygon.last(), m_mousePos); painter.drawEllipse(m_mousePos, 5, 5); painter.setPen(createPenToUse(Qt::red)); painter.setBrush(Qt::red); painter.drawEllipse(m_mousePos, 1, 1); } } } QVector PolygonForPicking::getClosestPointsToMousePos() const { QVector res; struct { QPoint center; bool operator()(const QPoint &o1, const QPoint &o2) { double dist1 = Math::distancePoints(o1, center); double dist2 = Math::distancePoints(o2, center); return dist2 > dist1; } } customSort; customSort.center = m_mousePos; QVector vec = m_polygon; std::sort(vec.begin(), vec.end(), customSort); foreach (const QPoint &p, vec) { PolygonForPicking::ClosestResult r; r.point = p; r.distance = Math::distancePoints(p, m_mousePos); r.index = m_polygon.indexOf(p); res.append(r); } return res; } bool PolygonForPicking::isMousePosInPolygon() const { return m_polygonClosed && Math::isPointInPolygon(m_polygon, m_mousePos); } void PolygonForPicking::redrawOverlay() { emit mustBeRedraw(); } int PolygonForPicking::getWhereToInsertPoint() const { return m_polygon.size(); } bool PolygonForPicking::newPointCanBeAdded() const { if(!m_polygon.isEmpty() && m_polygon.contains(m_mousePos)) return false; return (m_polygon.size() < 2) || (!m_polygonClosed && !isLineIntersectPolygon(m_polygon, QLineF(m_polygon.last(), m_mousePos))); } bool PolygonForPicking::currentPointCanBeMoved() const { if((m_pointToMoveIndex == -1) || (m_polygon.size() < 4)) return true; QPolygon nextPolygon = m_polygon; nextPolygon[m_pointToMoveIndex] = m_mousePos; int previous = m_pointToMoveIndex-1; int next = m_pointToMoveIndex+1; if(previous < 0) previous = nextPolygon.size()-1; if(next == nextPolygon.size()) next = 0; return !isLineIntersectPolygon(nextPolygon, QLineF(nextPolygon.at(m_pointToMoveIndex), nextPolygon.at(previous))) && !isLineIntersectPolygon(nextPolygon, QLineF(nextPolygon.at(m_pointToMoveIndex), nextPolygon.at(next))); } bool PolygonForPicking::isLineIntersectPolygon(const QPolygon &pol, const QLineF &line, QPointF *intersectionPoint) const { QVectorIterator it(pol); bool error = false; if(it.hasNext()) { QPoint lastP = it.next(); QPoint firstP = lastP; while(it.hasNext() && !error) { const QPoint &p = it.next(); error = isLineIntersectLine(line, QLineF(lastP, p), intersectionPoint); lastP = p; } if(!error && m_polygonClosed) error = isLineIntersectLine(line, QLineF(lastP, firstP), intersectionPoint); } return error; } bool PolygonForPicking::isLineIntersectLine(const QLineF &line1, const QLineF &line2, QPointF *intersectionPoint) const { bool intersect = false; QPointF commonPoint; if(line1.p1() == line2.p1()) commonPoint = line1.p1(); else if(line1.p2() == line2.p2()) commonPoint = line1.p2(); else if(line1.p1() == line2.p2()) commonPoint = line1.p1(); else if(line1.p2() == line2.p1()) commonPoint = line1.p2(); QPointF intersection; QLineF::IntersectType type = line1.intersects(line2, &intersection); if(type == QLineF::BoundedIntersection) { if(commonPoint.isNull() || (intersection != commonPoint)) { intersect = true; if(intersectionPoint != nullptr) *intersectionPoint = intersection; } } else if(type == QLineF::UnboundedIntersection) { if(Math::distancePointSegment(line1, intersection) < 2 && Math::distancePointSegment(line2, intersection) < 2) { intersect = true; if(intersectionPoint != nullptr) *intersectionPoint = intersection; } } return intersect; } void PolygonForPicking::reorderPolygonInClockWiseOrder() { if(m_polygon.size() > 2) { // find the point at min X int nPoint = m_polygon.size(); int minX = m_polygon.boundingRect().right(); int minPIndex = -1; int i = 0; foreach (const QPointF &p, m_polygon) { if(p.x() < minX) { minX = p.x(); minPIndex = i; } ++i; } QPolygon newPolygon; int next = minPIndex+1; int goTo = 1; if(next == nPoint) next = 0; if(m_polygon.at(next).y() > m_polygon.at(minPIndex).y()) goTo = -1; i = minPIndex; for(int j=0; j