QGIS API Documentation  3.14.0-Pi (9f7028fd23)
qgstessellator.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgstessellator.cpp
3  --------------------------------------
4  Date : July 2017
5  Copyright : (C) 2017 by Martin Dobias
6  Email : wonder dot sk at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgstessellator.h"
17 
18 #include "qgscurve.h"
19 #include "qgsgeometry.h"
20 #include "qgsmessagelog.h"
21 #include "qgsmultipolygon.h"
22 #include "qgspoint.h"
23 #include "qgspolygon.h"
24 #include "qgstriangle.h"
25 #include "qgis_sip.h"
26 #include "qgsgeometryengine.h"
27 
28 #include "poly2tri.h"
29 
30 #include <QtDebug>
31 #include <QMatrix4x4>
32 #include <QVector3D>
33 #include <algorithm>
34 #include <unordered_set>
35 
36 
37 static void make_quad( float x0, float y0, float z0, float x1, float y1, float z1, float height, QVector<float> &data, bool addNormals )
38 {
39  float dx = x1 - x0;
40  float dy = -( y1 - y0 );
41 
42  // perpendicular vector in plane to [x,y] is [-y,x]
43  QVector3D vn( -dy, 0, dx );
44  vn = -vn;
45  vn.normalize();
46 
47  // triangle 1
48  data << x0 << z0 + height << -y0;
49  if ( addNormals )
50  data << vn.x() << vn.y() << vn.z();
51  data << x1 << z1 + height << -y1;
52  if ( addNormals )
53  data << vn.x() << vn.y() << vn.z();
54  data << x0 << z0 << -y0;
55  if ( addNormals )
56  data << vn.x() << vn.y() << vn.z();
57 
58  // triangle 2
59  data << x0 << z0 << -y0;
60  if ( addNormals )
61  data << vn.x() << vn.y() << vn.z();
62  data << x1 << z1 + height << -y1;
63  if ( addNormals )
64  data << vn.x() << vn.y() << vn.z();
65  data << x1 << z1 << -y1;
66  if ( addNormals )
67  data << vn.x() << vn.y() << vn.z();
68 }
69 
70 
71 QgsTessellator::QgsTessellator( double originX, double originY, bool addNormals, bool invertNormals, bool addBackFaces )
72  : mOriginX( originX )
73  , mOriginY( originY )
74  , mAddNormals( addNormals )
75  , mInvertNormals( invertNormals )
76  , mAddBackFaces( addBackFaces )
77 {
78  init();
79 }
80 
81 QgsTessellator::QgsTessellator( const QgsRectangle &bounds, bool addNormals, bool invertNormals, bool addBackFaces, bool noZ )
82  : mBounds( bounds )
83  , mOriginX( mBounds.xMinimum() )
84  , mOriginY( mBounds.yMinimum() )
85  , mAddNormals( addNormals )
86  , mInvertNormals( invertNormals )
87  , mAddBackFaces( addBackFaces )
88  , mNoZ( noZ )
89 {
90  init();
91 }
92 
93 void QgsTessellator::init()
94 {
95  mStride = 3 * sizeof( float );
96  if ( mAddNormals )
97  mStride += 3 * sizeof( float );
98 }
99 
100 static bool _isRingCounterClockWise( const QgsCurve &ring )
101 {
102  double a = 0;
103  int count = ring.numPoints();
105  QgsPoint pt, ptPrev;
106  ring.pointAt( 0, ptPrev, vt );
107  for ( int i = 1; i < count + 1; ++i )
108  {
109  ring.pointAt( i % count, pt, vt );
110  a += ptPrev.x() * pt.y() - ptPrev.y() * pt.x();
111  ptPrev = pt;
112  }
113  return a > 0; // clockwise if a is negative
114 }
115 
116 static void _makeWalls( const QgsLineString &ring, bool ccw, float extrusionHeight, QVector<float> &data, bool addNormals, double originX, double originY )
117 {
118  // we need to find out orientation of the ring so that the triangles we generate
119  // face the right direction
120  // (for exterior we want clockwise order, for holes we want counter-clockwise order)
121  bool is_counter_clockwise = _isRingCounterClockWise( ring );
122 
123  QgsPoint pt;
124  QgsPoint ptPrev = ring.pointN( is_counter_clockwise == ccw ? 0 : ring.numPoints() - 1 );
125  for ( int i = 1; i < ring.numPoints(); ++i )
126  {
127  pt = ring.pointN( is_counter_clockwise == ccw ? i : ring.numPoints() - i - 1 );
128  float x0 = ptPrev.x() - originX, y0 = ptPrev.y() - originY;
129  float x1 = pt.x() - originX, y1 = pt.y() - originY;
130  float z0 = std::isnan( ptPrev.z() ) ? 0 : ptPrev.z();
131  float z1 = std::isnan( pt.z() ) ? 0 : pt.z();
132 
133  // make a quad
134  make_quad( x0, y0, z0, x1, y1, z1, extrusionHeight, data, addNormals );
135  ptPrev = pt;
136  }
137 }
138 
139 static QVector3D _calculateNormal( const QgsLineString *curve, double originX, double originY, bool invertNormal )
140 {
141  // if it is just plain 2D curve there is no need to calculate anything
142  // because it will be a flat horizontally oriented patch
143  if ( !QgsWkbTypes::hasZ( curve->wkbType() ) || curve->isEmpty() )
144  return QVector3D( 0, 0, 1 );
145 
146  // often we have 3D coordinates, but Z is the same for all vertices
147  // so in order to save calculation and avoid possible issues with order of vertices
148  // (the calculation below may decide that a polygon faces downwards)
149  bool sameZ = true;
150  QgsPoint pt1 = curve->pointN( 0 );
151  QgsPoint pt2;
152  for ( int i = 1; i < curve->numPoints(); i++ )
153  {
154  pt2 = curve->pointN( i );
155  if ( pt1.z() != pt2.z() )
156  {
157  sameZ = false;
158  break;
159  }
160  }
161  if ( sameZ )
162  return QVector3D( 0, 0, 1 );
163 
164  // Calculate the polygon's normal vector, based on Newell's method
165  // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
166  //
167  // Order of vertices is important here as it determines the front/back face of the polygon
168 
169  double nx = 0, ny = 0, nz = 0;
170  pt1 = curve->pointN( 0 );
171 
172  // shift points by the tessellator's origin - this does not affect normal calculation and it may save us from losing some precision
173  pt1.setX( pt1.x() - originX );
174  pt1.setY( pt1.y() - originY );
175  for ( int i = 1; i < curve->numPoints(); i++ )
176  {
177  pt2 = curve->pointN( i );
178  pt2.setX( pt2.x() - originX );
179  pt2.setY( pt2.y() - originY );
180 
181  if ( std::isnan( pt1.z() ) || std::isnan( pt2.z() ) )
182  continue;
183 
184  nx += ( pt1.y() - pt2.y() ) * ( pt1.z() + pt2.z() );
185  ny += ( pt1.z() - pt2.z() ) * ( pt1.x() + pt2.x() );
186  nz += ( pt1.x() - pt2.x() ) * ( pt1.y() + pt2.y() );
187 
188  pt1 = pt2;
189  }
190 
191  QVector3D normal( nx, ny, nz );
192  if ( invertNormal )
193  normal = -normal;
194  normal.normalize();
195  return normal;
196 }
197 
198 
199 static void _normalVectorToXYVectors( const QVector3D &pNormal, QVector3D &pXVector, QVector3D &pYVector )
200 {
201  // Here we define the two perpendicular vectors that define the local
202  // 2D space on the plane. They will act as axis for which we will
203  // calculate the projection coordinates of a 3D point to the plane.
204  if ( pNormal.z() > 0.001 || pNormal.z() < -0.001 )
205  {
206  pXVector = QVector3D( 1, 0, -pNormal.x() / pNormal.z() );
207  }
208  else if ( pNormal.y() > 0.001 || pNormal.y() < -0.001 )
209  {
210  pXVector = QVector3D( 1, -pNormal.x() / pNormal.y(), 0 );
211  }
212  else
213  {
214  pXVector = QVector3D( -pNormal.y() / pNormal.x(), 1, 0 );
215  }
216  pXVector.normalize();
217  pYVector = QVector3D::normal( pNormal, pXVector );
218 }
219 
221 {
222  std::size_t operator()( const std::pair<float, float> pair ) const
223  {
224  std::size_t h1 = std::hash<float>()( pair.first );
225  std::size_t h2 = std::hash<float>()( pair.second );
226 
227  return h1 ^ h2;
228  }
229 };
230 
231 static void _ringToPoly2tri( const QgsLineString *ring, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> *zHash )
232 {
233  const int pCount = ring->numPoints();
234 
235  polyline.reserve( pCount );
236 
237  const double *srcXData = ring->xData();
238  const double *srcYData = ring->yData();
239  const double *srcZData = ring->zData();
240  std::unordered_set<std::pair<float, float>, float_pair_hash> foundPoints;
241 
242  for ( int i = 0; i < pCount - 1; ++i )
243  {
244  const float x = *srcXData++;
245  const float y = *srcYData++;
246 
247  auto res = foundPoints.insert( std::make_pair( x, y ) );
248  if ( !res.second )
249  {
250  // already used this point, don't add a second time
251  continue;
252  }
253 
254  p2t::Point *pt2 = new p2t::Point( x, y );
255  polyline.push_back( pt2 );
256  if ( zHash )
257  {
258  ( *zHash )[pt2] = *srcZData++;
259  }
260  }
261 }
262 
263 
264 inline double _round_coord( double x )
265 {
266  const double exp = 1e10; // round to 10 decimal digits
267  return round( x * exp ) / exp;
268 }
269 
270 
271 static QgsCurve *_transform_ring_to_new_base( const QgsLineString &curve, const QgsPoint &pt0, const QMatrix4x4 *toNewBase, const float scale )
272 {
273  int count = curve.numPoints();
274  QVector<double> x;
275  QVector<double> y;
276  QVector<double> z;
277  x.resize( count );
278  y.resize( count );
279  z.resize( count );
280  double *xData = x.data();
281  double *yData = y.data();
282  double *zData = z.data();
283 
284  const double *srcXData = curve.xData();
285  const double *srcYData = curve.yData();
286  const double *srcZData = curve.is3D() ? curve.zData() : nullptr;
287 
288  for ( int i = 0; i < count; ++i )
289  {
290  QVector4D v( *srcXData++ - pt0.x(),
291  *srcYData++ - pt0.y(),
292  srcZData ? *srcZData++ - pt0.z() : 0,
293  0 );
294  if ( toNewBase )
295  v = toNewBase->map( v );
296 
297  // scale coordinates
298  v.setX( v.x() * scale );
299  v.setY( v.y() * scale );
300 
301  // we also round coordinates before passing them to poly2tri triangulation in order to fix possible numerical
302  // stability issues. We had crashes with nearly collinear points where one of the points was off by a tiny bit (e.g. by 1e-20).
303  // See TestQgsTessellator::testIssue17745().
304  //
305  // A hint for a similar issue: https://github.com/greenm01/poly2tri/issues/99
306  //
307  // The collinear tests uses epsilon 1e-12. Seems rounding to 12 places you still
308  // can get problems with this test when points are pretty much on a straight line.
309  // I suggest you round to 10 decimals for stability and you can live with that
310  // precision.
311  *xData++ = _round_coord( v.x() );
312  *yData++ = _round_coord( v.y() );
313  *zData++ = _round_coord( v.z() );
314  }
315  return new QgsLineString( x, y, z );
316 }
317 
318 
319 static QgsPolygon *_transform_polygon_to_new_base( const QgsPolygon &polygon, const QgsPoint &pt0, const QMatrix4x4 *toNewBase, const float scale )
320 {
321  QgsPolygon *p = new QgsPolygon;
322  p->setExteriorRing( _transform_ring_to_new_base( *qgsgeometry_cast< const QgsLineString * >( polygon.exteriorRing() ), pt0, toNewBase, scale ) );
323  for ( int i = 0; i < polygon.numInteriorRings(); ++i )
324  p->addInteriorRing( _transform_ring_to_new_base( *qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ), pt0, toNewBase, scale ) );
325  return p;
326 }
327 
328 static bool _check_intersecting_rings( const QgsPolygon &polygon )
329 {
330  std::vector< std::unique_ptr< QgsGeometryEngine > > ringEngines;
331  ringEngines.reserve( 1 + polygon.numInteriorRings() );
332  ringEngines.emplace_back( QgsGeometry::createGeometryEngine( polygon.exteriorRing() ) );
333  for ( int i = 0; i < polygon.numInteriorRings(); ++i )
334  ringEngines.emplace_back( QgsGeometry::createGeometryEngine( polygon.interiorRing( i ) ) );
335 
336  // we need to make sure that the polygon has no rings with self-intersection: that may
337  // crash the tessellator. The original geometry maybe have been valid and the self-intersection
338  // was introduced when transforming to a new base (in a rare case when all points are not in the same plane)
339 
340  for ( const std::unique_ptr< QgsGeometryEngine > &ring : ringEngines )
341  {
342  if ( !ring->isSimple() )
343  return false;
344  }
345 
346  // At this point we assume that input polygons are valid according to the OGC definition.
347  // This means e.g. no duplicate points, polygons are simple (no butterfly shaped polygon with self-intersection),
348  // internal rings are inside exterior rings, rings do not cross each other, no dangles.
349 
350  // There is however an issue with polygons where rings touch:
351  // +---+
352  // | |
353  // | +-+-+
354  // | | | |
355  // | +-+ |
356  // | |
357  // +-----+
358  // This is a valid polygon with one exterior and one interior ring that touch at one point,
359  // but poly2tri library does not allow interior rings touch each other or exterior ring.
360  // TODO: Handle the situation better - rather than just detecting the problem, try to fix
361  // it by converting touching rings into one ring.
362 
363  if ( ringEngines.size() > 1 )
364  {
365  for ( size_t i = 0; i < ringEngines.size(); ++i )
366  {
367  std::unique_ptr< QgsGeometryEngine > &first = ringEngines.at( i );
368  if ( polygon.numInteriorRings() > 1 )
369  first->prepareGeometry();
370 
371  // TODO this is inefficient - QgsGeometryEngine::intersects only works with QgsAbstractGeometry
372  // objects and accordingly we have to use those, instead of the previously build geos
373  // representations available in ringEngines
374  // This needs addressing by extending the QgsGeometryEngine relation tests to allow testing against
375  // another QgsGeometryEngine object.
376  for ( int interiorRing = static_cast< int >( i ); interiorRing < polygon.numInteriorRings(); ++interiorRing )
377  {
378  if ( first->intersects( polygon.interiorRing( interiorRing ) ) )
379  return false;
380  }
381  }
382  }
383  return true;
384 }
385 
386 
388 {
389  double min_d = 1e20;
390 
391  std::vector< const QgsLineString * > rings;
392  rings.reserve( 1 + polygon.numInteriorRings() );
393  rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.exteriorRing() ) );
394  for ( int i = 0; i < polygon.numInteriorRings(); ++i )
395  rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ) );
396 
397  for ( const QgsLineString *ring : rings )
398  {
399  const int numPoints = ring->numPoints();
400  if ( numPoints <= 1 )
401  continue;
402 
403  const double *srcXData = ring->xData();
404  const double *srcYData = ring->yData();
405  double x0 = *srcXData++;
406  double y0 = *srcYData++;
407  for ( int i = 1; i < numPoints; ++i )
408  {
409  double x1 = *srcXData++;
410  double y1 = *srcYData++;
411  double d = ( x0 - x1 ) * ( x0 - x1 ) + ( y0 - y1 ) * ( y0 - y1 );
412  if ( d < min_d )
413  min_d = d;
414  x0 = x1;
415  y0 = y1;
416  }
417  }
418 
419  return min_d != 1e20 ? std::sqrt( min_d ) : 1e20;
420 }
421 
422 
423 void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeight )
424 {
425  const QgsLineString *exterior = qgsgeometry_cast< const QgsLineString * >( polygon.exteriorRing() );
426  if ( !exterior )
427  return;
428 
429  const QVector3D pNormal = !mNoZ ? _calculateNormal( exterior, mOriginX, mOriginY, mInvertNormals ) : QVector3D();
430  const int pCount = exterior->numPoints();
431  if ( pCount == 0 )
432  return;
433 
434  float zMin = std::numeric_limits<float>::max();
435  float zMax = std::numeric_limits<float>::min();
436 
437  if ( pCount == 4 && polygon.numInteriorRings() == 0 )
438  {
439  // polygon is a triangle - write vertices to the output data array without triangulation
440  const double *xData = exterior->xData();
441  const double *yData = exterior->yData();
442  const double *zData = !mNoZ ? exterior->zData() : nullptr;
443  for ( int i = 0; i < 3; i++ )
444  {
445  float z = ( !zData ? 0 : *zData++ );
446  if ( z < zMin )
447  zMin = z;
448  if ( z > zMax )
449  zMax = z;
450 
451  mData << *xData++ - mOriginX << z << - *yData++ + mOriginY;
452  if ( mAddNormals )
453  mData << pNormal.x() << pNormal.z() << - pNormal.y();
454  }
455 
456  if ( mAddBackFaces )
457  {
458  // the same triangle with reversed order of coordinates and inverted normal
459  for ( int i = 2; i >= 0; i-- )
460  {
461  mData << exterior->xAt( i ) - mOriginX << ( mNoZ ? 0 : exterior->zAt( i ) ) << - exterior->yAt( i ) + mOriginY;
462  if ( mAddNormals )
463  mData << -pNormal.x() << -pNormal.z() << pNormal.y();
464  }
465  }
466  }
467  else
468  {
469  if ( !mNoZ && !qgsDoubleNear( pNormal.length(), 1, 0.001 ) )
470  return; // this should not happen - pNormal should be normalized to unit length
471 
472  std::unique_ptr<QMatrix4x4> toNewBase, toOldBase;
473  if ( !mNoZ && pNormal != QVector3D( 0, 0, 1 ) )
474  {
475  // this is not a horizontal plane - need to reproject the polygon to a new base so that
476  // we can do the triangulation in a plane
477 
478  QVector3D pXVector, pYVector;
479  _normalVectorToXYVectors( pNormal, pXVector, pYVector );
480 
481  // so now we have three orthogonal unit vectors defining new base
482  // let's build transform matrix. We actually need just a 3x3 matrix,
483  // but Qt does not have good support for it, so using 4x4 matrix instead.
484  toNewBase.reset( new QMatrix4x4(
485  pXVector.x(), pXVector.y(), pXVector.z(), 0,
486  pYVector.x(), pYVector.y(), pYVector.z(), 0,
487  pNormal.x(), pNormal.y(), pNormal.z(), 0,
488  0, 0, 0, 0 ) );
489 
490  // our 3x3 matrix is orthogonal, so for inverse we only need to transpose it
491  toOldBase.reset( new QMatrix4x4( toNewBase->transposed() ) );
492  }
493 
494  const QgsPoint ptStart( exterior->startPoint() );
495  const QgsPoint pt0( QgsWkbTypes::PointZ, ptStart.x(), ptStart.y(), std::isnan( ptStart.z() ) ? 0 : ptStart.z() );
496 
497  const float scale = mBounds.isNull() ? 1.0 : std::max( 10000.0 / mBounds.width(), 10000.0 / mBounds.height() );
498 
499  // subtract ptFirst from geometry for better numerical stability in triangulation
500  // and apply new 3D vector base if the polygon is not horizontal
501 
502  std::unique_ptr<QgsPolygon> polygonNew( _transform_polygon_to_new_base( polygon, pt0, toNewBase.get(), scale ) );
503 
504  if ( _minimum_distance_between_coordinates( *polygonNew ) < 0.001 )
505  {
506  // when the distances between coordinates of input points are very small,
507  // the triangulation likes to crash on numerical errors - when the distances are ~ 1e-5
508  // Assuming that the coordinates should be in a projected CRS, we should be able
509  // to simplify geometries that may cause problems and avoid possible crashes
510  QgsGeometry polygonSimplified = QgsGeometry( polygonNew->clone() ).simplify( 0.001 );
511  if ( polygonSimplified.isNull() )
512  {
513  QgsMessageLog::logMessage( QObject::tr( "geometry simplification failed - skipping" ), QObject::tr( "3D" ) );
514  return;
515  }
516  const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet() );
517  if ( _minimum_distance_between_coordinates( *polygonSimplifiedData ) < 0.001 )
518  {
519  // Failed to fix that. It could be a really tiny geometry... or maybe they gave us
520  // geometry in unprojected lat/lon coordinates
521  QgsMessageLog::logMessage( QObject::tr( "geometry's coordinates are too close to each other and simplification failed - skipping" ), QObject::tr( "3D" ) );
522  return;
523  }
524  else
525  {
526  polygonNew.reset( polygonSimplifiedData->clone() );
527  }
528  }
529 
530  if ( !_check_intersecting_rings( *polygonNew ) )
531  {
532  // skip the polygon - it would cause a crash inside poly2tri library
533  QgsMessageLog::logMessage( QObject::tr( "polygon rings self-intersect or intersect each other - skipping" ), QObject::tr( "3D" ) );
534  return;
535  }
536 
537  QList< std::vector<p2t::Point *> > polylinesToDelete;
538  QHash<p2t::Point *, float> z;
539 
540  // polygon exterior
541  std::vector<p2t::Point *> polyline;
542  _ringToPoly2tri( qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() ), polyline, mNoZ ? nullptr : &z );
543  polylinesToDelete << polyline;
544 
545  std::unique_ptr<p2t::CDT> cdt( new p2t::CDT( polyline ) );
546 
547  // polygon holes
548  for ( int i = 0; i < polygonNew->numInteriorRings(); ++i )
549  {
550  std::vector<p2t::Point *> holePolyline;
551  const QgsLineString *hole = qgsgeometry_cast< const QgsLineString *>( polygonNew->interiorRing( i ) );
552 
553  _ringToPoly2tri( hole, holePolyline, mNoZ ? nullptr : &z );
554 
555  cdt->AddHole( holePolyline );
556  polylinesToDelete << holePolyline;
557  }
558 
559  // run triangulation and write vertices to the output data array
560  try
561  {
562  cdt->Triangulate();
563 
564  std::vector<p2t::Triangle *> triangles = cdt->GetTriangles();
565 
566  mData.reserve( mData.size() + triangles.size() * ( ( mAddNormals ? 6 : 3 ) * ( mAddBackFaces ? 2 : 1 ) ) );
567  for ( size_t i = 0; i < triangles.size(); ++i )
568  {
569  p2t::Triangle *t = triangles[i];
570  for ( int j = 0; j < 3; ++j )
571  {
572  p2t::Point *p = t->GetPoint( j );
573  QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
574  if ( toOldBase )
575  pt = *toOldBase * pt;
576  const double fx = ( pt.x() / scale ) - mOriginX + pt0.x();
577  const double fy = ( pt.y() / scale ) - mOriginY + pt0.y();
578  const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.z() );
579  if ( fz < zMin )
580  zMin = fz;
581  if ( fz > zMax )
582  zMax = fz;
583 
584  mData << fx << fz << -fy;
585  if ( mAddNormals )
586  mData << pNormal.x() << pNormal.z() << - pNormal.y();
587  }
588 
589  if ( mAddBackFaces )
590  {
591  // the same triangle with reversed order of coordinates and inverted normal
592  for ( int j = 2; j >= 0; --j )
593  {
594  p2t::Point *p = t->GetPoint( j );
595  QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
596  if ( toOldBase )
597  pt = *toOldBase * pt;
598  const double fx = ( pt.x() / scale ) - mOriginX + pt0.x();
599  const double fy = ( pt.y() / scale ) - mOriginY + pt0.y();
600  const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.z() );
601  mData << fx << fz << -fy;
602  if ( mAddNormals )
603  mData << -pNormal.x() << -pNormal.z() << pNormal.y();
604  }
605  }
606  }
607  }
608  catch ( ... )
609  {
610  QgsMessageLog::logMessage( QObject::tr( "Triangulation failed. Skipping polygon…" ), QObject::tr( "3D" ) );
611  }
612 
613  for ( int i = 0; i < polylinesToDelete.count(); ++i )
614  qDeleteAll( polylinesToDelete[i] );
615  }
616 
617  // add walls if extrusion is enabled
618  if ( extrusionHeight != 0 )
619  {
620  _makeWalls( *exterior, false, extrusionHeight, mData, mAddNormals, mOriginX, mOriginY );
621 
622  for ( int i = 0; i < polygon.numInteriorRings(); ++i )
623  _makeWalls( *qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ), true, extrusionHeight, mData, mAddNormals, mOriginX, mOriginY );
624 
625  zMax += extrusionHeight;
626  }
627 
628  if ( zMin < mZMin )
629  mZMin = zMin;
630  if ( zMax > mZMax )
631  mZMax = zMax;
632 }
633 
634 QgsPoint getPointFromData( QVector< float >::const_iterator &it )
635 {
636  // tessellator geometry is x, z, -y
637  double x = *it;
638  ++it;
639  double z = *it;
640  ++it;
641  double y = -( *it );
642  ++it;
643  return QgsPoint( x, y, z );
644 }
645 
647 {
648  return mData.size() / ( mAddNormals ? 6 : 3 );
649 }
650 
651 std::unique_ptr<QgsMultiPolygon> QgsTessellator::asMultiPolygon() const
652 {
653  std::unique_ptr< QgsMultiPolygon > mp = qgis::make_unique< QgsMultiPolygon >();
654  const QVector<float> data = mData;
655  mp->reserve( mData.size() );
656  for ( auto it = data.constBegin(); it != data.constEnd(); )
657  {
658  QgsPoint p1 = getPointFromData( it );
659  QgsPoint p2 = getPointFromData( it );
660  QgsPoint p3 = getPointFromData( it );
661  mp->addGeometry( new QgsTriangle( p1, p2, p3 ) );
662  }
663  return mp;
664 }
_round_coord
double _round_coord(double x)
Definition: qgstessellator.cpp:264
QgsCurve
Abstract base class for curved geometry type.
Definition: qgscurve.h:35
qgspolygon.h
QgsCurve::pointAt
virtual bool pointAt(int node, QgsPoint &point, QgsVertexId::VertexType &type) const =0
Returns the point and vertex id of a point within the curve.
QgsPoint::setY
void setY(double y)
Sets the point's y-coordinate.
Definition: qgspoint.h:298
QgsLineString::pointN
QgsPoint pointN(int i) const
Returns the specified point from inside the line string.
Definition: qgslinestring.cpp:660
QgsLineString::xAt
double xAt(int index) const override
Returns the x-coordinate of the specified node in the line string.
Definition: qgslinestring.cpp:709
QgsLineString::zData
const double * zData() const
Returns a const pointer to the z vertex data, or nullptr if the linestring does not have z values.
Definition: qgslinestring.h:241
QgsAbstractGeometry::wkbType
QgsWkbTypes::Type wkbType() const
Returns the WKB type of the geometry.
Definition: qgsabstractgeometry.h:189
QgsPolygon
Polygon geometry type.
Definition: qgspolygon.h:33
QgsPoint
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:37
getPointFromData
QgsPoint getPointFromData(QVector< float >::const_iterator &it)
Definition: qgstessellator.cpp:634
float_pair_hash::operator()
std::size_t operator()(const std::pair< float, float > pair) const
Definition: qgstessellator.cpp:222
QgsPolygon::clone
QgsPolygon * clone() const override
Clones the geometry by performing a deep copy.
Definition: qgspolygon.cpp:54
QgsLineString::numPoints
int numPoints() const override
Returns the number of points in the curve.
Definition: qgslinestring.cpp:650
QgsLineString::yAt
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
Definition: qgslinestring.cpp:717
QgsTessellator::asMultiPolygon
std::unique_ptr< QgsMultiPolygon > asMultiPolygon() const
Returns the triangulation as a multipolygon geometry.
Definition: qgstessellator.cpp:651
QgsLineString::zAt
double zAt(int index) const
Returns the z-coordinate of the specified node in the line string.
Definition: qgslinestring.h:274
QgsPolygon::setExteriorRing
void setExteriorRing(QgsCurve *ring) override
Sets the exterior ring of the polygon.
Definition: qgspolygon.cpp:214
QgsCurvePolygon::exteriorRing
const QgsCurve * exteriorRing() const
Returns the curve polygon's exterior ring.
Definition: qgscurvepolygon.h:86
QgsPoint::z
double z
Definition: qgspoint.h:60
QgsLineString::yData
const double * yData() const
Returns a const pointer to the y vertex data.
Definition: qgslinestring.h:228
QgsWkbTypes::hasZ
static bool hasZ(Type type)
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:1042
qgspoint.h
QgsLineString
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:43
QgsTessellator::addPolygon
void addPolygon(const QgsPolygon &polygon, float extrusionHeight)
Tessellates a triangle and adds its vertex entries to the output data array.
Definition: qgstessellator.cpp:423
QgsRectangle
Definition: qgsrectangle.h:41
QgsCurvePolygon::interiorRing
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
Definition: qgscurvepolygon.h:99
QgsLineString::xData
const double * xData() const
Returns a const pointer to the x vertex data.
Definition: qgslinestring.h:217
float_pair_hash
Definition: qgstessellator.cpp:220
QgsPoint::y
double y
Definition: qgspoint.h:59
qgsgeometryengine.h
QgsTessellator::dataVerticesCount
int dataVerticesCount() const
Returns the number of vertices stored in the output data array.
Definition: qgstessellator.cpp:646
qgsmultipolygon.h
qgsDoubleNear
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:315
QgsPoint::setX
void setX(double x)
Sets the point's x-coordinate.
Definition: qgspoint.h:287
qgis_sip.h
qgstriangle.h
qgstessellator.h
QgsWkbTypes::PointZ
@ PointZ
Definition: qgswkbtypes.h:85
QgsTessellator::QgsTessellator
QgsTessellator(double originX, double originY, bool addNormals, bool invertNormals=false, bool addBackFaces=false)
Creates tessellator with a specified origin point of the world (in map coordinates)
Definition: qgstessellator.cpp:71
QgsAbstractGeometry::is3D
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
Definition: qgsabstractgeometry.h:202
QgsGeometry::isNull
bool isNull
Definition: qgsgeometry.h:125
QgsGeometry::constGet
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Definition: qgsgeometry.cpp:128
QgsGeometry::simplify
QgsGeometry simplify(double tolerance) const
Returns a simplified version of this geometry using a specified tolerance value.
Definition: qgsgeometry.cpp:2113
QgsLineString::startPoint
QgsPoint startPoint() const override
Returns the starting point of the curve.
Definition: qgslinestring.cpp:619
_minimum_distance_between_coordinates
double _minimum_distance_between_coordinates(const QgsPolygon &polygon)
Definition: qgstessellator.cpp:387
QgsLineString::isEmpty
bool isEmpty() const override
Returns true if the geometry is empty.
Definition: qgslinestring.cpp:308
QgsGeometry::createGeometryEngine
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry)
Creates and returns a new geometry engine.
Definition: qgsgeometry.cpp:3659
QgsCurvePolygon::numInteriorRings
int numInteriorRings() const
Returns the number of interior rings contained with the curve polygon.
Definition: qgscurvepolygon.h:76
qgsgeometry.h
qgscurve.h
QgsGeometry
Definition: qgsgeometry.h:122
QgsRectangle::height
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:209
QgsMessageLog::logMessage
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Definition: qgsmessagelog.cpp:27
QgsVertexId::VertexType
VertexType
Definition: qgsabstractgeometry.h:1035
QgsCurve::numPoints
virtual int numPoints() const =0
Returns the number of points in the curve.
QgsTessellator::data
QVector< float > data() const
Returns array of triangle vertex data.
Definition: qgstessellator.h:65
QgsPolygon::addInteriorRing
void addInteriorRing(QgsCurve *ring) override
Adds an interior ring to the geometry (takes ownership)
Definition: qgspolygon.cpp:183
QgsPoint::x
double x
Definition: qgspoint.h:58
QgsRectangle::isNull
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:447
QgsRectangle::width
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:202
QgsTriangle
Triangle geometry type.
Definition: qgstriangle.h:33
qgsmessagelog.h