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