diff --git a/python/core/auto_generated/qgstessellator.sip.in b/python/core/auto_generated/qgstessellator.sip.in index ca69802e847..bf678c96e72 100644 --- a/python/core/auto_generated/qgstessellator.sip.in +++ b/python/core/auto_generated/qgstessellator.sip.in @@ -32,12 +32,14 @@ Optionally provides extrusion by adding triangles that serve as walls when extru Creates tessellator with a specified origin point of the world (in map coordinates) %End - QgsTessellator( const QgsRectangle &bounds, bool addNormals, bool invertNormals = false, bool addBackFaces = false ); + QgsTessellator( const QgsRectangle &bounds, bool addNormals, bool invertNormals = false, bool addBackFaces = false, bool noZ = false ); %Docstring Creates tessellator with a specified ``bounds`` of input geometry coordinates. This constructor allows the tessellator to map input coordinates to a desirable range for numerically stability during calculations. +If ``noZ`` is ``True``, then a 2-dimensional tesselation only will be performed and all z coordinates will be ignored. + .. versionadded:: 3.10 %End diff --git a/src/core/qgstessellator.cpp b/src/core/qgstessellator.cpp index 1d7fc6e7219..b987ecc53ad 100644 --- a/src/core/qgstessellator.cpp +++ b/src/core/qgstessellator.cpp @@ -78,13 +78,14 @@ QgsTessellator::QgsTessellator( double originX, double originY, bool addNormals, init(); } -QgsTessellator::QgsTessellator( const QgsRectangle &bounds, bool addNormals, bool invertNormals, bool addBackFaces ) +QgsTessellator::QgsTessellator( const QgsRectangle &bounds, bool addNormals, bool invertNormals, bool addBackFaces, bool noZ ) : mBounds( bounds ) , mOriginX( mBounds.xMinimum() ) , mOriginY( mBounds.yMinimum() ) , mAddNormals( addNormals ) , mInvertNormals( invertNormals ) , mAddBackFaces( addBackFaces ) + , mNoZ( noZ ) { init(); } @@ -227,7 +228,7 @@ struct float_pair_hash } }; -static void _ringToPoly2tri( const QgsLineString *ring, std::vector &polyline, QHash &zHash ) +static void _ringToPoly2tri( const QgsLineString *ring, std::vector &polyline, QHash *zHash ) { const int pCount = ring->numPoints(); @@ -242,7 +243,6 @@ static void _ringToPoly2tri( const QgsLineString *ring, std::vector( polygon.exteriorRing() ); - const QVector3D pNormal = _calculateNormal( exterior, mOriginX, mOriginY, mInvertNormals ); + const QVector3D pNormal = !mNoZ ? _calculateNormal( exterior, mOriginX, mOriginY, mInvertNormals ) : QVector3D(); const int pCount = exterior->numPoints(); if ( pCount == 4 && polygon.numInteriorRings() == 0 ) @@ -429,10 +432,10 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh // polygon is a triangle - write vertices to the output data array without triangulation const double *xData = exterior->xData(); const double *yData = exterior->yData(); - const double *zData = exterior->zData(); + const double *zData = !mNoZ ? exterior->zData() : nullptr; for ( int i = 0; i < 3; i++ ) { - mData << *xData++ - mOriginX << *zData++ << - *yData++ + mOriginY; + mData << *xData++ - mOriginX << ( mNoZ ? 0 : *zData++ ) << - *yData++ + mOriginY; if ( mAddNormals ) mData << pNormal.x() << pNormal.z() << - pNormal.y(); } @@ -442,7 +445,7 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh // the same triangle with reversed order of coordinates and inverted normal for ( int i = 2; i >= 0; i-- ) { - mData << exterior->xAt( i ) - mOriginX << exterior->zAt( i ) << - exterior->yAt( i ) + mOriginY; + mData << exterior->xAt( i ) - mOriginX << ( mNoZ ? 0 : exterior->zAt( i ) ) << - exterior->yAt( i ) + mOriginY; if ( mAddNormals ) mData << -pNormal.x() << -pNormal.z() << pNormal.y(); } @@ -450,11 +453,11 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh } else { - if ( !qgsDoubleNear( pNormal.length(), 1, 0.001 ) ) + if ( !mNoZ && !qgsDoubleNear( pNormal.length(), 1, 0.001 ) ) return; // this should not happen - pNormal should be normalized to unit length std::unique_ptr toNewBase, toOldBase; - if ( pNormal != QVector3D( 0, 0, 1 ) ) + if ( !mNoZ && pNormal != QVector3D( 0, 0, 1 ) ) { // this is not a horizontal plane - need to reproject the polygon to a new base so that // we can do the triangulation in a plane @@ -524,7 +527,7 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh // polygon exterior std::vector polyline; - _ringToPoly2tri( qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() ), polyline, z ); + _ringToPoly2tri( qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() ), polyline, mNoZ ? nullptr : &z ); polylinesToDelete << polyline; std::unique_ptr cdt( new p2t::CDT( polyline ) ); @@ -535,7 +538,7 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh std::vector holePolyline; const QgsLineString *hole = qgsgeometry_cast< const QgsLineString *>( polygonNew->interiorRing( i ) ); - _ringToPoly2tri( hole, holePolyline, z ); + _ringToPoly2tri( hole, holePolyline, mNoZ ? nullptr : &z ); cdt->AddHole( holePolyline ); polylinesToDelete << holePolyline; @@ -555,12 +558,12 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh for ( int j = 0; j < 3; ++j ) { p2t::Point *p = t->GetPoint( j ); - QVector4D pt( p->x, p->y, z[p], 0 ); + QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 ); if ( toOldBase ) pt = *toOldBase * pt; const double fx = ( pt.x() / scaleX ) - mOriginX + pt0.x(); const double fy = ( pt.y() / scaleY ) - mOriginY + pt0.y(); - const double fz = pt.z() + extrusionHeight + pt0.z(); + const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.z() ); mData << fx << fz << -fy; if ( mAddNormals ) mData << pNormal.x() << pNormal.z() << - pNormal.y(); @@ -572,12 +575,12 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh for ( int j = 2; j >= 0; --j ) { p2t::Point *p = t->GetPoint( j ); - QVector4D pt( p->x, p->y, z[p], 0 ); + QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 ); if ( toOldBase ) pt = *toOldBase * pt; const double fx = ( pt.x() / scaleX ) - mOriginX + pt0.x(); const double fy = ( pt.y() / scaleY ) - mOriginY + pt0.y(); - const double fz = pt.z() + extrusionHeight + pt0.z(); + const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.z() ); mData << fx << fz << -fy; if ( mAddNormals ) mData << -pNormal.x() << -pNormal.z() << pNormal.y(); diff --git a/src/core/qgstessellator.h b/src/core/qgstessellator.h index 4664f583b21..ef14c9cdbfa 100644 --- a/src/core/qgstessellator.h +++ b/src/core/qgstessellator.h @@ -47,9 +47,12 @@ class CORE_EXPORT QgsTessellator * Creates tessellator with a specified \a bounds of input geometry coordinates. * This constructor allows the tessellator to map input coordinates to a desirable range for numerically * stability during calculations. + * + * If \a noZ is TRUE, then a 2-dimensional tesselation only will be performed and all z coordinates will be ignored. + * * \since QGIS 3.10 */ - QgsTessellator( const QgsRectangle &bounds, bool addNormals, bool invertNormals = false, bool addBackFaces = false ); + QgsTessellator( const QgsRectangle &bounds, bool addNormals, bool invertNormals = false, bool addBackFaces = false, bool noZ = false ); //! Tessellates a triangle and adds its vertex entries to the output data array void addPolygon( const QgsPolygon &polygon, float extrusionHeight ); @@ -82,6 +85,7 @@ class CORE_EXPORT QgsTessellator bool mAddBackFaces = false; QVector mData; int mStride; + bool mNoZ = false; }; #endif // QGSTESSELLATOR_H diff --git a/tests/src/3d/testqgstessellator.cpp b/tests/src/3d/testqgstessellator.cpp index 423c8a1da17..c28930be8c6 100644 --- a/tests/src/3d/testqgstessellator.cpp +++ b/tests/src/3d/testqgstessellator.cpp @@ -101,6 +101,7 @@ bool checkTriangleOutput( const QVector &data, bool withNormals, const QL TriangleCoords out( dataRaw, withNormals ); if ( exp != out ) { + qDebug() << i; qDebug() << "expected:"; exp.dump(); qDebug() << "got:"; @@ -136,6 +137,7 @@ class TestQgsTessellator : public QObject void testCrashSelfIntersection(); void testCrashEmptyPolygon(); void testBoundsScaling(); + void testNoZ(); private: }; @@ -369,6 +371,21 @@ void TestQgsTessellator::testBoundsScaling() QVERIFY( checkTriangleOutput( t2.data(), true, tc ) ); } +void TestQgsTessellator::testNoZ() +{ + // test tessellation with no z support + QgsPolygon polygonZ; + polygonZ.fromWkt( "POLYGONZ((1 1 1, 2 1 1, 3 2 1, 1 2 1, 1 1 1))" ); + + QList tc; + tc << TriangleCoords( QVector3D( 0, 1, 0 ), QVector3D( 1, 0, 0 ), QVector3D( 2, 1, 0 ) ); + tc << TriangleCoords( QVector3D( 0, 1, 0 ), QVector3D( 0, 0, 0 ), QVector3D( 1, 0, 0 ) ); + + QgsTessellator t( polygonZ.boundingBox(), false, false, false, true ); + t.addPolygon( polygonZ, 0 ); + QVERIFY( checkTriangleOutput( t.data(), false, tc ) ); +} + QGSTEST_MAIN( TestQgsTessellator ) #include "testqgstessellator.moc"