QGIS API Documentation  3.16.0-Hannover (43b64b13f3)
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 <QtMath>
34 #include <algorithm>
35 #include <unordered_set>
36 
37 static std::pair<float, float> rotateCoords( float x, float y, float origin_x, float origin_y, float r )
38 {
39  r = qDegreesToRadians( r );
40  float x0 = x - origin_x, y0 = y - origin_y;
41  // p0 = x0 + i * y0
42  // rot = cos(r) + i * sin(r)
43  // p0 * rot = x0 * cos(r) - y0 * sin(r) + i * [ x0 * sin(r) + y0 * cos(r) ]
44  float x1 = origin_x + x0 * qCos( r ) - y0 * qSin( r );
45  float y1 = origin_y + x0 * qSin( r ) + y0 * qCos( r );
46  return std::make_pair( x1, y1 );
47 }
48 
49 static void make_quad( float x0, float y0, float z0, float x1, float y1, float z1, float height, QVector<float> &data, bool addNormals, bool addTextureCoords, float textureRotation )
50 {
51  float dx = x1 - x0;
52  float dy = -( y1 - y0 );
53 
54  // perpendicular vector in plane to [x,y] is [-y,x]
55  QVector3D vn( -dy, 0, dx );
56  vn = -vn;
57  vn.normalize();
58 
59  float u0, v0;
60  float u1, v1;
61  float u2, v2;
62  float u3, v3;
63 
64  QVector<double> textureCoordinates;
65  textureCoordinates.reserve( 12 );
66  // select which side of the coordinates to use (x, z or y, z) depending on which side is smaller
67  if ( fabsf( dy ) <= fabsf( dx ) )
68  {
69  // consider x and z as the texture coordinates
70  u0 = x0;
71  v0 = z0 + height;
72 
73  u1 = x1;
74  v1 = z1 + height;
75 
76  u2 = x0;
77  v2 = z0;
78 
79  u3 = x1;
80  v3 = z1;
81  }
82  else
83  {
84  // consider y and z as the texture coowallsTextureRotationrdinates
85  u0 = -y0;
86  v0 = z0 + height;
87 
88  u1 = -y1;
89  v1 = z1 + height;
90 
91  u2 = -y0;
92  v2 = z0;
93 
94  u3 = -y1;
95  v3 = z1;
96  }
97 
98  textureCoordinates.push_back( u0 );
99  textureCoordinates.push_back( v0 );
100 
101  textureCoordinates.push_back( u1 );
102  textureCoordinates.push_back( v1 );
103 
104  textureCoordinates.push_back( u2 );
105  textureCoordinates.push_back( v2 );
106 
107  textureCoordinates.push_back( u2 );
108  textureCoordinates.push_back( v2 );
109 
110  textureCoordinates.push_back( u1 );
111  textureCoordinates.push_back( v1 );
112 
113  textureCoordinates.push_back( u3 );
114  textureCoordinates.push_back( v3 );
115 
116  for ( int i = 0; i < textureCoordinates.size(); i += 2 )
117  {
118  std::pair<float, float> rotated = rotateCoords( textureCoordinates[i], textureCoordinates[i + 1], 0, 0, textureRotation );
119  textureCoordinates[i] = rotated.first;
120  textureCoordinates[i + 1] = rotated.second;
121  }
122 
123  // triangle 1
124  // vertice 1
125  data << x0 << z0 + height << -y0;
126  if ( addNormals )
127  data << vn.x() << vn.y() << vn.z();
128  if ( addTextureCoords )
129  data << textureCoordinates[0] << textureCoordinates[1];
130  // vertice 2
131  data << x1 << z1 + height << -y1;
132  if ( addNormals )
133  data << vn.x() << vn.y() << vn.z();
134  if ( addTextureCoords )
135  data << textureCoordinates[2] << textureCoordinates[3];
136  // verice 3
137  data << x0 << z0 << -y0;
138  if ( addNormals )
139  data << vn.x() << vn.y() << vn.z();
140  if ( addTextureCoords )
141  data << textureCoordinates[4] << textureCoordinates[5];
142 
143  // triangle 2
144  // vertice 1
145  data << x0 << z0 << -y0;
146  if ( addNormals )
147  data << vn.x() << vn.y() << vn.z();
148  if ( addTextureCoords )
149  data << textureCoordinates[6] << textureCoordinates[7];
150  // vertice 2
151  data << x1 << z1 + height << -y1;
152  if ( addNormals )
153  data << vn.x() << vn.y() << vn.z();
154  if ( addTextureCoords )
155  data << textureCoordinates[8] << textureCoordinates[9];
156  // vertice 3
157  data << x1 << z1 << -y1;
158  if ( addNormals )
159  data << vn.x() << vn.y() << vn.z();
160  if ( addTextureCoords )
161  data << textureCoordinates[10] << textureCoordinates[11];
162 }
163 
164 
165 QgsTessellator::QgsTessellator( double originX, double originY, bool addNormals, bool invertNormals, bool addBackFaces, bool noZ,
166  bool addTextureCoords, int facade, float textureRotation )
167  : mOriginX( originX )
168  , mOriginY( originY )
169  , mAddNormals( addNormals )
170  , mInvertNormals( invertNormals )
171  , mAddBackFaces( addBackFaces )
172  , mAddTextureCoords( addTextureCoords )
173  , mNoZ( noZ )
174  , mTessellatedFacade( facade )
175  , mTextureRotation( textureRotation )
176 {
177  init();
178 }
179 
180 QgsTessellator::QgsTessellator( const QgsRectangle &bounds, bool addNormals, bool invertNormals, bool addBackFaces, bool noZ,
181  bool addTextureCoords, int facade, float textureRotation )
182  : mBounds( bounds )
183  , mOriginX( mBounds.xMinimum() )
184  , mOriginY( mBounds.yMinimum() )
185  , mAddNormals( addNormals )
186  , mInvertNormals( invertNormals )
187  , mAddBackFaces( addBackFaces )
188  , mAddTextureCoords( addTextureCoords )
189  , mNoZ( noZ )
190  , mTessellatedFacade( facade )
191  , mTextureRotation( textureRotation )
192 {
193  init();
194 }
195 
196 void QgsTessellator::init()
197 {
198  mStride = 3 * sizeof( float );
199  if ( mAddNormals )
200  mStride += 3 * sizeof( float );
201  if ( mAddTextureCoords )
202  mStride += 2 * sizeof( float );
203 }
204 
205 static bool _isRingCounterClockWise( const QgsCurve &ring )
206 {
207  double a = 0;
208  int count = ring.numPoints();
210  QgsPoint pt, ptPrev;
211  ring.pointAt( 0, ptPrev, vt );
212  for ( int i = 1; i < count + 1; ++i )
213  {
214  ring.pointAt( i % count, pt, vt );
215  a += ptPrev.x() * pt.y() - ptPrev.y() * pt.x();
216  ptPrev = pt;
217  }
218  return a > 0; // clockwise if a is negative
219 }
220 
221 static void _makeWalls( const QgsLineString &ring, bool ccw, float extrusionHeight, QVector<float> &data,
222  bool addNormals, bool addTextureCoords, double originX, double originY, float textureRotation )
223 {
224  // we need to find out orientation of the ring so that the triangles we generate
225  // face the right direction
226  // (for exterior we want clockwise order, for holes we want counter-clockwise order)
227  bool is_counter_clockwise = _isRingCounterClockWise( ring );
228 
229  QgsPoint pt;
230  QgsPoint ptPrev = ring.pointN( is_counter_clockwise == ccw ? 0 : ring.numPoints() - 1 );
231  for ( int i = 1; i < ring.numPoints(); ++i )
232  {
233  pt = ring.pointN( is_counter_clockwise == ccw ? i : ring.numPoints() - i - 1 );
234  float x0 = ptPrev.x() - originX, y0 = ptPrev.y() - originY;
235  float x1 = pt.x() - originX, y1 = pt.y() - originY;
236  float z0 = std::isnan( ptPrev.z() ) ? 0 : ptPrev.z();
237  float z1 = std::isnan( pt.z() ) ? 0 : pt.z();
238 
239  // make a quad
240  make_quad( x0, y0, z0, x1, y1, z1, extrusionHeight, data, addNormals, addTextureCoords, textureRotation );
241  ptPrev = pt;
242  }
243 }
244 
245 static QVector3D _calculateNormal( const QgsLineString *curve, double originX, double originY, bool invertNormal )
246 {
247  // if it is just plain 2D curve there is no need to calculate anything
248  // because it will be a flat horizontally oriented patch
249  if ( !QgsWkbTypes::hasZ( curve->wkbType() ) || curve->isEmpty() )
250  return QVector3D( 0, 0, 1 );
251 
252  // often we have 3D coordinates, but Z is the same for all vertices
253  // so in order to save calculation and avoid possible issues with order of vertices
254  // (the calculation below may decide that a polygon faces downwards)
255  bool sameZ = true;
256  QgsPoint pt1 = curve->pointN( 0 );
257  QgsPoint pt2;
258  for ( int i = 1; i < curve->numPoints(); i++ )
259  {
260  pt2 = curve->pointN( i );
261  if ( pt1.z() != pt2.z() )
262  {
263  sameZ = false;
264  break;
265  }
266  }
267  if ( sameZ )
268  return QVector3D( 0, 0, 1 );
269 
270  // Calculate the polygon's normal vector, based on Newell's method
271  // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
272  //
273  // Order of vertices is important here as it determines the front/back face of the polygon
274 
275  double nx = 0, ny = 0, nz = 0;
276  pt1 = curve->pointN( 0 );
277 
278  // shift points by the tessellator's origin - this does not affect normal calculation and it may save us from losing some precision
279  pt1.setX( pt1.x() - originX );
280  pt1.setY( pt1.y() - originY );
281  for ( int i = 1; i < curve->numPoints(); i++ )
282  {
283  pt2 = curve->pointN( i );
284  pt2.setX( pt2.x() - originX );
285  pt2.setY( pt2.y() - originY );
286 
287  if ( std::isnan( pt1.z() ) || std::isnan( pt2.z() ) )
288  continue;
289 
290  nx += ( pt1.y() - pt2.y() ) * ( pt1.z() + pt2.z() );
291  ny += ( pt1.z() - pt2.z() ) * ( pt1.x() + pt2.x() );
292  nz += ( pt1.x() - pt2.x() ) * ( pt1.y() + pt2.y() );
293 
294  pt1 = pt2;
295  }
296 
297  QVector3D normal( nx, ny, nz );
298  if ( invertNormal )
299  normal = -normal;
300  normal.normalize();
301  return normal;
302 }
303 
304 
305 static void _normalVectorToXYVectors( const QVector3D &pNormal, QVector3D &pXVector, QVector3D &pYVector )
306 {
307  // Here we define the two perpendicular vectors that define the local
308  // 2D space on the plane. They will act as axis for which we will
309  // calculate the projection coordinates of a 3D point to the plane.
310  if ( pNormal.z() > 0.001 || pNormal.z() < -0.001 )
311  {
312  pXVector = QVector3D( 1, 0, -pNormal.x() / pNormal.z() );
313  }
314  else if ( pNormal.y() > 0.001 || pNormal.y() < -0.001 )
315  {
316  pXVector = QVector3D( 1, -pNormal.x() / pNormal.y(), 0 );
317  }
318  else
319  {
320  pXVector = QVector3D( -pNormal.y() / pNormal.x(), 1, 0 );
321  }
322  pXVector.normalize();
323  pYVector = QVector3D::normal( pNormal, pXVector );
324 }
325 
327 {
328  std::size_t operator()( const std::pair<float, float> pair ) const
329  {
330  std::size_t h1 = std::hash<float>()( pair.first );
331  std::size_t h2 = std::hash<float>()( pair.second );
332 
333  return h1 ^ h2;
334  }
335 };
336 
337 static void _ringToPoly2tri( const QgsLineString *ring, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> *zHash )
338 {
339  const int pCount = ring->numPoints();
340 
341  polyline.reserve( pCount );
342 
343  const double *srcXData = ring->xData();
344  const double *srcYData = ring->yData();
345  const double *srcZData = ring->zData();
346  std::unordered_set<std::pair<float, float>, float_pair_hash> foundPoints;
347 
348  for ( int i = 0; i < pCount - 1; ++i )
349  {
350  const float x = *srcXData++;
351  const float y = *srcYData++;
352 
353  auto res = foundPoints.insert( std::make_pair( x, y ) );
354  if ( !res.second )
355  {
356  // already used this point, don't add a second time
357  continue;
358  }
359 
360  p2t::Point *pt2 = new p2t::Point( x, y );
361  polyline.push_back( pt2 );
362  if ( zHash )
363  {
364  ( *zHash )[pt2] = *srcZData++;
365  }
366  }
367 }
368 
369 
370 inline double _round_coord( double x )
371 {
372  const double exp = 1e10; // round to 10 decimal digits
373  return round( x * exp ) / exp;
374 }
375 
376 
377 static QgsCurve *_transform_ring_to_new_base( const QgsLineString &curve, const QgsPoint &pt0, const QMatrix4x4 *toNewBase, const float scale )
378 {
379  int count = curve.numPoints();
380  QVector<double> x;
381  QVector<double> y;
382  QVector<double> z;
383  x.resize( count );
384  y.resize( count );
385  z.resize( count );
386  double *xData = x.data();
387  double *yData = y.data();
388  double *zData = z.data();
389 
390  const double *srcXData = curve.xData();
391  const double *srcYData = curve.yData();
392  const double *srcZData = curve.is3D() ? curve.zData() : nullptr;
393 
394  for ( int i = 0; i < count; ++i )
395  {
396  QVector4D v( *srcXData++ - pt0.x(),
397  *srcYData++ - pt0.y(),
398  srcZData ? *srcZData++ - pt0.z() : 0,
399  0 );
400  if ( toNewBase )
401  v = toNewBase->map( v );
402 
403  // scale coordinates
404  v.setX( v.x() * scale );
405  v.setY( v.y() * scale );
406 
407  // we also round coordinates before passing them to poly2tri triangulation in order to fix possible numerical
408  // 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).
409  // See TestQgsTessellator::testIssue17745().
410  //
411  // A hint for a similar issue: https://github.com/greenm01/poly2tri/issues/99
412  //
413  // The collinear tests uses epsilon 1e-12. Seems rounding to 12 places you still
414  // can get problems with this test when points are pretty much on a straight line.
415  // I suggest you round to 10 decimals for stability and you can live with that
416  // precision.
417  *xData++ = _round_coord( v.x() );
418  *yData++ = _round_coord( v.y() );
419  *zData++ = _round_coord( v.z() );
420  }
421  return new QgsLineString( x, y, z );
422 }
423 
424 
425 static QgsPolygon *_transform_polygon_to_new_base( const QgsPolygon &polygon, const QgsPoint &pt0, const QMatrix4x4 *toNewBase, const float scale )
426 {
427  QgsPolygon *p = new QgsPolygon;
428  p->setExteriorRing( _transform_ring_to_new_base( *qgsgeometry_cast< const QgsLineString * >( polygon.exteriorRing() ), pt0, toNewBase, scale ) );
429  for ( int i = 0; i < polygon.numInteriorRings(); ++i )
430  p->addInteriorRing( _transform_ring_to_new_base( *qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ), pt0, toNewBase, scale ) );
431  return p;
432 }
433 
434 static bool _check_intersecting_rings( const QgsPolygon &polygon )
435 {
436  std::vector< std::unique_ptr< QgsGeometryEngine > > ringEngines;
437  ringEngines.reserve( 1 + polygon.numInteriorRings() );
438  ringEngines.emplace_back( QgsGeometry::createGeometryEngine( polygon.exteriorRing() ) );
439  for ( int i = 0; i < polygon.numInteriorRings(); ++i )
440  ringEngines.emplace_back( QgsGeometry::createGeometryEngine( polygon.interiorRing( i ) ) );
441 
442  // we need to make sure that the polygon has no rings with self-intersection: that may
443  // crash the tessellator. The original geometry maybe have been valid and the self-intersection
444  // was introduced when transforming to a new base (in a rare case when all points are not in the same plane)
445 
446  for ( const std::unique_ptr< QgsGeometryEngine > &ring : ringEngines )
447  {
448  if ( !ring->isSimple() )
449  return false;
450  }
451 
452  // At this point we assume that input polygons are valid according to the OGC definition.
453  // This means e.g. no duplicate points, polygons are simple (no butterfly shaped polygon with self-intersection),
454  // internal rings are inside exterior rings, rings do not cross each other, no dangles.
455 
456  // There is however an issue with polygons where rings touch:
457  // +---+
458  // | |
459  // | +-+-+
460  // | | | |
461  // | +-+ |
462  // | |
463  // +-----+
464  // This is a valid polygon with one exterior and one interior ring that touch at one point,
465  // but poly2tri library does not allow interior rings touch each other or exterior ring.
466  // TODO: Handle the situation better - rather than just detecting the problem, try to fix
467  // it by converting touching rings into one ring.
468 
469  if ( ringEngines.size() > 1 )
470  {
471  for ( size_t i = 0; i < ringEngines.size(); ++i )
472  {
473  std::unique_ptr< QgsGeometryEngine > &first = ringEngines.at( i );
474  if ( polygon.numInteriorRings() > 1 )
475  first->prepareGeometry();
476 
477  // TODO this is inefficient - QgsGeometryEngine::intersects only works with QgsAbstractGeometry
478  // objects and accordingly we have to use those, instead of the previously build geos
479  // representations available in ringEngines
480  // This needs addressing by extending the QgsGeometryEngine relation tests to allow testing against
481  // another QgsGeometryEngine object.
482  for ( int interiorRing = static_cast< int >( i ); interiorRing < polygon.numInteriorRings(); ++interiorRing )
483  {
484  if ( first->intersects( polygon.interiorRing( interiorRing ) ) )
485  return false;
486  }
487  }
488  }
489  return true;
490 }
491 
492 
494 {
495  double min_d = 1e20;
496 
497  std::vector< const QgsLineString * > rings;
498  rings.reserve( 1 + polygon.numInteriorRings() );
499  rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.exteriorRing() ) );
500  for ( int i = 0; i < polygon.numInteriorRings(); ++i )
501  rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ) );
502 
503  for ( const QgsLineString *ring : rings )
504  {
505  const int numPoints = ring->numPoints();
506  if ( numPoints <= 1 )
507  continue;
508 
509  const double *srcXData = ring->xData();
510  const double *srcYData = ring->yData();
511  double x0 = *srcXData++;
512  double y0 = *srcYData++;
513  for ( int i = 1; i < numPoints; ++i )
514  {
515  double x1 = *srcXData++;
516  double y1 = *srcYData++;
517  double d = ( x0 - x1 ) * ( x0 - x1 ) + ( y0 - y1 ) * ( y0 - y1 );
518  if ( d < min_d )
519  min_d = d;
520  x0 = x1;
521  y0 = y1;
522  }
523  }
524 
525  return min_d != 1e20 ? std::sqrt( min_d ) : 1e20;
526 }
527 
528 void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeight )
529 {
530  const QgsLineString *exterior = qgsgeometry_cast< const QgsLineString * >( polygon.exteriorRing() );
531  if ( !exterior )
532  return;
533 
534  const QVector3D pNormal = !mNoZ ? _calculateNormal( exterior, mOriginX, mOriginY, mInvertNormals ) : QVector3D();
535  const int pCount = exterior->numPoints();
536  if ( pCount == 0 )
537  return;
538 
539  float zMin = std::numeric_limits<float>::max();
540  float zMax = std::numeric_limits<float>::min();
541 
542  const float scale = mBounds.isNull() ? 1.0 : std::max( 10000.0 / mBounds.width(), 10000.0 / mBounds.height() );
543 
544  std::unique_ptr<QMatrix4x4> toNewBase, toOldBase;
545  QgsPoint ptStart, pt0;
546  std::unique_ptr<QgsPolygon> polygonNew;
547  auto rotatePolygonToXYPlane = [&]()
548  {
549  if ( !mNoZ && pNormal != QVector3D( 0, 0, 1 ) )
550  {
551  // this is not a horizontal plane - need to reproject the polygon to a new base so that
552  // we can do the triangulation in a plane
553  QVector3D pXVector, pYVector;
554  _normalVectorToXYVectors( pNormal, pXVector, pYVector );
555 
556  // so now we have three orthogonal unit vectors defining new base
557  // let's build transform matrix. We actually need just a 3x3 matrix,
558  // but Qt does not have good support for it, so using 4x4 matrix instead.
559  toNewBase.reset( new QMatrix4x4(
560  pXVector.x(), pXVector.y(), pXVector.z(), 0,
561  pYVector.x(), pYVector.y(), pYVector.z(), 0,
562  pNormal.x(), pNormal.y(), pNormal.z(), 0,
563  0, 0, 0, 0 ) );
564 
565  // our 3x3 matrix is orthogonal, so for inverse we only need to transpose it
566  toOldBase.reset( new QMatrix4x4( toNewBase->transposed() ) );
567  }
568 
569  ptStart = QgsPoint( exterior->startPoint() );
570  pt0 = QgsPoint( QgsWkbTypes::PointZ, ptStart.x(), ptStart.y(), std::isnan( ptStart.z() ) ? 0 : ptStart.z() );
571 
572  // subtract ptFirst from geometry for better numerical stability in triangulation
573  // and apply new 3D vector base if the polygon is not horizontal
574 
575  polygonNew.reset( _transform_polygon_to_new_base( polygon, pt0, toNewBase.get(), scale ) );
576  };
577 
578  if ( !mNoZ && !qgsDoubleNear( pNormal.length(), 1, 0.001 ) )
579  return; // this should not happen - pNormal should be normalized to unit length
580 
581  QVector3D upVector( 0, 0, 1 );
582  float pNormalUpVectorDotProduct = QVector3D::dotProduct( upVector, pNormal );
583  float radsBetwwenUpNormal = qAcos( pNormalUpVectorDotProduct );
584 
585  float detectionDelta = qDegreesToRadians( 10.0f );
586  int facade = 0;
587  if ( radsBetwwenUpNormal > M_PI_2 - detectionDelta && radsBetwwenUpNormal < M_PI_2 + detectionDelta ) facade = 1;
588  else if ( radsBetwwenUpNormal > - M_PI_2 - detectionDelta && radsBetwwenUpNormal < -M_PI_2 + detectionDelta ) facade = 1;
589  else facade = 2;
590 
591  if ( pCount == 4 && polygon.numInteriorRings() == 0 && ( mTessellatedFacade & facade ) )
592  {
593  QgsLineString *triangle = nullptr;
594  if ( mAddTextureCoords )
595  {
596  rotatePolygonToXYPlane();
597  triangle = qgsgeometry_cast< QgsLineString * >( polygonNew->exteriorRing() );
598  Q_ASSERT( polygonNew->exteriorRing()->numPoints() >= 3 );
599  }
600 
601  // polygon is a triangle - write vertices to the output data array without triangulation
602  const double *xData = exterior->xData();
603  const double *yData = exterior->yData();
604  const double *zData = !mNoZ ? exterior->zData() : nullptr;
605  for ( int i = 0; i < 3; i++ )
606  {
607  float z = mNoZ ? 0 : *zData;
608  if ( z < zMin )
609  zMin = z;
610  if ( z > zMax )
611  zMax = z;
612 
613  mData << *xData - mOriginX << z << - *yData + mOriginY;
614  if ( mAddNormals )
615  mData << pNormal.x() << pNormal.z() << - pNormal.y();
616  if ( mAddTextureCoords )
617  {
618  std::pair<float, float> p( triangle->xAt( i ), triangle->yAt( i ) );
619  if ( facade & 1 )
620  {
621  p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
622  }
623  else if ( facade & 2 )
624  {
625  p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
626  }
627  mData << p.first << p.second;
628  }
629  xData++; yData++; zData++;
630  }
631 
632  if ( mAddBackFaces )
633  {
634  // the same triangle with reversed order of coordinates and inverted normal
635  for ( int i = 2; i >= 0; i-- )
636  {
637  mData << exterior->xAt( i ) - mOriginX << ( mNoZ ? 0 : exterior->zAt( i ) ) << - exterior->yAt( i ) + mOriginY;
638  if ( mAddNormals )
639  mData << -pNormal.x() << -pNormal.z() << pNormal.y();
640  if ( mAddTextureCoords )
641  {
642  std::pair<float, float> p( triangle->xAt( i ), triangle->yAt( i ) );
643  if ( facade & 1 )
644  {
645  p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
646  }
647  else if ( facade & 2 )
648  {
649  p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
650  }
651  mData << p.first << p.second;
652  }
653  }
654  }
655  }
656  else if ( mTessellatedFacade & facade )
657  {
658 
659  rotatePolygonToXYPlane();
660 
661  if ( _minimum_distance_between_coordinates( *polygonNew ) < 0.001 )
662  {
663  // when the distances between coordinates of input points are very small,
664  // the triangulation likes to crash on numerical errors - when the distances are ~ 1e-5
665  // Assuming that the coordinates should be in a projected CRS, we should be able
666  // to simplify geometries that may cause problems and avoid possible crashes
667  QgsGeometry polygonSimplified = QgsGeometry( polygonNew->clone() ).simplify( 0.001 );
668  if ( polygonSimplified.isNull() )
669  {
670  QgsMessageLog::logMessage( QObject::tr( "geometry simplification failed - skipping" ), QObject::tr( "3D" ) );
671  return;
672  }
673  const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet() );
674  if ( _minimum_distance_between_coordinates( *polygonSimplifiedData ) < 0.001 )
675  {
676  // Failed to fix that. It could be a really tiny geometry... or maybe they gave us
677  // geometry in unprojected lat/lon coordinates
678  QgsMessageLog::logMessage( QObject::tr( "geometry's coordinates are too close to each other and simplification failed - skipping" ), QObject::tr( "3D" ) );
679  return;
680  }
681  else
682  {
683  polygonNew.reset( polygonSimplifiedData->clone() );
684  }
685  }
686 
687  if ( !_check_intersecting_rings( *polygonNew ) )
688  {
689  // skip the polygon - it would cause a crash inside poly2tri library
690  QgsMessageLog::logMessage( QObject::tr( "polygon rings self-intersect or intersect each other - skipping" ), QObject::tr( "3D" ) );
691  return;
692  }
693 
694  QList< std::vector<p2t::Point *> > polylinesToDelete;
695  QHash<p2t::Point *, float> z;
696 
697  // polygon exterior
698  std::vector<p2t::Point *> polyline;
699  _ringToPoly2tri( qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() ), polyline, mNoZ ? nullptr : &z );
700  polylinesToDelete << polyline;
701 
702  std::unique_ptr<p2t::CDT> cdt( new p2t::CDT( polyline ) );
703 
704  // polygon holes
705  for ( int i = 0; i < polygonNew->numInteriorRings(); ++i )
706  {
707  std::vector<p2t::Point *> holePolyline;
708  const QgsLineString *hole = qgsgeometry_cast< const QgsLineString *>( polygonNew->interiorRing( i ) );
709 
710  _ringToPoly2tri( hole, holePolyline, mNoZ ? nullptr : &z );
711 
712  cdt->AddHole( holePolyline );
713  polylinesToDelete << holePolyline;
714  }
715 
716  // run triangulation and write vertices to the output data array
717  try
718  {
719  cdt->Triangulate();
720 
721  std::vector<p2t::Triangle *> triangles = cdt->GetTriangles();
722 
723  mData.reserve( mData.size() + 3 * triangles.size() * ( stride() / sizeof( float ) ) );
724  for ( size_t i = 0; i < triangles.size(); ++i )
725  {
726  p2t::Triangle *t = triangles[i];
727  for ( int j = 0; j < 3; ++j )
728  {
729  p2t::Point *p = t->GetPoint( j );
730  QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
731  if ( toOldBase )
732  pt = *toOldBase * pt;
733  const double fx = ( pt.x() / scale ) - mOriginX + pt0.x();
734  const double fy = ( pt.y() / scale ) - mOriginY + pt0.y();
735  const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.z() );
736  if ( fz < zMin )
737  zMin = fz;
738  if ( fz > zMax )
739  zMax = fz;
740 
741  mData << fx << fz << -fy;
742  if ( mAddNormals )
743  mData << pNormal.x() << pNormal.z() << - pNormal.y();
744  if ( mAddTextureCoords )
745  {
746  std::pair<float, float> pr = rotateCoords( p->x, p->y, 0.0f, 0.0f, mTextureRotation );
747  mData << pr.first << pr.second;
748  }
749  }
750 
751  if ( mAddBackFaces )
752  {
753  // the same triangle with reversed order of coordinates and inverted normal
754  for ( int j = 2; j >= 0; --j )
755  {
756  p2t::Point *p = t->GetPoint( j );
757  QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
758  if ( toOldBase )
759  pt = *toOldBase * pt;
760  const double fx = ( pt.x() / scale ) - mOriginX + pt0.x();
761  const double fy = ( pt.y() / scale ) - mOriginY + pt0.y();
762  const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.z() );
763  mData << fx << fz << -fy;
764  if ( mAddNormals )
765  mData << -pNormal.x() << -pNormal.z() << pNormal.y();
766  if ( mAddTextureCoords )
767  {
768  std::pair<float, float> pr = rotateCoords( p->x, p->y, 0.0f, 0.0f, mTextureRotation );
769  mData << pr.first << pr.second;
770  }
771  }
772  }
773  }
774  }
775  catch ( ... )
776  {
777  QgsMessageLog::logMessage( QObject::tr( "Triangulation failed. Skipping polygon…" ), QObject::tr( "3D" ) );
778  }
779 
780  for ( int i = 0; i < polylinesToDelete.count(); ++i )
781  qDeleteAll( polylinesToDelete[i] );
782  }
783 
784  // add walls if extrusion is enabled
785  if ( extrusionHeight != 0 && ( mTessellatedFacade & 1 ) )
786  {
787  _makeWalls( *exterior, false, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation );
788 
789  for ( int i = 0; i < polygon.numInteriorRings(); ++i )
790  _makeWalls( *qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ), true, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation );
791 
792  zMax += extrusionHeight;
793  }
794 
795  if ( zMin < mZMin )
796  mZMin = zMin;
797  if ( zMax > mZMax )
798  mZMax = zMax;
799 }
800 
801 QgsPoint getPointFromData( QVector< float >::const_iterator &it )
802 {
803  // tessellator geometry is x, z, -y
804  double x = *it;
805  ++it;
806  double z = *it;
807  ++it;
808  double y = -( *it );
809  ++it;
810  return QgsPoint( x, y, z );
811 }
812 
814 {
815  return mData.size() / ( stride() / sizeof( float ) );
816 }
817 
818 std::unique_ptr<QgsMultiPolygon> QgsTessellator::asMultiPolygon() const
819 {
820  std::unique_ptr< QgsMultiPolygon > mp = qgis::make_unique< QgsMultiPolygon >();
821  const QVector<float> data = mData;
822  mp->reserve( mData.size() );
823  for ( auto it = data.constBegin(); it != data.constEnd(); )
824  {
825  QgsPoint p1 = getPointFromData( it );
826  QgsPoint p2 = getPointFromData( it );
827  QgsPoint p3 = getPointFromData( it );
828  mp->addGeometry( new QgsTriangle( p1, p2, p3 ) );
829  }
830  return mp;
831 }
_round_coord
double _round_coord(double x)
Definition: qgstessellator.cpp:370
QgsCurve
Abstract base class for curved geometry type.
Definition: qgscurve.h:36
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.
QgsLineString::pointN
QgsPoint pointN(int i) const
Returns the specified point from inside the line string.
Definition: qgslinestring.cpp:713
QgsRectangle::height
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:209
QgsLineString::xAt
double xAt(int index) const override
Returns the x-coordinate of the specified node in the line string.
Definition: qgslinestring.cpp:762
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:245
QgsPolygon
Polygon geometry type.
Definition: qgspolygon.h:34
QgsPoint
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:38
getPointFromData
QgsPoint getPointFromData(QVector< float >::const_iterator &it)
Definition: qgstessellator.cpp:801
float_pair_hash::operator()
std::size_t operator()(const std::pair< float, float > pair) const
Definition: qgstessellator.cpp:328
QgsPolygon::clone
QgsPolygon * clone() const override
Clones the geometry by performing a deep copy.
Definition: qgspolygon.cpp:54
QgsCurvePolygon::exteriorRing
const QgsCurve * exteriorRing() const SIP_HOLDGIL
Returns the curve polygon's exterior ring.
Definition: qgscurvepolygon.h:87
QgsGeometry::isNull
Q_GADGET bool isNull
Definition: qgsgeometry.h:126
QgsLineString::yAt
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
Definition: qgslinestring.cpp:770
QgsTessellator::asMultiPolygon
std::unique_ptr< QgsMultiPolygon > asMultiPolygon() const
Returns the triangulation as a multipolygon geometry.
Definition: qgstessellator.cpp:818
QgsLineString::zAt
double zAt(int index) const
Returns the z-coordinate of the specified node in the line string.
Definition: qgslinestring.h:278
QgsPolygon::setExteriorRing
void setExteriorRing(QgsCurve *ring) override
Sets the exterior ring of the polygon.
Definition: qgspolygon.cpp:219
QgsPoint::z
double z
Definition: qgspoint.h:43
QgsTessellator::stride
int stride() const
Returns size of one vertex entry in bytes.
Definition: qgstessellator.h:73
QgsLineString::yData
const double * yData() const
Returns a const pointer to the y vertex data.
Definition: qgslinestring.h:232
qgspoint.h
QgsLineString
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:44
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:528
QgsRectangle
A rectangle specified with double values.
Definition: qgsrectangle.h:42
QgsLineString::xData
const double * xData() const
Returns a const pointer to the x vertex data.
Definition: qgslinestring.h:221
float_pair_hash
Definition: qgstessellator.cpp:327
QgsLineString::isEmpty
bool isEmpty() const override SIP_HOLDGIL
Returns true if the geometry is empty.
Definition: qgslinestring.cpp:308
QgsCurvePolygon::numInteriorRings
int numInteriorRings() const SIP_HOLDGIL
Returns the number of interior rings contained with the curve polygon.
Definition: qgscurvepolygon.h:77
QgsPoint::y
double y
Definition: qgspoint.h:42
qgsgeometryengine.h
QgsAbstractGeometry::wkbType
QgsWkbTypes::Type wkbType() const SIP_HOLDGIL
Returns the WKB type of the geometry.
Definition: qgsabstractgeometry.h:193
QgsTessellator::dataVerticesCount
int dataVerticesCount() const
Returns the number of vertices stored in the output data array.
Definition: qgstessellator.cpp:813
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
qgis_sip.h
qgstriangle.h
QgsPoint::setX
void setX(double x) SIP_HOLDGIL
Sets the point's x-coordinate.
Definition: qgspoint.h:269
qgstessellator.h
QgsWkbTypes::PointZ
@ PointZ
Definition: qgswkbtypes.h:86
QgsPoint::x
Q_GADGET double x
Definition: qgspoint.h:41
QgsCurvePolygon::interiorRing
const QgsCurve * interiorRing(int i) const SIP_HOLDGIL
Retrieves an interior ring from the curve polygon.
Definition: qgscurvepolygon.h:100
QgsGeometry::constGet
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
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:2098
QgsAbstractGeometry::is3D
bool is3D() const SIP_HOLDGIL
Returns true if the geometry is 3D and contains a z-value.
Definition: qgsabstractgeometry.h:206
_minimum_distance_between_coordinates
double _minimum_distance_between_coordinates(const QgsPolygon &polygon)
Definition: qgstessellator.cpp:493
QgsGeometry::createGeometryEngine
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry)
Creates and returns a new geometry engine.
Definition: qgsgeometry.cpp:3636
qgsgeometry.h
qgscurve.h
QgsGeometry
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:124
QgsRectangle::width
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:202
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
QgsTessellator::QgsTessellator
QgsTessellator(double originX, double originY, bool addNormals, bool invertNormals=false, bool addBackFaces=false, bool noZ=false, bool addTextureCoords=false, int facade=3, float textureRotation=0.0f)
Creates tessellator with a specified origin point of the world (in map coordinates)
Definition: qgstessellator.cpp:165
QgsVertexId::VertexType
VertexType
Type of vertex.
Definition: qgsabstractgeometry.h:1065
QgsWkbTypes::hasZ
static bool hasZ(Type type) SIP_HOLDGIL
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:1043
QgsCurve::numPoints
virtual int numPoints() const =0
Returns the number of points in the curve.
QgsPoint::setY
void setY(double y) SIP_HOLDGIL
Sets the point's y-coordinate.
Definition: qgspoint.h:280
QgsLineString::numPoints
int numPoints() const override SIP_HOLDGIL
Returns the number of points in the curve.
Definition: qgslinestring.cpp:703
QgsTessellator::data
QVector< float > data() const
Returns array of triangle vertex data.
Definition: qgstessellator.h:67
QgsLineString::startPoint
QgsPoint startPoint() const override SIP_HOLDGIL
Returns the starting point of the curve.
Definition: qgslinestring.cpp:672
QgsPolygon::addInteriorRing
void addInteriorRing(QgsCurve *ring) override
Adds an interior ring to the geometry (takes ownership)
Definition: qgspolygon.cpp:188
QgsRectangle::isNull
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:447
QgsTriangle
Triangle geometry type.
Definition: qgstriangle.h:34
qgsmessagelog.h