Skip to content
Permalink
Browse files

[3d] Fix crash in tessellator with near coords (fixes #17286, fixes #…

…17515)

As the readme of poly2tri library says: "Poly2Tri does not support repeat points within epsilon."

When the coordinates are very near to each other, we get crashes in triangulation code.
To prevent that, we try to simplify geometries to hopefully fix the problem automatically,
if that fails we just skip the polygon as the last resort.

Usually this happens if user tries to use 3D renderer on unprojected lat/lon coordinates.
  • Loading branch information
wonder-sk committed Nov 23, 2017
1 parent d147064 commit 7bce7ea9ca25f5744ca6dc8d02ba8f7dbe408d47
Showing with 62 additions and 0 deletions.
  1. +43 −0 src/3d/qgstessellator.cpp
  2. +19 −0 tests/src/3d/testqgstessellator.cpp
@@ -265,8 +265,51 @@ static bool _check_intersecting_rings( const QgsPolygon &polygon )
}


double _minimum_distance_between_coordinates( const QgsPolygon &polygon )
{
double min_d = 1e20;
auto it = polygon.vertices_begin();

if ( it == polygon.vertices_end() )
return min_d;

QgsPoint p0 = *it;
++it;
for ( ; it != polygon.vertices_end(); ++it )
{
QgsPoint p1 = *it;
double d = p0.distance( p1 );
if ( d < min_d )
min_d = d;
p0 = p1;
}
return min_d;
}


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
qDebug() << "geometry's coordinates are too close to each other and simplification failed - skipping";
}
else
{
addPolygon( *polygonSimplifiedData, extrusionHeight );
}
return;
}

if ( !_check_intersecting_rings( polygon ) )
{
// skip the polygon - it would cause a crash inside poly2tri library
@@ -78,7 +78,10 @@ bool checkTriangleOutput( const QVector<float> &data, bool withNormals, const QL
{
int valuesPerTriangle = withNormals ? 18 : 9;
if ( data.count() != expected.count() * valuesPerTriangle )
{
qDebug() << "expected" << expected.count() << "triangles, got" << data.count() / valuesPerTriangle;
return false;
}

// TODO: allow arbitrary order of triangles in output
const float *dataRaw = data.constData();
@@ -117,6 +120,7 @@ class TestQgsTessellator : public QObject
void testBasic();
void testWalls();
void asMultiPolygon();
void testBadCoordinates();

private:
};
@@ -208,7 +212,22 @@ void TestQgsTessellator::asMultiPolygon()
QgsTessellator t2( 0, 0, false );
t2.addPolygon( polygonZ, 0 );
QCOMPARE( t2.asMultiPolygon()->asWkt(), QStringLiteral( "MultiPolygonZ (((1 2 4, 2 1 2, 3 2 3, 1 2 4)),((1 2 4, 1 1 1, 2 1 2, 1 2 4)))" ) );
}

void TestQgsTessellator::testBadCoordinates()
{
// 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;
polygon.fromWkt( "POLYGON((1 1, 2 1, 2.0000001 1.0000001, 2.0000002 1.0000001, 2.0000001 1.0000002, 2.0000002 1.0000002, 3 2, 1 2, 1 1))" );

QList<TriangleCoords> tc;
tc << TriangleCoords( QVector3D( 1, 2, 0 ), QVector3D( 2, 1, 0 ), QVector3D( 3, 2, 0 ) );
tc << TriangleCoords( QVector3D( 1, 2, 0 ), QVector3D( 1, 1, 0 ), QVector3D( 2, 1, 0 ) );

QgsTessellator t( 0, 0, false );
t.addPolygon( polygon, 0 );
QVERIFY( checkTriangleOutput( t.data(), false, tc ) );
}


0 comments on commit 7bce7ea

Please sign in to comment.
You can’t perform that action at this time.