QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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 *
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;
50  data << vn.x() << vn.y() << vn.z();
51  data << x1 << z1 + height << -y1;
53  data << vn.x() << vn.y() << vn.z();
54  data << x0 << z0 << -y0;
56  data << vn.x() << vn.y() << vn.z();
57
58  // triangle 2
59  data << x0 << z0 << -y0;
61  data << vn.x() << vn.y() << vn.z();
62  data << x1 << z1 + height << -y1;
64  data << vn.x() << vn.y() << vn.z();
65  data << x1 << z1 << -y1;
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 )
75  , mInvertNormals( invertNormals )
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() )
86  , mInvertNormals( invertNormals )
88  , mNoZ( noZ )
89 {
90  init();
91 }
92
93 void QgsTessellator::init()
94 {
95  mStride = 3 * sizeof( float );
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
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  {
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  if ( pCount == 4 && polygon.numInteriorRings() == 0 )
435  {
436  // polygon is a triangle - write vertices to the output data array without triangulation
437  const double *xData = exterior->xData();
438  const double *yData = exterior->yData();
439  const double *zData = !mNoZ ? exterior->zData() : nullptr;
440  for ( int i = 0; i < 3; i++ )
441  {
442  mData << *xData++ - mOriginX << ( mNoZ ? 0 : *zData++ ) << - *yData++ + mOriginY;
444  mData << pNormal.x() << pNormal.z() << - pNormal.y();
445  }
446
448  {
449  // the same triangle with reversed order of coordinates and inverted normal
450  for ( int i = 2; i >= 0; i-- )
451  {
452  mData << exterior->xAt( i ) - mOriginX << ( mNoZ ? 0 : exterior->zAt( i ) ) << - exterior->yAt( i ) + mOriginY;
454  mData << -pNormal.x() << -pNormal.z() << pNormal.y();
455  }
456  }
457  }
458  else
459  {
460  if ( !mNoZ && !qgsDoubleNear( pNormal.length(), 1, 0.001 ) )
461  return; // this should not happen - pNormal should be normalized to unit length
462
463  std::unique_ptr<QMatrix4x4> toNewBase, toOldBase;
464  if ( !mNoZ && pNormal != QVector3D( 0, 0, 1 ) )
465  {
466  // this is not a horizontal plane - need to reproject the polygon to a new base so that
467  // we can do the triangulation in a plane
468
469  QVector3D pXVector, pYVector;
470  _normalVectorToXYVectors( pNormal, pXVector, pYVector );
471
472  // so now we have three orthogonal unit vectors defining new base
473  // let's build transform matrix. We actually need just a 3x3 matrix,
474  // but Qt does not have good support for it, so using 4x4 matrix instead.
475  toNewBase.reset( new QMatrix4x4(
476  pXVector.x(), pXVector.y(), pXVector.z(), 0,
477  pYVector.x(), pYVector.y(), pYVector.z(), 0,
478  pNormal.x(), pNormal.y(), pNormal.z(), 0,
479  0, 0, 0, 0 ) );
480
481  // our 3x3 matrix is orthogonal, so for inverse we only need to transpose it
482  toOldBase.reset( new QMatrix4x4( toNewBase->transposed() ) );
483  }
484
485  const QgsPoint ptStart( exterior->startPoint() );
486  const QgsPoint pt0( QgsWkbTypes::PointZ, ptStart.x(), ptStart.y(), std::isnan( ptStart.z() ) ? 0 : ptStart.z() );
487
488  const float scaleX = !mBounds.isNull() ? 10000.0 / mBounds.width() : 1.0;
489  const float scaleY = !mBounds.isNull() ? 10000.0 / mBounds.height() : 1.0;
490
491  // subtract ptFirst from geometry for better numerical stability in triangulation
492  // and apply new 3D vector base if the polygon is not horizontal
493
494  std::unique_ptr<QgsPolygon> polygonNew( _transform_polygon_to_new_base( polygon, pt0, toNewBase.get(), scaleX, scaleY ) );
495
496  if ( _minimum_distance_between_coordinates( *polygonNew ) < 0.001 )
497  {
498  // when the distances between coordinates of input points are very small,
499  // the triangulation likes to crash on numerical errors - when the distances are ~ 1e-5
500  // Assuming that the coordinates should be in a projected CRS, we should be able
501  // to simplify geometries that may cause problems and avoid possible crashes
502  QgsGeometry polygonSimplified = QgsGeometry( polygonNew->clone() ).simplify( 0.001 );
503  if ( polygonSimplified.isNull() )
504  {
505  QgsMessageLog::logMessage( QObject::tr( "geometry simplification failed - skipping" ), QObject::tr( "3D" ) );
506  return;
507  }
508  const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet() );
509  if ( _minimum_distance_between_coordinates( *polygonSimplifiedData ) < 0.001 )
510  {
511  // Failed to fix that. It could be a really tiny geometry... or maybe they gave us
512  // geometry in unprojected lat/lon coordinates
513  QgsMessageLog::logMessage( QObject::tr( "geometry's coordinates are too close to each other and simplification failed - skipping" ), QObject::tr( "3D" ) );
514  return;
515  }
516  else
517  {
518  polygonNew.reset( polygonSimplifiedData->clone() );
519  }
520  }
521
522  if ( !_check_intersecting_rings( *polygonNew ) )
523  {
524  // skip the polygon - it would cause a crash inside poly2tri library
525  QgsMessageLog::logMessage( QObject::tr( "polygon rings self-intersect or intersect each other - skipping" ), QObject::tr( "3D" ) );
526  return;
527  }
528
529  QList< std::vector<p2t::Point *> > polylinesToDelete;
530  QHash<p2t::Point *, float> z;
531
532  // polygon exterior
533  std::vector<p2t::Point *> polyline;
534  _ringToPoly2tri( qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() ), polyline, mNoZ ? nullptr : &z );
535  polylinesToDelete << polyline;
536
537  std::unique_ptr<p2t::CDT> cdt( new p2t::CDT( polyline ) );
538
539  // polygon holes
540  for ( int i = 0; i < polygonNew->numInteriorRings(); ++i )
541  {
542  std::vector<p2t::Point *> holePolyline;
543  const QgsLineString *hole = qgsgeometry_cast< const QgsLineString *>( polygonNew->interiorRing( i ) );
544
545  _ringToPoly2tri( hole, holePolyline, mNoZ ? nullptr : &z );
546
548  polylinesToDelete << holePolyline;
549  }
550
551  // run triangulation and write vertices to the output data array
552  try
553  {
554  cdt->Triangulate();
555
556  std::vector<p2t::Triangle *> triangles = cdt->GetTriangles();
557
558  mData.reserve( mData.size() + triangles.size() * ( ( mAddNormals ? 6 : 3 ) * ( mAddBackFaces ? 2 : 1 ) ) );
559  for ( size_t i = 0; i < triangles.size(); ++i )
560  {
561  p2t::Triangle *t = triangles[i];
562  for ( int j = 0; j < 3; ++j )
563  {
564  p2t::Point *p = t->GetPoint( j );
565  QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
566  if ( toOldBase )
567  pt = *toOldBase * pt;
568  const double fx = ( pt.x() / scaleX ) - mOriginX + pt0.x();
569  const double fy = ( pt.y() / scaleY ) - mOriginY + pt0.y();
570  const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.z() );
571  mData << fx << fz << -fy;
573  mData << pNormal.x() << pNormal.z() << - pNormal.y();
574  }
575
577  {
578  // the same triangle with reversed order of coordinates and inverted normal
579  for ( int j = 2; j >= 0; --j )
580  {
581  p2t::Point *p = t->GetPoint( j );
582  QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
583  if ( toOldBase )
584  pt = *toOldBase * pt;
585  const double fx = ( pt.x() / scaleX ) - mOriginX + pt0.x();
586  const double fy = ( pt.y() / scaleY ) - mOriginY + pt0.y();
587  const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.z() );
588  mData << fx << fz << -fy;
590  mData << -pNormal.x() << -pNormal.z() << pNormal.y();
591  }
592  }
593  }
594  }
595  catch ( ... )
596  {
597  QgsMessageLog::logMessage( QObject::tr( "Triangulation failed. Skipping polygon…" ), QObject::tr( "3D" ) );
598  }
599
600  for ( int i = 0; i < polylinesToDelete.count(); ++i )
601  qDeleteAll( polylinesToDelete[i] );
602  }
603
604  // add walls if extrusion is enabled
605  if ( extrusionHeight != 0 )
606  {
607  _makeWalls( *exterior, false, extrusionHeight, mData, mAddNormals, mOriginX, mOriginY );
608
609  for ( int i = 0; i < polygon.numInteriorRings(); ++i )
610  _makeWalls( *qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ), true, extrusionHeight, mData, mAddNormals, mOriginX, mOriginY );
611  }
612 }
613
614 QgsPoint getPointFromData( QVector< float >::const_iterator &it )
615 {
616  // tessellator geometry is x, z, -y
617  double x = *it;
618  ++it;
619  double z = *it;
620  ++it;
621  double y = -( *it );
622  ++it;
623  return QgsPoint( x, y, z );
624 }
625
627 {
628  return mData.size() / ( mAddNormals ? 6 : 3 );
629 }
630
631 std::unique_ptr<QgsMultiPolygon> QgsTessellator::asMultiPolygon() const
632 {
633  std::unique_ptr< QgsMultiPolygon > mp = qgis::make_unique< QgsMultiPolygon >();
634  const QVector<float> data = mData;
635  mp->reserve( mData.size() );
636  for ( auto it = data.constBegin(); it != data.constEnd(); )
637  {
638  QgsPoint p1 = getPointFromData( it );
639  QgsPoint p2 = getPointFromData( it );
640  QgsPoint p3 = getPointFromData( it );
641  mp->addGeometry( new QgsTriangle( p1, p2, p3 ) );
642  }
643  return mp;
644 }
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:280
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.
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)
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
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:262
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:273
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