Skip to content

Commit 057f2b3

Browse files
committed
[3d] Allow the user to invert calculated normals of faces
It seems that some data sources / formats with 3D polygons order vertices in clockwise order for the front side of the polygons, while others use counter-clockwise order of vertices. While culling mode configuration fixes some problems with rendering (e.g. only back walls are rendered instead of front walls), there still may be issues with shading if the normals are pointing the other way than the polygon was supposed to.
1 parent 24c1c86 commit 057f2b3

9 files changed

+89
-60
lines changed

src/3d/qgstessellatedpolygongeometry.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ QgsTessellatedPolygonGeometry::~QgsTessellatedPolygonGeometry()
6262

6363
void QgsTessellatedPolygonGeometry::setPolygons( const QList<QgsPolygon *> &polygons, const QgsPointXY &origin, float extrusionHeight, const QList<float> &extrusionHeightPerPolygon )
6464
{
65-
QgsTessellator tessellator( origin.x(), origin.y(), mWithNormals );
65+
QgsTessellator tessellator( origin.x(), origin.y(), mWithNormals, mInvertNormals );
6666
for ( int i = 0; i < polygons.count(); ++i )
6767
{
6868
QgsPolygon *polygon = polygons.at( i );

src/3d/qgstessellatedpolygongeometry.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ class QgsTessellatedPolygonGeometry : public Qt3DRender::QGeometry
4141
QgsTessellatedPolygonGeometry( QNode *parent = nullptr );
4242
~QgsTessellatedPolygonGeometry();
4343

44+
//! Returns whether the normals of triangles will be inverted (useful for fixing clockwise / counter-clockwise face vertex orders)
45+
bool invertNormals() const { return mInvertNormals; }
46+
//! Sets whether the normals of triangles will be inverted (useful for fixing clockwise / counter-clockwise face vertex orders)
47+
void setInvertNormals( bool invert ) { mInvertNormals = invert; }
48+
4449
//! Initializes vertex buffer from given polygons. Takes ownership of passed polygon geometries
4550
void setPolygons( const QList<QgsPolygon *> &polygons, const QgsPointXY &origin, float extrusionHeight, const QList<float> &extrusionHeightPerPolygon = QList<float>() );
4651

@@ -51,6 +56,7 @@ class QgsTessellatedPolygonGeometry : public Qt3DRender::QGeometry
5156
Qt3DRender::QBuffer *mVertexBuffer = nullptr;
5257

5358
bool mWithNormals = true;
59+
bool mInvertNormals = false;
5460
};
5561

5662
#endif // QGSTESSELLATEDPOLYGONGEOMETRY_H

src/3d/qgstessellator.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include <QVector3D>
3232
#include <algorithm>
3333

34+
3435
static void make_quad( float x0, float y0, float z0, float x1, float y1, float z1, float height, QVector<float> &data, bool addNormals )
3536
{
3637
float dx = x1 - x0;
@@ -65,10 +66,11 @@ static void make_quad( float x0, float y0, float z0, float x1, float y1, float z
6566
}
6667

6768

68-
QgsTessellator::QgsTessellator( double originX, double originY, bool addNormals )
69+
QgsTessellator::QgsTessellator( double originX, double originY, bool addNormals, bool invertNormals )
6970
: mOriginX( originX )
7071
, mOriginY( originY )
7172
, mAddNormals( addNormals )
73+
, mInvertNormals( invertNormals )
7274
{
7375
mStride = 3 * sizeof( float );
7476
if ( addNormals )
@@ -118,7 +120,7 @@ static void _makeWalls( const QgsCurve &ring, bool ccw, float extrusionHeight, Q
118120
}
119121
}
120122

121-
static QVector3D _calculateNormal( const QgsCurve *curve, double originX, double originY )
123+
static QVector3D _calculateNormal( const QgsCurve *curve, double originX, double originY, bool invertNormal )
122124
{
123125
QgsVertexId::VertexType vt;
124126
QgsPoint pt1, pt2;
@@ -171,7 +173,8 @@ static QVector3D _calculateNormal( const QgsCurve *curve, double originX, double
171173
}
172174

173175
QVector3D normal( nx, ny, nz );
174-
//normal = -normal; // TODO: some datasets seem to work better with, others without inversion
176+
if ( invertNormal )
177+
normal = -normal;
175178
normal.normalize();
176179
return normal;
177180
}
@@ -320,7 +323,7 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh
320323
{
321324
const QgsCurve *exterior = polygon.exteriorRing();
322325

323-
const QVector3D pNormal = _calculateNormal( exterior, mOriginX, mOriginY );
326+
const QVector3D pNormal = _calculateNormal( exterior, mOriginX, mOriginY, mInvertNormals );
324327
const int pCount = exterior->numPoints();
325328

326329
if ( pCount == 4 && polygon.numInteriorRings() == 0 )

src/3d/qgstessellator.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class _3D_EXPORT QgsTessellator
3939
{
4040
public:
4141
//! Creates tessellator with a specified origin point of the world (in map coordinates)
42-
QgsTessellator( double originX, double originY, bool addNormals );
42+
QgsTessellator( double originX, double originY, bool addNormals, bool invertNormals = false );
4343

4444
//! Tessellates a triangle and adds its vertex entries to the output data array
4545
void addPolygon( const QgsPolygon &polygon, float extrusionHeight );
@@ -57,6 +57,7 @@ class _3D_EXPORT QgsTessellator
5757
private:
5858
double mOriginX, mOriginY;
5959
bool mAddNormals;
60+
bool mInvertNormals;
6061
QVector<float> mData;
6162
int mStride;
6263
};

src/3d/symbols/qgspolygon3dsymbol.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ void QgsPolygon3DSymbol::writeXml( QDomElement &elem, const QgsReadWriteContext
3232
elemDataProperties.setAttribute( QStringLiteral( "height" ), mHeight );
3333
elemDataProperties.setAttribute( QStringLiteral( "extrusion-height" ), mExtrusionHeight );
3434
elemDataProperties.setAttribute( QStringLiteral( "culling-mode" ), Qgs3DUtils::cullingModeToString( mCullingMode ) );
35+
elemDataProperties.setAttribute( QStringLiteral( "invert-normals" ), mInvertNormals ? "1" : "0" );
3536
elem.appendChild( elemDataProperties );
3637

3738
QDomElement elemMaterial = doc.createElement( QStringLiteral( "material" ) );
@@ -53,6 +54,7 @@ void QgsPolygon3DSymbol::readXml( const QDomElement &elem, const QgsReadWriteCon
5354
mHeight = elemDataProperties.attribute( QStringLiteral( "height" ) ).toFloat();
5455
mExtrusionHeight = elemDataProperties.attribute( QStringLiteral( "extrusion-height" ) ).toFloat();
5556
mCullingMode = Qgs3DUtils::cullingModeFromString( elemDataProperties.attribute( QStringLiteral( "culling-mode" ) ) );
57+
mInvertNormals = elemDataProperties.attribute( QStringLiteral( "invert-normals" ) ).toInt();
5658

5759
QDomElement elemMaterial = elem.firstChildElement( QStringLiteral( "material" ) );
5860
mMaterial.readXml( elemMaterial );

src/3d/symbols/qgspolygon3dsymbol.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ class _3D_EXPORT QgsPolygon3DSymbol : public QgsAbstract3DSymbol
7171
//! Sets front/back culling mode
7272
void setCullingMode( Qt3DRender::QCullFace::CullingMode mode ) { mCullingMode = mode; }
7373

74+
//! Returns whether the normals of triangles will be inverted (useful for fixing clockwise / counter-clockwise face vertex orders)
75+
bool invertNormals() const { return mInvertNormals; }
76+
//! Sets whether the normals of triangles will be inverted (useful for fixing clockwise / counter-clockwise face vertex orders)
77+
void setInvertNormals( bool invert ) { mInvertNormals = invert; }
78+
7479
private:
7580
//! how to handle altitude of vector features
7681
AltitudeClamping mAltClamping = AltClampRelative;
@@ -81,6 +86,7 @@ class _3D_EXPORT QgsPolygon3DSymbol : public QgsAbstract3DSymbol
8186
float mExtrusionHeight = 0.0f; //!< How much to extrude (0 means no walls)
8287
QgsPhongMaterialSettings mMaterial; //!< Defines appearance of objects
8388
Qt3DRender::QCullFace::CullingMode mCullingMode = Qt3DRender::QCullFace::NoCulling; //!< Front/back culling mode
89+
bool mInvertNormals = false;
8490
};
8591

8692

src/3d/symbols/qgspolygon3dsymbol_p.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ Qt3DRender::QGeometryRenderer *QgsPolygon3DSymbolEntityNode::renderer( const Qgs
199199
}
200200

201201
mGeometry = new QgsTessellatedPolygonGeometry;
202+
mGeometry->setInvertNormals( symbol.invertNormals() );
202203
mGeometry->setPolygons( polygons, origin, symbol.extrusionHeight(), extrusionHeightPerPolygon );
203204

204205
Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;

src/app/3d/qgspolygon3dsymbolwidget.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ QgsPolygon3DSymbolWidget::QgsPolygon3DSymbolWidget( QWidget *parent )
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 );
3434
connect( cboCullingMode, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsPolygon3DSymbolWidget::changed );
35+
connect( chkInvertNormals, &QCheckBox::clicked, this, &QgsPolygon3DSymbolWidget::changed );
3536
connect( widgetMaterial, &QgsPhongMaterialWidget::changed, this, &QgsPolygon3DSymbolWidget::changed );
3637
connect( btnHeightDD, &QgsPropertyOverrideButton::changed, this, &QgsPolygon3DSymbolWidget::changed );
3738
connect( btnExtrusionDD, &QgsPropertyOverrideButton::changed, this, &QgsPolygon3DSymbolWidget::changed );
@@ -70,6 +71,7 @@ void QgsPolygon3DSymbolWidget::setSymbol( const QgsPolygon3DSymbol &symbol, QgsV
7071
cboAltClamping->setCurrentIndex( ( int ) symbol.altitudeClamping() );
7172
cboAltBinding->setCurrentIndex( ( int ) symbol.altitudeBinding() );
7273
cboCullingMode->setCurrentIndex( _cullingModeToIndex( symbol.cullingMode() ) );
74+
chkInvertNormals->setChecked( symbol.invertNormals() );
7375
widgetMaterial->setMaterial( symbol.material() );
7476

7577
btnHeightDD->init( QgsAbstract3DSymbol::PropertyHeight, symbol.dataDefinedProperties(), QgsAbstract3DSymbol::propertyDefinitions(), layer, true );
@@ -84,6 +86,7 @@ QgsPolygon3DSymbol QgsPolygon3DSymbolWidget::symbol() const
8486
sym.setAltitudeClamping( ( AltitudeClamping ) cboAltClamping->currentIndex() );
8587
sym.setAltitudeBinding( ( AltitudeBinding ) cboAltBinding->currentIndex() );
8688
sym.setCullingMode( _cullingModeFromIndex( cboCullingMode->currentIndex() ) );
89+
sym.setInvertNormals( chkInvertNormals->isChecked() );
8790
sym.setMaterial( widgetMaterial->material() );
8891

8992
QgsPropertyCollection ddp;

src/ui/3d/polygon3dsymbolwidget.ui

Lines changed: 61 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,46 @@
1414
<string>Form</string>
1515
</property>
1616
<layout class="QGridLayout" name="gridLayout">
17+
<item row="4" column="0">
18+
<widget class="QLabel" name="label_5">
19+
<property name="text">
20+
<string>Culling Mode</string>
21+
</property>
22+
</widget>
23+
</item>
24+
<item row="4" column="1">
25+
<widget class="QComboBox" name="cboCullingMode">
26+
<item>
27+
<property name="text">
28+
<string>No culling</string>
29+
</property>
30+
</item>
31+
<item>
32+
<property name="text">
33+
<string>Front</string>
34+
</property>
35+
</item>
36+
<item>
37+
<property name="text">
38+
<string>Back</string>
39+
</property>
40+
</item>
41+
</widget>
42+
</item>
43+
<item row="0" column="2">
44+
<widget class="QgsPropertyOverrideButton" name="btnHeightDD">
45+
<property name="text">
46+
<string>...</string>
47+
</property>
48+
</widget>
49+
</item>
50+
<item row="0" column="0">
51+
<widget class="QLabel" name="label">
52+
<property name="text">
53+
<string>Height</string>
54+
</property>
55+
</widget>
56+
</item>
1757
<item row="2" column="0">
1858
<widget class="QLabel" name="label_3">
1959
<property name="text">
@@ -45,44 +85,6 @@
4585
</property>
4686
</widget>
4787
</item>
48-
<item row="0" column="2">
49-
<widget class="QgsPropertyOverrideButton" name="btnHeightDD">
50-
<property name="text">
51-
<string>...</string>
52-
</property>
53-
</widget>
54-
</item>
55-
<item row="0" column="0">
56-
<widget class="QLabel" name="label">
57-
<property name="text">
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>
83-
</property>
84-
</widget>
85-
</item>
8688
<item row="2" column="1">
8789
<widget class="QComboBox" name="cboAltClamping">
8890
<item>
@@ -116,32 +118,37 @@
116118
</property>
117119
</widget>
118120
</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">
128-
<item>
129-
<property name="text">
130-
<string>No culling</string>
131-
</property>
132-
</item>
121+
<item row="3" column="1">
122+
<widget class="QComboBox" name="cboAltBinding">
133123
<item>
134124
<property name="text">
135-
<string>Front</string>
125+
<string>Vertex</string>
136126
</property>
137127
</item>
138128
<item>
139129
<property name="text">
140-
<string>Back</string>
130+
<string>Centroid</string>
141131
</property>
142132
</item>
143133
</widget>
144134
</item>
135+
<item row="7" column="0" colspan="3">
136+
<widget class="QgsPhongMaterialWidget" name="widgetMaterial" native="true"/>
137+
</item>
138+
<item row="6" column="0" colspan="3">
139+
<widget class="Line" name="line">
140+
<property name="orientation">
141+
<enum>Qt::Horizontal</enum>
142+
</property>
143+
</widget>
144+
</item>
145+
<item row="5" column="0" colspan="2">
146+
<widget class="QCheckBox" name="chkInvertNormals">
147+
<property name="text">
148+
<string>Invert Normals (Experimental)</string>
149+
</property>
150+
</widget>
151+
</item>
145152
</layout>
146153
</widget>
147154
<customwidgets>

0 commit comments

Comments
 (0)