mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-10 00:04:23 -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;
|
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 )
|
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 "qgs3dmapsettings.h"
|
||||||
#include "qgsaabb.h"
|
#include "qgsaabb.h"
|
||||||
|
|
||||||
|
#include <Qt3DRender/QCullFace>
|
||||||
|
|
||||||
//! how to handle altitude of vector features
|
//! how to handle altitude of vector features
|
||||||
enum AltitudeClamping
|
enum AltitudeClamping
|
||||||
{
|
{
|
||||||
@ -64,6 +66,11 @@ class _3D_EXPORT Qgs3DUtils
|
|||||||
//! Converts a string to a value from AltitudeBinding enum
|
//! Converts a string to a value from AltitudeBinding enum
|
||||||
static AltitudeBinding altBindingFromString( const QString &str );
|
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
|
//! 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 );
|
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
|
//! Clamps altitude of vertices of a polygon according to the settings
|
||||||
|
@ -58,14 +58,10 @@ QgsTessellatedPolygonGeometry::QgsTessellatedPolygonGeometry( QNode *parent )
|
|||||||
|
|
||||||
QgsTessellatedPolygonGeometry::~QgsTessellatedPolygonGeometry()
|
QgsTessellatedPolygonGeometry::~QgsTessellatedPolygonGeometry()
|
||||||
{
|
{
|
||||||
qDeleteAll( mPolygons );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void QgsTessellatedPolygonGeometry::setPolygons( const QList<QgsPolygon *> &polygons, const QgsPointXY &origin, float extrusionHeight, const QList<float> &extrusionHeightPerPolygon )
|
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 );
|
QgsTessellator tessellator( origin.x(), origin.y(), mWithNormals );
|
||||||
for ( int i = 0; i < polygons.count(); ++i )
|
for ( int i = 0; i < polygons.count(); ++i )
|
||||||
{
|
{
|
||||||
@ -74,6 +70,8 @@ void QgsTessellatedPolygonGeometry::setPolygons( const QList<QgsPolygon *> &poly
|
|||||||
tessellator.addPolygon( *polygon, extr );
|
tessellator.addPolygon( *polygon, extr );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qDeleteAll( polygons );
|
||||||
|
|
||||||
QByteArray data( ( const char * )tessellator.data().constData(), tessellator.data().count() * sizeof( float ) );
|
QByteArray data( ( const char * )tessellator.data().constData(), tessellator.data().count() * sizeof( float ) );
|
||||||
int nVerts = data.count() / tessellator.stride();
|
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>() );
|
void setPolygons( const QList<QgsPolygon *> &polygons, const QgsPointXY &origin, float extrusionHeight, const QList<float> &extrusionHeightPerPolygon = QList<float>() );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<QgsPolygon *> mPolygons;
|
|
||||||
|
|
||||||
Qt3DRender::QAttribute *mPositionAttribute = nullptr;
|
Qt3DRender::QAttribute *mPositionAttribute = nullptr;
|
||||||
Qt3DRender::QAttribute *mNormalAttribute = 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]
|
// perpendicular vector in plane to [x,y] is [-y,x]
|
||||||
QVector3D vn( -dy, 0, dx );
|
QVector3D vn( -dy, 0, dx );
|
||||||
|
vn = -vn;
|
||||||
vn.normalize();
|
vn.normalize();
|
||||||
|
|
||||||
// triangle 1
|
// 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 );
|
ring.pointAt( is_counter_clockwise == ccw ? i : ring.numPoints() - i - 1, pt, vt );
|
||||||
float x0 = ptPrev.x() - originX, y0 = ptPrev.y() - originY;
|
float x0 = ptPrev.x() - originX, y0 = ptPrev.y() - originY;
|
||||||
float x1 = pt.x() - originX, y1 = pt.y() - originY;
|
float x1 = pt.x() - originX, y1 = pt.y() - originY;
|
||||||
float z0 = ptPrev.z();
|
float z0 = std::isnan( ptPrev.z() ) ? 0 : ptPrev.z();
|
||||||
float z1 = pt.z();
|
float z1 = std::isnan( pt.z() ) ? 0 : pt.z();
|
||||||
|
|
||||||
// make a quad
|
// make a quad
|
||||||
make_quad( x0, y0, z0, x1, y1, z1, extrusionHeight, data, addNormals );
|
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 );
|
QVector3D normal( nx, ny, nz );
|
||||||
|
//normal = -normal; // TODO: some datasets seem to work better with, others without inversion
|
||||||
normal.normalize();
|
normal.normalize();
|
||||||
return normal;
|
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;
|
QgsVertexId::VertexType vt;
|
||||||
QgsPoint pt;
|
QgsPoint pt;
|
||||||
|
|
||||||
const int pCount = ring->numPoints();
|
const int pCount = ring->numPoints();
|
||||||
double x0 = ptFirst.x(), y0 = ptFirst.y(), z0 = ( std::isnan( ptFirst.z() ) ? 0 : ptFirst.z() );
|
|
||||||
|
|
||||||
polyline.reserve( pCount );
|
polyline.reserve( pCount );
|
||||||
|
|
||||||
for ( int i = 0; i < pCount - 1; ++i )
|
for ( int i = 0; i < pCount - 1; ++i )
|
||||||
{
|
{
|
||||||
ring->pointAt( i, pt, vt );
|
ring->pointAt( i, pt, vt );
|
||||||
QVector4D tempPt( pt.x() - x0, pt.y() - y0, std::isnan( pt.z() ) ? 0 : pt.z() - z0, 0 );
|
const float x = pt.x();
|
||||||
QVector4D newBasePt = toNewBase * tempPt;
|
const float y = pt.y();
|
||||||
const float x = newBasePt.x();
|
const float z = pt.z();
|
||||||
const float y = newBasePt.y();
|
|
||||||
const float z = newBasePt.z();
|
|
||||||
|
|
||||||
const bool found = std::find_if( polyline.begin(), polyline.end(), [x, y]( p2t::Point *&p ) { return *p == p2t::Point( x, y ); } ) != polyline.end();
|
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 )
|
static bool _check_intersecting_rings( const QgsPolygon &polygon )
|
||||||
{
|
{
|
||||||
// At this point we assume that input polygons are valid according to the OGC definition.
|
// 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 )
|
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();
|
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 QVector3D pNormal = _calculateNormal( exterior, mOriginX, mOriginY );
|
||||||
const int pCount = exterior->numPoints();
|
const int pCount = exterior->numPoints();
|
||||||
|
|
||||||
// Polygon is a triangle
|
if ( pCount == 4 && polygon.numInteriorRings() == 0 )
|
||||||
if ( pCount == 4 )
|
|
||||||
{
|
{
|
||||||
|
// polygon is a triangle - write vertices to the output data array without triangulation
|
||||||
QgsPoint pt;
|
QgsPoint pt;
|
||||||
QgsVertexId::VertexType vt;
|
QgsVertexId::VertexType vt;
|
||||||
for ( int i = 0; i < 3; i++ )
|
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 ) )
|
if ( !qgsDoubleNear( pNormal.length(), 1, 0.001 ) )
|
||||||
return; // this should not happen - pNormal should be normalized to unit length
|
return; // this should not happen - pNormal should be normalized to unit length
|
||||||
|
|
||||||
QVector3D pXVector, pYVector;
|
std::unique_ptr<QMatrix4x4> toNewBase, toOldBase;
|
||||||
_normalVectorToXYVectors( pNormal, pXVector, pYVector );
|
if ( pNormal != QVector3D( 0, 0, 1 ) )
|
||||||
|
|
||||||
// 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 )
|
|
||||||
{
|
{
|
||||||
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;
|
// Failed to fix that. It could be a really tiny geometry... or maybe they gave us
|
||||||
QVector4D ptInNewBase( p->x, p->y, z[p], 0 );
|
// geometry in unprojected lat/lon coordinates
|
||||||
QVector4D nPoint = toOldBase * ptInNewBase;
|
QgsMessageLog::logMessage( "geometry's coordinates are too close to each other and simplification failed - skipping", "3D" );
|
||||||
const double fx = nPoint.x() - mOriginX + x0;
|
return;
|
||||||
const double fy = nPoint.y() - mOriginY + y0;
|
}
|
||||||
const double fz = nPoint.z() + extrusionHeight + z0;
|
else
|
||||||
mData << fx << fz << -fy;
|
{
|
||||||
if ( mAddNormals )
|
polygonNew.reset( polygonSimplifiedData->clone() );
|
||||||
mData << pNormal.x() << pNormal.z() << - pNormal.y();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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
|
QList< std::vector<p2t::Point *> > polylinesToDelete;
|
||||||
for ( int i = 0; i < polygon.numInteriorRings(); ++i )
|
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;
|
p2t::Triangle *t = triangles[i];
|
||||||
const QgsCurve *hole = polygon.interiorRing( i );
|
for ( int j = 0; j < 3; ++j )
|
||||||
|
|
||||||
_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];
|
p2t::Point *p = t->GetPoint( j );
|
||||||
for ( int j = 0; j < 3; ++j )
|
QVector4D pt( p->x, p->y, z[p], 0 );
|
||||||
{
|
if ( toOldBase )
|
||||||
p2t::Point *p = t->GetPoint( j );
|
pt = *toOldBase * pt;
|
||||||
QVector4D ptInNewBase( p->x, p->y, z[p], 0 );
|
const double fx = pt.x() - mOriginX + pt0.x();
|
||||||
QVector4D nPoint = toOldBase * ptInNewBase;
|
const double fy = pt.y() - mOriginY + pt0.y();
|
||||||
const double fx = nPoint.x() - mOriginX + x0;
|
const double fz = pt.z() + extrusionHeight + pt0.z();
|
||||||
const double fy = nPoint.y() - mOriginY + y0;
|
mData << fx << fz << -fy;
|
||||||
const double fz = nPoint.z() + extrusionHeight + z0;
|
if ( mAddNormals )
|
||||||
mData << fx << fz << -fy;
|
mData << pNormal.x() << pNormal.z() << - pNormal.y();
|
||||||
if ( mAddNormals )
|
|
||||||
mData << pNormal.x() << pNormal.z() << - pNormal.y();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch ( ... )
|
}
|
||||||
{
|
catch ( ... )
|
||||||
QgsMessageLog::logMessage( "Triangulation failed. Skipping polygon...", "3D" );
|
{
|
||||||
}
|
QgsMessageLog::logMessage( "Triangulation failed. Skipping polygon...", "3D" );
|
||||||
|
|
||||||
delete cdt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( int i = 0; i < polylinesToDelete.count(); ++i )
|
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( "alt-binding" ), Qgs3DUtils::altBindingToString( mAltBinding ) );
|
||||||
elemDataProperties.setAttribute( QStringLiteral( "height" ), mHeight );
|
elemDataProperties.setAttribute( QStringLiteral( "height" ), mHeight );
|
||||||
elemDataProperties.setAttribute( QStringLiteral( "extrusion-height" ), mExtrusionHeight );
|
elemDataProperties.setAttribute( QStringLiteral( "extrusion-height" ), mExtrusionHeight );
|
||||||
|
elemDataProperties.setAttribute( QStringLiteral( "culling-mode" ), Qgs3DUtils::cullingModeToString( mCullingMode ) );
|
||||||
elem.appendChild( elemDataProperties );
|
elem.appendChild( elemDataProperties );
|
||||||
|
|
||||||
QDomElement elemMaterial = doc.createElement( QStringLiteral( "material" ) );
|
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" ) ) );
|
mAltBinding = Qgs3DUtils::altBindingFromString( elemDataProperties.attribute( QStringLiteral( "alt-binding" ) ) );
|
||||||
mHeight = elemDataProperties.attribute( QStringLiteral( "height" ) ).toFloat();
|
mHeight = elemDataProperties.attribute( QStringLiteral( "height" ) ).toFloat();
|
||||||
mExtrusionHeight = elemDataProperties.attribute( QStringLiteral( "extrusion-height" ) ).toFloat();
|
mExtrusionHeight = elemDataProperties.attribute( QStringLiteral( "extrusion-height" ) ).toFloat();
|
||||||
|
mCullingMode = Qgs3DUtils::cullingModeFromString( elemDataProperties.attribute( QStringLiteral( "culling-mode" ) ) );
|
||||||
|
|
||||||
QDomElement elemMaterial = elem.firstChildElement( QStringLiteral( "material" ) );
|
QDomElement elemMaterial = elem.firstChildElement( QStringLiteral( "material" ) );
|
||||||
mMaterial.readXml( elemMaterial );
|
mMaterial.readXml( elemMaterial );
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include "qgsphongmaterialsettings.h"
|
#include "qgsphongmaterialsettings.h"
|
||||||
#include "qgs3dutils.h"
|
#include "qgs3dutils.h"
|
||||||
|
|
||||||
|
#include <Qt3DRender/QCullFace>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \ingroup 3d
|
* \ingroup 3d
|
||||||
@ -65,6 +66,11 @@ class _3D_EXPORT QgsPolygon3DSymbol : public QgsAbstract3DSymbol
|
|||||||
//! Sets material used for shading of the symbol
|
//! Sets material used for shading of the symbol
|
||||||
void setMaterial( const QgsPhongMaterialSettings &material ) { mMaterial = material; }
|
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:
|
private:
|
||||||
//! how to handle altitude of vector features
|
//! how to handle altitude of vector features
|
||||||
AltitudeClamping mAltClamping = AltClampRelative;
|
AltitudeClamping mAltClamping = AltClampRelative;
|
||||||
@ -74,6 +80,7 @@ class _3D_EXPORT QgsPolygon3DSymbol : public QgsAbstract3DSymbol
|
|||||||
float mHeight = 0.0f; //!< Base height of polygons
|
float mHeight = 0.0f; //!< Base height of polygons
|
||||||
float mExtrusionHeight = 0.0f; //!< How much to extrude (0 means no walls)
|
float mExtrusionHeight = 0.0f; //!< How much to extrude (0 means no walls)
|
||||||
QgsPhongMaterialSettings mMaterial; //!< Defines appearance of objects
|
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 "qgs3dutils.h"
|
||||||
|
|
||||||
#include <Qt3DCore/QTransform>
|
#include <Qt3DCore/QTransform>
|
||||||
|
#include <Qt3DRender/QEffect>
|
||||||
|
#include <Qt3DRender/QTechnique>
|
||||||
|
#include <Qt3DRender/QCullFace>
|
||||||
|
|
||||||
#include "qgsvectorlayer.h"
|
#include "qgsvectorlayer.h"
|
||||||
#include "qgsmultipolygon.h"
|
#include "qgsmultipolygon.h"
|
||||||
@ -103,9 +106,24 @@ void QgsPolygon3DSymbolEntity::addEntityForNotSelectedPolygons( const Qgs3DMapSe
|
|||||||
entity->setParent( this );
|
entity->setParent( this );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Qt3DExtras::QPhongMaterial *QgsPolygon3DSymbolEntity::material( const QgsPolygon3DSymbol &symbol ) const
|
Qt3DExtras::QPhongMaterial *QgsPolygon3DSymbolEntity::material( const QgsPolygon3DSymbol &symbol ) const
|
||||||
{
|
{
|
||||||
Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial;
|
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->setAmbient( symbol.material().ambient() );
|
||||||
material->setDiffuse( symbol.material().diffuse() );
|
material->setDiffuse( symbol.material().diffuse() );
|
||||||
material->setSpecular( symbol.material().specular() );
|
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( spinExtrusion, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsPolygon3DSymbolWidget::changed );
|
||||||
connect( cboAltClamping, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), 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( 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( widgetMaterial, &QgsPhongMaterialWidget::changed, this, &QgsPolygon3DSymbolWidget::changed );
|
||||||
connect( btnHeightDD, &QgsPropertyOverrideButton::changed, this, &QgsPolygon3DSymbolWidget::changed );
|
connect( btnHeightDD, &QgsPropertyOverrideButton::changed, this, &QgsPolygon3DSymbolWidget::changed );
|
||||||
connect( btnExtrusionDD, &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 )
|
void QgsPolygon3DSymbolWidget::setSymbol( const QgsPolygon3DSymbol &symbol, QgsVectorLayer *layer )
|
||||||
{
|
{
|
||||||
spinHeight->setValue( symbol.height() );
|
spinHeight->setValue( symbol.height() );
|
||||||
spinExtrusion->setValue( symbol.extrusionHeight() );
|
spinExtrusion->setValue( symbol.extrusionHeight() );
|
||||||
cboAltClamping->setCurrentIndex( ( int ) symbol.altitudeClamping() );
|
cboAltClamping->setCurrentIndex( ( int ) symbol.altitudeClamping() );
|
||||||
cboAltBinding->setCurrentIndex( ( int ) symbol.altitudeBinding() );
|
cboAltBinding->setCurrentIndex( ( int ) symbol.altitudeBinding() );
|
||||||
|
cboCullingMode->setCurrentIndex( _cullingModeToIndex( symbol.cullingMode() ) );
|
||||||
widgetMaterial->setMaterial( symbol.material() );
|
widgetMaterial->setMaterial( symbol.material() );
|
||||||
|
|
||||||
btnHeightDD->init( QgsAbstract3DSymbol::PropertyHeight, symbol.dataDefinedProperties(), QgsAbstract3DSymbol::propertyDefinitions(), layer, true );
|
btnHeightDD->init( QgsAbstract3DSymbol::PropertyHeight, symbol.dataDefinedProperties(), QgsAbstract3DSymbol::propertyDefinitions(), layer, true );
|
||||||
@ -55,6 +83,7 @@ QgsPolygon3DSymbol QgsPolygon3DSymbolWidget::symbol() const
|
|||||||
sym.setExtrusionHeight( spinExtrusion->value() );
|
sym.setExtrusionHeight( spinExtrusion->value() );
|
||||||
sym.setAltitudeClamping( ( AltitudeClamping ) cboAltClamping->currentIndex() );
|
sym.setAltitudeClamping( ( AltitudeClamping ) cboAltClamping->currentIndex() );
|
||||||
sym.setAltitudeBinding( ( AltitudeBinding ) cboAltBinding->currentIndex() );
|
sym.setAltitudeBinding( ( AltitudeBinding ) cboAltBinding->currentIndex() );
|
||||||
|
sym.setCullingMode( _cullingModeFromIndex( cboCullingMode->currentIndex() ) );
|
||||||
sym.setMaterial( widgetMaterial->material() );
|
sym.setMaterial( widgetMaterial->material() );
|
||||||
|
|
||||||
QgsPropertyCollection ddp;
|
QgsPropertyCollection ddp;
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>538</width>
|
<width>561</width>
|
||||||
<height>452</height>
|
<height>452</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
@ -14,10 +14,10 @@
|
|||||||
<string>Form</string>
|
<string>Form</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item row="0" column="0">
|
<item row="2" column="0">
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QLabel" name="label_3">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Height</string>
|
<string>Altitude Clamping</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -31,13 +31,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="2">
|
|
||||||
<widget class="QgsPropertyOverrideButton" name="btnHeightDD">
|
|
||||||
<property name="text">
|
|
||||||
<string>...</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
<widget class="QLabel" name="label_2">
|
<widget class="QLabel" name="label_2">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -45,13 +38,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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">
|
<item row="1" column="2">
|
||||||
<widget class="QgsPropertyOverrideButton" name="btnExtrusionDD">
|
<widget class="QgsPropertyOverrideButton" name="btnExtrusionDD">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -59,10 +45,41 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
<item row="0" column="2">
|
||||||
<widget class="QLabel" name="label_3">
|
<widget class="QgsPropertyOverrideButton" name="btnHeightDD">
|
||||||
<property name="text">
|
<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>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -92,29 +109,38 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="1">
|
<item row="1" column="1">
|
||||||
<widget class="QComboBox" name="cboAltBinding">
|
<widget class="QgsDoubleSpinBox" name="spinExtrusion">
|
||||||
<item>
|
<property name="maximum">
|
||||||
<property name="text">
|
<double>99999.000000000000000</double>
|
||||||
<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>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0" colspan="3">
|
<item row="4" column="0">
|
||||||
<widget class="QgsPhongMaterialWidget" name="widgetMaterial" native="true"/>
|
<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>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
@ -22,6 +22,11 @@
|
|||||||
#include "qgstessellator.h"
|
#include "qgstessellator.h"
|
||||||
#include "qgsmultipolygon.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.
|
* Simple structure to record an expected triangle from tessellator.
|
||||||
* Triangle vertices are expected to be in counter-clockwise order.
|
* Triangle vertices are expected to be in counter-clockwise order.
|
||||||
@ -55,8 +60,13 @@ struct TriangleCoords
|
|||||||
bool operator==( const TriangleCoords &other ) const
|
bool operator==( const TriangleCoords &other ) const
|
||||||
{
|
{
|
||||||
// TODO: allow that the two triangles have coordinates shifted (but still in the same order)
|
// 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] &&
|
const double eps = 1e-6;
|
||||||
normals[0] == other.normals[0] && normals[1] == other.normals[1] && normals[2] == other.normals[2];
|
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
|
bool operator!=( const TriangleCoords &other ) const
|
||||||
@ -175,6 +185,42 @@ void TestQgsTessellator::testBasic()
|
|||||||
|
|
||||||
void TestQgsTessellator::testWalls()
|
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;
|
QgsPolygon polygonZ;
|
||||||
polygonZ.fromWkt( "POLYGONZ((1 1 1, 2 1 2, 3 2 3, 1 2 4, 1 1 1))" );
|
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()
|
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
|
// triangulation would crash for me with this polygon if there is no simplification
|
||||||
// to remove the coordinates that are very close to each other
|
// to remove the coordinates that are very close to each other
|
||||||
QgsPolygon polygon;
|
QgsPolygon polygon;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user