QGIS API Documentation  3.12.1-București (121cc00ff0)
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, float scaleX, float scaleY )
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() * scaleX );
299  v.setY( v.y() * scaleY );
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, float scaleX, float scaleY )
320 {
321  QgsPolygon *p = new QgsPolygon;
322  p->setExteriorRing( _transform_ring_to_new_base( *qgsgeometry_cast< const QgsLineString * >( polygon.exteriorRing() ), pt0, toNewBase, scaleX, scaleY ) );
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, scaleX, scaleY ) );
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 = ( mNoZ ? 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 scaleX = !mBounds.isNull() ? 10000.0 / mBounds.width() : 1.0;
498  const float scaleY = !mBounds.isNull() ? 10000.0 / mBounds.height() : 1.0;
499 
500  // subtract ptFirst from geometry for better numerical stability in triangulation
501  // and apply new 3D vector base if the polygon is not horizontal
502 
503  std::unique_ptr<QgsPolygon> polygonNew( _transform_polygon_to_new_base( polygon, pt0, toNewBase.get(), scaleX, scaleY ) );
504 
505  if ( _minimum_distance_between_coordinates( *polygonNew ) < 0.001 )
506  {
507  // when the distances between coordinates of input points are very small,
508  // the triangulation likes to crash on numerical errors - when the distances are ~ 1e-5
509  // Assuming that the coordinates should be in a projected CRS, we should be able
510  // to simplify geometries that may cause problems and avoid possible crashes
511  QgsGeometry polygonSimplified = QgsGeometry( polygonNew->clone() ).simplify( 0.001 );
512  if ( polygonSimplified.isNull() )
513  {
514  QgsMessageLog::logMessage( QObject::tr( "geometry simplification failed - skipping" ), QObject::tr( "3D" ) );
515  return;
516  }
517  const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet() );
518  if ( _minimum_distance_between_coordinates( *polygonSimplifiedData ) < 0.001 )
519  {
520  // Failed to fix that. It could be a really tiny geometry... or maybe they gave us
521  // geometry in unprojected lat/lon coordinates
522  QgsMessageLog::logMessage( QObject::tr( "geometry's coordinates are too close to each other and simplification failed - skipping" ), QObject::tr( "3D" ) );
523  return;
524  }
525  else
526  {
527  polygonNew.reset( polygonSimplifiedData->clone() );
528  }
529  }
530 
531  if ( !_check_intersecting_rings( *polygonNew ) )
532  {
533  // skip the polygon - it would cause a crash inside poly2tri library
534  QgsMessageLog::logMessage( QObject::tr( "polygon rings self-intersect or intersect each other - skipping" ), QObject::tr( "3D" ) );
535  return;
536  }
537 
538  QList< std::vector<p2t::Point *> > polylinesToDelete;
539  QHash<p2t::Point *, float> z;
540 
541  // polygon exterior
542  std::vector<p2t::Point *> polyline;
543  _ringToPoly2tri( qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() ), polyline, mNoZ ? nullptr : &z );
544  polylinesToDelete << polyline;
545 
546  std::unique_ptr<p2t::CDT> cdt( new p2t::CDT( polyline ) );
547 
548  // polygon holes
549  for ( int i = 0; i < polygonNew->numInteriorRings(); ++i )
550  {
551  std::vector<p2t::Point *> holePolyline;
552  const QgsLineString *hole = qgsgeometry_cast< const QgsLineString *>( polygonNew->interiorRing( i ) );
553 
554  _ringToPoly2tri( hole, holePolyline, mNoZ ? nullptr : &z );
555 
556  cdt->AddHole( holePolyline );
557  polylinesToDelete << holePolyline;
558  }
559 
560  // run triangulation and write vertices to the output data array
561  try
562  {
563  cdt->Triangulate();
564 
565  std::vector<p2t::Triangle *> triangles = cdt->GetTriangles();
566 
567  mData.reserve( mData.size() + triangles.size() * ( ( mAddNormals ? 6 : 3 ) * ( mAddBackFaces ? 2 : 1 ) ) );
568  for ( size_t i = 0; i < triangles.size(); ++i )
569  {
570  p2t::Triangle *t = triangles[i];
571  for ( int j = 0; j < 3; ++j )
572  {
573  p2t::Point *p = t->GetPoint( j );
574  QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
575  if ( toOldBase )
576  pt = *toOldBase * pt;
577  const double fx = ( pt.x() / scaleX ) - mOriginX + pt0.x();
578  const double fy = ( pt.y() / scaleY ) - mOriginY + pt0.y();
579  const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.z() );
580  if ( fz < zMin )
581  zMin = fz;
582  if ( fz > zMax )
583  zMax = fz;
584 
585  mData << fx << fz << -fy;
586  if ( mAddNormals )
587  mData << pNormal.x() << pNormal.z() << - pNormal.y();
588  }
589 
590  if ( mAddBackFaces )
591  {
592  // the same triangle with reversed order of coordinates and inverted normal
593  for ( int j = 2; j >= 0; --j )
594  {
595  p2t::Point *p = t->GetPoint( j );
596  QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
597  if ( toOldBase )
598  pt = *toOldBase * pt;
599  const double fx = ( pt.x() / scaleX ) - mOriginX + pt0.x();
600  const double fy = ( pt.y() / scaleY ) - mOriginY + pt0.y();
601  const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.z() );
602  mData << fx << fz << -fy;
603  if ( mAddNormals )
604  mData << -pNormal.x() << -pNormal.z() << pNormal.y();
605  }
606  }
607  }
608  }
609  catch ( ... )
610  {
611  QgsMessageLog::logMessage( QObject::tr( "Triangulation failed. Skipping polygon…" ), QObject::tr( "3D" ) );
612  }
613 
614  for ( int i = 0; i < polylinesToDelete.count(); ++i )
615  qDeleteAll( polylinesToDelete[i] );
616  }
617 
618  // add walls if extrusion is enabled
619  if ( extrusionHeight != 0 )
620  {
621  _makeWalls( *exterior, false, extrusionHeight, mData, mAddNormals, mOriginX, mOriginY );
622 
623  for ( int i = 0; i < polygon.numInteriorRings(); ++i )
624  _makeWalls( *qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ), true, extrusionHeight, mData, mAddNormals, mOriginX, mOriginY );
625 
626  zMax += extrusionHeight;
627  }
628 
629  if ( zMin < mZMin )
630  mZMin = zMin;
631  if ( zMax > mZMax )
632  mZMax = zMax;
633 }
634 
635 QgsPoint getPointFromData( QVector< float >::const_iterator &it )
636 {
637  // tessellator geometry is x, z, -y
638  double x = *it;
639  ++it;
640  double z = *it;
641  ++it;
642  double y = -( *it );
643  ++it;
644  return QgsPoint( x, y, z );
645 }
646 
648 {
649  return mData.size() / ( mAddNormals ? 6 : 3 );
650 }
651 
652 std::unique_ptr<QgsMultiPolygon> QgsTessellator::asMultiPolygon() const
653 {
654  std::unique_ptr< QgsMultiPolygon > mp = qgis::make_unique< QgsMultiPolygon >();
655  const QVector<float> data = mData;
656  mp->reserve( mData.size() );
657  for ( auto it = data.constBegin(); it != data.constEnd(); )
658  {
659  QgsPoint p1 = getPointFromData( it );
660  QgsPoint p2 = getPointFromData( it );
661  QgsPoint p3 = getPointFromData( it );
662  mp->addGeometry( new QgsTriangle( p1, p2, p3 ) );
663  }
664  return mp;
665 }
A rectangle specified with double values.
Definition: qgsrectangle.h:41
bool isEmpty() const override
Returns true if the geometry is empty.
double y
Definition: qgspoint.h:42
double zAt(int index) const
Returns the z-coordinate of the specified node in the line string.
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
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:122
int dataVerticesCount() const
Returns the number of vertices stored in the output data array.
virtual bool pointAt(int node, QgsPoint &point, QgsVertexId::VertexType &type) const =0
Returns the point and vertex id of a point within the curve.
std::unique_ptr< QgsMultiPolygon > asMultiPolygon() const
Returns the triangulation as a multipolygon geometry.
QgsPoint getPointFromData(QVector< float >::const_iterator &it)
Triangle geometry type.
Definition: qgstriangle.h:33
static bool hasZ(Type type)
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:917
int numPoints() const override
Returns the number of points in the curve.
const double * xData() const
Returns a const pointer to the x vertex data.
void addInteriorRing(QgsCurve *ring) override
Adds an interior ring to the geometry (takes ownership)
Definition: qgspolygon.cpp:148
int numInteriorRings() const
Returns the number of interior rings contained with the curve polygon.
double _round_coord(double x)
void addPolygon(const QgsPolygon &polygon, float extrusionHeight)
Tessellates a triangle and adds its vertex entries to the output data array.
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).
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
T qgsgeometry_cast(const QgsAbstractGeometry *geom)
std::size_t operator()(const std::pair< float, float > pair) const
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) ...
Abstract base class for curved geometry type.
Definition: qgscurve.h:35
QgsWkbTypes::Type wkbType() const
Returns the WKB type of the geometry.
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:37
double _minimum_distance_between_coordinates(const QgsPolygon &polygon)
const double * yData() const
Returns a const pointer to the y vertex data.
QgsPoint startPoint() const override
Returns the starting point of the curve.
const double * zData() const
Returns a const pointer to the z vertex data, or nullptr if the linestring does not have z values...
void setX(double x)
Sets the point&#39;s x-coordinate.
Definition: qgspoint.h:269
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry)
Creates and returns a new geometry engine.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
void setY(double y)
Sets the point&#39;s y-coordinate.
Definition: qgspoint.h:280
void setExteriorRing(QgsCurve *ring) override
Sets the exterior ring of the polygon.
Definition: qgspolygon.cpp:179
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:43
QgsPolygon * clone() const override
Clones the geometry by performing a deep copy.
Definition: qgspolygon.cpp:42
QgsPoint pointN(int i) const
Returns the specified point from inside the line string.
double xAt(int index) const override
Returns the x-coordinate of the specified node in the line string.
double z
Definition: qgspoint.h:43
Polygon geometry type.
Definition: qgspolygon.h:31
const QgsCurve * exteriorRing() const
Returns the curve polygon&#39;s exterior ring.
virtual int numPoints() const =0
Returns the number of points in the curve.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
double x
Definition: qgspoint.h:41