Skip to content

Commit ba9e199

Browse files
authoredDec 5, 2017
Merge pull request #5798 from wonder-sk/polygon-3d-fixes
[3d] Tessellator fixes + culling mode configuration for 3D polygons
2 parents db9fc28 + 1f6cd31 commit ba9e199

11 files changed

+339
-150
lines changed
 

‎src/3d/qgs3dutils.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,30 @@ AltitudeBinding Qgs3DUtils::altBindingFromString( const QString &str )
8282
return AltBindCentroid;
8383
}
8484

85+
QString Qgs3DUtils::cullingModeToString( Qt3DRender::QCullFace::CullingMode mode )
86+
{
87+
switch ( mode )
88+
{
89+
case Qt3DRender::QCullFace::NoCulling: return QStringLiteral( "no-culling" );
90+
case Qt3DRender::QCullFace::Front: return QStringLiteral( "front" );
91+
case Qt3DRender::QCullFace::Back: return QStringLiteral( "back" );
92+
case Qt3DRender::QCullFace::FrontAndBack: return QStringLiteral( "front-and-back" );
93+
}
94+
return QString();
95+
}
96+
97+
Qt3DRender::QCullFace::CullingMode Qgs3DUtils::cullingModeFromString( const QString &str )
98+
{
99+
if ( str == QStringLiteral( "front" ) )
100+
return Qt3DRender::QCullFace::Front;
101+
else if ( str == QStringLiteral( "back" ) )
102+
return Qt3DRender::QCullFace::Back;
103+
else if ( str == QStringLiteral( "front-and-back" ) )
104+
return Qt3DRender::QCullFace::FrontAndBack;
105+
else
106+
return Qt3DRender::QCullFace::NoCulling;
107+
}
108+
85109

