QGIS API Documentation 3.43.0-Master (c67cf405802)
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 void _makeWalls( const QgsLineString &ring, bool ccw, float extrusionHeight, QVector<float> &data,
220 bool addNormals, bool addTextureCoords, double originX, double originY, float textureRotation, bool zUp )
221{
222 // we need to find out orientation of the ring so that the triangles we generate
223 // face the right direction
224 // (for exterior we want clockwise order, for holes we want counter-clockwise order)
225 const bool is_counter_clockwise = ring.orientation() == Qgis::AngularDirection::CounterClockwise;
226
227 QgsPoint pt;
228 QgsPoint ptPrev = ring.pointN( is_counter_clockwise == ccw ? 0 : ring.numPoints() - 1 );
229 for ( int i = 1; i < ring.numPoints(); ++i )
230 {
231 pt = ring.pointN( is_counter_clockwise == ccw ? i : ring.numPoints() - i - 1 );
232 float x0 = ptPrev.x() - originX, y0 = ptPrev.y() - originY;
233 float x1 = pt.x() - originX, y1 = pt.y() - originY;
234 const float z0 = std::isnan( ptPrev.z() ) ? 0 : ptPrev.z();
235 const float z1 = std::isnan( pt.z() ) ? 0 : pt.z();
236
237 // make a quad
238 make_quad( x0, y0, z0, x1, y1, z1, extrusionHeight, data, addNormals, addTextureCoords, textureRotation, zUp );
239 ptPrev = pt;
240 }
241}
242
243static QVector3D _calculateNormal( const QgsLineString *curve, double originX, double originY, bool invertNormal, float extrusionHeight )
244{
245 if ( !QgsWkbTypes::hasZ( curve->wkbType() ) )
246 {
247 // In case of extrusions, flat polygons always face up
248 if ( extrusionHeight != 0 )
249 return QVector3D( 0, 0, 1 );
250
251 // For non-extrusions, decide based on polygon winding order and invertNormal flag
252 float orientation = 1.f;
254 orientation = -orientation;
255 if ( invertNormal )
256 orientation = -orientation;
257 return QVector3D( 0, 0, orientation );
258 }
259
260 // often we have 3D coordinates, but Z is the same for all vertices
261 // if these flat polygons are extruded, we consider them up-facing regardless of winding order
262 bool sameZ = true;
263 QgsPoint pt1 = curve->pointN( 0 );
264 QgsPoint pt2;
265 for ( int i = 1; i < curve->numPoints(); i++ )
266 {
267 pt2 = curve->pointN( i );
268 if ( pt1.z() != pt2.z() )
269 {
270 sameZ = false;
271 break;
272 }
273 }
274 if ( sameZ && extrusionHeight != 0 )
275 return QVector3D( 0, 0, 1 );
276
277 // Calculate the polygon's normal vector, based on Newell's method
278 // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
279 //
280 // Order of vertices is important here as it determines the front/back face of the polygon
281
282 double nx = 0, ny = 0, nz = 0;
283
284 // shift points by the tessellator's origin - this does not affect normal calculation and it may save us from losing some precision
285 pt1.setX( pt1.x() - originX );
286 pt1.setY( pt1.y() - originY );
287 for ( int i = 1; i < curve->numPoints(); i++ )
288 {
289 pt2 = curve->pointN( i );
290 pt2.setX( pt2.x() - originX );
291 pt2.setY( pt2.y() - originY );
292
293 if ( std::isnan( pt1.z() ) || std::isnan( pt2.z() ) )
294 continue;
295
296 nx += ( pt1.y() - pt2.y() ) * ( pt1.z() + pt2.z() );
297 ny += ( pt1.z() - pt2.z() ) * ( pt1.x() + pt2.x() );
298 nz += ( pt1.x() - pt2.x() ) * ( pt1.y() + pt2.y() );
299
300 pt1 = pt2;
301 }
302
303 QVector3D normal( nx, ny, nz );
304 if ( invertNormal )
305 normal = -normal;
306 normal.normalize();
307 return normal;
308}
309
310
311static void _normalVectorToXYVectors( const QVector3D &pNormal, QVector3D &pXVector, QVector3D &pYVector )
312{
313 // Here we define the two perpendicular vectors that define the local
314 // 2D space on the plane. They will act as axis for which we will
315 // calculate the projection coordinates of a 3D point to the plane.
316 if ( pNormal.z() > 0.001 || pNormal.z() < -0.001 )
317 {
318 pXVector = QVector3D( 1, 0, -pNormal.x() / pNormal.z() );
319 }
320 else if ( pNormal.y() > 0.001 || pNormal.y() < -0.001 )
321 {
322 pXVector = QVector3D( 1, -pNormal.x() / pNormal.y(), 0 );
323 }
324 else
325 {
326 pXVector = QVector3D( -pNormal.y() / pNormal.x(), 1, 0 );
327 }
328 pXVector.normalize();
329 pYVector = QVector3D::normal( pNormal, pXVector );
330}
331
333{
334 std::size_t operator()( const std::pair<float, float> pair ) const
335 {
336 const std::size_t h1 = std::hash<float>()( pair.first );
337 const std::size_t h2 = std::hash<float>()( pair.second );
338
339 return h1 ^ h2;
340 }
341};
342
343static void _ringToPoly2tri( const QgsLineString *ring, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> *zHash )
344{
345 const int pCount = ring->numPoints();
346
347 polyline.reserve( pCount );
348
349 const double *srcXData = ring->xData();
350 const double *srcYData = ring->yData();
351 const double *srcZData = ring->zData();
352 std::unordered_set<std::pair<float, float>, float_pair_hash> foundPoints;
353
354 for ( int i = 0; i < pCount - 1; ++i )
355 {
356 const float x = *srcXData++;
357 const float y = *srcYData++;
358
359 const auto res = foundPoints.insert( std::make_pair( x, y ) );
360 if ( !res.second )
361 {
362 // already used this point, don't add a second time
363 continue;
364 }
365
366 p2t::Point *pt2 = new p2t::Point( x, y );
367 polyline.push_back( pt2 );
368 if ( zHash )
369 {
370 ( *zHash )[pt2] = *srcZData++;
371 }
372 }
373}
374
375
376inline double _round_coord( double x )
377{
378 const double exp = 1e10; // round to 10 decimal digits
379 return round( x * exp ) / exp;
380}
381
382
383static QgsCurve *_transform_ring_to_new_base( const QgsLineString &curve, const QgsPoint &pt0, const QMatrix4x4 *toNewBase, const float scale )
384{
385 const int count = curve.numPoints();
386 QVector<double> x;
387 QVector<double> y;
388 QVector<double> z;
389 x.resize( count );
390 y.resize( count );
391 z.resize( count );
392 double *xData = x.data();
393 double *yData = y.data();
394 double *zData = z.data();
395
396 const double *srcXData = curve.xData();
397 const double *srcYData = curve.yData();
398 const double *srcZData = curve.is3D() ? curve.zData() : nullptr;
399
400 for ( int i = 0; i < count; ++i )
401 {
402 QVector4D v( *srcXData++ - pt0.x(),
403 *srcYData++ - pt0.y(),
404 srcZData ? *srcZData++ - pt0.z() : 0,
405 0 );
406 if ( toNewBase )
407 v = toNewBase->map( v );
408
409 // scale coordinates
410 v.setX( v.x() * scale );
411 v.setY( v.y() * scale );
412
413 // we also round coordinates before passing them to poly2tri triangulation in order to fix possible numerical
414 // 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).
415 // See TestQgsTessellator::testIssue17745().
416 //
417 // A hint for a similar issue: https://github.com/greenm01/poly2tri/issues/99
418 //
419 // The collinear tests uses epsilon 1e-12. Seems rounding to 12 places you still
420 // can get problems with this test when points are pretty much on a straight line.
421 // I suggest you round to 10 decimals for stability and you can live with that
422 // precision.
423 *xData++ = _round_coord( v.x() );
424 *yData++ = _round_coord( v.y() );
425 *zData++ = _round_coord( v.z() );
426 }
427 return new QgsLineString( x, y, z );
428}
429
430
431static QgsPolygon *_transform_polygon_to_new_base( const QgsPolygon &polygon, const QgsPoint &pt0, const QMatrix4x4 *toNewBase, const float scale )
432{
433 QgsPolygon *p = new QgsPolygon;
434 p->setExteriorRing( _transform_ring_to_new_base( *qgsgeometry_cast< const QgsLineString * >( polygon.exteriorRing() ), pt0, toNewBase, scale ) );
435 for ( int i = 0; i < polygon.numInteriorRings(); ++i )
436 p->addInteriorRing( _transform_ring_to_new_base( *qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ), pt0, toNewBase, scale ) );
437 return p;
438}
439
440
442{
443 double min_d = 1e20;
444
445 std::vector< const QgsLineString * > rings;
446 rings.reserve( 1 + polygon.numInteriorRings() );
447 rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.exteriorRing() ) );
448 for ( int i = 0; i < polygon.numInteriorRings(); ++i )
449 rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ) );
450
451 for ( const QgsLineString *ring : rings )
452 {
453 const int numPoints = ring->numPoints();
454 if ( numPoints <= 1 )
455 continue;
456
457 const double *srcXData = ring->xData();
458 const double *srcYData = ring->yData();
459 double x0 = *srcXData++;
460 double y0 = *srcYData++;
461 for ( int i = 1; i < numPoints; ++i )
462 {
463 const double x1 = *srcXData++;
464 const double y1 = *srcYData++;
465 const double d = QgsGeometryUtilsBase::sqrDistance2D( x0, y0, x1, y1 );
466 if ( d < min_d )
467 min_d = d;
468 x0 = x1;
469 y0 = y1;
470 }
471 }
472
473 return min_d != 1e20 ? std::sqrt( min_d ) : 1e20;
474}
475
476void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeight )
477{
478 const QgsLineString *exterior = qgsgeometry_cast< const QgsLineString * >( polygon.exteriorRing() );
479 if ( !exterior )
480 return;
481
482 const QVector3D pNormal = !mNoZ ? _calculateNormal( exterior, mOriginX, mOriginY, mInvertNormals, extrusionHeight ) : QVector3D();
483 const int pCount = exterior->numPoints();
484 if ( pCount == 0 )
485 return;
486
487 float zMin = std::numeric_limits<float>::max();
488 float zMaxBase = -std::numeric_limits<float>::max();
489 float zMaxExtruded = -std::numeric_limits<float>::max();
490
491 const float scale = mBounds.isNull() ? 1.0 : std::max( 10000.0 / mBounds.width(), 10000.0 / mBounds.height() );
492
493 std::unique_ptr<QMatrix4x4> toNewBase, toOldBase;
494 QgsPoint ptStart, pt0;
495 std::unique_ptr<QgsPolygon> polygonNew;
496 auto rotatePolygonToXYPlane = [&]()
497 {
498 if ( !mNoZ && pNormal != QVector3D( 0, 0, 1 ) )
499 {
500 // this is not a horizontal plane - need to reproject the polygon to a new base so that
501 // we can do the triangulation in a plane
502 QVector3D pXVector, pYVector;
503 _normalVectorToXYVectors( pNormal, pXVector, pYVector );
504
505 // so now we have three orthogonal unit vectors defining new base
506 // let's build transform matrix. We actually need just a 3x3 matrix,
507 // but Qt does not have good support for it, so using 4x4 matrix instead.
508 toNewBase.reset( new QMatrix4x4(
509 pXVector.x(), pXVector.y(), pXVector.z(), 0,
510 pYVector.x(), pYVector.y(), pYVector.z(), 0,
511 pNormal.x(), pNormal.y(), pNormal.z(), 0,
512 0, 0, 0, 0 ) );
513
514 // our 3x3 matrix is orthogonal, so for inverse we only need to transpose it
515 toOldBase.reset( new QMatrix4x4( toNewBase->transposed() ) );
516 }
517
518 ptStart = QgsPoint( exterior->startPoint() );
519 pt0 = QgsPoint( Qgis::WkbType::PointZ, ptStart.x(), ptStart.y(), std::isnan( ptStart.z() ) ? 0 : ptStart.z() );
520
521 // subtract ptFirst from geometry for better numerical stability in triangulation
522 // and apply new 3D vector base if the polygon is not horizontal
523
524 polygonNew.reset( _transform_polygon_to_new_base( polygon, pt0, toNewBase.get(), scale ) );
525 };
526
527 if ( !mNoZ && !qgsDoubleNear( pNormal.length(), 1, 0.001 ) )
528 return; // this should not happen - pNormal should be normalized to unit length
529
530 const QVector3D upVector( 0, 0, 1 );
531 const float pNormalUpVectorDotProduct = QVector3D::dotProduct( upVector, pNormal );
532 const float radsBetweenUpNormal = static_cast<float>( qAcos( pNormalUpVectorDotProduct ) );
533
534 const float detectionDelta = qDegreesToRadians( 10.0f );
535 int facade = 0;
536 if ( ( radsBetweenUpNormal > M_PI_2 - detectionDelta && radsBetweenUpNormal < M_PI_2 + detectionDelta )
537 || ( radsBetweenUpNormal > - M_PI_2 - detectionDelta && radsBetweenUpNormal < -M_PI_2 + detectionDelta ) )
538 facade = 1;
539 else facade = 2;
540
541 if ( pCount == 4 && polygon.numInteriorRings() == 0 && ( mTessellatedFacade & facade ) )
542 {
543 QgsLineString *triangle = nullptr;
544 if ( mAddTextureCoords )
545 {
546 rotatePolygonToXYPlane();
547 triangle = qgsgeometry_cast< QgsLineString * >( polygonNew->exteriorRing() );
548 Q_ASSERT( polygonNew->exteriorRing()->numPoints() >= 3 );
549 }
550
551 // polygon is a triangle - write vertices to the output data array without triangulation
552 const double *xData = exterior->xData();
553 const double *yData = exterior->yData();
554 const double *zData = !mNoZ ? exterior->zData() : nullptr;
555 for ( int i = 0; i < 3; i++ )
556 {
557 const float baseHeight = !zData || mNoZ ? 0.0f : static_cast<float>( * zData );
558 const float z = mNoZ ? 0.0f : ( baseHeight + extrusionHeight );
559 if ( baseHeight < zMin )
560 zMin = baseHeight;
561 if ( baseHeight > zMaxBase )
562 zMaxBase = baseHeight;
563 if ( z > zMaxExtruded )
564 zMaxExtruded = z;
565
566 if ( mOutputZUp )
567 {
568 mData << static_cast<float>( *xData - mOriginX ) << static_cast<float>( *yData - mOriginY ) << z;
569 if ( mAddNormals )
570 mData << pNormal.x() << pNormal.y() << pNormal.z();
571 }
572 else // Y axis is the up direction
573 {
574 mData << static_cast<float>( *xData - mOriginX ) << z << static_cast<float>( - *yData + mOriginY );
575 if ( mAddNormals )
576 mData << pNormal.x() << pNormal.z() << - pNormal.y();
577 }
578
579 if ( mAddTextureCoords )
580 {
581 std::pair<float, float> p( triangle->xAt( i ), triangle->yAt( i ) );
582 if ( facade & 1 )
583 {
584 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
585 }
586 else if ( facade & 2 )
587 {
588 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
589 }
590 mData << p.first << p.second;
591 }
592 xData++; yData++;
593 // zData can be nullptr if mNoZ or triangle is 2D
594 if ( zData )
595 zData++;
596 }
597
598 if ( mAddBackFaces )
599 {
600 // the same triangle with reversed order of coordinates and inverted normal
601 for ( int i = 2; i >= 0; i-- )
602 {
603 if ( mOutputZUp )
604 {
605 mData << static_cast<float>( exterior->xAt( i ) - mOriginX )
606 << static_cast<float>( exterior->yAt( i ) - mOriginY )
607 << static_cast<float>( mNoZ ? 0 : exterior->zAt( i ) );
608 if ( mAddNormals )
609 mData << -pNormal.x() << -pNormal.y() << -pNormal.z();
610 }
611 else // Y axis is the up direction
612 {
613 mData << static_cast<float>( exterior->xAt( i ) - mOriginX )
614 << static_cast<float>( mNoZ ? 0 : exterior->zAt( i ) )
615 << static_cast<float>( - exterior->yAt( i ) + mOriginY );
616 if ( mAddNormals )
617 mData << -pNormal.x() << -pNormal.z() << pNormal.y();
618 }
619
620 if ( mAddTextureCoords )
621 {
622 std::pair<float, float> p( triangle->xAt( i ), triangle->yAt( i ) );
623 if ( facade & 1 )
624 {
625 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
626 }
627 else if ( facade & 2 )
628 {
629 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
630 }
631 mData << p.first << p.second;
632 }
633 }
634 }
635 }
636 else if ( mTessellatedFacade & facade )
637 {
638
639 rotatePolygonToXYPlane();
640
641 if ( _minimum_distance_between_coordinates( *polygonNew ) < 0.001 )
642 {
643 // when the distances between coordinates of input points are very small,
644 // the triangulation likes to crash on numerical errors - when the distances are ~ 1e-5
645 // Assuming that the coordinates should be in a projected CRS, we should be able
646 // to simplify geometries that may cause problems and avoid possible crashes
647 const QgsGeometry polygonSimplified = QgsGeometry( polygonNew->clone() ).simplify( 0.001 );
648 if ( polygonSimplified.isNull() )
649 {
650 mError = QObject::tr( "geometry simplification failed - skipping" );
651 return;
652 }
653 const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet() );
654 if ( !polygonSimplifiedData || _minimum_distance_between_coordinates( *polygonSimplifiedData ) < 0.001 )
655 {
656 // Failed to fix that. It could be a really tiny geometry... or maybe they gave us
657 // geometry in unprojected lat/lon coordinates
658 mError = QObject::tr( "geometry's coordinates are too close to each other and simplification failed - skipping" );
659 return;
660 }
661 else
662 {
663 polygonNew.reset( polygonSimplifiedData->clone() );
664 }
665 }
666
667 QList< std::vector<p2t::Point *> > polylinesToDelete;
668 QHash<p2t::Point *, float> z;
669
670 // polygon exterior
671 std::vector<p2t::Point *> polyline;
672 _ringToPoly2tri( qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() ), polyline, mNoZ ? nullptr : &z );
673 polylinesToDelete << polyline;
674
675 auto cdt = std::make_unique<p2t::CDT>( polyline );
676
677 // polygon holes
678 for ( int i = 0; i < polygonNew->numInteriorRings(); ++i )
679 {
680 std::vector<p2t::Point *> holePolyline;
681 const QgsLineString *hole = qgsgeometry_cast< const QgsLineString *>( polygonNew->interiorRing( i ) );
682
683 _ringToPoly2tri( hole, holePolyline, mNoZ ? nullptr : &z );
684
685 cdt->AddHole( holePolyline );
686 polylinesToDelete << holePolyline;
687 }
688
689 // run triangulation and write vertices to the output data array
690 try
691 {
692 cdt->Triangulate();
693
694 std::vector<p2t::Triangle *> triangles = cdt->GetTriangles();
695
696 mData.reserve( mData.size() + 3 * triangles.size() * ( stride() / sizeof( float ) ) );
697 for ( size_t i = 0; i < triangles.size(); ++i )
698 {
699 p2t::Triangle *t = triangles[i];
700 for ( int j = 0; j < 3; ++j )
701 {
702 p2t::Point *p = t->GetPoint( j );
703 QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
704 if ( toOldBase )
705 pt = *toOldBase * pt;
706 const double fx = ( pt.x() / scale ) - mOriginX + pt0.x();
707 const double fy = ( pt.y() / scale ) - mOriginY + pt0.y();
708 const double baseHeight = mNoZ ? 0 : ( pt.z() + pt0.z() );
709 const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.z() );
710 if ( baseHeight < zMin )
711 zMin = baseHeight;
712 if ( baseHeight > zMaxBase )
713 zMaxBase = baseHeight;
714 if ( fz > zMaxExtruded )
715 zMaxExtruded = fz;
716
717 if ( mOutputZUp )
718 {
719 mData << static_cast<float>( fx ) << static_cast<float>( fy ) << static_cast<float>( fz );
720 if ( mAddNormals )
721 mData << pNormal.x() << pNormal.y() << pNormal.z();
722 }
723 else
724 {
725 mData << static_cast<float>( fx ) << static_cast<float>( fz ) << static_cast<float>( -fy );
726 if ( mAddNormals )
727 mData << pNormal.x() << pNormal.z() << - pNormal.y();
728 }
729
730 if ( mAddTextureCoords )
731 {
732 const std::pair<float, float> pr = rotateCoords( p->x, p->y, 0.0f, 0.0f, mTextureRotation );
733 mData << pr.first << pr.second;
734 }
735 }
736
737 if ( mAddBackFaces )
738 {
739 // the same triangle with reversed order of coordinates and inverted normal
740 for ( int j = 2; j >= 0; --j )
741 {
742 p2t::Point *p = t->GetPoint( j );
743 QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
744 if ( toOldBase )
745 pt = *toOldBase * pt;
746 const double fx = ( pt.x() / scale ) - mOriginX + pt0.x();
747 const double fy = ( pt.y() / scale ) - mOriginY + pt0.y();
748 const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.z() );
749
750 if ( mOutputZUp )
751 {
752 mData << static_cast<float>( fx ) << static_cast<float>( fy ) << static_cast<float>( fz );
753 if ( mAddNormals )
754 mData << -pNormal.x() << -pNormal.y() << -pNormal.z();
755 }
756 else
757 {
758 mData << static_cast<float>( fx ) << static_cast<float>( fz ) << static_cast<float>( -fy );
759 if ( mAddNormals )
760 mData << -pNormal.x() << -pNormal.z() << pNormal.y();
761 }
762
763 if ( mAddTextureCoords )
764 {
765 const std::pair<float, float> pr = rotateCoords( p->x, p->y, 0.0f, 0.0f, mTextureRotation );
766 mData << pr.first << pr.second;
767 }
768 }
769 }
770 }
771 }
772 catch ( std::runtime_error &err )
773 {
774 mError = err.what();
775 }
776 catch ( ... )
777 {
778 mError = QObject::tr( "An unknown error occurred" );
779 }
780
781 for ( int i = 0; i < polylinesToDelete.count(); ++i )
782 qDeleteAll( polylinesToDelete[i] );
783 }
784
785 // add walls if extrusion is enabled
786 if ( extrusionHeight != 0 && ( mTessellatedFacade & 1 ) )
787 {
788 _makeWalls( *exterior, false, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation, mOutputZUp );
789
790 for ( int i = 0; i < polygon.numInteriorRings(); ++i )
791 _makeWalls( *qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ), true, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation, mOutputZUp );
792
793 if ( zMaxBase + extrusionHeight > zMaxExtruded )
794 zMaxExtruded = zMaxBase + extrusionHeight;
795 }
796
797 if ( zMin < mZMin )
798 mZMin = zMin;
799 if ( zMaxExtruded > mZMax )
800 mZMax = zMaxExtruded;
801 if ( zMaxBase > mZMax )
802 mZMax = zMaxBase;
803}
804
806{
807 return mData.size() / ( stride() / sizeof( float ) );
808}
809
810std::unique_ptr<QgsMultiPolygon> QgsTessellator::asMultiPolygon() const
811{
812 auto mp = std::make_unique< QgsMultiPolygon >();
813 const auto nVals = mData.size();
814 mp->reserve( nVals / 9 );
815 for ( auto i = decltype( nVals ) {0}; i + 8 < nVals; i += 9 )
816 {
817 if ( mOutputZUp )
818 {
819 const QgsPoint p1( mData[i + 0], mData[i + 1], mData[i + 2] );
820 const QgsPoint p2( mData[i + 3], mData[i + 4], mData[i + 5] );
821 const QgsPoint p3( mData[i + 6], mData[i + 7], mData[i + 8] );
822 mp->addGeometry( new QgsTriangle( p1, p2, p3 ) );
823 }
824 else
825 {
826 // tessellator geometry is x, z, -y
827 const QgsPoint p1( mData[i + 0], -mData[i + 2], mData[i + 1] );
828 const QgsPoint p2( mData[i + 3], -mData[i + 5], mData[i + 4] );
829 const QgsPoint p3( mData[i + 6], -mData[i + 8], mData[i + 7] );
830 mp->addGeometry( new QgsTriangle( p1, p2, p3 ) );
831 }
832 }
833 return mp;
834}
@ CounterClockwise
Counter-clockwise direction.
@ Clockwise
Clockwise direction.
@ 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
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:6286
double _round_coord(double x)
double _minimum_distance_between_coordinates(const QgsPolygon &polygon)
std::size_t operator()(const std::pair< float, float > pair) const