@@ -38,6 +38,7 @@ static void make_quad( float x0, float y0, float z0, float x1, float y1, float z
38
38
39
39
// perpendicular vector in plane to [x,y] is [-y,x]
40
40
QVector3D vn ( -dy, 0 , dx );
41
+ vn = -vn;
41
42
vn.normalize ();
42
43
43
44
// triangle 1
@@ -108,8 +109,8 @@ static void _makeWalls( const QgsCurve &ring, bool ccw, float extrusionHeight, Q
108
109
ring.pointAt ( is_counter_clockwise == ccw ? i : ring.numPoints () - i - 1 , pt, vt );
109
110
float x0 = ptPrev.x () - originX, y0 = ptPrev.y () - originY;
110
111
float x1 = pt.x () - originX, y1 = pt.y () - originY;
111
- float z0 = ptPrev.z ();
112
- float z1 = pt.z ();
112
+ float z0 = std::isnan ( ptPrev. z () ) ? 0 : ptPrev.z ();
113
+ float z1 = std::isnan ( pt. z () ) ? 0 : pt.z ();
113
114
114
115
// make a quad
115
116
make_quad ( x0, y0, z0, x1, y1, z1, extrusionHeight, data, addNormals );
@@ -170,6 +171,7 @@ static QVector3D _calculateNormal( const QgsCurve *curve, double originX, double
170
171
}
171
172
172
173
QVector3D normal ( nx, ny, nz );
174
+ // normal = -normal; // TODO: some datasets seem to work better with, others without inversion
173
175
normal.normalize ();
174
176
return normal;
175
177
}
@@ -197,24 +199,21 @@ static void _normalVectorToXYVectors( const QVector3D &pNormal, QVector3D &pXVec
197
199
}
198
200
199
201
200
- static void _ringToPoly2tri ( const QgsCurve *ring, const QgsPoint &ptFirst, const QMatrix4x4 &toNewBase, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float > &zHash )
202
+ static void _ringToPoly2tri ( const QgsCurve *ring, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float > &zHash )
201
203
{
202
204
QgsVertexId::VertexType vt;
203
205
QgsPoint pt;
204
206
205
207
const int pCount = ring->numPoints ();
206
- double x0 = ptFirst.x (), y0 = ptFirst.y (), z0 = ( std::isnan ( ptFirst.z () ) ? 0 : ptFirst.z () );
207
208
208
209
polyline.reserve ( pCount );
209
210
210
211
for ( int i = 0 ; i < pCount - 1 ; ++i )
211
212
{
212
213
ring->pointAt ( i, pt, vt );
213
- QVector4D tempPt ( pt.x () - x0, pt.y () - y0, std::isnan ( pt.z () ) ? 0 : pt.z () - z0, 0 );
214
- QVector4D newBasePt = toNewBase * tempPt;
215
- const float x = newBasePt.x ();
216
- const float y = newBasePt.y ();
217
- const float z = newBasePt.z ();
214
+ const float x = pt.x ();
215
+ const float y = pt.y ();
216
+ const float z = pt.z ();
218
217
219
218
const bool found = std::find_if ( polyline.begin (), polyline.end (), [x, y]( p2t::Point *&p ) { return *p == p2t::Point ( x, y ); } ) != polyline.end ();
220
219
@@ -229,6 +228,35 @@ static void _ringToPoly2tri( const QgsCurve *ring, const QgsPoint &ptFirst, cons
229
228
}
230
229
}
231
230
231
+ static QgsCurve *_transform_ring_to_new_base ( const QgsCurve &curve, const QgsPoint &pt0, const QMatrix4x4 *toNewBase )
232
+ {
233
+ int count = curve.numPoints ();
234
+ QVector<QgsPoint> pts;
235
+ pts.reserve ( count );
236
+ QgsVertexId::VertexType vt;
237
+ for ( int i = 0 ; i < count; ++i )
238
+ {
239
+ QgsPoint pt;
240
+ curve.pointAt ( i, pt, vt );
241
+ QgsPoint pt2 ( QgsWkbTypes::PointZ, pt.x () - pt0.x (), pt.y () - pt0.y (), std::isnan ( pt.z () ) ? 0 : pt.z () - pt0.z () );
242
+ QVector4D v ( pt2.x (), pt2.y (), pt2.z (), 0 );
243
+ if ( toNewBase )
244
+ v = toNewBase->map ( v );
245
+ pts << QgsPoint ( QgsWkbTypes::PointZ, v.x (), v.y (), v.z () );
246
+ }
247
+ return new QgsLineString ( pts );
248
+ }
249
+
250
+
251
+ static QgsPolygon *_transform_polygon_to_new_base ( const QgsPolygon &polygon, const QgsPoint &pt0, const QMatrix4x4 *toNewBase )
252
+ {
253
+ QgsPolygon *p = new QgsPolygon;
254
+ p->setExteriorRing ( _transform_ring_to_new_base ( *polygon.exteriorRing (), pt0, toNewBase ) );
255
+ for ( int i = 0 ; i < polygon.numInteriorRings (); ++i )
256
+ p->addInteriorRing ( _transform_ring_to_new_base ( *polygon.interiorRing ( i ), pt0, toNewBase ) );
257
+ return p;
258
+ }
259
+
232
260
static bool _check_intersecting_rings ( const QgsPolygon &polygon )
233
261
{
234
262
// 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 )
290
318
291
319
void QgsTessellator::addPolygon ( const QgsPolygon &polygon, float extrusionHeight )
292
320
{
293
- if ( _minimum_distance_between_coordinates ( polygon ) < 0.001 )
294
- {
295
- // when the distances between coordinates of input points are very small,
296
- // the triangulation likes to crash on numerical errors - when the distances are ~ 1e-5
297
- // Assuming that the coordinates should be in a projected CRS, we should be able
298
- // to simplify geometries that may cause problems and avoid possible crashes
299
- QgsGeometry polygonSimplified = QgsGeometry ( polygon.clone () ).simplify ( 0.001 );
300
- const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet () );
301
- if ( _minimum_distance_between_coordinates ( *polygonSimplifiedData ) < 0.001 )
302
- {
303
- // Failed to fix that. It could be a really tiny geometry... or maybe they gave us
304
- // geometry in unprojected lat/lon coordinates
305
- QgsMessageLog::logMessage ( " geometry's coordinates are too close to each other and simplification failed - skipping" , " 3D" );
306
- }
307
- else
308
- {
309
- addPolygon ( *polygonSimplifiedData, extrusionHeight );
310
- }
311
- return ;
312
- }
313
-
314
- if ( !_check_intersecting_rings ( polygon ) )
315
- {
316
- // skip the polygon - it would cause a crash inside poly2tri library
317
- QgsMessageLog::logMessage ( " polygon rings intersect each other - skipping" , " 3D" );
318
- return ;
319
- }
320
-
321
321
const QgsCurve *exterior = polygon.exteriorRing ();
322
322
323
- QList< std::vector<p2t::Point *> > polylinesToDelete;
324
- QHash<p2t::Point *, float > z;
325
-
326
- std::vector<p2t::Point *> polyline;
327
-
328
323
const QVector3D pNormal = _calculateNormal ( exterior, mOriginX , mOriginY );
329
324
const int pCount = exterior->numPoints ();
330
325
331
- // Polygon is a triangle
332
- if ( pCount == 4 )
326
+ if ( pCount == 4 && polygon.numInteriorRings () == 0 )
333
327
{
328
+ // polygon is a triangle - write vertices to the output data array without triangulation
334
329
QgsPoint pt;
335
330
QgsVertexId::VertexType vt;
336
331
for ( int i = 0 ; i < 3 ; i++ )
@@ -346,88 +341,113 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh
346
341
if ( !qgsDoubleNear ( pNormal.length (), 1 , 0.001 ) )
347
342
return ; // this should not happen - pNormal should be normalized to unit length
348
343
349
- QVector3D pXVector, pYVector;
350
- _normalVectorToXYVectors ( pNormal, pXVector, pYVector );
351
-
352
- // so now we have three orthogonal unit vectors defining new base
353
- // let's build transform matrix. We actually need just a 3x3 matrix,
354
- // but Qt does not have good support for it, so using 4x4 matrix instead.
355
- QMatrix4x4 toNewBase (
356
- pXVector.x (), pXVector.y (), pXVector.z (), 0 ,
357
- pYVector.x (), pYVector.y (), pYVector.z (), 0 ,
358
- pNormal.x (), pNormal.y (), pNormal.z (), 0 ,
359
- 0 , 0 , 0 , 0 );
360
-
361
- // our 3x3 matrix is orthogonal, so for inverse we only need to transpose it
362
- QMatrix4x4 toOldBase = toNewBase.transposed ();
344
+ std::unique_ptr<QMatrix4x4> toNewBase, toOldBase;
345
+ if ( pNormal != QVector3D ( 0 , 0 , 1 ) )
346
+ {
347
+ // this is not a horizontal plane - need to reproject the polygon to a new base so that
348
+ // we can do the triangulation in a plane
349
+
350
+ QVector3D pXVector, pYVector;
351
+ _normalVectorToXYVectors ( pNormal, pXVector, pYVector );
352
+
353
+ // so now we have three orthogonal unit vectors defining new base
354
+ // let's build transform matrix. We actually need just a 3x3 matrix,
355
+ // but Qt does not have good support for it, so using 4x4 matrix instead.
356
+ toNewBase.reset ( new QMatrix4x4 (
357
+ pXVector.x (), pXVector.y (), pXVector.z (), 0 ,
358
+ pYVector.x (), pYVector.y (), pYVector.z (), 0 ,
359
+ pNormal.x (), pNormal.y (), pNormal.z (), 0 ,
360
+ 0 , 0 , 0 , 0 ) );
361
+
362
+ // our 3x3 matrix is orthogonal, so for inverse we only need to transpose it
363
+ toOldBase.reset ( new QMatrix4x4 ( toNewBase->transposed () ) );
364
+ }
363
365
364
- const QgsPoint ptFirst ( exterior->startPoint () );
365
- _ringToPoly2tri ( exterior, ptFirst, toNewBase, polyline, z );
366
- polylinesToDelete << polyline;
366
+ const QgsPoint ptStart ( exterior->startPoint () );
367
+ const QgsPoint pt0 ( QgsWkbTypes::PointZ, ptStart.x (), ptStart.y (), std::isnan ( ptStart.z () ) ? 0 : ptStart.z () );
367
368
368
- // TODO: robustness (no nearly duplicate points, invalid geometries ...)
369
+ // subtract ptFirst from geometry for better numerical stability in triangulation
370
+ // and apply new 3D vector base if the polygon is not horizontal
371
+ std::unique_ptr<QgsPolygon> polygonNew ( _transform_polygon_to_new_base ( polygon, pt0, toNewBase.get () ) );
369
372
370
- double x0 = ptFirst.x (), y0 = ptFirst.y (), z0 = ( std::isnan ( ptFirst.z () ) ? 0 : ptFirst.z () );
371
- if ( polyline.size () == 3 && polygon.numInteriorRings () == 0 )
373
+ if ( _minimum_distance_between_coordinates ( *polygonNew ) < 0.001 )
372
374
{
373
- for ( std::vector<p2t::Point *>::iterator it = polyline.begin (); it != polyline.end (); it++ )
375
+ // when the distances between coordinates of input points are very small,
376
+ // the triangulation likes to crash on numerical errors - when the distances are ~ 1e-5
377
+ // Assuming that the coordinates should be in a projected CRS, we should be able
378
+ // to simplify geometries that may cause problems and avoid possible crashes
379
+ QgsGeometry polygonSimplified = QgsGeometry ( polygonNew->clone () ).simplify ( 0.001 );
380
+ const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet () );
381
+ if ( _minimum_distance_between_coordinates ( *polygonSimplifiedData ) < 0.001 )
382
+ {
383
+ // Failed to fix that. It could be a really tiny geometry... or maybe they gave us
384
+ // geometry in unprojected lat/lon coordinates
385
+ QgsMessageLog::logMessage ( " geometry's coordinates are too close to each other and simplification failed - skipping" , " 3D" );
386
+ return ;
387
+ }
388
+ else
374
389
{
375
- p2t::Point *p = *it;
376
- QVector4D ptInNewBase ( p->x , p->y , z[p], 0 );
377
- QVector4D nPoint = toOldBase * ptInNewBase;
378
- const double fx = nPoint.x () - mOriginX + x0;
379
- const double fy = nPoint.y () - mOriginY + y0;
380
- const double fz = nPoint.z () + extrusionHeight + z0;
381
- mData << fx << fz << -fy;
382
- if ( mAddNormals )
383
- mData << pNormal.x () << pNormal.z () << - pNormal.y ();
390
+ polygonNew.reset ( polygonSimplifiedData->clone () );
384
391
}
385
392
}
386
- else if ( polyline.size () >= 3 )
393
+
394
+ if ( !_check_intersecting_rings ( *polygonNew.get () ) )
387
395
{
388
- p2t::CDT *cdt = new p2t::CDT ( polyline );
396
+ // skip the polygon - it would cause a crash inside poly2tri library
397
+ QgsMessageLog::logMessage ( " polygon rings intersect each other - skipping" , " 3D" );
398
+ return ;
399
+ }
389
400
390
- // polygon holes
391
- for ( int i = 0 ; i < polygon.numInteriorRings (); ++i )
392
- {
393
- std::vector<p2t::Point *> holePolyline;
394
- const QgsCurve *hole = polygon.interiorRing ( i );
401
+ QList< std::vector<p2t::Point *> > polylinesToDelete;
402
+ QHash<p2t::Point *, float > z;
395
403
396
- _ringToPoly2tri ( hole, ptFirst, toNewBase, holePolyline, z );
404
+ // polygon exterior
405
+ std::vector<p2t::Point *> polyline;
406
+ _ringToPoly2tri ( polygonNew->exteriorRing (), polyline, z );
407
+ polylinesToDelete << polyline;
397
408
398
- cdt->AddHole ( holePolyline );
399
- polylinesToDelete << holePolyline;
400
- }
409
+ std::unique_ptr<p2t::CDT> cdt ( new p2t::CDT ( polyline ) );
401
410
402
- try
403
- {
404
- cdt->Triangulate ();
411
+ // polygon holes
412
+ for ( int i = 0 ; i < polygonNew->numInteriorRings (); ++i )
413
+ {
414
+ std::vector<p2t::Point *> holePolyline;
415
+ const QgsCurve *hole = polygonNew->interiorRing ( i );
416
+
417
+ _ringToPoly2tri ( hole, holePolyline, z );
418
+
419
+ cdt->AddHole ( holePolyline );
420
+ polylinesToDelete << holePolyline;
421
+ }
422
+
423
+ // run triangulation and write vertices to the output data array
424
+ try
425
+ {
426
+ cdt->Triangulate ();
405
427
406
- std::vector<p2t::Triangle *> triangles = cdt->GetTriangles ();
428
+ std::vector<p2t::Triangle *> triangles = cdt->GetTriangles ();
407
429
408
- for ( size_t i = 0 ; i < triangles.size (); ++i )
430
+ for ( size_t i = 0 ; i < triangles.size (); ++i )
431
+ {
432
+ p2t::Triangle *t = triangles[i];
433
+ for ( int j = 0 ; j < 3 ; ++j )
409
434
{
410
- p2t::Triangle *t = triangles[i];
411
- for ( int j = 0 ; j < 3 ; ++j )
412
- {
413
- p2t::Point *p = t->GetPoint ( j );
414
- QVector4D ptInNewBase ( p->x , p->y , z[p], 0 );
415
- QVector4D nPoint = toOldBase * ptInNewBase;
416
- const double fx = nPoint.x () - mOriginX + x0;
417
- const double fy = nPoint.y () - mOriginY + y0;
418
- const double fz = nPoint.z () + extrusionHeight + z0;
419
- mData << fx << fz << -fy;
420
- if ( mAddNormals )
421
- mData << pNormal.x () << pNormal.z () << - pNormal.y ();
422
- }
435
+ p2t::Point *p = t->GetPoint ( j );
436
+ QVector4D pt ( p->x , p->y , z[p], 0 );
437
+ if ( toOldBase )
438
+ pt = *toOldBase * pt;
439
+ const double fx = pt.x () - mOriginX + pt0.x ();
440
+ const double fy = pt.y () - mOriginY + pt0.y ();
441
+ const double fz = pt.z () + extrusionHeight + pt0.z ();
442
+ mData << fx << fz << -fy;
443
+ if ( mAddNormals )
444
+ mData << pNormal.x () << pNormal.z () << - pNormal.y ();
423
445
}
424
446
}
425
- catch ( ... )
426
- {
427
- QgsMessageLog::logMessage ( " Triangulation failed. Skipping polygon..." , " 3D" );
428
- }
429
-
430
- delete cdt;
447
+ }
448
+ catch ( ... )
449
+ {
450
+ QgsMessageLog::logMessage ( " Triangulation failed. Skipping polygon..." , " 3D" );
431
451
}
432
452
433
453
for ( int i = 0 ; i < polylinesToDelete.count (); ++i )
0 commit comments