Optimise 3d clipping to work directly with coordinate arrays, so that

we avoid the (signficant) overhead of point conversion and sequence
allocations
This commit is contained in:
Nyall Dawson 2022-05-25 15:20:35 +10:00
parent 54ab1dc4fe
commit f439a649d6
7 changed files with 430 additions and 203 deletions

View File

@ -358,6 +358,11 @@ corresponds to the last point in the line.
double zAt( int index ) const; double zAt( int index ) const;
%Docstring %Docstring
Returns the z-coordinate of the specified node in the line string. Returns the z-coordinate of the specified node in the line string.

View File

@ -77,27 +77,7 @@ Trims the given polygon to a rectangular box, by modifying the given polygon in
:param clipRect: clipping rectangle :param clipRect: clipping rectangle
%End %End
static void trimPolygon( QgsLineString &polygon, const QgsBox3d &clipRect );
%Docstring
Trims the given polygon to a pseudo 3D box, by modifying the given polygon in place.
:param polygon: polygon as 2D or 3D coordinates
:param clipRect: clipping 3D box
.. versionadded:: 3.22
%End
static QgsLineString clipped3dLine( const QgsCurve &curve, const QgsBox3d &clipExtent );
%Docstring
Takes a ``curve`` with 3D coordinates and clips it to clipExtent
:param curve: the linestring to clip
:param clipExtent: clipping bounds
:return: clipped line coordinates
.. versionadded:: 3.22
%End
static QPolygonF clippedLine( const QgsCurve &curve, const QgsRectangle &clipExtent ); static QPolygonF clippedLine( const QgsCurve &curve, const QgsRectangle &clipExtent );
%Docstring %Docstring

View File