86110
void Qgs3DUtils::clampAltitudes( QgsLineString *lineString, AltitudeClamping altClamp, AltitudeBinding altBind, const QgsPoint &centroid, float height, const Qgs3DMapSettings &map )
87111
{

‎src/3d/qgs3dutils.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class QgsPolygon;
2222
#include "qgs3dmapsettings.h"
2323
#include "qgsaabb.h"
2424

25+
#include <Qt3DRender/QCullFace>
26+
2527
//! how to handle altitude of vector features
2628
enum AltitudeClamping
2729
{
@@ -64,6 +66,11 @@ class _3D_EXPORT Qgs3DUtils
6466
//! Converts a string to a value from AltitudeBinding enum
6567
static AltitudeBinding altBindingFromString( const QString &str );
6668

69+
//! Converts a value from CullingMode enum to a string
70+
static QString cullingModeToString( Qt3DRender::QCullFace::CullingMode mode );
71+
//! Converts a string to a value from CullingMode enum
72+
static Qt3DRender::QCullFace::CullingMode cullingModeFromString( const QString &str );
73+
6774
//! Clamps altitude of vertices of a linestring according to the settings
6875
static void clampAltitudes( QgsLineString *lineString, AltitudeClamping altClamp, AltitudeBinding altBind, const QgsPoint &centroid, float height, const Qgs3DMapSettings &map );
6976
//! Clamps altitude of vertices of a polygon according to the settings

‎src/3d/qgstessellatedpolygongeometry.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,10 @@ QgsTessellatedPolygonGeometry::QgsTessellatedPolygonGeometry( QNode *parent )
5858

5959
QgsTessellatedPolygonGeometry::~QgsTessellatedPolygonGeometry()
6060
{
61-
qDeleteAll( mPolygons );
6261
}
6362

6463
void QgsTessellatedPolygonGeometry::setPolygons( const QList<QgsPolygon *> &polygons, const QgsPointXY &origin, float extrusionHeight, const QList<float> &extrusionHeightPerPolygon )
6564
{
66-
qDeleteAll( mPolygons );
67-
mPolygons = polygons;
68-
6965
QgsTessellator tessellator( origin.x(), origin.y(), mWithNormals );
7066
for ( int i = 0; i < polygons.count(); ++i )
7167
{
@@ -74,6 +70,8 @@ void QgsTessellatedPolygonGeometry::setPolygons( const QList<QgsPolygon *> &poly
7470
tessellator.addPolygon( *polygon, extr );
7571
}
7672

73+
qDeleteAll( polygons );
74+
7775
QByteArray data( ( const char * )tessellator.data().constData(), tessellator.data().count() * sizeof( float ) );
7876
int nVerts = data.count() / tessellator.stride();
7977

‎src/3d/qgstessellatedpolygongeometry.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ class QgsTessellatedPolygonGeometry : public Qt3DRender::QGeometry
4545
void setPolygons( const QList<QgsPolygon *> &polygons, const QgsPointXY &origin, float extrusionHeight, const QList<float> &extrusionHeightPerPolygon = QList<float>() );
4646

4747
private:
48-
QList<QgsPolygon *> mPolygons;
4948

5049
Qt3DRender::QAttribute *mPositionAttribute = nullptr;
5150
Qt3DRender::QAttribute *mNormalAttribute = nullptr;

‎src/3d/qgstessellator.cpp

Lines changed: 129 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ static void make_quad( float x0, float y0, float z0, float x1, float y1, float z
3838

3939
// perpendicular vector in plane to [x,y] is [-y,x]
4040
QVector3D vn( -dy, 0, dx );
41+
vn = -vn;
4142
vn.normalize();
4243

4344
// triangle 1
@@ -108,8 +109,8 @@ static void _makeWalls( const QgsCurve &ring, bool ccw, float extrusionHeight, Q
108109
ring.pointAt( is_counter_clockwise == ccw ? i : ring.numPoints() - i - 1, pt, vt );
109110
float x0 = ptPrev.x() - originX, y0 = ptPrev.y() - originY;
110111
float x1 = pt.x() - originX, y1 = pt.y() - originY;
111-
float z0 = ptPrev.z();
112-
float z1 = pt.z();
112+
float z0 = std::isnan( ptPrev.z() ) ? 0 : ptPrev.z();
113+
float z1 = std::isnan( pt.z() ) ? 0 : pt.z();
113114

114115
// make a quad
115116
make_quad( x0, y0, z0, x1, y1, z1, extrusionHeight, data, addNormals );
@@ -170,6 +171,7 @@ static QVector3D _calculateNormal( const QgsCurve *curve, double originX, double
170171
}
171172

172173
QVector3D normal( nx, ny, nz );
174+
//normal = -normal; // TODO: some datasets seem to work better with, others without inversion
173175
normal.normalize();
174176
return normal;
175177
}
@@ -197,24 +199,21 @@ static void _normalVectorToXYVectors( const QVector3D &pNormal, QVector3D &pXVec
197199
}
198200

199201

200-
static void _ringToPoly2tri( const QgsCurve *ring, const QgsPoint &ptFirst, const QMatrix4x4 &toNewBase, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> &zHash )
202+
static void _ringToPoly2tri( const QgsCurve *ring, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> &zHash )
201203
{
202204
QgsVertexId::VertexType vt;
203205
QgsPoint pt;
204206

205207
const int pCount = ring->numPoints();
206-
double x0 = ptFirst.x(), y0 = ptFirst.y(), z0 = ( std::isnan( ptFirst.z() ) ? 0 : ptFirst.z() );
207208

208209
polyline.reserve( pCount );
209210

210211
for ( int i = 0; i < pCount - 1; ++i )
211212
{
212213
ring->pointAt( i, pt, vt );
213-
QVector4D tempPt( pt.x() - x0, pt.y() - y0, std::isnan( pt.z() ) ? 0 : pt.z() - z0, 0 );
214-
QVector4D newBasePt = toNewBase * tempPt;
215-
const float x = newBasePt.x();
216-
const float y = newBasePt.y();
217-
const float z = newBasePt.z();
214+
const float x = pt.x();
215+
const float y = pt.y();
216+
const float z = pt.z();
218217

219218
const bool found = std::find_if( polyline.begin(), polyline.end(), [x, y]( p2t::Point *&p ) { return *p == p2t::Point( x, y ); } ) != polyline.end();
220219

@@ -229,6 +228,35 @@ static void _ringToPoly2tri( const QgsCurve *ring, const QgsPoint &ptFirst, cons
229228
}
230229
}
231230

231+
static QgsCurve *_transform_ring_to_new_base( const QgsCurve &curve, const QgsPoint &pt0, const QMatrix4x4 *toNewBase )
232+
{
233+
int count = curve.numPoints();
234+
QVector<QgsPoint> pts;
235+
pts.reserve( count );
236+
QgsVertexId::VertexType vt;
237+
for ( int i = 0; i < count; ++i )
238+
{
239+
QgsPoint pt;
240+
curve.pointAt( i, pt, vt );
241+
QgsPoint pt2( QgsWkbTypes::PointZ, pt.x() - pt0.x(), pt.y() - pt0.y(), std::isnan( pt.z() ) ? 0 : pt.z() - pt0.z() );
242+
QVector4D v( pt2.x(), pt2.y(), pt2.z(), 0 );
243+
if ( toNewBase )
244+
v = toNewBase->map( v );
245+
pts << QgsPoint( QgsWkbTypes::PointZ, v.x(), v.y(), v.z() );
246+
}
247+
return new QgsLineString( pts );
248+
}
249+
250+
251+
static QgsPolygon *_transform_polygon_to_new_base( const QgsPolygon &polygon, const QgsPoint &pt0, const QMatrix4x4 *toNewBase )
252+
{
253+
QgsPolygon *p = new QgsPolygon;
254+
p->setExteriorRing( _transform_ring_to_new_base( *polygon.exteriorRing(), pt0, toNewBase ) );
255+
for ( int i = 0; i < polygon.numInteriorRings(); ++i )
256+
p->addInteriorRing( _transform_ring_to_new_base( *polygon.interiorRing( i ), pt0, toNewBase ) );
257+
return p;
258+
}
259+
232260
static bool _check_intersecting_rings( const QgsPolygon &polygon )
233261
{
234262
// At this point we assume that input polygons are valid according to the OGC definition.
@@ -290,47 +318,14 @@ double _minimum_distance_between_coordinates( const QgsPolygon &polygon )
290318

291319
void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeight )
292320
{
293-
if ( _minimum_distance_between_coordinates( polygon ) < 0.001 )
294-
{
295-
// when the distances between coordinates of input points are very small,
296-
// the triangulation likes to crash on numerical errors - when the distances are ~ 1e-5
297-
// Assuming that the coordinates should be in a projected CRS, we should be able
298-
// to simplify geometries that may cause problems and avoid possible crashes
299-
QgsGeometry polygonSimplified = QgsGeometry( polygon.clone() ).simplify( 0.001 );
300-
const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet() );
301-
if ( _minimum_distance_between_coordinates( *polygonSimplifiedData ) < 0.001 )
302-
{
303-
// Failed to fix that. It could be a really tiny geometry... or maybe they gave us
304-
// geometry in unprojected lat/lon coordinates
305-
QgsMessageLog::logMessage( "geometry's coordinates are too close to each other and simplification failed - skipping", "3D" );
306-
}
307-
else
308-
{
309-
addPolygon( *polygonSimplifiedData, extrusionHeight );
310-
}
311-
return;
312-
}
313-
314-
if ( !_check_intersecting_rings( polygon ) )
315-
{
316-
// skip the polygon - it would cause a crash inside poly2tri library
317-
QgsMessageLog::logMessage( "polygon rings intersect each other - skipping", "3D" );
318-
return;
319-
}
320-
321321
const QgsCurve *exterior = polygon.exteriorRing();
322322

323-
QList< std::vector<p2t::Point *> > polylinesToDelete;
324-
QHash<p2t::Point *, float> z;
325-
326-
std::vector<p2t::Point *> polyline;
327-
328323
const QVector3D pNormal = _calculateNormal( exterior, mOriginX, mOriginY );
329324
const int pCount = exterior->numPoints();
330325

331-
// Polygon is a triangle
332-
if ( pCount == 4 )
326+
if ( pCount == 4 && polygon.numInteriorRings() == 0 )
333327
{
328+
// polygon is a triangle - write vertices to the output data array without triangulation
334329
QgsPoint pt;
335330
QgsVertexId::VertexType vt;
336331
for ( int i = 0; i < 3; i++ )
@@ -346,88 +341,113 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh
346341
if ( !qgsDoubleNear( pNormal.length(), 1, 0.001 ) )
347342
return; // this should not happen - pNormal should be normalized to unit length
348343

349-
QVector3D pXVector, pYVector;
350-
_normalVectorToXYVectors( pNormal, pXVector, pYVector );
351-
352-
// so now we have three orthogonal unit vectors defining new base
353-
// let's build transform matrix. We actually need just a 3x3 matrix,
354-
// but Qt does not have good support for it, so using 4x4 matrix instead.
355-
QMatrix4x4 toNewBase(
356-
pXVector.x(), pXVector.y(), pXVector.z(), 0,
357-
pYVector.x(), pYVector.y(), pYVector.z(), 0,
358-
pNormal.x(), pNormal.y(), pNormal.z(), 0,
359-
0, 0, 0, 0 );
360-
361-
// our 3x3 matrix is orthogonal, so for inverse we only need to transpose it
362-
QMatrix4x4 toOldBase = toNewBase.transposed();
344+
std::unique_ptr<QMatrix4x4> toNewBase, toOldBase;
345+
if ( pNormal != QVector3D( 0, 0, 1 ) )
346+
{
347+
// this is not a horizontal plane - need to reproject the polygon to a new base so that
348+
// we can do the triangulation in a plane
349+
350+
QVector3D pXVector, pYVector;
351+
_normalVectorToXYVectors( pNormal, pXVector, pYVector );
352+
353+
// so now we have three orthogonal unit vectors defining new base
354+
// let's build transform matrix. We actually need just a 3x3 matrix,
355+
// but Qt does not have good support for it, so using 4x4 matrix instead.
356+
toNewBase.reset( new QMatrix4x4(
357+
pXVector.x(), pXVector.y(), pXVector.z(), 0,
358+
pYVector.x(), pYVector.y(), pYVector.z(), 0,
359+
pNormal.x(), pNormal.y(), pNormal.z(), 0,
360+
0, 0, 0, 0 ) );
361+
362+
// our 3x3 matrix is orthogonal, so for inverse we only need to transpose it
363+
toOldBase.reset( new QMatrix4x4( toNewBase->transposed() ) );
364+
}
363365

364-
const QgsPoint ptFirst( exterior->startPoint() );
365-
_ringToPoly2tri( exterior, ptFirst, toNewBase, polyline, z );
366-
polylinesToDelete << polyline;
366+
const QgsPoint ptStart( exterior->startPoint() );
367+
const QgsPoint pt0( QgsWkbTypes::PointZ, ptStart.x(), ptStart.y(), std::isnan( ptStart.z() ) ? 0 : ptStart.z() );
367368

368-
// TODO: robustness (no nearly duplicate points, invalid geometries ...)
369+
// subtract ptFirst from geometry for better numerical stability in triangulation
370+
// and apply new 3D vector base if the polygon is not horizontal
371+
std::unique_ptr<QgsPolygon> polygonNew( _transform_polygon_to_new_base( polygon, pt0, toNewBase.get() ) );
369372

370-
double x0 = ptFirst.x(), y0 = ptFirst.y(), z0 = ( std::isnan( ptFirst.z() ) ? 0 : ptFirst.z() );
371-
if ( polyline.size() == 3 && polygon.numInteriorRings() == 0 )
373+
if ( _minimum_distance_between_coordinates( *polygonNew ) < 0.001 )
372374
{
373-
for ( std::vector<p2t::Point *>::iterator it = polyline.begin(); it != polyline.end(); it++ )
375+
// when the distances between coordinates of input points are very small,
376+
// the triangulation likes to crash on numerical errors - when the distances are ~ 1e-5
377+
// Assuming that the coordinates should be in a projected CRS, we should be able
378+
// to simplify geometries that may cause problems and avoid possible crashes
379+
QgsGeometry polygonSimplified = QgsGeometry( polygonNew->clone() ).simplify( 0.001 );
380+
const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet() );
381+
if ( _minimum_distance_between_coordinates( *polygonSimplifiedData ) < 0.001 )
382+
{
383+
// Failed to fix that. It could be a really tiny geometry... or maybe they gave us
384+
// geometry in unprojected lat/lon coordinates
385+
QgsMessageLog::logMessage( "geometry's coordinates are too close to each other and simplification failed - skipping", "3D" );
386+
return;
387+
}
388+
else
374389
{
375-
p2t::Point *p = *it;
376-
QVector4D ptInNewBase( p->x, p->y, z[p], 0 );
377-
QVector4D nPoint = toOldBase * ptInNewBase;
378-
const double fx = nPoint.x() - mOriginX + x0;
379-
const double fy = nPoint.y() - mOriginY + y0;
380-
const double fz = nPoint.z() + extrusionHeight + z0;
381-
mData << fx << fz << -fy;
382-
if ( mAddNormals )
383-
mData << pNormal.x() << pNormal.z() << - pNormal.y();
390+
polygonNew.reset( polygonSimplifiedData->clone() );
384391
}
385392
}
386-
else if ( polyline.size() >= 3 )
393+
394+
if ( !_check_intersecting_rings( *polygonNew.get() ) )
387395
{
388-
p2t::CDT *cdt = new p2t::CDT( polyline );
396+
// skip the polygon - it would cause a crash inside poly2tri library
397+
QgsMessageLog::logMessage( "polygon rings intersect each other - skipping", "3D" );
398+
return;
399+
}
389400

390-
// polygon holes
391-
for ( int i = 0; i < polygon.numInteriorRings(); ++i )
392-
{
393-
std::vector<p2t::Point *> holePolyline;
394-
const QgsCurve *hole = polygon.interiorRing( i );
401+
QList< std::vector<p2t::Point *> > polylinesToDelete;
402+
QHash<p2t::Point *, float> z;
395403

396-
_ringToPoly2tri( hole, ptFirst, toNewBase, holePolyline, z );
404+
// polygon exterior
405+
std::vector<p2t::Point *> polyline;
406+
_ringToPoly2tri( polygonNew->exteriorRing(), polyline, z );
407+
polylinesToDelete << polyline;
397408

398-
cdt->AddHole( holePolyline );
399-
polylinesToDelete << holePolyline;
400-
}
409+
std::unique_ptr<p2t::CDT> cdt( new p2t::CDT( polyline ) );
401410

402-
try
403-
{
404-
cdt->Triangulate();
411+
// polygon holes
412+
for ( int i = 0; i < polygonNew->numInteriorRings(); ++i )
413+
{
414+
std::vector<p2t::Point *> holePolyline;
415+
const QgsCurve *hole = polygonNew->interiorRing( i );
416+
417+
_ringToPoly2tri( hole, holePolyline, z );
418+
419+
cdt->AddHole( holePolyline );
420+
polylinesToDelete << holePolyline;
421+
}
422+
423+
// run triangulation and write vertices to the output data array
424+
try
425+
{
426+
cdt->Triangulate();
405427

406-
std::vector<p2t::Triangle *> triangles = cdt->GetTriangles();
428+
std::vector<p2t::Triangle *> triangles = cdt->GetTriangles();
407429

408-
for ( size_t i = 0; i < triangles.size(); ++i )
430+
for ( size_t i = 0; i < triangles.size(); ++i )
431+
{
432+
p2t::Triangle *t = triangles[i];
433+
for ( int j = 0; j < 3; ++j )
409434
{
410-
p2t::Triangle *t = triangles[i];
411-
for ( int j = 0; j < 3; ++j )
412-
{
413-
p2t::Point *p = t->GetPoint( j );
414-
QVector4D ptInNewBase( p->x, p->y, z[p], 0 );
415-
QVector4D nPoint = toOldBase * ptInNewBase;
416-
const double fx = nPoint.x() - mOriginX + x0;
417-
const double fy = nPoint.y() - mOriginY + y0;
418-
const double fz = nPoint.z() + extrusionHeight + z0;
419-
mData << fx << fz << -fy;
420-
if ( mAddNormals )
421-
mData << pNormal.x() << pNormal.z() << - pNormal.y();
422-
}
435+
p2t::Point *p = t->GetPoint( j );
436+
QVector4D pt( p->x, p->y, z[p], 0 );
437+
if ( toOldBase )
438+
pt = *toOldBase * pt;
439+
const double fx = pt.x() - mOriginX + pt0.x();
440+
const double fy = pt.y() - mOriginY + pt0.y();
441+
const double fz = pt.z() + extrusionHeight + pt0.z();
442+
mData << fx << fz << -fy;
443+
if ( mAddNormals )
444+
mData << pNormal.x() << pNormal.z() << - pNormal.y();
423445
}
424446
}
425-
catch ( ... )
426-
{
427-
QgsMessageLog::logMessage( "Triangulation failed. Skipping polygon...", "3D" );
428-
}
429-
430-
delete cdt;
447+
}
448+
catch ( ... )
449+
{
450+
QgsMessageLog::logMessage( "Triangulation failed. Skipping polygon...", "3D" );
431451
}
432452

433453
for ( int i = 0; i < polylinesToDelete.count(); ++i )

‎src/3d/symbols/qgspolygon3dsymbol.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ void QgsPolygon3DSymbol::writeXml( QDomElement &elem, const QgsReadWriteContext
3131
elemDataProperties.setAttribute( QStringLiteral( "alt-binding" ), Qgs3DUtils::altBindingToString( mAltBinding ) );
3232
elemDataProperties.setAttribute( QStringLiteral( "height" ), mHeight );
3333
elemDataProperties.setAttribute( QStringLiteral( "extrusion-height" ), mExtrusionHeight );
34+
elemDataProperties.setAttribute( QStringLiteral( "culling-mode" ), Qgs3DUtils::cullingModeToString( mCullingMode ) );
3435
elem.appendChild( elemDataProperties );
3536

3637
QDomElement elemMaterial = doc.createElement( QStringLiteral( "material" ) );
@@ -51,6 +52,7 @@ void QgsPolygon3DSymbol::readXml( const QDomElement &elem, const QgsReadWriteCon
5152
mAltBinding = Qgs3DUtils::altBindingFromString( elemDataProperties.attribute( QStringLiteral( "alt-binding" ) ) );
5253
mHeight = elemDataProperties.attribute( QStringLiteral( "height" ) ).toFloat();
5354
mExtrusionHeight = elemDataProperties.attribute( QStringLiteral( "extrusion-height" ) ).toFloat();
55+
mCullingMode = Qgs3DUtils::cullingModeFromString( elemDataProperties.attribute( QStringLiteral( "culling-mode" ) ) );
5456

5557
QDomElement elemMaterial = elem.firstChildElement( QStringLiteral( "material" ) );
5658
mMaterial.readXml( elemMaterial );

‎src/3d/symbols/qgspolygon3dsymbol.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "qgsphongmaterialsettings.h"
2323
#include "qgs3dutils.h"
2424

25+
#include <Qt3DRender/QCullFace>
2526

2627
/**
2728
* \ingroup 3d
@@ -65,6 +66,11 @@ class _3D_EXPORT QgsPolygon3DSymbol : public QgsAbstract3DSymbol
6566
//! Sets material used for shading of the symbol
6667
void setMaterial( const QgsPhongMaterialSettings &material ) { mMaterial = material; }
6768

69+
//! Returns front/back culling mode
70+
Qt3DRender::QCullFace::CullingMode cullingMode() const { return mCullingMode; }
71+
//! Sets front/back culling mode
72+
void setCullingMode( Qt3DRender::QCullFace::CullingMode mode ) { mCullingMode = mode; }
73+
6874
private:
6975
//! how to handle altitude of vector features
7076
AltitudeClamping mAltClamping = AltClampRelative;
@@ -74,6 +80,7 @@ class _3D_EXPORT QgsPolygon3DSymbol : public QgsAbstract3DSymbol
7480
float mHeight = 0.0f; //!< Base height of polygons
7581
float mExtrusionHeight = 0.0f; //!< How much to extrude (0 means no walls)
7682
QgsPhongMaterialSettings mMaterial; //!< Defines appearance of objects
83+
Qt3DRender::QCullFace::CullingMode mCullingMode = Qt3DRender::QCullFace::NoCulling; //!< Front/back culling mode
7784
};
7885

7986

‎src/3d/symbols/qgspolygon3dsymbol_p.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
#include "qgs3dutils.h"
2222

2323
#include <Qt3DCore/QTransform>
24+
#include <Qt3DRender/QEffect>
25+
#include <Qt3DRender/QTechnique>
26+
#include <Qt3DRender/QCullFace>
2427

2528
#include "qgsvectorlayer.h"
2629
#include "qgsmultipolygon.h"
@@ -103,9 +106,24 @@ void QgsPolygon3DSymbolEntity::addEntityForNotSelectedPolygons( const Qgs3DMapSe
103106
entity->setParent( this );
104107
}
105108

109+
106110
Qt3DExtras::QPhongMaterial *QgsPolygon3DSymbolEntity::material( const QgsPolygon3DSymbol &symbol ) const
107111
{
108112
Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial;
113+
114+
// front/back side culling
115+
auto techniques = material->effect()->techniques();
116+
for ( auto tit = techniques.constBegin(); tit != techniques.constEnd(); ++tit )
117+
{
118+
auto renderPasses = ( *tit )->renderPasses();
119+
for ( auto rpit = renderPasses.begin(); rpit != renderPasses.end(); ++rpit )
120+
{
121+
Qt3DRender::QCullFace *cullFace = new Qt3DRender::QCullFace;
122+
cullFace->setMode( symbol.cullingMode() );
123+
( *rpit )->addRenderState( cullFace );
124+
}
125+
}
126+
109127
material->setAmbient( symbol.material().ambient() );
110128
material->setDiffuse( symbol.material().diffuse() );
111129
material->setSpecular( symbol.material().specular() );

‎src/app/3d/qgspolygon3dsymbolwidget.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,45 @@ QgsPolygon3DSymbolWidget::QgsPolygon3DSymbolWidget( QWidget *parent )
3131
connect( spinExtrusion, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsPolygon3DSymbolWidget::changed );
3232
connect( cboAltClamping, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsPolygon3DSymbolWidget::changed );
3333
connect( cboAltBinding, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsPolygon3DSymbolWidget::changed );
34+
connect( cboCullingMode, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsPolygon3DSymbolWidget::changed );
3435
connect( widgetMaterial, &QgsPhongMaterialWidget::changed, this, &QgsPolygon3DSymbolWidget::changed );
3536
connect( btnHeightDD, &QgsPropertyOverrideButton::changed, this, &QgsPolygon3DSymbolWidget::changed );
3637
connect( btnExtrusionDD, &QgsPropertyOverrideButton::changed, this, &QgsPolygon3DSymbolWidget::changed );
3738
}
3839

40+
41+
static int _cullingModeToIndex( Qt3DRender::QCullFace::CullingMode mode )
42+
{
43+
switch ( mode )
44+
{
45+
case Qt3DRender::QCullFace::NoCulling: return 0;
46+
case Qt3DRender::QCullFace::Front: return 1;
47+
case Qt3DRender::QCullFace::Back: return 2;
48+
case Qt3DRender::QCullFace::FrontAndBack: return 3;
49+
}
50+
return 0;
51+
}
52+
53+
static Qt3DRender::QCullFace::CullingMode _cullingModeFromIndex( int index )
54+
{
55+
switch ( index )
56+
{
57+
case 0: return Qt3DRender::QCullFace::NoCulling;
58+
case 1: return Qt3DRender::QCullFace::Front;
59+
case 2: return Qt3DRender::QCullFace::Back;
60+
case 3: return Qt3DRender::QCullFace::FrontAndBack;
61+
}
62+
return Qt3DRender::QCullFace::NoCulling;
63+
}
64+
65+
3966
void QgsPolygon3DSymbolWidget::setSymbol( const QgsPolygon3DSymbol &symbol, QgsVectorLayer *layer )
4067
{
4168
spinHeight->setValue( symbol.height() );
4269
spinExtrusion->setValue( symbol.extrusionHeight() );
4370
cboAltClamping->setCurrentIndex( ( int ) symbol.altitudeClamping() );
4471
cboAltBinding->setCurrentIndex( ( int ) symbol.altitudeBinding() );
72+
cboCullingMode->setCurrentIndex( _cullingModeToIndex( symbol.cullingMode() ) );
4573
widgetMaterial->setMaterial( symbol.material() );
4674

4775
btnHeightDD->init( QgsAbstract3DSymbol::PropertyHeight, symbol.dataDefinedProperties(), QgsAbstract3DSymbol::propertyDefinitions(), layer, true );
@@ -55,6 +83,7 @@ QgsPolygon3DSymbol QgsPolygon3DSymbolWidget::symbol() const
5583
sym.setExtrusionHeight( spinExtrusion->value() );
5684
sym.setAltitudeClamping( ( AltitudeClamping ) cboAltClamping->currentIndex() );
5785
sym.setAltitudeBinding( ( AltitudeBinding ) cboAltBinding->currentIndex() );
86+
sym.setCullingMode( _cullingModeFromIndex( cboCullingMode->currentIndex() ) );
5887
sym.setMaterial( widgetMaterial->material() );
5988

6089
QgsPropertyCollection ddp;

‎src/ui/3d/polygon3dsymbolwidget.ui

Lines changed: 60 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,18 @@
66
<rect>
77
<x>0</x>
88
<y>0</y>
9-
<width>538</width>
9+
<width>561</width>
1010
<height>452</height>
1111
</rect>
1212
</property>
1313
<property name="windowTitle">
1414
<string>Form</string>
1515
</property>
1616
<layout class="QGridLayout" name="gridLayout">
17-
<item row="0" column="0">
18-
<widget class="QLabel" name="label">
17+
<item row="2" column="0">
18+
<widget class="QLabel" name="label_3">
1919
<property name="text">
20-
<string>Height</string>
20+
<string>Altitude Clamping</string>
2121
</property>
2222
</widget>
2323
</item>
@@ -31,38 +31,55 @@
3131
</property>
3232
</widget>
3333
</item>
34-
<item row="0" column="2">
35-
<widget class="QgsPropertyOverrideButton" name="btnHeightDD">
36-
<property name="text">
37-
<string>...</string>
38-
</property>
39-
</widget>
40-
</item>
4134
<item row="1" column="0">
4235
<widget class="QLabel" name="label_2">
4336
<property name="text">
4437
<string>Extrusion</string>
4538
</property>
4639
</widget>
4740
</item>
48-
<item row="1" column="1">
49-
<widget class="QgsDoubleSpinBox" name="spinExtrusion">
50-
<property name="maximum">
51-
<double>99999.000000000000000</double>
41+
<item row="1" column="2">
42+
<widget class="QgsPropertyOverrideButton" name="btnExtrusionDD">
43+
<property name="text">
44+
<string>...</string>
5245
</property>
5346
</widget>
5447
</item>
55-
<item row="1" column="2">
56-
<widget class="QgsPropertyOverrideButton" name="btnExtrusionDD">
48+
<item row="0" column="2">
49+
<widget class="QgsPropertyOverrideButton" name="btnHeightDD">
5750
<property name="text">
5851
<string>...</string>
5952
</property>
6053
</widget>
6154
</item>
62-
<item row="2" column="0">
63-
<widget class="QLabel" name="label_3">
55+
<item row="0" column="0">
56+
<widget class="QLabel" name="label">
6457
<property name="text">
65-
<string>Altitude Clamping</string>
58+
<string>Height</string>
59+
</property>
60+
</widget>
61+
</item>
62+
<item row="3" column="1">
63+
<widget class="QComboBox" name="cboAltBinding">
64+
<item>
65+
<property name="text">
66+
<string>Vertex</string>
67+
</property>
68+
</item>
69+
<item>
70+
<property name="text">
71+
<string>Centroid</string>
72+
</property>
73+
</item>
74+
</widget>
75+
</item>
76+
<item row="6" column="0" colspan="3">
77+
<widget class="QgsPhongMaterialWidget" name="widgetMaterial" native="true"/>
78+
</item>
79+
<item row="5" column="0" colspan="3">
80+
<widget class="Line" name="line">
81+
<property name="orientation">
82+
<enum>Qt::Horizontal</enum>
6683
</property>
6784
</widget>
6885
</item>
@@ -92,30 +109,39 @@
92109
</property>
93110
</widget>
94111
</item>
95-
<item row="3" column="1">
96-
<widget class="QComboBox" name="cboAltBinding">
112+
<item row="1" column="1">
113+
<widget class="QgsDoubleSpinBox" name="spinExtrusion">
114+
<property name="maximum">
115+
<double>99999.000000000000000</double>
116+
</property>
117+
</widget>
118+
</item>
119+
<item row="4" column="0">
120+
<widget class="QLabel" name="label_5">
121+
<property name="text">
122+
<string>Culling Mode</string>
123+
</property>
124+
</widget>
125+
</item>
126+
<item row="4" column="1">
127+
<widget class="QComboBox" name="cboCullingMode">
97128
<item>
98129
<property name="text">
99-
<string>Vertex</string>
130+
<string>No culling</string>
100131
</property>
101132
</item>
102133
<item>
103134
<property name="text">
104-
<string>Centroid</string>
135+
<string>Front</string>
136+
</property>
137+
</item>
138+
<item>
139+
<property name="text">
140+
<string>Back</string>
105141
</property>
106142
</item>
107143
</widget>
108144
</item>
109-
<item row="4" column="0" colspan="3">
110-
<widget class="Line" name="line">
111-
<property name="orientation">
112-
<enum>Qt::Horizontal</enum>
113-
</property>
114-
</widget>
115-
</item>
116-
<item row="5" column="0" colspan="3">
117-
<widget class="QgsPhongMaterialWidget" name="widgetMaterial" native="true"/>
118-
</item>
119145
</layout>
120146
</widget>
121147
<customwidgets>

‎tests/src/3d/testqgstessellator.cpp

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222
#include "qgstessellator.h"
2323
#include "qgsmultipolygon.h"
2424

25+
static bool qgsVectorNear( const QVector3D &v1, const QVector3D &v2, double eps )
26+
{
27+
return qgsDoubleNear( v1.x(), v2.x(), eps ) && qgsDoubleNear( v1.y(), v2.y(), eps ) && qgsDoubleNear( v1.z(), v2.z(), eps );
28+
}
29+
2530
/**
2631
* Simple structure to record an expected triangle from tessellator.
2732
* Triangle vertices are expected to be in counter-clockwise order.
@@ -55,8 +60,13 @@ struct TriangleCoords
5560
bool operator==( const TriangleCoords &other ) const
5661
{
5762
// TODO: allow that the two triangles have coordinates shifted (but still in the same order)
58-
return pts[0] == other.pts[0] && pts[1] == other.pts[1] && pts[2] == other.pts[2] &&
59-
normals[0] == other.normals[0] && normals[1] == other.normals[1] && normals[2] == other.normals[2];
63+
const double eps = 1e-6;
64+
return qgsVectorNear( pts[0], other.pts[0], eps ) &&
65+
qgsVectorNear( pts[1], other.pts[1], eps ) &&
66+
qgsVectorNear( pts[2], other.pts[2], eps ) &&
67+
qgsVectorNear( normals[0], other.normals[0], eps ) &&
68+
qgsVectorNear( normals[1], other.normals[1], eps ) &&
69+
qgsVectorNear( normals[2], other.normals[2], eps );
6070
}
6171

6272
bool operator!=( const TriangleCoords &other ) const
@@ -175,6 +185,42 @@ void TestQgsTessellator::testBasic()
175185

176186
void TestQgsTessellator::testWalls()
177187
{
188+
QgsPolygon rect;
189+
rect.fromWkt( "POLYGON((0 0, 3 0, 3 2, 0 2, 0 0))" );
190+
191+
QVector3D zPos( 0, 0, 1 );
192+
QVector3D xPos( 1, 0, 0 );
193+
QVector3D yPos( 0, 1, 0 );
194+
QVector3D xNeg( -1, 0, 0 );
195+
QVector3D yNeg( 0, -1, 0 );
196+
197+
QList<TriangleCoords> tcRect;
198+
tcRect << TriangleCoords( QVector3D( 0, 2, 1 ), QVector3D( 3, 0, 1 ), QVector3D( 3, 2, 1 ), zPos, zPos, zPos );
199+
tcRect << TriangleCoords( QVector3D( 0, 2, 1 ), QVector3D( 0, 0, 1 ), QVector3D( 3, 0, 1 ), zPos, zPos, zPos );
200+
tcRect << TriangleCoords( QVector3D( 0, 0, 1 ), QVector3D( 0, 2, 1 ), QVector3D( 0, 0, 0 ), xNeg, xNeg, xNeg );
201+
tcRect << TriangleCoords( QVector3D( 0, 0, 0 ), QVector3D( 0, 2, 1 ), QVector3D( 0, 2, 0 ), xNeg, xNeg, xNeg );
202+
tcRect << TriangleCoords( QVector3D( 0, 2, 1 ), QVector3D( 3, 2, 1 ), QVector3D( 0, 2, 0 ), yPos, yPos, yPos );
203+
tcRect << TriangleCoords( QVector3D( 0, 2, 0 ), QVector3D( 3, 2, 1 ), QVector3D( 3, 2, 0 ), yPos, yPos, yPos );
204+
tcRect << TriangleCoords( QVector3D( 3, 2, 1 ), QVector3D( 3, 0, 1 ), QVector3D( 3, 2, 0 ), xPos, xPos, xPos );
205+
tcRect << TriangleCoords( QVector3D( 3, 2, 0 ), QVector3D( 3, 0, 1 ), QVector3D( 3, 0, 0 ), xPos, xPos, xPos );
206+
tcRect << TriangleCoords( QVector3D( 3, 0, 1 ), QVector3D( 0, 0, 1 ), QVector3D( 3, 0, 0 ), yNeg, yNeg, yNeg );
207+
tcRect << TriangleCoords( QVector3D( 3, 0, 0 ), QVector3D( 0, 0, 1 ), QVector3D( 0, 0, 0 ), yNeg, yNeg, yNeg );
208+
209+
QgsTessellator tRect( 0, 0, true );
210+
tRect.addPolygon( rect, 1 );
211+
QVERIFY( checkTriangleOutput( tRect.data(), true, tcRect ) );
212+
213+
// try to extrude a polygon with reverse (clock-wise) order of vertices and check it is still fine
214+
215+
QgsPolygon rectRev;
216+
rectRev.fromWkt( "POLYGON((0 0, 0 2, 3 2, 3 0, 0 0))" );
217+
218+
QgsTessellator tRectRev( 0, 0, true );
219+
tRectRev.addPolygon( rectRev, 1 );
220+
QVERIFY( checkTriangleOutput( tRectRev.data(), true, tcRect ) );
221+
222+
// this is a more complicated polygon with Z coordinates where the "roof" is not in one plane
223+
178224
QgsPolygon polygonZ;
179225
polygonZ.fromWkt( "POLYGONZ((1 1 1, 2 1 2, 3 2 3, 1 2 4, 1 1 1))" );
180226

@@ -216,6 +262,19 @@ void TestQgsTessellator::asMultiPolygon()
216262

217263
void TestQgsTessellator::testBadCoordinates()
218264
{
265+
// check with a vertical "wall" polygon - if the Z coordinates are ignored,
266+
// the polygon may be incorrectly considered as having close/repeated coordinates
267+
QList<TriangleCoords> tcZ;
268+
tcZ << TriangleCoords( QVector3D( 1, 2, 2 ), QVector3D( 2, 1, 1 ), QVector3D( 2, 1, 2 ) );
269+
tcZ << TriangleCoords( QVector3D( 1, 2, 2 ), QVector3D( 1, 2, 1 ), QVector3D( 2, 1, 1 ) );
270+
271+
QgsPolygon polygonZ;
272+
polygonZ.fromWkt( "POLYGONZ((1 2 1, 2 1 1, 2 1 2, 1 2 2, 1 2 1))" );
273+
274+
QgsTessellator tZ( 0, 0, false );
275+
tZ.addPolygon( polygonZ, 0 );
276+
QVERIFY( checkTriangleOutput( tZ.data(), false, tcZ ) );
277+
219278
// triangulation would crash for me with this polygon if there is no simplification
220279
// to remove the coordinates that are very close to each other
221280
QgsPolygon polygon;

0 commit comments

Comments
 (0)
Please sign in to comment.