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