@ -458,6 +458,47 @@ class CORE_EXPORT QgsLineString: public QgsCurve
return mM.constData(); return mM.constData();
} }
/**
* Returns the x vertex values as a vector.
* \note Not available in Python bindings
* \since QGIS 3.26
*/
QVector< double > xVector() const SIP_SKIP
{
return mX;
}
/**
* Returns the y vertex values as a vector.
* \note Not available in Python bindings
* \since QGIS 3.26
*/
QVector< double > yVector() const SIP_SKIP
{
return mY;
}
/**
* Returns the z vertex values as a vector.
* \note Not available in Python bindings
* \since QGIS 3.26
*/
QVector< double > zVector() const SIP_SKIP
{
return mZ;
}
/**
* Returns the m vertex values as a vector.
* \note Not available in Python bindings
* \since QGIS 3.26
*/
QVector< double > mVector() const SIP_SKIP
{
return mM;
}
#ifndef SIP_RUN #ifndef SIP_RUN
/** /**

View File

@ -37,22 +37,29 @@ const double QgsClipper::MIN_Y = -16000;
const double QgsClipper::SMALL_NUM = 1e-12; const double QgsClipper::SMALL_NUM = 1e-12;
QgsLineString QgsClipper::clipped3dLine( const QgsCurve &curve, const QgsBox3d &clipExtent ) void QgsClipper::clipped3dLine( const QVector< double > &xIn, const QVector< double > &yIn, const QVector<double> &zIn, QVector<double> &x, QVector<double> &y, QVector<double> &z, const QgsBox3d &clipExtent )
{ {
double p0x, p0y, p0z, p1x = 0.0, p1y = 0.0, p1z = 0.0; //original coordinates double p0x, p0y, p0z, p1x = 0.0, p1y = 0.0, p1z = 0.0; //original coordinates
double p1x_c, p1y_c, p1z_c; //clipped end coordinates double p1x_c, p1y_c, p1z_c; //clipped end coordinates
double lastClipX = 0.0, lastClipY = 0.0, lastClipZ = 0.0; // last successfully clipped coordinates double lastClipX = 0.0, lastClipY = 0.0, lastClipZ = 0.0; // last successfully clipped coordinates
QgsPointSequence seq; const int nPoints = xIn.size();
for ( QgsAbstractGeometry::vertex_iterator ite = curve.vertices_begin() ; ite != curve.vertices_end(); ++ite ) x.reserve( nPoints );
y.reserve( nPoints );
z.reserve( nPoints );
const double *sourceX = xIn.data();
const double *sourceY = yIn.data();
const double *sourceZ = zIn.data();
for ( int i = 0; i < nPoints; ++i )
{ {
QgsPoint curPoint = *ite; if ( i == 0 )
if ( ite == curve.vertices_begin() )
{ {
p1x = curPoint.x(); p1x = *sourceX++;
p1y = curPoint.y(); p1y = *sourceY++;
p1z = curPoint.z(); p1z = *sourceZ++;
} }
else else
{ {
@ -60,9 +67,9 @@ QgsLineString QgsClipper::clipped3dLine( const QgsCurve &curve, const QgsBox3d &
p0y = p1y; p0y = p1y;
p0z = p1z; p0z = p1z;
p1x = curPoint.x(); p1x = *sourceX++;
p1y = curPoint.y(); p1y = *sourceY++;
p1z = curPoint.z(); p1z = *sourceZ++;
p1x_c = p1x; p1x_c = p1x;
p1y_c = p1y; p1y_c = p1y;
@ -71,31 +78,33 @@ QgsLineString QgsClipper::clipped3dLine( const QgsCurve &curve, const QgsBox3d &
// TODO: should be in 3D // TODO: should be in 3D
if ( clipLineSegment( clipExtent, p0x, p0y, p0z, p1x_c, p1y_c, p1z_c ) ) if ( clipLineSegment( clipExtent, p0x, p0y, p0z, p1x_c, p1y_c, p1z_c ) )
{ {
bool newLine = !seq.isEmpty() && ( !qgsDoubleNear( p0x, lastClipX ) bool newLine = !x.isEmpty() && ( !qgsDoubleNear( p0x, lastClipX )
|| !qgsDoubleNear( p0y, lastClipY ) || !qgsDoubleNear( p0y, lastClipY )
|| !qgsDoubleNear( p0z, lastClipZ ) ); || !qgsDoubleNear( p0z, lastClipZ ) );
if ( newLine ) if ( newLine )
{ {
//add edge points to connect old and new line //add edge points to connect old and new line
// TODO: should be (really) in 3D // TODO: should be (really) in 3D
connectSeparatedLines( lastClipX, lastClipY, lastClipZ, p0x, p0y, p0z, clipExtent, seq ); connectSeparatedLines( lastClipX, lastClipY, lastClipZ, p0x, p0y, p0z, clipExtent, x, y, z );
} }
if ( seq.isEmpty() || newLine ) if ( x.isEmpty() || newLine )
{ {
//add first point //add first point
seq << QgsPoint( p0x, p0y, p0z ) ; x << p0x;
y << p0y;
z << p0z;
} }
//add second point //add second point
lastClipX = p1x_c; lastClipX = p1x_c;
lastClipY = p1y_c; lastClipY = p1y_c;
lastClipZ = p1z_c; lastClipZ = p1z_c;
seq << QgsPoint( p1x_c, p1y_c, p1z_c ) ; x << p1x_c;
y << p1y_c;
z << p1z_c;
} }
} }
} }
return QgsLineString( seq );
} }
QPolygonF QgsClipper::clippedLine( const QgsCurve &curve, const QgsRectangle &clipExtent ) QPolygonF QgsClipper::clippedLine( const QgsCurve &curve, const QgsRectangle &clipExtent )
@ -258,7 +267,7 @@ void QgsClipper::connectSeparatedLines( double x0, double y0, double x1, double
} }
void QgsClipper::connectSeparatedLines( double x0, double y0, double z0, double x1, double y1, double z1, void QgsClipper::connectSeparatedLines( double x0, double y0, double z0, double x1, double y1, double z1,
const QgsBox3d &clipRect, QgsPointSequence &pts ) const QgsBox3d &clipRect, QVector< double > &ptsX, QVector< double > &ptsY, QVector<double> &ptsZ )
{ {
// TODO: really relevant and sufficient? // TODO: really relevant and sufficient?
double meanZ = ( z0 + z1 ) / 2.0; double meanZ = ( z0 + z1 ) / 2.0;
@ -272,18 +281,26 @@ void QgsClipper::connectSeparatedLines( double x0, double y0, double z0, double
} }
else if ( qgsDoubleNear( y1, clipRect.yMaximum() ) ) else if ( qgsDoubleNear( y1, clipRect.yMaximum() ) )
{ {
pts << QgsPoint( clipRect.xMinimum(), clipRect.yMaximum(), meanZ ); ptsX << clipRect.xMinimum();
ptsY << clipRect.yMaximum();
ptsZ << meanZ;
return; return;
} }
else if ( qgsDoubleNear( x1, clipRect.xMaximum() ) ) else if ( qgsDoubleNear( x1, clipRect.xMaximum() ) )
{ {
pts << QgsPoint( clipRect.xMinimum(), clipRect.yMaximum(), meanZ ); ptsX << clipRect.xMinimum();
pts << QgsPoint( clipRect.xMaximum(), clipRect.yMaximum(), meanZ ); ptsY << clipRect.yMaximum();
ptsZ << meanZ;
ptsX << clipRect.xMaximum();
ptsY << clipRect.yMaximum();
ptsZ << meanZ;
return; return;
} }
else if ( qgsDoubleNear( y1, clipRect.yMinimum() ) ) else if ( qgsDoubleNear( y1, clipRect.yMinimum() ) )
{ {
pts << QgsPoint( clipRect.xMinimum(), clipRect.yMinimum(), meanZ ); ptsX << clipRect.xMinimum();
ptsY << clipRect.yMinimum();
ptsZ << meanZ;
return; return;
} }
} }
@ -295,18 +312,26 @@ void QgsClipper::connectSeparatedLines( double x0, double y0, double z0, double
} }
else if ( qgsDoubleNear( x1, clipRect.xMaximum() ) ) else if ( qgsDoubleNear( x1, clipRect.xMaximum() ) )
{ {
pts << QgsPoint( clipRect.xMaximum(), clipRect.yMaximum(), meanZ ); ptsX << clipRect.xMaximum();
ptsY << clipRect.yMaximum();
ptsZ << meanZ;
return; return;
} }
else if ( qgsDoubleNear( y1, clipRect.yMinimum() ) ) else if ( qgsDoubleNear( y1, clipRect.yMinimum() ) )
{ {
pts << QgsPoint( clipRect.xMaximum(), clipRect.yMaximum(), meanZ ); ptsX << clipRect.xMaximum();
pts << QgsPoint( clipRect.xMaximum(), clipRect.yMinimum(), meanZ ); ptsY << clipRect.yMaximum();
ptsZ << meanZ;
ptsX << clipRect.xMaximum();
ptsY << clipRect.yMinimum();
ptsZ << meanZ;
return; return;
} }
else if ( qgsDoubleNear( x1, clipRect.xMinimum() ) ) else if ( qgsDoubleNear( x1, clipRect.xMinimum() ) )
{ {
pts << QgsPoint( clipRect.xMinimum(), clipRect.yMaximum(), meanZ ); ptsX << clipRect.xMinimum();
ptsY << clipRect.yMaximum();
ptsZ << meanZ;
return; return;
} }
} }
@ -318,18 +343,26 @@ void QgsClipper::connectSeparatedLines( double x0, double y0, double z0, double
} }
else if ( qgsDoubleNear( y1, clipRect.yMinimum() ) ) else if ( qgsDoubleNear( y1, clipRect.yMinimum() ) )
{ {
pts << QgsPoint( clipRect.xMaximum(), clipRect.yMinimum(), meanZ ); ptsX << clipRect.xMaximum();
ptsY << clipRect.yMinimum();
ptsZ << meanZ;
return; return;
} }
else if ( qgsDoubleNear( x1, clipRect.xMinimum() ) ) else if ( qgsDoubleNear( x1, clipRect.xMinimum() ) )
{ {
pts << QgsPoint( clipRect.xMaximum(), clipRect.yMinimum(), meanZ ); ptsX << clipRect.xMaximum();
pts << QgsPoint( clipRect.xMinimum(), clipRect.yMinimum(), meanZ ); ptsY << clipRect.yMinimum();
ptsZ << meanZ;
ptsX << clipRect.xMinimum();
ptsY << clipRect.yMinimum();
ptsZ << meanZ;
return; return;
} }
else if ( qgsDoubleNear( y1, clipRect.yMaximum() ) ) else if ( qgsDoubleNear( y1, clipRect.yMaximum() ) )
{ {
pts << QgsPoint( clipRect.xMaximum(), clipRect.yMaximum(), meanZ ); ptsX << clipRect.xMaximum();
ptsY << clipRect.yMaximum();
ptsZ << meanZ;
return; return;
} }
} }
@ -341,18 +374,26 @@ void QgsClipper::connectSeparatedLines( double x0, double y0, double z0, double
} }
else if ( qgsDoubleNear( x1, clipRect.xMinimum() ) ) else if ( qgsDoubleNear( x1, clipRect.xMinimum() ) )
{ {
pts << QgsPoint( clipRect.xMinimum(), clipRect.yMinimum(), meanZ ); ptsX << clipRect.xMinimum();
ptsY << clipRect.yMinimum();
ptsZ << meanZ;
return; return;
} }
else if ( qgsDoubleNear( y1, clipRect.yMaximum() ) ) else if ( qgsDoubleNear( y1, clipRect.yMaximum() ) )
{ {
pts << QgsPoint( clipRect.xMinimum(), clipRect.yMinimum(), meanZ ); ptsX << clipRect.xMinimum();
pts << QgsPoint( clipRect.xMinimum(), clipRect.yMaximum(), meanZ ); ptsY << clipRect.yMinimum();
ptsZ << meanZ;
ptsX << clipRect.xMinimum();
ptsY << clipRect.yMaximum();
ptsZ << meanZ;
return; return;
} }
else if ( qgsDoubleNear( x1, clipRect.xMaximum() ) ) else if ( qgsDoubleNear( x1, clipRect.xMaximum() ) )
{ {
pts << QgsPoint( clipRect.xMaximum(), clipRect.yMinimum(), meanZ ); ptsX << clipRect.xMaximum();
ptsY << clipRect.yMinimum();
ptsZ << meanZ;
return; return;
} }
} }

View File

@ -85,8 +85,8 @@ class CORE_EXPORT QgsClipper
XMin, XMin,
YMax, YMax,
YMin, YMin,
ZMax, //!< Maximum Z (since QGIS 3.22) ZMax, //!< Maximum Z (since QGIS 3.26)
ZMin, //!< Minimum Z (since QGIS 3.22) ZMin, //!< Minimum Z (since QGIS 3.26)
}; };
SIP_IF_FEATURE( !ARM ) // Not available on ARM sip bindings because of qreal issues SIP_IF_FEATURE( !ARM ) // Not available on ARM sip bindings because of qreal issues
@ -114,22 +114,19 @@ class CORE_EXPORT QgsClipper
static void trimPolygon( QPolygonF &pts, const QgsRectangle &clipRect ); static void trimPolygon( QPolygonF &pts, const QgsRectangle &clipRect );
/** /**
* Trims the given polygon to a pseudo 3D box, by modifying the given polygon in place. * Trims a polygon consisting of the specified \a x, \a y and \a z values to a pseudo 3D box, by modifying the arrays in place.
* *
* \param polygon polygon as 2D or 3D coordinates * \note Not available in Python bindings
* \param clipRect clipping 3D box * \since QGIS 3.26
* \since QGIS 3.22
*/ */
static void trimPolygon( QgsLineString &polygon, const QgsBox3d &clipRect ); static void trimPolygon( QVector< double > &x, QVector< double > &y, QVector< double> &z, const QgsBox3d &clipRect ) SIP_SKIP;
/** /**
* Takes a \a curve with 3D coordinates and clips it to clipExtent * Takes a line with 3D coordinates and clips it to clipExtent.
* \param curve the linestring to clip * \note Not available in Python bindings
* \param clipExtent clipping bounds * \since QGIS 3.26
* \returns clipped line coordinates
* \since QGIS 3.22
*/ */
static QgsLineString clipped3dLine( const QgsCurve &curve, const QgsBox3d &clipExtent ); static void clipped3dLine( const QVector< double > &xIn, const QVector< double > &yIn, const QVector<double> &zIn, QVector< double > &x, QVector< double > &y, QVector< double > &z, const QgsBox3d &clipExtent ) SIP_SKIP;
/** /**
* Takes a linestring and clips it to clipExtent * Takes a linestring and clips it to clipExtent
@ -183,14 +180,15 @@ class CORE_EXPORT QgsClipper
static inline void trimPolygonToBoundary( const QPolygonF &inPts, QPolygonF &outPts, const QgsRectangle &rect, Boundary b, double boundaryValue ); static inline void trimPolygonToBoundary( const QPolygonF &inPts, QPolygonF &outPts, const QgsRectangle &rect, Boundary b, double boundaryValue );
static inline void trimPolygonToBoundary( const QgsPointSequence &inPts, QgsPointSequence &outPts, const QgsBox3d &rect, Boundary b, double boundaryValue ); static inline void trimPolygonToBoundary( const QVector<double> &inX, const QVector<double> &inY, const QVector< double > &inZ,
QVector<double> &outX, QVector<double> &outY, QVector<double> &outZ, const QgsBox3d &rect, Boundary boundary, double boundaryValue );
// Determines if a point is inside or outside the given boundary // Determines if a point is inside or outside the given boundary
static inline bool inside( double x, double y, Boundary b ); static inline bool inside( double x, double y, Boundary b );
static inline bool inside( QPointF pt, Boundary b, double val ); static inline bool inside( QPointF pt, Boundary b, double val );
static inline bool inside( QgsPoint pt, Boundary b, double val ); static inline bool inside( double x, double y, double z, Boundary boundary, double boundaryValue );
// Calculates the intersection point between a line defined by a // Calculates the intersection point between a line defined by a
// (x1, y1), and (x2, y2) and the given boundary // (x1, y1), and (x2, y2) and the given boundary
@ -202,9 +200,9 @@ class CORE_EXPORT QgsClipper
QPointF pt2, QPointF pt2,
Boundary b, const QgsRectangle &rect ); Boundary b, const QgsRectangle &rect );
static inline QgsPoint intersectRect( QgsPoint pt1, static inline void intersectRect( double x1, double y1, double z1, double x2, double y2, double z2,
QgsPoint pt2, double &xOut, double &yOut, double &zOut,
Boundary b, const QgsBox3d &rect ); Boundary boundary, const QgsBox3d &rect );
static bool clipLineSegment( const QgsBox3d &extent, double &x0, double &y0, double &z0, double &x1, double &y1, double &z1 ); static bool clipLineSegment( const QgsBox3d &extent, double &x0, double &y0, double &z0, double &x1, double &y1, double &z1 );
@ -229,10 +227,12 @@ class CORE_EXPORT QgsClipper
* \param y1 y-coordinate of the second line start * \param y1 y-coordinate of the second line start
* \param z1 z-coordinate of the second line start * \param z1 z-coordinate of the second line start
* \param clipRect clip box * \param clipRect clip box
* \param pts: in/out array of clipped points * \param ptsX: in/out array of clipped points x
* \param ptsY: in/out array of clipped points y
* \param ptsZ: in/out array of clipped points z
*/ */
static void connectSeparatedLines( double x0, double y0, double z0, double x1, double y1, double z1, static void connectSeparatedLines( double x0, double y0, double z0, double x1, double y1, double z1,
const QgsBox3d &clipRect, QgsPointSequence &pts ); const QgsBox3d &clipRect, QVector< double > &ptsX, QVector< double > &ptsY, QVector<double> &ptsZ );
//low level clip methods for fast clip algorithm //low level clip methods for fast clip algorithm
static void clipStartTop( double &x0, double &y0, double x1, double y1, double yMax ); static void clipStartTop( double &x0, double &y0, double x1, double y1, double yMax );
@ -296,27 +296,41 @@ inline void QgsClipper::trimPolygon( QPolygonF &pts, const QgsRectangle &clipRec
trimPolygonToBoundary( tmpPts, pts, clipRect, YMin, clipRect.yMinimum() ); trimPolygonToBoundary( tmpPts, pts, clipRect, YMin, clipRect.yMinimum() );
} }
inline void QgsClipper::trimPolygon( QgsLineString &pts, const QgsBox3d &clipRect ) inline void QgsClipper::trimPolygon( QVector<double> &x, QVector<double> &y, QVector<double> &z, const QgsBox3d &clipRect )
{ {
QgsPointSequence seqPts, seqTmpPts; QVector< double > tempX;
pts.points( seqPts ); QVector< double > tempY;
QVector< double > tempZ;
const int size = x.size();
tempX.reserve( size );
tempY.reserve( size );
tempZ.reserve( size );
trimPolygonToBoundary( x, y, z, tempX, tempY, tempZ, clipRect, XMax, clipRect.xMaximum() );
x.resize( 0 );
y.resize( 0 );
z.resize( 0 );
trimPolygonToBoundary( tempX, tempY, tempZ, x, y, z, clipRect, YMax, clipRect.yMaximum() );
tempX.resize( 0 );
tempY.resize( 0 );
tempZ.resize( 0 );
trimPolygonToBoundary( x, y, z, tempX, tempY, tempZ, clipRect, XMin, clipRect.xMinimum() );
x.resize( 0 );
y.resize( 0 );
z.resize( 0 );
trimPolygonToBoundary( tempX, tempY, tempZ, x, y, z, clipRect, YMin, clipRect.yMinimum() );
trimPolygonToBoundary( seqPts, seqTmpPts, clipRect, XMax, clipRect.xMaximum() );
seqPts.clear();
trimPolygonToBoundary( seqTmpPts, seqPts, clipRect, YMax, clipRect.yMaximum() );
seqTmpPts.clear();
trimPolygonToBoundary( seqPts, seqTmpPts, clipRect, XMin, clipRect.xMinimum() );
seqPts.clear();
trimPolygonToBoundary( seqTmpPts, seqPts, clipRect, YMin, clipRect.yMinimum() );
if ( !clipRect.is2d() ) if ( !clipRect.is2d() )
{ {
seqTmpPts.clear(); tempX.resize( 0 );
trimPolygonToBoundary( seqPts, seqTmpPts, clipRect, ZMax, clipRect.zMaximum() ); tempY.resize( 0 );
seqPts.clear(); tempZ.resize( 0 );
trimPolygonToBoundary( seqTmpPts, seqPts, clipRect, ZMin, clipRect.zMinimum() ); trimPolygonToBoundary( x, y, z, tempX, tempY, tempZ, clipRect, ZMax, clipRect.zMaximum() );
x.resize( 0 );
y.resize( 0 );
z.resize( 0 );
trimPolygonToBoundary( tempX, tempY, tempZ, x, y, z, clipRect, ZMin, clipRect.zMinimum() );
} }
pts.setPoints( seqPts );
} }
// An auxiliary function that is part of the polygon trimming // An auxiliary function that is part of the polygon trimming
@ -424,40 +438,61 @@ inline void QgsClipper::trimPolygonToBoundary( const QPolygonF &inPts, QPolygonF
} }
} }
inline void QgsClipper::trimPolygonToBoundary( const QgsPointSequence &inPts, QgsPointSequence &outPts, const QgsBox3d &rect, Boundary b, double boundaryValue ) inline void QgsClipper::trimPolygonToBoundary( const QVector<double> &inX, const QVector<double> &inY, const QVector< double > &inZ,
QVector<double> &outX, QVector<double> &outY, QVector<double> &outZ, const QgsBox3d &rect, Boundary boundary, double boundaryValue )
{ {
QgsPoint inI1, inI2; const double len = inX.length();
double inI1X = inX.at( len - 1 ); // start with last point
double inI1Y = inY.at( len - 1 );
double inI1Z = inZ.at( len - 1 );
inI1 = inPts.at( inPts.length() - 1 ); // start with last point double xTemp;
double yTemp;
double zTemp;
// and compare to the first point initially. // and compare to the first point initially.
for ( int i2 = 0; i2 < inPts.length() ; ++i2 ) for ( int i2 = 0; i2 < len; ++i2 )
{ {
inI2 = inPts.at( i2 ); double inI2X = inX.at( i2 );
double inI2Y = inY.at( i2 );
double inI2Z = inZ.at( i2 );
// look at each edge of the polygon in turn // look at each edge of the polygon in turn
if ( inside( inI2, b, boundaryValue ) ) // end point of edge is inside boundary if ( inside( inI2X, inI2Y, inI2Z, boundary, boundaryValue ) ) // end point of edge is inside boundary
{ {
if ( inside( inI1, b, boundaryValue ) ) if ( inside( inI1X, inI1Y, inI1Z, boundary, boundaryValue ) )
{ {
outPts << inI2 ; outX << inI2X;
outY << inI2Y;
outZ << inI2Z;
} }
else else
{ {
// edge crosses into the boundary, so trim back to the boundary, and // edge crosses into the boundary, so trim back to the boundary, and
// store both ends of the new edge // store both ends of the new edge
outPts << intersectRect( inI1, inI2, b, rect ) ; intersectRect( inI1X, inI1Y, inI1Z, inI2X, inI2Y, inI2Z, xTemp, yTemp, zTemp, boundary, rect ) ;
outPts << inI2 ; outX << xTemp;
outY << yTemp;
outZ << zTemp;
outX << inI2X;
outY << inI2Y;
outZ << inI2Z;
} }
} }
else // end point of edge is outside boundary else // end point of edge is outside boundary
{ {
// start point is in boundary, so need to trim back // start point is in boundary, so need to trim back
if ( inside( inI1, b, boundaryValue ) ) if ( inside( inI1X, inI1Y, inI1Z, boundary, boundaryValue ) )
{ {
outPts << intersectRect( inI1, inI2, b, rect ) ; intersectRect( inI1X, inI1Y, inI1Z, inI2X, inI2Y, inI2Z, xTemp, yTemp, zTemp, boundary, rect );
outX << xTemp;
outY << yTemp;
outZ << zTemp;
} }
} }
inI1 = inPts.at( i2 ); inI1X = inI2X;
inI1Y = inI2Y;
inI1Z = inI2Z;
} }
} }
@ -510,22 +545,22 @@ inline bool QgsClipper::inside( QPointF pt, Boundary b, double val )
return false; return false;
} }
inline bool QgsClipper::inside( QgsPoint pt, Boundary b, double val ) inline bool QgsClipper::inside( double x, double y, double z, Boundary boundary, double boundaryValue )
{ {
switch ( b ) switch ( boundary )
{ {
case XMax: // x < MAX_X is inside case XMax: // x < MAX_X is inside
return ( pt.x() < val ); return ( x < boundaryValue );
case XMin: // x > MIN_X is inside case XMin: // x > MIN_X is inside
return ( pt.x() > val ); return ( x > boundaryValue );
case YMax: // y < MAX_Y is inside case YMax: // y < MAX_Y is inside
return ( pt.y() < val ); return ( y < boundaryValue );
case YMin: // y > MIN_Y is inside case YMin: // y > MIN_Y is inside
return ( pt.y() > val ); return ( y > boundaryValue );
case ZMax: // z < MAX_Z is inside case ZMax: // z < MAX_Z is inside
return ( pt.z() < val ); return ( z < boundaryValue );
case ZMin: // z > MIN_Z is inside case ZMin: // z > MIN_Z is inside
return ( pt.z() > val ); return ( z > boundaryValue );
} }
return false; return false;
} }
@ -629,20 +664,18 @@ inline QPointF QgsClipper::intersectRect( QPointF pt1,
return QPointF( x1 + r * ( x2 - x1 ), y1 + r * ( y2 - y1 ) ); return QPointF( x1 + r * ( x2 - x1 ), y1 + r * ( y2 - y1 ) );
} }
inline QgsPoint QgsClipper::intersectRect( QgsPoint pt1, inline void QgsClipper::intersectRect( const double x1, const double y1, const double z1,
QgsPoint pt2, const double x2, const double y2, const double z2,
Boundary b, const QgsBox3d &rect ) double &xOut, double &yOut, double &zOut,
Boundary boundary, const QgsBox3d &rect )
{ {
// This function assumes that the two given points (x1, y1), and // This function assumes that the two given points (x1, y1, z1), and
// (x2, y2) cross the given boundary. Making this assumption allows // (x2, y2, z2) cross the given boundary. Making this assumption allows
// some optimisations. // some optimisations.
double r_n = SMALL_NUM, r_d = SMALL_NUM; double r_n = SMALL_NUM, r_d = SMALL_NUM;
const double x1 = pt1.x(), x2 = pt2.x();
const double y1 = pt1.y(), y2 = pt2.y();
const double z1 = pt1.z(), z2 = pt2.z();
switch ( b ) switch ( boundary )
{ {
case XMax: // x = MAX_X boundary case XMax: // x = MAX_X boundary
r_n = -( x1 - rect.xMaximum() ) * ( rect.yMaximum() - rect.yMinimum() ); r_n = -( x1 - rect.xMaximum() ) * ( rect.yMaximum() - rect.yMinimum() );
@ -675,7 +708,9 @@ inline QgsPoint QgsClipper::intersectRect( QgsPoint pt1,
{ {
r = r_n / r_d; r = r_n / r_d;
} }
return QgsPoint( x1 + r * ( x2 - x1 ), y1 + r * ( y2 - y1 ), z1 + r * ( z2 - z1 ) ); xOut = x1 + r * ( x2 - x1 );
yOut = y1 + r * ( y2 - y1 );
zOut = z1 + r * ( z2 - z1 );
} }
inline void QgsClipper::clipStartTop( double &x0, double &y0, double x1, double y1, double yMax ) inline void QgsClipper::clipStartTop( double &x0, double &y0, double x1, double y1, double yMax )

View File

@ -95,7 +95,9 @@ QPolygonF QgsSymbol::_getLineString3d( QgsRenderContext &context, const QgsCurve
QgsCoordinateTransform ct = context.coordinateTransform(); QgsCoordinateTransform ct = context.coordinateTransform();
const QgsMapToPixel &mtp = context.mapToPixel(); const QgsMapToPixel &mtp = context.mapToPixel();
QgsLineString pts; QVector< double > pointsX;
QVector< double > pointsY;
QVector< double > pointsZ;
// apply clipping for large lines to achieve a better rendering performance // apply clipping for large lines to achieve a better rendering performance
if ( clipToExtent && nPoints > 1 && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) ) if ( clipToExtent && nPoints > 1 && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) )
@ -104,30 +106,51 @@ QPolygonF QgsSymbol::_getLineString3d( QgsRenderContext &context, const QgsCurve
const double cw = e.width() / 10; const double cw = e.width() / 10;
const double ch = e.height() / 10; const double ch = e.height() / 10;
const QgsBox3d clipRect( e.xMinimum() - cw, e.yMinimum() - ch, -HUGE_VAL, e.xMaximum() + cw, e.yMaximum() + ch, HUGE_VAL ); // TODO also need to be clipped according to z axis const QgsBox3d clipRect( e.xMinimum() - cw, e.yMinimum() - ch, -HUGE_VAL, e.xMaximum() + cw, e.yMaximum() + ch, HUGE_VAL ); // TODO also need to be clipped according to z axis
pts = QgsClipper::clipped3dLine( curve, clipRect );
const QgsLineString *lineString = nullptr;
if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( &curve ) )
{
lineString = ls;
}
else
{
std::unique_ptr< QgsLineString > segmentized;
segmentized.reset( qgsgeometry_cast< QgsLineString * >( curve.segmentize( ) ) );
lineString = segmentized.get();
}
QgsClipper::clipped3dLine( lineString->xVector(), lineString->yVector(), lineString->zVector(), pointsX, pointsY, pointsZ, clipRect );
} }
else else
{ {
// clone... // clone...
const QgsLineString *tmpLine = reinterpret_cast<const QgsLineString *>( &curve ); if ( const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( &curve ) )
if ( dynamic_cast<const QgsLineString *>( &curve ) )
{ {
pts.setPoints( curve.numPoints(), tmpLine->xData(), tmpLine->yData(), tmpLine->zData(), tmpLine->mData() ); pointsX = ls->xVector();
pointsY = ls->yVector();
pointsZ = ls->zVector();
} }
else else
{ {
QgsPointSequence seq; std::unique_ptr< QgsLineString > segmentized;
curve.points( seq ); segmentized.reset( qgsgeometry_cast< QgsLineString * >( curve.segmentize( ) ) );
pts.setPoints( seq );
pointsX = segmentized->xVector();
pointsY = segmentized->yVector();
pointsZ = segmentized->zVector();
} }
} }
// transform the QPolygonF to screen coordinates // transform the points to screen coordinates
if ( ct.isValid() ) if ( ct.isValid() )
{ {
//create x, y arrays
const int nVertices = pointsX.size();
QString err;
try try
{ {
pts.transform( ct, Qgis::TransformDirection::Forward, true ); ct.transformCoords( nVertices, pointsX.data(), pointsY.data(), pointsZ.data(), Qgis::TransformDirection::Forward );
} }
catch ( QgsCsException & ) catch ( QgsCsException & )
{ {
@ -136,17 +159,36 @@ QPolygonF QgsSymbol::_getLineString3d( QgsRenderContext &context, const QgsCurve
} }
// remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
pts.filterVertices( []( const QgsPoint & point )
{ {
if ( point.is3D() ) const int size = pointsX.size();
const double *xIn = pointsX.data();
const double *yIn = pointsY.data();
const double *zIn = pointsZ.data();
double *xOut = pointsX.data();
double *yOut = pointsY.data();
double *zOut = pointsZ.data();
int outSize = 0;
for ( int i = 0; i < size; ++i )
{ {
return std::isfinite( point.x() ) && std::isfinite( point.y() ) && std::isfinite( point.z() ); if ( std::isfinite( *xIn ) && std::isfinite( *yIn ) && std::isfinite( *zIn ) )
{
*xOut++ = *xIn++;
*yOut++ = *yIn++;
*zOut++ = *zIn++;
outSize++;
} }
else else
{ {
return std::isfinite( point.x() ) && std::isfinite( point.y() ); xIn++;
yIn++;
zIn++;
}
}
pointsX.resize( outSize );
pointsY.resize( outSize );
pointsZ.resize( outSize );
} }
} );
if ( clipToExtent && nPoints > 1 && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) if ( clipToExtent && nPoints > 1 && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection )
{ {
@ -155,14 +197,27 @@ QPolygonF QgsSymbol::_getLineString3d( QgsRenderContext &context, const QgsCurve
const double cw = e.width() / 10; const double cw = e.width() / 10;
const double ch = e.height() / 10; const double ch = e.height() / 10;
const QgsBox3d clipRect( e.xMinimum() - cw, e.yMinimum() - ch, -HUGE_VAL, e.xMaximum() + cw, e.yMaximum() + ch, HUGE_VAL ); // TODO also need to be clipped according to z axis const QgsBox3d clipRect( e.xMinimum() - cw, e.yMinimum() - ch, -HUGE_VAL, e.xMaximum() + cw, e.yMaximum() + ch, HUGE_VAL ); // TODO also need to be clipped according to z axis
pts = QgsClipper::clipped3dLine( pts, clipRect );
QVector< double > tempX;
QVector< double > tempY;
QVector< double > tempZ;
QgsClipper::clipped3dLine( pointsX, pointsY, pointsZ, tempX, tempY, tempZ, clipRect );
pointsX = tempX;
pointsY = tempY;
pointsZ = tempZ;
} }
QPolygonF out = pts.asQPolygonF(); const int polygonSize = pointsX.size();
QPointF *ptr = out.data(); QPolygonF out( polygonSize );
for ( int i = 0; i < out.size(); ++i, ++ptr ) const double *x = pointsX.constData();
const double *y = pointsY.constData();
QPointF *dest = out.data();
for ( int i = 0; i < polygonSize; ++i )
{ {
mtp.transformInPlace( ptr->rx(), ptr->ry() ); double screenX = *x++;
double screenY = *y++;
mtp.transformInPlace( screenX, screenY );
*dest++ = QPointF( screenX, screenY );
} }
return out; return out;
@ -243,40 +298,79 @@ QPolygonF QgsSymbol::_getPolygonRing3d( QgsRenderContext &context, const QgsCurv
const QgsCoordinateTransform ct = context.coordinateTransform(); const QgsCoordinateTransform ct = context.coordinateTransform();
const QgsMapToPixel &mtp = context.mapToPixel(); const QgsMapToPixel &mtp = context.mapToPixel();
// clone... QVector< double > pointsX;
QgsPointSequence seq; QVector< double > pointsY;
curve.points( seq ); QVector< double > pointsZ;
QgsLineString poly( seq );
if ( curve.numPoints() < 1 ) if ( curve.numPoints() < 1 )
return QPolygonF(); return QPolygonF();
bool reverseRing = false;
if ( correctRingOrientation ) if ( correctRingOrientation )
{ {
// ensure consistent polygon ring orientation // ensure consistent polygon ring orientation
if ( ( isExteriorRing && curve.orientation() != Qgis::AngularDirection::Clockwise ) || ( !isExteriorRing && curve.orientation() != Qgis::AngularDirection::CounterClockwise ) ) if ( ( isExteriorRing && curve.orientation() != Qgis::AngularDirection::Clockwise ) || ( !isExteriorRing && curve.orientation() != Qgis::AngularDirection::CounterClockwise ) )
{ {
poly.reversed()->points( seq ); reverseRing = true;
poly.setPoints( seq );
} }
} }
//clip close to view extent, if needed //clip close to view extent, if needed
if ( clipToExtent && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) && !context.extent().contains( poly.boundingBox() ) ) if ( clipToExtent && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) && !context.extent().contains( curve.boundingBox() ) )
{ {
const QgsRectangle e = context.extent(); const QgsRectangle e = context.extent();
const double cw = e.width() / 10; const double cw = e.width() / 10;
const double ch = e.height() / 10; const double ch = e.height() / 10;
const QgsBox3d clipRect( e.xMinimum() - cw, e.yMinimum() - ch, -HUGE_VAL, e.xMaximum() + cw, e.yMaximum() + ch, HUGE_VAL ); // TODO also need to be clipped according to z axis const QgsBox3d clipRect( e.xMinimum() - cw, e.yMinimum() - ch, -HUGE_VAL, e.xMaximum() + cw, e.yMaximum() + ch, HUGE_VAL ); // TODO also need to be clipped according to z axis
QgsClipper::trimPolygon( poly, clipRect );
const QgsLineString *lineString = nullptr;
if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( &curve ) )
{
lineString = ls;
}
else
{
std::unique_ptr< QgsLineString > segmentized;
segmentized.reset( qgsgeometry_cast< QgsLineString * >( curve.segmentize( ) ) );
lineString = segmentized.get();
}
QgsClipper::clipped3dLine( lineString->xVector(), lineString->yVector(), lineString->zVector(), pointsX, pointsY, pointsZ, clipRect );
}
else
{
// clone...
if ( const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( &curve ) )
{
pointsX = ls->xVector();
pointsY = ls->yVector();
pointsZ = ls->zVector();
}
else
{
std::unique_ptr< QgsLineString > segmentized;
segmentized.reset( qgsgeometry_cast< QgsLineString * >( curve.segmentize( ) ) );
pointsX = segmentized->xVector();
pointsY = segmentized->yVector();
pointsZ = segmentized->zVector();
}
}
if ( reverseRing )
{
std::reverse( pointsX.begin(), pointsX.end() );
std::reverse( pointsY.begin(), pointsY.end() );
std::reverse( pointsZ.begin(), pointsZ.end() );
} }
//transform the QPolygonF to screen coordinates //transform the QPolygonF to screen coordinates
if ( ct.isValid() ) if ( ct.isValid() )
{ {
const int nVertices = pointsX.size();
try try
{ {
poly.transform( ct, Qgis::TransformDirection::Forward, true ); ct.transformCoords( nVertices, pointsX.data(), pointsY.data(), pointsZ.data(), Qgis::TransformDirection::Forward );
} }
catch ( QgsCsException & ) catch ( QgsCsException & )
{ {
@ -285,37 +379,68 @@ QPolygonF QgsSymbol::_getPolygonRing3d( QgsRenderContext &context, const QgsCurv
} }
// remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
poly.filterVertices( []( const QgsPoint point )
{ {
if ( point.is3D() ) const int size = pointsX.size();
const double *xIn = pointsX.data();
const double *yIn = pointsY.data();
const double *zIn = pointsZ.data();
double *xOut = pointsX.data();
double *yOut = pointsY.data();
double *zOut = pointsZ.data();
int outSize = 0;
for ( int i = 0; i < size; ++i )
{ {
return std::isfinite( point.x() ) && std::isfinite( point.y() ) && std::isfinite( point.z() ); if ( std::isfinite( *xIn ) && std::isfinite( *yIn ) && std::isfinite( *zIn ) )
{
*xOut++ = *xIn++;
*yOut++ = *yIn++;
*zOut++ = *zIn++;
outSize++;
} }
else else
{ {
return std::isfinite( point.x() ) && std::isfinite( point.y() ); xIn++;
yIn++;
zIn++;
}
}
pointsX.resize( outSize );
pointsY.resize( outSize );
pointsZ.resize( outSize );
} }
} );
if ( clipToExtent && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection && !context.mapExtent().contains( curve.boundingBox() ) )
if ( clipToExtent && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection && !context.mapExtent().contains( poly.boundingBox() ) )
{ {
// early clipping was not possible, so we have to apply it here after transformation // early clipping was not possible, so we have to apply it here after transformation
const QgsRectangle e = context.mapExtent(); const QgsRectangle e = context.mapExtent();
const double cw = e.width() / 10; const double cw = e.width() / 10;
const double ch = e.height() / 10; const double ch = e.height() / 10;
const QgsBox3d clipRect( e.xMinimum() - cw, e.yMinimum() - ch, -HUGE_VAL, e.xMaximum() + cw, e.yMaximum() + ch, HUGE_VAL ); // TODO also need to be clipped according to z axis const QgsBox3d clipRect( e.xMinimum() - cw, e.yMinimum() - ch, -HUGE_VAL, e.xMaximum() + cw, e.yMaximum() + ch, HUGE_VAL ); // TODO also need to be clipped according to z axis
QgsClipper::trimPolygon( poly, clipRect );
QVector< double > tempX;
QVector< double > tempY;
QVector< double > tempZ;
QgsClipper::clipped3dLine( pointsX, pointsY, pointsZ, tempX, tempY, tempZ, clipRect );
pointsX = tempX;
pointsY = tempY;
pointsZ = tempZ;
} }
QPolygonF out = poly.asQPolygonF(); const int polygonSize = pointsX.size();
QPointF *ptr = out.data(); QPolygonF out( polygonSize );
for ( int i = 0; i < out.size(); ++i, ++ptr ) const double *x = pointsX.constData();
const double *y = pointsY.constData();
QPointF *dest = out.data();
for ( int i = 0; i < polygonSize; ++i )
{ {
mtp.transformInPlace( ptr->rx(), ptr->ry() ); double screenX = *x++;
double screenY = *y++;
mtp.transformInPlace( screenX, screenY );
*dest++ = QPointF( screenX, screenY );
} }
if ( !out.empty() && !poly.isClosed() ) if ( !out.empty() && !out.isClosed() )
out << out.at( 0 ); out << out.at( 0 );
return out; return out;

View File

@ -50,77 +50,77 @@ void TestQgsClipper::initTestCase()
void TestQgsClipper::basicWithZ() void TestQgsClipper::basicWithZ()
{ {
// QgsClipper is static only // QgsClipper is static only
QgsLineString polygon;
polygon.addVertex( QgsPoint( 10.4, 20.5, 10.0 ) );
polygon.addVertex( QgsPoint( 20.2, 30.2, 20.0 ) );
QgsBox3d clipRect( 10, 10, 11, 25, 30, 19 ); QgsBox3d clipRect( 10, 10, 11, 25, 30, 19 );
QgsClipper::trimPolygon( polygon, clipRect ); QVector< double > x = { 10.4, 20.2 };
QVector< double > y = {20.5, 30.2 };
QVector< double > z = {10.0, 20.0 };
QgsClipper::trimPolygon( x, y, z, clipRect );
// Check nothing sticks out. // Check nothing sticks out.
QVERIFY( checkBoundingBox( polygon, clipRect ) ); QVERIFY( checkBoundingBox( QgsLineString( x, y, z ), clipRect ) );
// Check that it didn't clip too much // Check that it didn't clip too much
QgsBox3d clipRectInner( clipRect ); QgsBox3d clipRectInner( clipRect );
clipRectInner.scale( 0.999 ); clipRectInner.scale( 0.999 );
QVERIFY( ! checkBoundingBox( polygon, clipRectInner ) ); QVERIFY( ! checkBoundingBox( QgsLineString( x, y, z ), clipRectInner ) );
// A more complex example // A more complex example
polygon.clear(); x = { 1.0, 11.0, 9.0 };
polygon.addVertex( QgsPoint( 1.0, 9.0, 1.0 ) ); y = { 9.0, 11.0, 1.0 };
polygon.addVertex( QgsPoint( 11.0, 11.0, 11.0 ) ); z = { 1.0, 11.0, 9.0 };
polygon.addVertex( QgsPoint( 9.0, 1.0, 9.0 ) );
clipRect = QgsBox3d( 0.0, 0.0, 0.0, 10.0, 10.0, 10.0 ); clipRect = QgsBox3d( 0.0, 0.0, 0.0, 10.0, 10.0, 10.0 );
QgsClipper::trimPolygon( polygon, clipRect ); QgsClipper::trimPolygon( x, y, z, clipRect );
// We should have 5 vertices now? // We should have 5 vertices now?
QCOMPARE( polygon.numPoints(), 5 ); QCOMPARE( x.size(), 5 );
QCOMPARE( y.size(), 5 );
QCOMPARE( z.size(), 5 );
// Check nothing sticks out. // Check nothing sticks out.
QVERIFY( checkBoundingBox( polygon, clipRect ) ); QVERIFY( checkBoundingBox( QgsLineString( x, y, z ), clipRect ) );
// Check that it didn't clip too much // Check that it didn't clip too much
clipRectInner = clipRect; clipRectInner = clipRect;
clipRectInner.scale( 0.999 ); clipRectInner.scale( 0.999 );
QVERIFY( ! checkBoundingBox( polygon, clipRectInner ) ); QVERIFY( ! checkBoundingBox( QgsLineString( x, y, z ), clipRectInner ) );
} }
void TestQgsClipper::basicWithZInf() void TestQgsClipper::basicWithZInf()
{ {
// QgsClipper is static only // QgsClipper is static only
QgsLineString polygon; QVector< double > x { 10.4, 20.2 };
polygon.addVertex( QgsPoint( 10.4, 20.5, 10.0 ) ); QVector< double > y { 20.5, 30.2 };
polygon.addVertex( QgsPoint( 20.2, 30.2, 20.0 ) ); QVector< double > z { 10.0, 20.0 };
QgsBox3d clipRect( 10, 10, -HUGE_VAL, 25, 30, HUGE_VAL ); QgsBox3d clipRect( 10, 10, -HUGE_VAL, 25, 30, HUGE_VAL );
QgsClipper::trimPolygon( polygon, clipRect ); QgsClipper::trimPolygon( x, y, z, clipRect );
// Check nothing sticks out. // Check nothing sticks out.
QVERIFY( checkBoundingBox( polygon, clipRect ) ); QVERIFY( checkBoundingBox( QgsLineString( x, y, z ), clipRect ) );
// Check that it didn't clip too much // Check that it didn't clip too much
QgsBox3d clipRectInner( clipRect ); QgsBox3d clipRectInner( clipRect );
clipRectInner.scale( 0.999 ); clipRectInner.scale( 0.999 );
QVERIFY( ! checkBoundingBox( polygon, clipRectInner ) ); QVERIFY( ! checkBoundingBox( QgsLineString( x, y, z ), clipRectInner ) );
// A more complex example // A more complex example
polygon.clear(); x = { 1.0, 11.0, 9.0 };
polygon.addVertex( QgsPoint( 1.0, 9.0, 1.0 ) ); y = { 9.0, 11.0, 1.0 };
polygon.addVertex( QgsPoint( 11.0, 11.0, 11.0 ) ); z = { 1.0, 11.0, 9.0 };
polygon.addVertex( QgsPoint( 9.0, 1.0, 9.0 ) );
clipRect = QgsBox3d( 0.0, 0.0, 0.0, 10.0, 10.0, 10.0 ); clipRect = QgsBox3d( 0.0, 0.0, 0.0, 10.0, 10.0, 10.0 );
QgsClipper::trimPolygon( polygon, clipRect ); QgsClipper::trimPolygon( x, y, z, clipRect );
// We should have 5 vertices now? // We should have 5 vertices now?
QCOMPARE( polygon.numPoints(), 5 ); QCOMPARE( x.size(), 5 );
QCOMPARE( y.size(), 5 );
QCOMPARE( z.size(), 5 );
// Check nothing sticks out. // Check nothing sticks out.
QVERIFY( checkBoundingBox( polygon, clipRect ) ); QVERIFY( checkBoundingBox( QgsLineString( x, y, z ), clipRect ) );
// Check that it didn't clip too much // Check that it didn't clip too much
clipRectInner = clipRect; clipRectInner = clipRect;
clipRectInner.scale( 0.999 ); clipRectInner.scale( 0.999 );
QVERIFY( ! checkBoundingBox( polygon, clipRectInner ) ); QVERIFY( ! checkBoundingBox( QgsLineString( x, y, z ), clipRectInner ) );
} }
void TestQgsClipper::basic() void TestQgsClipper::basic()