You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
459 lines
12 KiB
459 lines
12 KiB
/* -*- 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 <qrect.h> |
|
#include "qwt_math.h" |
|
#include "qwt_clipper.h" |
|
|
|
static inline QwtDoubleRect boundingRect(const QwtPolygonF &polygon) |
|
{ |
|
#if QT_VERSION < 0x040000 |
|
if (polygon.isEmpty()) |
|
return QwtDoubleRect(0, 0, 0, 0); |
|
|
|
register const QwtDoublePoint *pd = polygon.data(); |
|
|
|
double minx, maxx, miny, maxy; |
|
minx = maxx = pd->x(); |
|
miny = maxy = pd->y(); |
|
pd++; |
|
|
|
for (uint i = 1; i < polygon.size(); i++, pd++) { |
|
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(); |
|
} |
|
return QwtDoubleRect(minx, miny, maxx - minx, maxy - miny); |
|
#else |
|
return polygon.boundingRect(); |
|
#endif |
|
} |
|
|
|
enum Edge { |
|
Left, |
|
Top, |
|
Right, |
|
Bottom, |
|
NEdges |
|
}; |
|
|
|
class QwtPolygonClipper: public QRect |
|
{ |
|
public: |
|
QwtPolygonClipper(const QRect &r); |
|
|
|
QwtPolygon clipPolygon(const QwtPolygon &) const; |
|
|
|
private: |
|
void clipEdge(Edge, const QwtPolygon &, QwtPolygon &) const; |
|
bool insideEdge(const QPoint &, Edge edge) const; |
|
QPoint intersectEdge(const QPoint &p1, |
|
const QPoint &p2, Edge edge) const; |
|
|
|
void addPoint(QwtPolygon &, uint pos, const QPoint &point) const; |
|
}; |
|
|
|
class QwtPolygonClipperF: public QwtDoubleRect |
|
{ |
|
public: |
|
QwtPolygonClipperF(const QwtDoubleRect &r); |
|
QwtPolygonF clipPolygon(const QwtPolygonF &) const; |
|
|
|
private: |
|
void clipEdge(Edge, const QwtPolygonF &, QwtPolygonF &) const; |
|
bool insideEdge(const QwtDoublePoint &, Edge edge) const; |
|
QwtDoublePoint intersectEdge(const QwtDoublePoint &p1, |
|
const QwtDoublePoint &p2, Edge edge) const; |
|
|
|
void addPoint(QwtPolygonF &, uint pos, const QwtDoublePoint &point) const; |
|
}; |
|
|
|
#if QT_VERSION >= 0x040000 |
|
class QwtCircleClipper: public QwtDoubleRect |
|
{ |
|
public: |
|
QwtCircleClipper(const QwtDoubleRect &r); |
|
QwtArray<QwtDoubleInterval> clipCircle( |
|
const QwtDoublePoint &, double radius) const; |
|
|
|
private: |
|
QList<QwtDoublePoint> cuttingPoints( |
|
Edge, const QwtDoublePoint &pos, double radius) const; |
|
double toAngle(const QwtDoublePoint &, const QwtDoublePoint &) const; |
|
}; |
|
#endif |
|
|
|
QwtPolygonClipper::QwtPolygonClipper(const QRect &r): |
|
QRect(r) |
|
{ |
|
} |
|
|
|
inline void QwtPolygonClipper::addPoint( |
|
QwtPolygon &pa, uint pos, const QPoint &point) const |
|
{ |
|
if ( uint(pa.size()) <= pos ) |
|
pa.resize(pos + 5); |
|
|
|
pa.setPoint(pos, point); |
|
} |
|
|
|
//! Sutherland-Hodgman polygon clipping |
|
QwtPolygon QwtPolygonClipper::clipPolygon(const QwtPolygon &pa) const |
|
{ |
|
if ( contains( pa.boundingRect() ) ) |
|
return pa; |
|
|
|
QwtPolygon cpa(pa.size()); |
|
|
|
clipEdge((Edge)0, pa, cpa); |
|
|
|
for ( uint edge = 1; edge < NEdges; edge++ ) { |
|
const QwtPolygon rpa = cpa; |
|
#if QT_VERSION < 0x040000 |
|
cpa.detach(); |
|
#endif |
|
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(qwtAbs(p1.x() - x)) / qwtAbs(dx); |
|
y = p1.y() + int(dy * m); |
|
break; |
|
case Top: |
|
y = top(); |
|
m = double(qwtAbs(p1.y() - y)) / qwtAbs(dy); |
|
x = p1.x() + int(dx * m); |
|
break; |
|
case Right: |
|
x = right(); |
|
m = double(qwtAbs(p1.x() - x)) / qwtAbs(dx); |
|
y = p1.y() + int(dy * m); |
|
break; |
|
case Bottom: |
|
y = bottom(); |
|
m = double(qwtAbs(p1.y() - y)) / qwtAbs(dy); |
|
x = p1.x() + int(dx * m); |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
return QPoint(x,y); |
|
} |
|
|
|
void QwtPolygonClipper::clipEdge(Edge edge, |
|
const QwtPolygon &pa, QwtPolygon &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 QwtDoubleRect &r): |
|
QwtDoubleRect(r) |
|
{ |
|
} |
|
|
|
inline void QwtPolygonClipperF::addPoint(QwtPolygonF &pa, uint pos, const QwtDoublePoint &point) const |
|
{ |
|
if ( uint(pa.size()) <= pos ) |
|
pa.resize(pos + 5); |
|
|
|
pa[(int)pos] = point; |
|
} |
|
|
|
//! Sutherland-Hodgman polygon clipping |
|
QwtPolygonF QwtPolygonClipperF::clipPolygon(const QwtPolygonF &pa) const |
|
{ |
|
if ( contains( ::boundingRect(pa) ) ) |
|
return pa; |
|
|
|
QwtPolygonF cpa(pa.size()); |
|
|
|
clipEdge((Edge)0, pa, cpa); |
|
|
|
for ( uint edge = 1; edge < NEdges; edge++ ) { |
|
const QwtPolygonF rpa = cpa; |
|
#if QT_VERSION < 0x040000 |
|
cpa.detach(); |
|
#endif |
|
clipEdge((Edge)edge, rpa, cpa); |
|
} |
|
|
|
return cpa; |
|
} |
|
|
|
bool QwtPolygonClipperF::insideEdge(const QwtDoublePoint &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; |
|
} |
|
|
|
QwtDoublePoint QwtPolygonClipperF::intersectEdge(const QwtDoublePoint &p1, |
|
const QwtDoublePoint &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(qwtAbs(p1.x() - x)) / qwtAbs(dx); |
|
y = p1.y() + int(dy * m); |
|
break; |
|
case Top: |
|
y = top(); |
|
m = double(qwtAbs(p1.y() - y)) / qwtAbs(dy); |
|
x = p1.x() + int(dx * m); |
|
break; |
|
case Right: |
|
x = right(); |
|
m = double(qwtAbs(p1.x() - x)) / qwtAbs(dx); |
|
y = p1.y() + int(dy * m); |
|
break; |
|
case Bottom: |
|
y = bottom(); |
|
m = double(qwtAbs(p1.y() - y)) / qwtAbs(dy); |
|
x = p1.x() + int(dx * m); |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
return QwtDoublePoint(x,y); |
|
} |
|
|
|
void QwtPolygonClipperF::clipEdge(Edge edge, |
|
const QwtPolygonF &pa, QwtPolygonF &cpa) const |
|
{ |
|
if ( pa.count() == 0 ) { |
|
cpa.resize(0); |
|
return; |
|
} |
|
|
|
unsigned int count = 0; |
|
|
|
QwtDoublePoint p1 = pa[0]; |
|
if ( insideEdge(p1, edge) ) |
|
addPoint(cpa, count++, p1); |
|
|
|
const uint nPoints = pa.size(); |
|
for ( uint i = 1; i < nPoints; i++ ) { |
|
const QwtDoublePoint 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); |
|
} |
|
|
|
#if QT_VERSION >= 0x040000 |
|
|
|
QwtCircleClipper::QwtCircleClipper(const QwtDoubleRect &r): |
|
QwtDoubleRect(r) |
|
{ |
|
} |
|
|
|
QwtArray<QwtDoubleInterval> QwtCircleClipper::clipCircle( |
|
const QwtDoublePoint &pos, double radius) const |
|
{ |
|
QList<QwtDoublePoint> points; |
|
for ( int edge = 0; edge < NEdges; edge++ ) |
|
points += cuttingPoints((Edge)edge, pos, radius); |
|
|
|
QwtArray<QwtDoubleInterval> intv; |
|
if ( points.size() <= 0 ) { |
|
QwtDoubleRect cRect(0, 0, 2 * radius, 2* radius); |
|
cRect.moveCenter(pos); |
|
if ( contains(cRect) ) |
|
intv += QwtDoubleInterval(0.0, 2 * M_PI); |
|
} else { |
|
QList<double> 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 += QwtDoubleInterval(angles[i], angles[i+1]); |
|
} else { |
|
for ( int i = 1; i < angles.size() - 1; i += 2) |
|
intv += QwtDoubleInterval(angles[i], angles[i+1]); |
|
intv += QwtDoubleInterval(angles.last(), angles.first()); |
|
} |
|
} |
|
|
|
return intv; |
|
} |
|
|
|
double QwtCircleClipper::toAngle( |
|
const QwtDoublePoint &from, const QwtDoublePoint &to) const |
|
{ |
|
if ( from.x() == to.x() ) |
|
return from.y() <= to.y() ? M_PI / 2.0 : 3 * M_PI / 2.0; |
|
|
|
const double m = qwtAbs((to.y() - from.y()) / (to.x() - from.x()) ); |
|
|
|
double angle = ::atan(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<QwtDoublePoint> QwtCircleClipper::cuttingPoints( |
|
Edge edge, const QwtDoublePoint &pos, double radius) const |
|
{ |
|
QList<QwtDoublePoint> points; |
|
|
|
if ( edge == Left || edge == Right ) { |
|
const double x = (edge == Left) ? left() : right(); |
|
if ( qwtAbs(pos.x() - x) < radius ) { |
|
const double off = ::sqrt(qwtSqr(radius) - qwtSqr(pos.x() - x)); |
|
const double y1 = pos.y() + off; |
|
if ( y1 >= top() && y1 <= bottom() ) |
|
points += QwtDoublePoint(x, y1); |
|
const double y2 = pos.y() - off; |
|
if ( y2 >= top() && y2 <= bottom() ) |
|
points += QwtDoublePoint(x, y2); |
|
} |
|
} else { |
|
const double y = (edge == Top) ? top() : bottom(); |
|
if ( qwtAbs(pos.y() - y) < radius ) { |
|
const double off = ::sqrt(qwtSqr(radius) - qwtSqr(pos.y() - y)); |
|
const double x1 = pos.x() + off; |
|
if ( x1 >= left() && x1 <= right() ) |
|
points += QwtDoublePoint(x1, y); |
|
const double x2 = pos.x() - off; |
|
if ( x2 >= left() && x2 <= right() ) |
|
points += QwtDoublePoint(x2, y); |
|
} |
|
} |
|
return points; |
|
} |
|
#endif |
|
|
|
QwtPolygon QwtClipper::clipPolygon( |
|
const QRect &clipRect, const QwtPolygon &polygon) |
|
{ |
|
QwtPolygonClipper clipper(clipRect); |
|
return clipper.clipPolygon(polygon); |
|
} |
|
|
|
QwtPolygonF QwtClipper::clipPolygonF( |
|
const QwtDoubleRect &clipRect, const QwtPolygonF &polygon) |
|
{ |
|
QwtPolygonClipperF clipper(clipRect); |
|
return clipper.clipPolygon(polygon); |
|
} |
|
|
|
#if QT_VERSION >= 0x040000 |
|
QwtArray<QwtDoubleInterval> QwtClipper::clipCircle( |
|
const QwtDoubleRect &clipRect, |
|
const QwtDoublePoint ¢er, double radius) |
|
{ |
|
QwtCircleClipper clipper(clipRect); |
|
return clipper.clipCircle(center, radius); |
|
} |
|
#endif
|
|
|