Merge pull request #5798 from wonder-sk/polygon-3d-fixes

[3d] Tessellator fixes + culling mode configuration for 3D polygons
This commit is contained in:
Martin Dobias 2017-12-05 10:54:48 +01:00 committed by GitHub
commit ba9e19954b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 353 additions and 164 deletions

View File

@ -82,6 +82,30 @@ AltitudeBinding Qgs3DUtils::altBindingFromString( const QString &str )
return AltBindCentroid;
}
QString Qgs3DUtils::cullingModeToString( Qt3DRender::QCullFace::CullingMode mode )
{
switch ( mode )
{
case Qt3DRender::QCullFace::NoCulling: return QStringLiteral( "no-culling" );
case Qt3DRender::QCullFace::Front: return QStringLiteral( "front" );
case Qt3DRender::QCullFace::Back: return QStringLiteral( "back" );
case Qt3DRender::QCullFace::FrontAndBack: return QStringLiteral( "front-and-back" );
}
return QString();
}
Qt3DRender::QCullFace::CullingMode Qgs3DUtils::cullingModeFromString( const QString &str )
{
if ( str == QStringLiteral( "front" ) )
return Qt3DRender::QCullFace::Front;
else if ( str == QStringLiteral( "back" ) )
return Qt3DRender::QCullFace::Back;
else if ( str == QStringLiteral( "front-and-back" ) )
return Qt3DRender::QCullFace::FrontAndBack;
else
return Qt3DRender::QCullFace::NoCulling;
}
void Qgs3DUtils::clampAltitudes( QgsLineString *lineString, AltitudeClamping altClamp, AltitudeBinding altBind, const QgsPoint &centroid, float height, const Qgs3DMapSettings &map )
{

View File

@ -22,6 +22,8 @@ class QgsPolygon;
#include "qgs3dmapsettings.h"
#include "qgsaabb.h"
#include <Qt3DRender/QCullFace>
//! how to handle altitude of vector features
enum AltitudeClamping
{
@ -64,6 +66,11 @@ class _3D_EXPORT Qgs3DUtils
//! Converts a string to a value from AltitudeBinding enum
static AltitudeBinding altBindingFromString( const QString &str );
//! Converts a value from CullingMode enum to a string
static QString cullingModeToString( Qt3DRender::QCullFace::CullingMode mode );
//! Converts a string to a value from CullingMode enum
static Qt3DRender::QCullFace::CullingMode cullingModeFromString( const QString &str );
//! Clamps altitude of vertices of a linestring according to the settings
static void clampAltitudes( QgsLineString *lineString, AltitudeClamping altClamp, AltitudeBinding altBind, const QgsPoint &centroid, float height, const Qgs3DMapSettings &map );
//! Clamps altitude of vertices of a polygon according to the settings

View File

@ -58,14 +58,10 @@ QgsTessellatedPolygonGeometry::QgsTessellatedPolygonGeometry( QNode *parent )
QgsTessellatedPolygonGeometry::~QgsTessellatedPolygonGeometry()
{
qDeleteAll( mPolygons );
}
void QgsTessellatedPolygonGeometry::setPolygons( const QList<QgsPolygon *> &polygons, const QgsPointXY &origin, float extrusionHeight, const QList<float> &extrusionHeightPerPolygon )
{
qDeleteAll( mPolygons );
mPolygons = polygons;
QgsTessellator tessellator( origin.x(), origin.y(), mWithNormals );
for ( int i = 0; i < polygons.count(); ++i )
{
@ -74,6 +70,8 @@ void QgsTessellatedPolygonGeometry::setPolygons( const QList<QgsPolygon *> &poly
tessellator.addPolygon( *polygon, extr );
}
qDeleteAll( polygons );
QByteArray data( ( const char * )tessellator.data().constData(), tessellator.data().count() * sizeof( float ) );
int nVerts = data.count() / tessellator.stride();

View File

@ -45,7 +45,6 @@ class QgsTessellatedPolygonGeometry : public Qt3DRender::QGeometry
void setPolygons( const QList<QgsPolygon *> &polygons, const QgsPointXY &origin, float extrusionHeight, const QList<float> &extrusionHeightPerPolygon = QList<float>() );
private:
QList<QgsPolygon *> mPolygons;
Qt3DRender::QAttribute *mPositionAttribute = nullptr;
Qt3DRender::QAttribute *mNormalAttribute = nullptr;

View File

@ -38,6 +38,7 @@ static void make_quad( float x0, float y0, float z0, float x1, float y1, float z
// perpendicular vector in plane to [x,y] is [-y,x]
QVector3D vn( -dy, 0, dx );
vn = -vn;
vn.normalize();
// triangle 1
@ -108,8 +109,8 @@ static void _makeWalls( const QgsCurve &ring, bool ccw, float extrusionHeight, Q
ring.pointAt( is_counter_clockwise == ccw ? i : ring.numPoints() - i - 1, pt, vt );
float x0 = ptPrev.x() - originX, y0 = ptPrev.y() - originY;
float x1 = pt.x() - originX, y1 = pt.y() - originY;
float z0 = ptPrev.z();
float z1 = pt.z();
float z0 = std::isnan( ptPrev.z() ) ? 0 : ptPrev.z();
float z1 = std::isnan( pt.z() ) ? 0 : pt.z();
// make a quad
make_quad( x0, y0, z0, x1, y1, z1, extrusionHeight, data, addNormals );
@ -170,6 +171,7 @@ static QVector3D _calculateNormal( const QgsCurve *curve, double originX, double
}
QVector3D normal( nx, ny, nz );
//normal = -normal; // TODO: some datasets seem to work better with, others without inversion
normal.normalize();
return normal;
}
@ -197,24 +199,21 @@ static void _normalVectorToXYVectors( const QVector3D &pNormal, QVector3D &pXVec
}
static void _ringToPoly2tri( const QgsCurve *ring, const QgsPoint &ptFirst, const QMatrix4x4 &toNewBase, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> &zHash )
static void _ringToPoly2tri( const QgsCurve *ring, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> &zHash )
{
QgsVertexId::VertexType vt;
QgsPoint pt;
const int pCount = ring->numPoints();
double x0 = ptFirst.x(), y0 = ptFirst.y(), z0 = ( std::isnan( ptFirst.z() ) ? 0 : ptFirst.z() );
polyline.reserve( pCount );
for ( int i = 0; i < pCount - 1; ++i )
{
ring->pointAt( i, pt, vt );
QVector4D tempPt( pt.x() - x0, pt.y() - y0, std::isnan( pt.z() ) ? 0 : pt.z() - z0, 0 );
QVector4D newBasePt = toNewBase * tempPt;
const float x = newBasePt.x();
const float y = newBasePt.y();
const float z = newBasePt.z();
const float x = pt.x();
const float y = pt.y();
const float z = pt.z();
const bool found = std::find_if( polyline.begin(), polyline.end(), [x, y]( p2t::Point *&p ) { return *p == p2t::Point( x, y ); } ) != polyline.end();
@ -229,6 +228,35 @@ static void _ringToPoly2tri( const QgsCurve *ring, const QgsPoint &ptFirst, cons
}
}
static QgsCurve *_transform_ring_to_new_base( const QgsCurve &curve, const QgsPoint &pt0, const QMatrix4x4 *toNewBase )
{
int count = curve.numPoints();
QVector<QgsPoint> pts;
pts.reserve( count );
QgsVertexId::VertexType vt;
for ( int i = 0; i < count; ++i )
{
QgsPoint pt;
curve.pointAt( i, pt, vt );
QgsPoint pt2( QgsWkbTypes::PointZ, pt.x() - pt0.x(), pt.y() - pt0.y(), std::isnan( pt.z() ) ? 0 : pt.z() - pt0.z() );
QVector4D v( pt2.x(), pt2.y(), pt2.z(), 0 );
if ( toNewBase )
v = toNewBase->map( v );
pts << QgsPoint( QgsWkbTypes::PointZ, v.x(), v.y(), v.z() );
}
return new QgsLineString( pts );
}
static QgsPolygon *_transform_polygon_to_new_base( const QgsPolygon &polygon, const QgsPoint &pt0, const QMatrix4x4 *toNewBase )
{
QgsPolygon *p = new QgsPolygon;
p->setExteriorRing( _transform_ring_to_new_base( *polygon.exteriorRing(), pt0, toNewBase ) );
for ( int i = 0; i < polygon.numInteriorRings(); ++i )
p->addInteriorRing( _transform_ring_to_new_base( *polygon.interiorRing( i ), pt0, toNewBase ) );
return p;
}
static bool _check_intersecting_rings( const QgsPolygon &polygon )
{
// 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 )
void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeight )
{
if ( _minimum_distance_between_coordinates( polygon ) < 0.001 )
{
// when the distances between coordinates of input points are very small,
// the triangulation likes to crash on numerical errors - when the distances are ~ 1e-5
// Assuming that the coordinates should be in a projected CRS, we should be able
// to simplify geometries that may cause problems and avoid possible crashes
QgsGeometry polygonSimplified = QgsGeometry( polygon.clone() ).simplify( 0.001 );
const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet() );
if ( _minimum_distance_between_coordinates( *polygonSimplifiedData ) < 0.001 )
{
// Failed to fix that. It could be a really tiny geometry... or maybe they gave us
// geometry in unprojected lat/lon coordinates
QgsMessageLog::logMessage( "geometry's coordinates are too close to each other and simplification failed - skipping", "3D" );
}
else
{
addPolygon( *polygonSimplifiedData, extrusionHeight );
}
return;
}
if ( !_check_intersecting_rings( polygon ) )
{
// skip the polygon - it would cause a crash inside poly2tri library
QgsMessageLog::logMessage( "polygon rings intersect each other - skipping", "3D" );
return;
}
const QgsCurve *exterior = polygon.exteriorRing();
QList< std::vector<p2t::Point *> > polylinesToDelete;
QHash<p2t::Point *, float> z;
std::vector<p2t::Point *> polyline;
const QVector3D pNormal = _calculateNormal( exterior, mOriginX, mOriginY );
const int pCount = exterior->numPoints();
// Polygon is a triangle
if ( pCount == 4 )
if ( pCount == 4 && polygon.numInteriorRings() == 0 )
{
// polygon is a triangle - write vertices to the output data array without triangulation
QgsPoint pt;
QgsVertexId::VertexType vt;
for ( int i = 0; i < 3; i++ )
@ -346,88 +341,113 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh
if ( !qgsDoubleNear( pNormal.length(), 1, 0.001 ) )
return; // this should not happen - pNormal should be normalized to unit length
QVector3D pXVector, pYVector;
_normalVectorToXYVectors( pNormal, pXVector, pYVector );
// so now we have three orthogonal unit vectors defining new base
// let's build transform matrix. We actually need just a 3x3 matrix,
// but Qt does not have good support for it, so using 4x4 matrix instead.
QMatrix4x4 toNewBase(
pXVector.x(), pXVector.y(), pXVector.z(), 0,
pYVector.x(), pYVector.y(), pYVector.z(), 0,
pNormal.x(), pNormal.y(), pNormal.z(), 0,
0, 0, 0, 0 );
// our 3x3 matrix is orthogonal, so for inverse we only need to transpose it
QMatrix4x4 toOldBase = toNewBase.transposed();
const QgsPoint ptFirst( exterior->startPoint() );
_ringToPoly2tri( exterior, ptFirst, toNewBase, polyline, z );
polylinesToDelete << polyline;
// TODO: robustness (no nearly duplicate points, invalid geometries ...)
double x0 = ptFirst.x(), y0 = ptFirst.y(), z0 = ( std::isnan( ptFirst.z() ) ? 0 : ptFirst.z() );
if ( polyline.size() == 3 && polygon.numInteriorRings() == 0 )
std::unique_ptr<QMatrix4x4> toNewBase, toOldBase;
if ( pNormal != QVector3D( 0, 0, 1 ) )
{
for ( std::vector<p2t::Point *>::iterator it = polyline.begin(); it != polyline.end(); it++ )
// 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
QVector3D pXVector, pYVector;
_normalVectorToXYVectors( pNormal, pXVector, pYVector );
// so now we have three orthogonal unit vectors defining new base
// let's build transform matrix. We actually need just a 3x3 matrix,
// but Qt does not have good support for it, so using 4x4 matrix instead.
toNewBase.reset( new QMatrix4x4(
pXVector.x(), pXVector.y(), pXVector.z(), 0,
pYVector.x(), pYVector.y(), pYVector.z(), 0,
pNormal.x(), pNormal.y(), pNormal.z(), 0,
0, 0, 0, 0 ) );
// our 3x3 matrix is orthogonal, so for inverse we only need to transpose it
toOldBase.reset( new QMatrix4x4( toNewBase->transposed() ) );
}
const QgsPoint ptStart( exterior->startPoint() );
const QgsPoint pt0( QgsWkbTypes::PointZ, ptStart.x(), ptStart.y(), std::isnan( ptStart.z() ) ? 0 : ptStart.z() );
// subtract ptFirst from geometry for better numerical stability in triangulation
// and apply new 3D vector base if the polygon is not horizontal
std::unique_ptr<QgsPolygon> polygonNew( _transform_polygon_to_new_base( polygon, pt0, toNewBase.get() ) );
if ( _minimum_distance_between_coordinates( *polygonNew ) < 0.001 )
{
// when the distances between coordinates of input points are very small,
// the triangulation likes to crash on numerical errors - when the distances are ~ 1e-5
// Assuming that the coordinates should be in a projected CRS, we should be able
// to simplify geometries that may cause problems and avoid possible crashes
QgsGeometry polygonSimplified = QgsGeometry( polygonNew->clone() ).simplify( 0.001 );
const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet() );
if ( _minimum_distance_between_coordinates( *polygonSimplifiedData ) < 0.001 )
{
p2t::Point *p = *it;
QVector4D ptInNewBase( p->x, p->y, z[p], 0 );
QVector4D nPoint = toOldBase * ptInNewBase;
const double fx = nPoint.x() - mOriginX + x0;
const double fy = nPoint.y() - mOriginY + y0;
const double fz = nPoint.z() + extrusionHeight + z0;
mData << fx << fz << -fy;
if ( mAddNormals )
mData << pNormal.x() << pNormal.z() << - pNormal.y();
// Failed to fix that. It could be a really tiny geometry... or maybe they gave us
// geometry in unprojected lat/lon coordinates
QgsMessageLog::logMessage( "geometry's coordinates are too close to each other and simplification failed - skipping", "3D" );
return;
}
else
{
polygonNew.reset( polygonSimplifiedData->clone() );
}
}
else if ( polyline.size() >= 3 )
if ( !_check_intersecting_rings( *polygonNew.get() ) )
{
p2t::CDT *cdt = new p2t::CDT( polyline );
// skip the polygon - it would cause a crash inside poly2tri library
QgsMessageLog::logMessage( "polygon rings intersect each other - skipping", "3D" );
return;
}
// polygon holes
for ( int i = 0; i < polygon.numInteriorRings(); ++i )
QList< std::vector<p2t::Point *> > polylinesToDelete;
QHash<p2t::Point *, float> z;
// polygon exterior
std::vector<p2t::Point *> polyline;
_ringToPoly2tri( polygonNew->exteriorRing(), polyline, z );
polylinesToDelete << polyline;
std::unique_ptr<p2t::CDT> cdt( new p2t::CDT( polyline ) );
// polygon holes
for ( int i = 0; i < polygonNew->numInteriorRings(); ++i )
{
std::vector<p2t::Point *> holePolyline;
const QgsCurve *hole = polygonNew->interiorRing( i );
_ringToPoly2tri( hole, holePolyline, z );
cdt->AddHole( holePolyline );
polylinesToDelete << holePolyline;
}
// run triangulation and write vertices to the output data array
try
{
cdt->Triangulate();
std::vector<p2t::Triangle *> triangles = cdt->GetTriangles();
for ( size_t i = 0; i < triangles.size(); ++i )
{
std::vector<p2t::Point *> holePolyline;
const QgsCurve *hole = polygon.interiorRing( i );
_ringToPoly2tri( hole, ptFirst, toNewBase, holePolyline, z );
cdt->AddHole( holePolyline );
polylinesToDelete << holePolyline;
}
try
{
cdt->Triangulate();
std::vector<p2t::Triangle *> triangles = cdt->GetTriangles();
for ( size_t i = 0; i < triangles.size(); ++i )
p2t::Triangle *t = triangles[i];
for ( int j = 0; j < 3; ++j )
{
p2t::Triangle *t = triangles[i];
for ( int j = 0; j < 3; ++j )
{
p2t::Point *p = t->GetPoint( j );
QVector4D ptInNewBase( p->x, p->y, z[p], 0 );
QVector4D nPoint = toOldBase * ptInNewBase;
const double fx = nPoint.x() - mOriginX + x0;
const double fy = nPoint.y() - mOriginY + y0;
const double fz = nPoint.z() + extrusionHeight + z0;
mData << fx << fz << -fy;
if ( mAddNormals )
mData << pNormal.x() << pNormal.z() << - pNormal.y();
}
p2t::Point *p = t->GetPoint( j );
QVector4D pt( p->x, p->y, z[p], 0 );
if ( toOldBase )
pt = *toOldBase * pt;
const double fx = pt.x() - mOriginX + pt0.x();
const double fy = pt.y() - mOriginY + pt0.y();
const double fz = pt.z() + extrusionHeight + pt0.z();
mData << fx << fz << -fy;
if ( mAddNormals )
mData << pNormal.x() << pNormal.z() << - pNormal.y();
}
}
catch ( ... )
{
QgsMessageLog::logMessage( "Triangulation failed. Skipping polygon...", "3D" );
}
delete cdt;
}
catch ( ... )
{
QgsMessageLog::logMessage( "Triangulation failed. Skipping polygon...", "3D" );
}
for ( int i = 0; i < polylinesToDelete.count(); ++i )

View File

@ -31,6 +31,7 @@ void QgsPolygon3DSymbol::writeXml( QDomElement &elem, const QgsReadWriteContext
elemDataProperties.setAttribute( QStringLiteral( "alt-binding" ), Qgs3DUtils::altBindingToString( mAltBinding ) );
elemDataProperties.setAttribute( QStringLiteral( "height" ), mHeight );
elemDataProperties.setAttribute( QStringLiteral( "extrusion-height" ), mExtrusionHeight );
elemDataProperties.setAttribute( QStringLiteral( "culling-mode" ), Qgs3DUtils::cullingModeToString( mCullingMode ) );
elem.appendChild( elemDataProperties );
QDomElement elemMaterial = doc.createElement( QStringLiteral( "material" ) );
@ -51,6 +52,7 @@ void QgsPolygon3DSymbol::readXml( const QDomElement &elem, const QgsReadWriteCon
mAltBinding = Qgs3DUtils::altBindingFromString( elemDataProperties.attribute( QStringLiteral( "alt-binding" ) ) );
mHeight = elemDataProperties.attribute( QStringLiteral( "height" ) ).toFloat();
mExtrusionHeight = elemDataProperties.attribute( QStringLiteral( "extrusion-height" ) ).toFloat();
mCullingMode = Qgs3DUtils::cullingModeFromString( elemDataProperties.attribute( QStringLiteral( "culling-mode" ) ) );
QDomElement elemMaterial = elem.firstChildElement( QStringLiteral( "material" ) );
mMaterial.readXml( elemMaterial );

View File

@ -22,6 +22,7 @@
#include "qgsphongmaterialsettings.h"
#include "qgs3dutils.h"
#include <Qt3DRender/QCullFace>
/**
* \ingroup 3d
@ -65,6 +66,11 @@ class _3D_EXPORT QgsPolygon3DSymbol : public QgsAbstract3DSymbol
//! Sets material used for shading of the symbol
void setMaterial( const QgsPhongMaterialSettings &material ) { mMaterial = material; }
//! Returns front/back culling mode
Qt3DRender::QCullFace::CullingMode cullingMode() const { return mCullingMode; }
//! Sets front/back culling mode
void setCullingMode( Qt3DRender::QCullFace::CullingMode mode ) { mCullingMode = mode; }
private:
//! how to handle altitude of vector features
AltitudeClamping mAltClamping = AltClampRelative;
@ -74,6 +80,7 @@ class _3D_EXPORT QgsPolygon3DSymbol : public QgsAbstract3DSymbol
float mHeight = 0.0f; //!< Base height of polygons
float mExtrusionHeight = 0.0f; //!< How much to extrude (0 means no walls)
QgsPhongMaterialSettings mMaterial; //!< Defines appearance of objects
Qt3DRender::QCullFace::CullingMode mCullingMode = Qt3DRender::QCullFace::NoCulling; //!< Front/back culling mode
};

View File

@ -21,6 +21,9 @@
#include "qgs3dutils.h"
#include <Qt3DCore/QTransform>
#include <Qt3DRender/QEffect>
#include <Qt3DRender/QTechnique>
#include <Qt3DRender/QCullFace>
#include "qgsvectorlayer.h"
#include "qgsmultipolygon.h"
@ -103,9 +106,24 @@ void QgsPolygon3DSymbolEntity::addEntityForNotSelectedPolygons( const Qgs3DMapSe
entity->setParent( this );
}
Qt3DExtras::QPhongMaterial *QgsPolygon3DSymbolEntity::material( const QgsPolygon3DSymbol &symbol ) const
{
Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial;
// front/back side culling
auto techniques = material->effect()->techniques();
for ( auto tit = techniques.constBegin(); tit != techniques.constEnd(); ++tit )
{
auto renderPasses = ( *tit )->renderPasses();
for ( auto rpit = renderPasses.begin(); rpit != renderPasses.end(); ++rpit )
{
Qt3DRender::QCullFace *cullFace = new Qt3DRender::QCullFace;
cullFace->setMode( symbol.cullingMode() );
( *rpit )->addRenderState( cullFace );
}
}
material->setAmbient( symbol.material().ambient() );
material->setDiffuse( symbol.material().diffuse() );
material->setSpecular( symbol.material().specular() );

View File

@ -31,17 +31,45 @@ QgsPolygon3DSymbolWidget::QgsPolygon3DSymbolWidget( QWidget *parent )
connect( spinExtrusion, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsPolygon3DSymbolWidget::changed );
connect( cboAltClamping, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsPolygon3DSymbolWidget::changed );
connect( cboAltBinding, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsPolygon3DSymbolWidget::changed );
connect( cboCullingMode, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsPolygon3DSymbolWidget::changed );
connect( widgetMaterial, &QgsPhongMaterialWidget::changed, this, &QgsPolygon3DSymbolWidget::changed );
connect( btnHeightDD, &QgsPropertyOverrideButton::changed, this, &QgsPolygon3DSymbolWidget::changed );
connect( btnExtrusionDD, &QgsPropertyOverrideButton::changed, this, &QgsPolygon3DSymbolWidget::changed );
}
static int _cullingModeToIndex( Qt3DRender::QCullFace::CullingMode mode )
{
switch ( mode )
{
case Qt3DRender::QCullFace::NoCulling: return 0;
case Qt3DRender::QCullFace::Front: return 1;
case Qt3DRender::QCullFace::Back: return 2;
case Qt3DRender::QCullFace::FrontAndBack: return 3;
}
return 0;
}
static Qt3DRender::QCullFace::CullingMode _cullingModeFromIndex( int index )
{
switch ( index )
{
case 0: return Qt3DRender::QCullFace::NoCulling;
case 1: return Qt3DRender::QCullFace::Front;
case 2: return Qt3DRender::QCullFace::Back;
case 3: return Qt3DRender::QCullFace::FrontAndBack;
}
return Qt3DRender::QCullFace::NoCulling;
}
void QgsPolygon3DSymbolWidget::setSymbol( const QgsPolygon3DSymbol &symbol, QgsVectorLayer *layer )
{
spinHeight->setValue( symbol.height() );
spinExtrusion->setValue( symbol.extrusionHeight() );
cboAltClamping->setCurrentIndex( ( int ) symbol.altitudeClamping() );
cboAltBinding->setCurrentIndex( ( int ) symbol.altitudeBinding() );
cboCullingMode->setCurrentIndex( _cullingModeToIndex( symbol.cullingMode() ) );
widgetMaterial->setMaterial( symbol.material() );
btnHeightDD->init( QgsAbstract3DSymbol::PropertyHeight, symbol.dataDefinedProperties(), QgsAbstract3DSymbol::propertyDefinitions(), layer, true );
@ -55,6 +83,7 @@ QgsPolygon3DSymbol QgsPolygon3DSymbolWidget::symbol() const
sym.setExtrusionHeight( spinExtrusion->value() );
sym.setAltitudeClamping( ( AltitudeClamping ) cboAltClamping->currentIndex() );
sym.setAltitudeBinding( ( AltitudeBinding ) cboAltBinding->currentIndex() );
sym.setCullingMode( _cullingModeFromIndex( cboCullingMode->currentIndex() ) );
sym.setMaterial( widgetMaterial->material() );
QgsPropertyCollection ddp;

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>538</width>
<width>561</width>
<height>452</height>
</rect>
</property>
@ -14,10 +14,10 @@
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Height</string>
<string>Altitude Clamping</string>
</property>
</widget>
</item>
@ -31,13 +31,6 @@
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QgsPropertyOverrideButton" name="btnHeightDD">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
@ -45,13 +38,6 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QgsDoubleSpinBox" name="spinExtrusion">
<property name="maximum">
<double>99999.000000000000000</double>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QgsPropertyOverrideButton" name="btnExtrusionDD">
<property name="text">
@ -59,10 +45,41 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<item row="0" column="2">
<widget class="QgsPropertyOverrideButton" name="btnHeightDD">
<property name="text">
<string>Altitude Clamping</string>
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Height</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="cboAltBinding">
<item>
<property name="text">
<string>Vertex</string>
</property>
</item>
<item>
<property name="text">
<string>Centroid</string>
</property>
</item>
</widget>
</item>
<item row="6" column="0" colspan="3">
<widget class="QgsPhongMaterialWidget" name="widgetMaterial" native="true"/>
</item>
<item row="5" column="0" colspan="3">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
@ -92,29 +109,38 @@
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="cboAltBinding">
<item>
<property name="text">
<string>Vertex</string>
</property>
</item>
<item>
<property name="text">
<string>Centroid</string>
</property>
</item>
</widget>
</item>
<item row="4" column="0" colspan="3">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<item row="1" column="1">
<widget class="QgsDoubleSpinBox" name="spinExtrusion">
<property name="maximum">
<double>99999.000000000000000</double>
</property>
</widget>
</item>
<item row="5" column="0" colspan="3">
<widget class="QgsPhongMaterialWidget" name="widgetMaterial" native="true"/>
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Culling Mode</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="cboCullingMode">
<item>
<property name="text">
<string>No culling</string>
</property>
</item>
<item>
<property name="text">
<string>Front</string>
</property>
</item>
<item>
<property name="text">
<string>Back</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>

View File

@ -22,6 +22,11 @@
#include "qgstessellator.h"
#include "qgsmultipolygon.h"
static bool qgsVectorNear( const QVector3D &v1, const QVector3D &v2, double eps )
{
return qgsDoubleNear( v1.x(), v2.x(), eps ) && qgsDoubleNear( v1.y(), v2.y(), eps ) && qgsDoubleNear( v1.z(), v2.z(), eps );
}
/**
* Simple structure to record an expected triangle from tessellator.
* Triangle vertices are expected to be in counter-clockwise order.
@ -55,8 +60,13 @@ struct TriangleCoords
bool operator==( const TriangleCoords &other ) const
{
// TODO: allow that the two triangles have coordinates shifted (but still in the same order)
return pts[0] == other.pts[0] && pts[1] == other.pts[1] && pts[2] == other.pts[2] &&
normals[0] == other.normals[0] && normals[1] == other.normals[1] && normals[2] == other.normals[2];
const double eps = 1e-6;
return qgsVectorNear( pts[0], other.pts[0], eps ) &&
qgsVectorNear( pts[1], other.pts[1], eps ) &&
qgsVectorNear( pts[2], other.pts[2], eps ) &&
qgsVectorNear( normals[0], other.normals[0], eps ) &&
qgsVectorNear( normals[1], other.normals[1], eps ) &&
qgsVectorNear( normals[2], other.normals[2], eps );
}
bool operator!=( const TriangleCoords &other ) const
@ -175,6 +185,42 @@ void TestQgsTessellator::testBasic()
void TestQgsTessellator::testWalls()
{
QgsPolygon rect;
rect.fromWkt( "POLYGON((0 0, 3 0, 3 2, 0 2, 0 0))" );
QVector3D zPos( 0, 0, 1 );
QVector3D xPos( 1, 0, 0 );
QVector3D yPos( 0, 1, 0 );
QVector3D xNeg( -1, 0, 0 );
QVector3D yNeg( 0, -1, 0 );
QList<TriangleCoords> tcRect;
tcRect << TriangleCoords( QVector3D( 0, 2, 1 ), QVector3D( 3, 0, 1 ), QVector3D( 3, 2, 1 ), zPos, zPos, zPos );
tcRect << TriangleCoords( QVector3D( 0, 2, 1 ), QVector3D( 0, 0, 1 ), QVector3D( 3, 0, 1 ), zPos, zPos, zPos );
tcRect << TriangleCoords( QVector3D( 0, 0, 1 ), QVector3D( 0, 2, 1 ), QVector3D( 0, 0, 0 ), xNeg, xNeg, xNeg );
tcRect << TriangleCoords( QVector3D( 0, 0, 0 ), QVector3D( 0, 2, 1 ), QVector3D( 0, 2, 0 ), xNeg, xNeg, xNeg );
tcRect << TriangleCoords( QVector3D( 0, 2, 1 ), QVector3D( 3, 2, 1 ), QVector3D( 0, 2, 0 ), yPos, yPos, yPos );
tcRect << TriangleCoords( QVector3D( 0, 2, 0 ), QVector3D( 3, 2, 1 ), QVector3D( 3, 2, 0 ), yPos, yPos, yPos );
tcRect << TriangleCoords( QVector3D( 3, 2, 1 ), QVector3D( 3, 0, 1 ), QVector3D( 3, 2, 0 ), xPos, xPos, xPos );
tcRect << TriangleCoords( QVector3D( 3, 2, 0 ), QVector3D( 3, 0, 1 ), QVector3D( 3, 0, 0 ), xPos, xPos, xPos );
tcRect << TriangleCoords( QVector3D( 3, 0, 1 ), QVector3D( 0, 0, 1 ), QVector3D( 3, 0, 0 ), yNeg, yNeg, yNeg );
tcRect << TriangleCoords( QVector3D( 3, 0, 0 ), QVector3D( 0, 0, 1 ), QVector3D( 0, 0, 0 ), yNeg, yNeg, yNeg );
QgsTessellator tRect( 0, 0, true );
tRect.addPolygon( rect, 1 );
QVERIFY( checkTriangleOutput( tRect.data(), true, tcRect ) );
// try to extrude a polygon with reverse (clock-wise) order of vertices and check it is still fine
QgsPolygon rectRev;
rectRev.fromWkt( "POLYGON((0 0, 0 2, 3 2, 3 0, 0 0))" );
QgsTessellator tRectRev( 0, 0, true );
tRectRev.addPolygon( rectRev, 1 );
QVERIFY( checkTriangleOutput( tRectRev.data(), true, tcRect ) );
// this is a more complicated polygon with Z coordinates where the "roof" is not in one plane
QgsPolygon polygonZ;
polygonZ.fromWkt( "POLYGONZ((1 1 1, 2 1 2, 3 2 3, 1 2 4, 1 1 1))" );
@ -216,6 +262,19 @@ void TestQgsTessellator::asMultiPolygon()
void TestQgsTessellator::testBadCoordinates()
{
// check with a vertical "wall" polygon - if the Z coordinates are ignored,
// the polygon may be incorrectly considered as having close/repeated coordinates
QList<TriangleCoords> tcZ;
tcZ << TriangleCoords( QVector3D( 1, 2, 2 ), QVector3D( 2, 1, 1 ), QVector3D( 2, 1, 2 ) );
tcZ << TriangleCoords( QVector3D( 1, 2, 2 ), QVector3D( 1, 2, 1 ), QVector3D( 2, 1, 1 ) );
QgsPolygon polygonZ;
polygonZ.fromWkt( "POLYGONZ((1 2 1, 2 1 1, 2 1 2, 1 2 2, 1 2 1))" );
QgsTessellator tZ( 0, 0, false );
tZ.addPolygon( polygonZ, 0 );
QVERIFY( checkTriangleOutput( tZ.data(), false, tcZ ) );
// triangulation would crash for me with this polygon if there is no simplification
// to remove the coordinates that are very close to each other
QgsPolygon polygon;