diff --git a/MainWindow.cpp b/MainWindow.cpp index e655537..5a8d17e 100644 --- a/MainWindow.cpp +++ b/MainWindow.cpp @@ -518,13 +518,13 @@ uid MainWindow::createEllipseTextureMapping(uid mappingId, else { Paint::ptr paint = mappingManager->getPaintById(paintId); - Q_ASSERT(src.size() == 4 && dst.size() == 4); + Q_ASSERT(src.size() == 5 && dst.size() == 5); - Shape::ptr inputEllipse( new Ellipse(src[0], src[1], src[2], dst[3])); - Shape::ptr outputEllipse(new Ellipse(dst[0], dst[1], dst[2], dst[3])); + Shape::ptr inputEllipse( new Ellipse(src[0], src[1], src[2], dst[3], dst[4])); + Shape::ptr outputEllipse(new Ellipse(dst[0], dst[1], dst[2], dst[3], dst[4])); // Add it to the manager. - Mapping::ptr mapping(new TextureMapping(paint, inputEllipse, outputEllipse, mappingId)); + Mapping::ptr mapping(new TextureMapping(paint, outputEllipse, inputEllipse, mappingId)); uid id = mappingManager->addMapping(mapping); // Add it to the GUI. @@ -595,7 +595,7 @@ uid MainWindow::createTriangleColorMapping(uid mappingId, uid MainWindow::createEllipseColorMapping(uid mappingId, uid paintId, - const QList &dst) + const QVector &dst) { // Cannot create element with already existing id or element for which no paint exists. if (Mapping::getUidAllocator().exists(mappingId) || diff --git a/MainWindow.h b/MainWindow.h index 1b532b3..b1282cc 100644 --- a/MainWindow.h +++ b/MainWindow.h @@ -141,7 +141,7 @@ public slots: */ uid createEllipseColorMapping(uid mappingId, uid paintId, - const QList &dst); + const QVector &dst); private: // Methods. diff --git a/ProjectReader.cpp b/ProjectReader.cpp index 69b37d1..6e4568e 100644 --- a/ProjectReader.cpp +++ b/ProjectReader.cpp @@ -156,6 +156,21 @@ void ProjectReader::parseMapping(const QDomElement& mapping) _xml.raiseError(QObject::tr("Cannot create mesh texture mapping")); } + else if (mappingAttrType == "ellipse_texture") + { + // Parse destination ellipse. + _parseEllipse(dst, dstPoints); + + // Get / parse source shape. + QDomElement src = mapping.firstChildElement("source"); + QVector srcPoints; + _parseEllipse(src, srcPoints); + + uid id = _window->createEllipseTextureMapping(mappingAttrId.toInt(), mappingAttrPaintId.toInt(), srcPoints, dstPoints); + + if (id == NULL_UID) + _xml.raiseError(QObject::tr("Cannot create ellipse texture mapping")); + } else if (mappingAttrType == "triangle_color") { // Parse destination triangle. @@ -168,7 +183,7 @@ void ProjectReader::parseMapping(const QDomElement& mapping) } else if (mappingAttrType == "quad_color") { - // Parse destination triangle. + // Parse destination quad. _parseQuad(dst, dstPoints); uid id = _window->createQuadColorMapping(mappingAttrId.toInt(), mappingAttrPaintId.toInt(), dstPoints); @@ -176,11 +191,21 @@ void ProjectReader::parseMapping(const QDomElement& mapping) if (id == NULL_UID) _xml.raiseError(QObject::tr("Cannot create quad color mapping")); } + else if (mappingAttrType == "ellipse_color") + { + // Parse destination ellipse. + _parseEllipse(dst, dstPoints); + + uid id = _window->createEllipseColorMapping(mappingAttrId.toInt(), mappingAttrPaintId.toInt(), dstPoints); + + if (id == NULL_UID) + _xml.raiseError(QObject::tr("Cannot create ellipse color mapping")); + } else _xml.raiseError(QObject::tr("Unsupported mapping type: %1.").arg(mappingAttrType)); } -void ProjectReader::_parseStandardShape(const QString& type, int nVertices, const QDomElement& shape, QVector& points) +void ProjectReader::_parseStandardShape(const QString& type, const QDomElement& shape, QVector& points, int nVertices) { // Check that the element is really a triangle. QString typeAttr = shape.attribute("shape", ""); @@ -198,19 +223,25 @@ void ProjectReader::_parseStandardShape(const QString& type, int nVertices, cons vertex = vertex.nextSiblingElement("vertex"); } - if (points.size() != nVertices) - _xml.raiseError(QObject::tr("Shape has %1 vertices: expected %2.").arg(points.size()).arg(nVertices)); + if (nVertices >= 0 && points.size() != nVertices) + _xml.raiseError(QObject::tr("Shape of type '%1' has %2 vertices: expected %3.").arg(type).arg(points.size()).arg(nVertices)); } void ProjectReader::_parseQuad(const QDomElement& quad, QVector& points) { - _parseStandardShape("quad", 4, quad, points); + _parseStandardShape("quad", quad, points, 4); } - void ProjectReader::_parseTriangle(const QDomElement& triangle, QVector& points) { - _parseStandardShape("triangle", 3, triangle, points); + _parseStandardShape("triangle", triangle, points, 3); +} + +void ProjectReader::_parseEllipse(const QDomElement& ellipse, QVector& points) +{ + _parseStandardShape("ellipse", ellipse, points); + if (points.size() != 4 && points.size() != 5) + _xml.raiseError(QObject::tr("Shape has %1 vertices: expected 4 or 5.").arg(points.size())); } void ProjectReader::_parseMesh(const QDomElement& mesh, QVector& points, int& nColumns, int& nRows) diff --git a/ProjectReader.h b/ProjectReader.h index 747828e..4fcf71a 100644 --- a/ProjectReader.h +++ b/ProjectReader.h @@ -37,10 +37,11 @@ private: void parsePaint(const QDomElement& paint); void parseMapping(const QDomElement& mapping); - void _parseStandardShape(const QString& type, int nVertices, const QDomElement& shape, QVector& points); + void _parseStandardShape(const QString& type, const QDomElement& shape, QVector& points, int nVertices=-1); void _parseQuad(const QDomElement& quad, QVector& points); void _parseTriangle(const QDomElement& triangle, QVector& points); void _parseMesh(const QDomElement& mesh, QVector& points, int& nColumns, int& nRows); + void _parseEllipse(const QDomElement& ellipse, QVector& points); QPointF _parseVertex(const QDomElement& vertex); // void readPaint(); //Paint *item); diff --git a/Shape.cpp b/Shape.cpp index 8c8d8a4..c43c980 100644 --- a/Shape.cpp +++ b/Shape.cpp @@ -458,12 +458,10 @@ void Ellipse::setVertex(int i, const QPointF& v) // Center control point (make sure it stays inside!). else if (hasCenterControl()) { - // Map point as vector on a unit circle. - QVector2D vector(toUnitCircle().map(v)); - // Clip control point. - Shape::setVertex(4, vector.length() <= 1 ? - v : - fromUnitCircle().map(vector.normalized().toPointF())); + Shape::setVertex(4, clipInside(v)); } + + // Just to be sure. + sanitize(); } diff --git a/Shape.h b/Shape.h index c4f2378..b625149 100644 --- a/Shape.h +++ b/Shape.h @@ -213,6 +213,16 @@ protected: class Ellipse : public Shape { public: Ellipse() {} + Ellipse(QPointF p1, QPointF p2, QPointF p3, QPointF p4, QPointF p5) + { + _addVertex(p1); + _addVertex(p2); + _addVertex(p3); + _addVertex(p4); + _addVertex(p5); + sanitize(); + } + Ellipse(QPointF p1, QPointF p2, QPointF p3, QPointF p4, bool hasCenterControl=true) { _addVertex(p1); @@ -220,47 +230,94 @@ public: _addVertex(p3); _addVertex(p4); if (hasCenterControl) - _addVertex(getCenter()); // add a point in the center + _addVertex(getCenter()); + sanitize(); } + + /// Remaps points so as to make sure this is a correct ellipse, keeping vertices 0 and 2 as + /// reference for the horizzontal axis. + void sanitize() + { + // Get horizontal axis rotated 90 degrees CW + QVector2D hAxis = getHorizontalAxis(); + const QVector2D center(getCenter()); + QVector2D hAxisRotated(hAxis.y(), -hAxis.x()); + + // Project vertex 1 onto it. + QVector2D vAxisNormalized = hAxisRotated.normalized(); + + QVector2D vFromCenter = QVector2D(getVertex(1)) - center; + const QVector2D& projection = QVector2D::dotProduct( vFromCenter, vAxisNormalized ) * vAxisNormalized; + Shape::setVertex(1, (center + projection).toPointF()); + Shape::setVertex(3, (center - projection).toPointF()); + + if (hasCenterControl()) + { + // Clip control point. + Shape::setVertex(4, clipInside(getVertex(4))); + } + } + virtual ~Ellipse() {} virtual QString getType() const { return "ellipse"; } - qreal getRotationRadians() const { + qreal getRotationRadians() const + { QVector2D hAxis = getHorizontalAxis(); return atan2( hAxis.y(), hAxis.x() ); } - qreal getRotation() const { + qreal getRotation() const + { return radiansToDegrees( getRotationRadians() ); } - bool hasCenterControl() const { + bool hasCenterControl() const + { return (nVertices() == 5); } + /// If v is outside boundaries, remap it to the border. + QPointF clipInside(const QPointF& v) const + { + // Map point as vector on a unit circle. + QVector2D vector(toUnitCircle().map(v)); + + // Clip control point. + return (vector.length() <= 1 ? + v : + fromUnitCircle().map(vector.normalized().toPointF())); + + } + // QRect getBoundingRect() const { // return QRect(0, getVerticalAxis().manhattanLength(), // getHorizontalAxis().manhattanLength(), getVerticalAxis().manhattanLength()); // } // - QPointF getCenter() const { + QPointF getCenter() const + { return (QVector2D(getVertex(0)) - (getHorizontalAxis() / 2)).toPointF(); } - QVector2D getHorizontalAxis() const { + QVector2D getHorizontalAxis() const + { return QVector2D(getVertex(0)) - QVector2D(getVertex(2)); } - QVector2D getVerticalAxis() const { + QVector2D getVerticalAxis() const + { return QVector2D(getVertex(1)) - QVector2D(getVertex(3)); } - qreal getHorizontalRadius() const { + qreal getHorizontalRadius() const + { return getHorizontalAxis().length() / 2; } - qreal getVerticalRadius() const { + qreal getVerticalRadius() const + { return getVerticalAxis().length() / 2; } @@ -276,7 +333,8 @@ public: */ virtual bool includesPoint(qreal x, qreal y); - virtual bool includesPoint(const QPointF& p) { + virtual bool includesPoint(const QPointF& p) + { return includesPoint(p.x(), p.y()); }