/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_clipper.h" #include "qwt_math.h" #include #if QT_VERSION < 0x040601 #define qAtan(x) ::atan(x) #endif static inline QRectF boundingRect( const QPolygonF &polygon ) { return polygon.boundingRect(); } enum Edge { Left, Top, Right, Bottom, NEdges }; class QwtPolygonClipper: public QRect { public: QwtPolygonClipper( const QRect &r ); QPolygon clipPolygon( const QPolygon & ) const; private: void clipEdge( Edge, const QPolygon &, QPolygon & ) const; bool insideEdge( const QPoint &, Edge edge ) const; QPoint intersectEdge( const QPoint &p1, const QPoint &p2, Edge edge ) const; void addPoint( QPolygon &, uint pos, const QPoint &point ) const; }; class QwtPolygonClipperF: public QRectF { public: QwtPolygonClipperF( const QRectF &r ); QPolygonF clipPolygon( const QPolygonF & ) const; private: void clipEdge( Edge, const QPolygonF &, QPolygonF & ) const; bool insideEdge( const QPointF &, Edge edge ) const; QPointF intersectEdge( const QPointF &p1, const QPointF &p2, Edge edge ) const; void addPoint( QPolygonF &, uint pos, const QPointF &point ) const; }; class QwtCircleClipper: public QRectF { public: QwtCircleClipper( const QRectF &r ); QVector clipCircle( const QPointF &, double radius ) const; private: QList cuttingPoints( Edge, const QPointF &pos, double radius ) const; double toAngle( const QPointF &, const QPointF & ) const; }; QwtPolygonClipper::QwtPolygonClipper( const QRect &r ): QRect( r ) { } inline void QwtPolygonClipper::addPoint( QPolygon &pa, uint pos, const QPoint &point ) const { if ( uint( pa.size() ) <= pos ) pa.resize( pos + 5 ); pa.setPoint( pos, point ); } //! Sutherland-Hodgman polygon clipping QPolygon QwtPolygonClipper::clipPolygon( const QPolygon &pa ) const { if ( contains( pa.boundingRect() ) ) return pa; QPolygon cpa( pa.size() ); clipEdge( ( Edge )0, pa, cpa ); for ( uint edge = 1; edge < NEdges; edge++ ) { const QPolygon rpa = cpa; clipEdge( ( Edge )edge, rpa, cpa ); } return cpa; } bool QwtPolygonClipper::insideEdge( const QPoint &p, Edge edge ) const { switch ( edge ) { case Left: return p.x() > left(); case Top: return p.y() > top(); case Right: return p.x() < right(); case Bottom: return p.y() < bottom(); default: break; } return false; } QPoint QwtPolygonClipper::intersectEdge( const QPoint &p1, const QPoint &p2, Edge edge ) const { int x = 0, y = 0; double m = 0; const double dy = p2.y() - p1.y(); const double dx = p2.x() - p1.x(); switch ( edge ) { case Left: x = left(); m = double( qAbs( p1.x() - x ) ) / qAbs( dx ); y = p1.y() + int( dy * m ); break; case Top: y = top(); m = double( qAbs( p1.y() - y ) ) / qAbs( dy ); x = p1.x() + int( dx * m ); break; case Right: x = right(); m = double( qAbs( p1.x() - x ) ) / qAbs( dx ); y = p1.y() + int( dy * m ); break; case Bottom: y = bottom(); m = double( qAbs( p1.y() - y ) ) / qAbs( dy ); x = p1.x() + int( dx * m ); break; default: break; } return QPoint( x, y ); } void QwtPolygonClipper::clipEdge( Edge edge, const QPolygon &pa, QPolygon &cpa ) const { if ( pa.count() == 0 ) { cpa.resize( 0 ); return; } unsigned int count = 0; QPoint p1 = pa.point( 0 ); if ( insideEdge( p1, edge ) ) addPoint( cpa, count++, p1 ); const uint nPoints = pa.size(); for ( uint i = 1; i < nPoints; i++ ) { const QPoint p2 = pa.point( i ); if ( insideEdge( p2, edge ) ) { if ( insideEdge( p1, edge ) ) addPoint( cpa, count++, p2 ); else { addPoint( cpa, count++, intersectEdge( p1, p2, edge ) ); addPoint( cpa, count++, p2 ); } } else { if ( insideEdge( p1, edge ) ) addPoint( cpa, count++, intersectEdge( p1, p2, edge ) ); } p1 = p2; } cpa.resize( count ); } QwtPolygonClipperF::QwtPolygonClipperF( const QRectF &r ): QRectF( r ) { } inline void QwtPolygonClipperF::addPoint( QPolygonF &pa, uint pos, const QPointF &point ) const { if ( uint( pa.size() ) <= pos ) pa.resize( pos + 5 ); pa[( int )pos] = point; } //! Sutherland-Hodgman polygon clipping QPolygonF QwtPolygonClipperF::clipPolygon( const QPolygonF &pa ) const { if ( contains( ::boundingRect( pa ) ) ) return pa; QPolygonF cpa( pa.size() ); clipEdge( ( Edge )0, pa, cpa ); for ( uint edge = 1; edge < NEdges; edge++ ) { const QPolygonF rpa = cpa; clipEdge( ( Edge )edge, rpa, cpa ); } return cpa; } bool QwtPolygonClipperF::insideEdge( const QPointF &p, Edge edge ) const { switch ( edge ) { case Left: return p.x() > left(); case Top: return p.y() > top(); case Right: return p.x() < right(); case Bottom: return p.y() < bottom(); default: break; } return false; } QPointF QwtPolygonClipperF::intersectEdge( const QPointF &p1, const QPointF &p2, Edge edge ) const { double x = 0.0, y = 0.0; double m = 0; const double dy = p2.y() - p1.y(); const double dx = p2.x() - p1.x(); switch ( edge ) { case Left: x = left(); m = double( qAbs( p1.x() - x ) ) / qAbs( dx ); y = p1.y() + int( dy * m ); break; case Top: y = top(); m = double( qAbs( p1.y() - y ) ) / qAbs( dy ); x = p1.x() + int( dx * m ); break; case Right: x = right(); m = double( qAbs( p1.x() - x ) ) / qAbs( dx ); y = p1.y() + int( dy * m ); break; case Bottom: y = bottom(); m = double( qAbs( p1.y() - y ) ) / qAbs( dy ); x = p1.x() + int( dx * m ); break; default: break; } return QPointF( x, y ); } void QwtPolygonClipperF::clipEdge( Edge edge, const QPolygonF &pa, QPolygonF &cpa ) const { if ( pa.count() == 0 ) { cpa.resize( 0 ); return; } unsigned int count = 0; QPointF p1 = pa[0]; if ( insideEdge( p1, edge ) ) addPoint( cpa, count++, p1 ); const uint nPoints = pa.size(); for ( uint i = 1; i < nPoints; i++ ) { const QPointF p2 = pa[( int )i]; if ( insideEdge( p2, edge ) ) { if ( insideEdge( p1, edge ) ) addPoint( cpa, count++, p2 ); else { addPoint( cpa, count++, intersectEdge( p1, p2, edge ) ); addPoint( cpa, count++, p2 ); } } else { if ( insideEdge( p1, edge ) ) addPoint( cpa, count++, intersectEdge( p1, p2, edge ) ); } p1 = p2; } cpa.resize( count ); } QwtCircleClipper::QwtCircleClipper( const QRectF &r ): QRectF( r ) { } QVector QwtCircleClipper::clipCircle( const QPointF &pos, double radius ) const { QList points; for ( int edge = 0; edge < NEdges; edge++ ) points += cuttingPoints( ( Edge )edge, pos, radius ); QVector intv; if ( points.size() <= 0 ) { QRectF cRect( 0, 0, 2 * radius, 2* radius ); cRect.moveCenter( pos ); if ( contains( cRect ) ) intv += QwtInterval( 0.0, 2 * M_PI ); } else { QList angles; for ( int i = 0; i < points.size(); i++ ) angles += toAngle( pos, points[i] ); qSort( angles ); const int in = contains( qwtPolar2Pos( pos, radius, angles[0] + ( angles[1] - angles[0] ) / 2 ) ); if ( in ) { for ( int i = 0; i < angles.size() - 1; i += 2 ) intv += QwtInterval( angles[i], angles[i+1] ); } else { for ( int i = 1; i < angles.size() - 1; i += 2 ) intv += QwtInterval( angles[i], angles[i+1] ); intv += QwtInterval( angles.last(), angles.first() ); } } return intv; } double QwtCircleClipper::toAngle( const QPointF &from, const QPointF &to ) const { if ( from.x() == to.x() ) return from.y() <= to.y() ? M_PI / 2.0 : 3 * M_PI / 2.0; const double m = qAbs( ( to.y() - from.y() ) / ( to.x() - from.x() ) ); double angle = qAtan( m ); if ( to.x() > from.x() ) { if ( to.y() > from.y() ) angle = 2 * M_PI - angle; } else { if ( to.y() > from.y() ) angle = M_PI + angle; else angle = M_PI - angle; } return angle; } QList QwtCircleClipper::cuttingPoints( Edge edge, const QPointF &pos, double radius ) const { QList points; if ( edge == Left || edge == Right ) { const double x = ( edge == Left ) ? left() : right(); if ( qAbs( pos.x() - x ) < radius ) { const double off = qSqrt( qwtSqr( radius ) - qwtSqr( pos.x() - x ) ); const double y1 = pos.y() + off; if ( y1 >= top() && y1 <= bottom() ) points += QPointF( x, y1 ); const double y2 = pos.y() - off; if ( y2 >= top() && y2 <= bottom() ) points += QPointF( x, y2 ); } } else { const double y = ( edge == Top ) ? top() : bottom(); if ( qAbs( pos.y() - y ) < radius ) { const double off = qSqrt( qwtSqr( radius ) - qwtSqr( pos.y() - y ) ); const double x1 = pos.x() + off; if ( x1 >= left() && x1 <= right() ) points += QPointF( x1, y ); const double x2 = pos.x() - off; if ( x2 >= left() && x2 <= right() ) points += QPointF( x2, y ); } } return points; } /*! Sutherland-Hodgman polygon clipping \param clipRect Clip rectangle \param polygon Polygon \return Clipped polygon */ QPolygon QwtClipper::clipPolygon( const QRect &clipRect, const QPolygon &polygon ) { QwtPolygonClipper clipper( clipRect ); return clipper.clipPolygon( polygon ); } /*! Sutherland-Hodgman polygon clipping \param clipRect Clip rectangle \param polygon Polygon \return Clipped polygon */ QPolygonF QwtClipper::clipPolygonF( const QRectF &clipRect, const QPolygonF &polygon ) { QwtPolygonClipperF clipper( clipRect ); return clipper.clipPolygon( polygon ); } /*! Circle clipping clipCircle() devides a circle into intervals of angles representing arcs of the circle. When the circle is completely inside the clip rectangle an interval [0.0, 2 * M_PI] is returned. \param clipRect Clip rectangle \param center Center of the circle \param radius Radius of the circle \return Arcs of the circle */ QVector QwtClipper::clipCircle( const QRectF &clipRect, const QPointF ¢er, double radius ) { QwtCircleClipper clipper( clipRect ); return clipper.clipCircle( center, radius ); }