mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-08 00:05:09 -04:00
Merge pull request #5798 from wonder-sk/polygon-3d-fixes
[3d] Tessellator fixes + culling mode configuration for 3D polygons
This commit is contained in:
commit
ba9e19954b
@ -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 ¢roid, float height, const Qgs3DMapSettings &map )
|
||||
{
|
||||
|
@ -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 ¢roid, float height, const Qgs3DMapSettings &map );
|
||||
//! Clamps altitude of vertices of a polygon according to the settings
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 )
|
||||
|
@ -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 );
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
||||
|
@ -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() );
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user