QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
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 <algorithm>
19#include <earcut.hpp>
20#include <unordered_set>
21
22#include "poly2tri.h"
23#include "qgis.h"
24#include "qgscurve.h"
25#include "qgsgeometry.h"
27#include "qgsmultipolygon.h"
28#include "qgspoint.h"
29#include "qgspolygon.h"
30#include "qgstriangle.h"
31
32#include <QMatrix4x4>
33#include <QVector3D>
34#include <QtDebug>
35#include <QtMath>
36
37static std::pair<float, float> rotateCoords( float x, float y, float origin_x, float origin_y, float r )
38{
39 r = qDegreesToRadians( r );
40 float x0 = x - origin_x, y0 = y - origin_y;
41 // p0 = x0 + i * y0
42 // rot = cos(r) + i * sin(r)
43 // p0 * rot = x0 * cos(r) - y0 * sin(r) + i * [ x0 * sin(r) + y0 * cos(r) ]
44 const float x1 = origin_x + x0 * qCos( r ) - y0 * qSin( r );
45 const float y1 = origin_y + x0 * qSin( r ) + y0 * qCos( r );
46 return std::make_pair( x1, y1 );
47}
48
49static 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 )
50{
51 const float dx = x1 - x0;
52 const float dy = y1 - y0;
53
54 // perpendicular vector in plane to [x,y] is [-y,x]
55 QVector3D vn = zUp ? QVector3D( -dy, dx, 0 ) : QVector3D( -dy, 0, -dx );
56 vn.normalize();
57
58 float u0, v0;
59 float u1, v1;
60 float u2, v2;
61 float u3, v3;
62
63 QVector<double> textureCoordinates;
64 textureCoordinates.reserve( 12 );
65 // select which side of the coordinates to use (x, z or y, z) depending on which side is smaller
66 if ( fabsf( dy ) <= fabsf( dx ) )
67 {
68 // consider x and z as the texture coordinates
69 u0 = x0;
70 v0 = z0 + height;
71
72 u1 = x1;
73 v1 = z1 + height;
74
75 u2 = x0;
76 v2 = z0;
77
78 u3 = x1;
79 v3 = z1;
80 }
81 else
82 {
83 // consider y and z as the texture coowallsTextureRotationrdinates
84 u0 = -y0;
85 v0 = z0 + height;
86
87 u1 = -y1;
88 v1 = z1 + height;
89
90 u2 = -y0;
91 v2 = z0;
92
93 u3 = -y1;
94 v3 = z1;
95 }
96
97 textureCoordinates.push_back( u0 );
98 textureCoordinates.push_back( v0 );
99
100 textureCoordinates.push_back( u1 );
101 textureCoordinates.push_back( v1 );
102
103 textureCoordinates.push_back( u2 );
104 textureCoordinates.push_back( v2 );
105
106 textureCoordinates.push_back( u2 );
107 textureCoordinates.push_back( v2 );
108
109 textureCoordinates.push_back( u1 );
110 textureCoordinates.push_back( v1 );
111
112 textureCoordinates.push_back( u3 );
113 textureCoordinates.push_back( v3 );
114
115 for ( int i = 0; i < textureCoordinates.size(); i += 2 )
116 {
117 const std::pair<float, float> rotated = rotateCoords( textureCoordinates[i], textureCoordinates[i + 1], 0, 0, textureRotation );
118 textureCoordinates[i] = rotated.first;
119 textureCoordinates[i + 1] = rotated.second;
120 }
121
122 // triangle 1
123 // vertice 1
124 if ( zUp )
125 data << x0 << y0 << z0 + height;
126 else
127 data << x0 << z0 + height << -y0;
128 if ( addNormals )
129 data << vn.x() << vn.y() << vn.z();
130 if ( addTextureCoords )
131 data << textureCoordinates[0] << textureCoordinates[1];
132 // vertice 2
133 if ( zUp )
134 data << x1 << y1 << z1 + height;
135 else
136 data << x1 << z1 + height << -y1;
137 if ( addNormals )
138 data << vn.x() << vn.y() << vn.z();
139 if ( addTextureCoords )
140 data << textureCoordinates[2] << textureCoordinates[3];
141 // verice 3
142 if ( zUp )
143 data << x0 << y0 << z0;
144 else
145 data << x0 << z0 << -y0;
146 if ( addNormals )
147 data << vn.x() << vn.y() << vn.z();
148 if ( addTextureCoords )
149 data << textureCoordinates[4] << textureCoordinates[5];
150
151 // triangle 2
152 // vertice 1
153 if ( zUp )
154 data << x0 << y0 << z0;
155 else
156 data << x0 << z0 << -y0;
157 if ( addNormals )
158 data << vn.x() << vn.y() << vn.z();
159 if ( addTextureCoords )
160 data << textureCoordinates[6] << textureCoordinates[7];
161 // vertice 2
162 if ( zUp )
163 data << x1 << y1 << z1 + height;
164 else
165 data << x1 << z1 + height << -y1;
166 if ( addNormals )
167 data << vn.x() << vn.y() << vn.z();
168 if ( addTextureCoords )
169 data << textureCoordinates[8] << textureCoordinates[9];
170 // vertice 3
171 if ( zUp )
172 data << x1 << y1 << z1;
173 else
174 data << x1 << z1 << -y1;
175 if ( addNormals )
176 data << vn.x() << vn.y() << vn.z();
177 if ( addTextureCoords )
178 data << textureCoordinates[10] << textureCoordinates[11];
179}
180
182
183QgsTessellator::QgsTessellator( double originX, double originY, bool addNormals, bool invertNormals, bool addBackFaces, bool noZ,
184 bool addTextureCoords, int facade, float textureRotation )
185{
186 setOrigin( QgsVector3D( originX, originY, 0 ) );
187 setAddNormals( addNormals );
188 setInvertNormals( invertNormals );
189 setExtrusionFacesLegacy( facade );
190 setBackFacesEnabled( addBackFaces );
191 setAddTextureUVs( addTextureCoords );
194}
195
196QgsTessellator::QgsTessellator( const QgsRectangle &bounds, bool addNormals, bool invertNormals, bool addBackFaces, bool noZ,
197 bool addTextureCoords, int facade, float textureRotation )
198{
199 setAddTextureUVs( addTextureCoords );
200 setExtrusionFacesLegacy( facade );
201 setBounds( bounds );
202 setAddNormals( addNormals );
203 setInvertNormals( invertNormals );
204 setBackFacesEnabled( addBackFaces );
207}
208
210{
211 mOrigin = origin;
212}
213
215{
216 mOrigin = QgsVector3D( bounds.xMinimum(), bounds.yMinimum(), 0 );
217 mScale = bounds.isNull() ? 1.0 : std::max( 10000.0 / bounds.width(), 10000.0 / bounds.height() );
218}
219
221{
222 mInputZValueIgnored = ignore;
223}
224
226{
227 mExtrusionFaces = faces;
228}
229
230void QgsTessellator::setExtrusionFacesLegacy( int facade )
231{
232 switch ( facade )
233 {
234 case 0:
235 mExtrusionFaces = Qgis::ExtrusionFace::NoFace;
236 break;
237 case 1:
238 mExtrusionFaces = Qgis::ExtrusionFace::Walls;
239 break;
240 case 2:
241 mExtrusionFaces = Qgis::ExtrusionFace::Roof;
242 break;
243 case 3:
245 break;
246 case 7:
248 break;
249 default:
250 break;
251 }
252}
253
255{
256 mTextureRotation = rotation;
257}
258
259void QgsTessellator::setBackFacesEnabled( bool addBackFaces )
260{
261 mAddBackFaces = addBackFaces;
262}
263
264void QgsTessellator::setInvertNormals( bool invertNormals )
265{
266 mInvertNormals = invertNormals;
267}
268
269void QgsTessellator::setAddNormals( bool addNormals )
270{
271 mAddNormals = addNormals;
272 updateStride();
273}
274
275void QgsTessellator::setAddTextureUVs( bool addTextureUVs )
276{
277 mAddTextureCoords = addTextureUVs;
278 updateStride();
279}
280
285
286void QgsTessellator::updateStride()
287{
288 mStride = 3 * sizeof( float );
289 if ( mAddNormals )
290 mStride += 3 * sizeof( float );
291 if ( mAddTextureCoords )
292 mStride += 2 * sizeof( float );
293}
294
295static void _makeWalls( const QgsLineString &ring, bool ccw, float extrusionHeight, QVector<float> &data,
296 bool addNormals, bool addTextureCoords, double originX, double originY, double originZ, float textureRotation, bool zUp )
297{
298 // we need to find out orientation of the ring so that the triangles we generate
299 // face the right direction
300 // (for exterior we want clockwise order, for holes we want counter-clockwise order)
301 const bool is_counter_clockwise = ring.orientation() == Qgis::AngularDirection::CounterClockwise;
302
303 QgsPoint pt;
304 QgsPoint ptPrev = ring.pointN( is_counter_clockwise == ccw ? 0 : ring.numPoints() - 1 );
305 for ( int i = 1; i < ring.numPoints(); ++i )
306 {
307 pt = ring.pointN( is_counter_clockwise == ccw ? i : ring.numPoints() - i - 1 );
308 const double x0 = ptPrev.x() - originX, y0 = ptPrev.y() - originY;
309 const double x1 = pt.x() - originX, y1 = pt.y() - originY;
310 const double z0 = std::isnan( ptPrev.z() ) ? 0.0 : ptPrev.z() - originZ;
311 const double z1 = std::isnan( pt.z() ) ? 0.0 : pt.z() - originZ;
312
313 // make a quad
314 make_quad( static_cast<float>( x0 ), static_cast<float>( y0 ), static_cast<float>( z0 ), static_cast<float>( x1 ), static_cast<float>( y1 ), static_cast<float>( z1 ), extrusionHeight, data, addNormals, addTextureCoords, textureRotation, zUp );
315 ptPrev = pt;
316 }
317}
318
319static QVector3D calculateNormal( const QgsLineString *curve, double originX, double originY, double originZ, bool invertNormal, float extrusionHeight )
320{
321 if ( !QgsWkbTypes::hasZ( curve->wkbType() ) )
322 {
323 // In case of extrusions, flat polygons always face up
324 if ( extrusionHeight != 0 )
325 return QVector3D( 0, 0, 1 );
326
327 // For non-extrusions, decide based on polygon winding order and invertNormal flag
328 float orientation = 1.f;
330 orientation = -orientation;
331 if ( invertNormal )
332 orientation = -orientation;
333 return QVector3D( 0, 0, orientation );
334 }
335
336 // often we have 3D coordinates, but Z is the same for all vertices
337 // if these flat polygons are extruded, we consider them up-facing regardless of winding order
338 bool sameZ = true;
339 QgsPoint pt1 = curve->pointN( 0 );
340 QgsPoint pt2;
341 for ( int i = 1; i < curve->numPoints(); i++ )
342 {
343 pt2 = curve->pointN( i );
344 if ( pt1.z() != pt2.z() )
345 {
346 sameZ = false;
347 break;
348 }
349 }
350 if ( sameZ && extrusionHeight != 0 )
351 return QVector3D( 0, 0, 1 );
352
353 // Calculate the polygon's normal vector, based on Newell's method
354 // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
355 //
356 // Order of vertices is important here as it determines the front/back face of the polygon
357
358 double nx = 0, ny = 0, nz = 0;
359
360 // shift points by the tessellator's origin - this does not affect normal calculation and it may save us from losing some precision
361 pt1.setX( pt1.x() - originX );
362 pt1.setY( pt1.y() - originY );
363 pt1.setZ( std::isnan( pt1.z() ) ? 0.0 : pt1.z() - originZ );
364 for ( int i = 1; i < curve->numPoints(); i++ )
365 {
366 pt2 = curve->pointN( i );
367 pt2.setX( pt2.x() - originX );
368 pt2.setY( pt2.y() - originY );
369 pt2.setZ( std::isnan( pt2.z() ) ? 0.0 : pt2.z() - originZ );
370
371 nx += ( pt1.y() - pt2.y() ) * ( pt1.z() + pt2.z() );
372 ny += ( pt1.z() - pt2.z() ) * ( pt1.x() + pt2.x() );
373 nz += ( pt1.x() - pt2.x() ) * ( pt1.y() + pt2.y() );
374
375 pt1 = pt2;
376 }
377
378 QVector3D normal( nx, ny, nz );
379 if ( invertNormal )
380 normal = -normal;
381 normal.normalize();
382 return normal;
383}
384
385
386static void _normalVectorToXYVectors( const QVector3D &pNormal, QVector3D &pXVector, QVector3D &pYVector )
387{
388 // Here we define the two perpendicular vectors that define the local
389 // 2D space on the plane. They will act as axis for which we will
390 // calculate the projection coordinates of a 3D point to the plane.
391 if ( pNormal.z() > 0.001 || pNormal.z() < -0.001 )
392 {
393 pXVector = QVector3D( 1, 0, -pNormal.x() / pNormal.z() );
394 }
395 else if ( pNormal.y() > 0.001 || pNormal.y() < -0.001 )
396 {
397 pXVector = QVector3D( 1, -pNormal.x() / pNormal.y(), 0 );
398 }
399 else
400 {
401 pXVector = QVector3D( -pNormal.y() / pNormal.x(), 1, 0 );
402 }
403 pXVector.normalize();
404 pYVector = QVector3D::normal( pNormal, pXVector );
405}
406
408{
409 std::size_t operator()( const std::pair<float, float> pair ) const
410 {
411 const std::size_t h1 = std::hash<float>()( pair.first );
412 const std::size_t h2 = std::hash<float>()( pair.second );
413
414 return h1 ^ h2;
415 }
416};
417
418static void _ringToPoly2tri( const QgsLineString *ring, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> *zHash )
419{
420 const int pCount = ring->numPoints();
421
422 polyline.reserve( pCount );
423
424 const double *srcXData = ring->xData();
425 const double *srcYData = ring->yData();
426 const double *srcZData = ring->zData();
427 std::unordered_set<std::pair<float, float>, float_pair_hash> foundPoints;
428
429 for ( int i = 0; i < pCount - 1; ++i )
430 {
431 const float x = *srcXData++;
432 const float y = *srcYData++;
433
434 const auto res = foundPoints.insert( std::make_pair( x, y ) );
435 if ( !res.second )
436 {
437 // already used this point, don't add a second time
438 continue;
439 }
440
441 p2t::Point *pt2 = new p2t::Point( x, y );
442 polyline.push_back( pt2 );
443 if ( zHash )
444 {
445 ( *zHash )[pt2] = *srcZData++;
446 }
447 }
448}
449
450
451inline double _round_coord( double x )
452{
453 const double exp = 1e10; // round to 10 decimal digits
454 return round( x * exp ) / exp;
455}
456
457
458static QgsCurve *_transform_ring_to_new_base( const QgsLineString &curve, const QgsPoint &pt0, const QMatrix4x4 *toNewBase, const float scale )
459{
460 const int count = curve.numPoints();
461 QVector<double> x;
462 QVector<double> y;
463 QVector<double> z;
464 x.resize( count );
465 y.resize( count );
466 z.resize( count );
467 double *xData = x.data();
468 double *yData = y.data();
469 double *zData = z.data();
470
471 const double *srcXData = curve.xData();
472 const double *srcYData = curve.yData();
473 const double *srcZData = curve.is3D() ? curve.zData() : nullptr;
474
475 for ( int i = 0; i < count; ++i )
476 {
477 QVector4D v( *srcXData++ - pt0.x(),
478 *srcYData++ - pt0.y(),
479 srcZData ? *srcZData++ - pt0.z() : 0,
480 0 );
481 if ( toNewBase )
482 v = toNewBase->map( v );
483
484 // scale coordinates
485 v.setX( v.x() * scale );
486 v.setY( v.y() * scale );
487
488 // we also round coordinates before passing them to poly2tri triangulation in order to fix possible numerical
489 // 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).
490 // See TestQgsTessellator::testIssue17745().
491 //
492 // A hint for a similar issue: https://github.com/greenm01/poly2tri/issues/99
493 //
494 // The collinear tests uses epsilon 1e-12. Seems rounding to 12 places you still
495 // can get problems with this test when points are pretty much on a straight line.
496 // I suggest you round to 10 decimals for stability and you can live with that
497 // precision.
498 *xData++ = _round_coord( v.x() );
499 *yData++ = _round_coord( v.y() );
500 *zData++ = _round_coord( v.z() );
501 }
502 return new QgsLineString( x, y, z );
503}
504
505
506static QgsPolygon *_transform_polygon_to_new_base( const QgsPolygon &polygon, const QgsPoint &pt0, const QMatrix4x4 *toNewBase, const float scale )
507{
508 QgsPolygon *p = new QgsPolygon;
509 p->setExteriorRing( _transform_ring_to_new_base( *qgsgeometry_cast< const QgsLineString * >( polygon.exteriorRing() ), pt0, toNewBase, scale ) );
510 for ( int i = 0; i < polygon.numInteriorRings(); ++i )
511 p->addInteriorRing( _transform_ring_to_new_base( *qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ), pt0, toNewBase, scale ) );
512 return p;
513}
514
515
517{
518 double min_d = 1e20;
519
520 std::vector< const QgsLineString * > rings;
521 rings.reserve( 1 + polygon.numInteriorRings() );
522 rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.exteriorRing() ) );
523 for ( int i = 0; i < polygon.numInteriorRings(); ++i )
524 rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ) );
525
526 for ( const QgsLineString *ring : rings )
527 {
528 const int numPoints = ring->numPoints();
529 if ( numPoints <= 1 )
530 continue;
531
532 const double *srcXData = ring->xData();
533 const double *srcYData = ring->yData();
534 double x0 = *srcXData++;
535 double y0 = *srcYData++;
536 for ( int i = 1; i < numPoints; ++i )
537 {
538 const double x1 = *srcXData++;
539 const double y1 = *srcYData++;
540 const double d = QgsGeometryUtilsBase::sqrDistance2D( x0, y0, x1, y1 );
541 if ( d < min_d )
542 min_d = d;
543 x0 = x1;
544 y0 = y1;
545 }
546 }
547
548 return min_d != 1e20 ? std::sqrt( min_d ) : 1e20;
549}
550
551void QgsTessellator::calculateBaseTransform( const QVector3D &pNormal, QMatrix4x4 *base ) const
552{
553 if ( !mInputZValueIgnored && pNormal != QVector3D( 0, 0, 1 ) )
554 {
555 // this is not a horizontal plane - need to reproject to a new base so that
556 // we can do the triangulation in a plane
557 QVector3D pXVector, pYVector;
558 _normalVectorToXYVectors( pNormal, pXVector, pYVector );
559
560 // so now we have three orthogonal unit vectors defining new base
561 // let's build transform matrix. We actually need just a 3x3 matrix,
562 // but Qt does not have good support for it, so using 4x4 matrix instead.
563 *base = QMatrix4x4(
564 pXVector.x(), pXVector.y(), pXVector.z(), 0,
565 pYVector.x(), pYVector.y(), pYVector.z(), 0,
566 pNormal.x(), pNormal.y(), pNormal.z(), 0,
567 0, 0, 0, 0 );
568 }
569 else
570 {
571 base->setToIdentity();
572 }
573}
574
575void QgsTessellator::addTriangleVertices(
576 const std::array<QVector3D, 3> &points,
577 QVector3D pNormal,
578 float extrusionHeight,
579 QMatrix4x4 *transformMatrix,
580 const QgsPoint *originOffset,
581 bool reverse
582)
583{
584 // if reverse is true, the triangle vertices are added in reverse order and normal is inverted
585 const QVector3D normal = reverse ? -pNormal : pNormal;
586 for ( int i = 0; i < 3; ++i )
587 {
588 const int index = reverse ? 2 - i : i;
589
590 // cppcheck-suppress negativeContainerIndex
591 QVector3D point = points[ index ];
592 const float z = mInputZValueIgnored ? 0.0f : point.z();
593 QVector4D pt( point.x(), point.y(), z, 0 );
594
595 pt = *transformMatrix * pt;
596
597 const double fx = pt.x() - mOrigin.x() + originOffset->x();
598 const double fy = pt.y() - mOrigin.y() + originOffset->y();
599 const double baseHeight = mInputZValueIgnored ? 0 : pt.z() - mOrigin.z() + originOffset->z();
600 const double fz = mInputZValueIgnored ? 0.0 : ( baseHeight + extrusionHeight );
601
602 if ( baseHeight < mZMin )
603 mZMin = static_cast<float>( baseHeight );
604 if ( baseHeight > mZMax )
605 mZMax = static_cast<float>( baseHeight );
606 if ( fz > mZMax )
607 mZMax = static_cast<float>( fz );
608
609 // NOLINTBEGIN(bugprone-branch-clone)
610 if ( mOutputZUp )
611 {
612 mData << static_cast<float>( fx ) << static_cast<float>( fy ) << static_cast<float>( fz );
613 if ( mAddNormals )
614 mData << normal.x() << normal.y() << normal.z();
615 }
616 else
617 {
618 mData << static_cast<float>( fx ) << static_cast<float>( fz ) << static_cast<float>( -fy );
619 if ( mAddNormals )
620 mData << normal.x() << normal.z() << - normal.y();
621 }
622 // NOLINTEND(bugprone-branch-clone)
623
624 if ( mAddTextureCoords )
625 {
626 const std::pair<float, float> pr = rotateCoords( static_cast<float>( pt.x() ), static_cast<float>( pt.y() ), 0.0f, 0.0f, mTextureRotation );
627 mData << pr.first << pr.second;
628 }
629 }
630}
631
632void QgsTessellator::ringToEarcutPoints( const QgsLineString *ring, std::vector<std::array<double, 2>> &polyline, QHash<std::array<double, 2>*, float> *zHash )
633{
634 const int pCount = ring->numPoints();
635
636 polyline.reserve( pCount );
637
638 const double *srcXData = ring->xData();
639 const double *srcYData = ring->yData();
640 const double *srcZData = ring->zData();
641
642 // earcut handles duplicates, we do not need to remove them here
643 for ( int i = 0; i < pCount - 1; ++i )
644 {
645 const float x = static_cast<float>( *srcXData++ );
646 const float y = static_cast<float>( *srcYData++ );
647
648 std::array<double, 2> pt = { x, y };
649 polyline.push_back( pt );
650
651 if ( zHash && srcZData )
652 {
653 ( *zHash )[ &pt ] = *srcZData++;
654 }
655 }
656}
657
658std::vector<QVector3D> QgsTessellator::generateConstrainedDelaunayTriangles( const QgsPolygon *polygonNew )
659{
660 QList<std::vector<p2t::Point *>> polylinesToDelete;
661 QHash<p2t::Point *, float> z;
662
663 // polygon exterior
664 std::vector<p2t::Point *> polyline;
665 _ringToPoly2tri( qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() ), polyline, mInputZValueIgnored ? nullptr : &z );
666 polylinesToDelete << polyline;
667
668 p2t::CDT cdt = p2t::CDT( polyline );
669
670 // polygon holes
671 for ( int i = 0; i < polygonNew->numInteriorRings(); ++i )
672 {
673 std::vector<p2t::Point *> holePolyline;
674 const QgsLineString *hole = qgsgeometry_cast< const QgsLineString *>( polygonNew->interiorRing( i ) );
675
676 _ringToPoly2tri( hole, holePolyline, mInputZValueIgnored ? nullptr : &z );
677
678 cdt.AddHole( holePolyline );
679 polylinesToDelete << holePolyline;
680 }
681
682 cdt.Triangulate();
683 std::vector<p2t::Triangle *> triangles = cdt.GetTriangles();
684
685 std::vector<QVector3D> trianglePoints;
686 trianglePoints.reserve( triangles.size() * 3 );
687
688 for ( p2t::Triangle *t : triangles )
689 {
690 trianglePoints.emplace_back( t->GetPoint( 0 )->x / mScale, t->GetPoint( 0 )->y / mScale, z.value( t->GetPoint( 0 ) ) );
691 trianglePoints.emplace_back( t->GetPoint( 1 )->x / mScale, t->GetPoint( 1 )->y / mScale, z.value( t->GetPoint( 1 ) ) );
692 trianglePoints.emplace_back( t->GetPoint( 2 )->x / mScale, t->GetPoint( 2 )->y / mScale, z.value( t->GetPoint( 2 ) ) );
693 }
694
695 for ( int i = 0; i < polylinesToDelete.count(); ++i )
696 qDeleteAll( polylinesToDelete[i] );
697
698 return trianglePoints;
699}
700
701std::vector<QVector3D> QgsTessellator::generateEarcutTriangles( const QgsPolygon *polygonNew )
702{
703 QHash<std::array<double, 2>*, float> z;
704 std::vector<std::vector<std::array<double, 2>>> rings;
705 std::vector<std::array<double, 2>> polyline;
706
707 ringToEarcutPoints( qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() ), polyline, mInputZValueIgnored ? nullptr : &z );
708 rings.push_back( polyline );
709
710 for ( int i = 0; i < polygonNew->numInteriorRings(); ++i )
711 {
712 std::vector<std::array<double, 2>> holePolyline;
713 ringToEarcutPoints( qgsgeometry_cast<const QgsLineString *>( polygonNew->interiorRing( i ) ), holePolyline, mInputZValueIgnored ? nullptr : &z );
714 rings.push_back( holePolyline );
715 }
716
717 std::vector<std::array<double, 2>> points;
718 for ( const auto &ring : rings )
719 {
720 points.insert( points.end(), ring.begin(), ring.end() );
721 }
722
723 std::vector<uint32_t> indices = mapbox::earcut<uint32_t>( rings );
724 std::vector<QVector3D> trianglePoints;
725 trianglePoints.reserve( points.size() );
726
727 for ( size_t i = 0; i < indices.size(); i++ )
728 {
729 uint32_t vertexIndex = indices[ i ];
730 std::array<double, 2> vertex = points[ vertexIndex ];
731
732 double x = vertex[ 0 ];
733 double y = vertex[ 1 ];
734
735 float zValue = z.value( &vertex, 0.0f );
736
737 trianglePoints.emplace_back( x / mScale, y / mScale, zValue );
738 }
739
740 return trianglePoints;
741}
742
743void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeight )
744{
746 if ( !exterior )
747 return;
748
749 const QVector3D pNormal = !mInputZValueIgnored ? calculateNormal( exterior, mOrigin.x(), mOrigin.y(), mOrigin.z(), mInvertNormals, extrusionHeight ) : QVector3D();
750 const int pCount = exterior->numPoints();
751 if ( pCount == 0 )
752 return;
753
754 QMatrix4x4 base; // identity matrix by default
755 const QgsPoint ptStart( exterior->startPoint() );
756 const QgsPoint extrusionOrigin( Qgis::WkbType::PointZ, ptStart.x(), ptStart.y(), std::isnan( ptStart.z() ) ? 0 : ptStart.z() );
757 std::unique_ptr<QgsPolygon> polygonNew;
758
759 if ( !mInputZValueIgnored && !qgsDoubleNear( pNormal.length(), 1, 0.001 ) )
760 return; // this should not happen - pNormal should be normalized to unit length
761
762 const bool buildWalls = mExtrusionFaces.testFlag( Qgis::ExtrusionFace::Walls );
763 const bool buildFloor = mExtrusionFaces.testFlag( Qgis::ExtrusionFace::Floor );
764 const bool buildRoof = mExtrusionFaces.testFlag( Qgis::ExtrusionFace::Roof );
765
766 if ( buildFloor || buildRoof )
767 {
768 calculateBaseTransform( pNormal, &base );
769 polygonNew.reset( _transform_polygon_to_new_base( polygon, extrusionOrigin, &base, mScale ) );
770
771 // our 3x3 matrix is orthogonal, so for inverse we only need to transpose it
772 base = base.transposed();
773
774 if ( pCount == 4 && polygon.numInteriorRings() == 0 )
775 {
776 Q_ASSERT( polygonNew->exteriorRing()->numPoints() >= 3 );
777
778 const QgsLineString *triangle = qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() );
779 const QVector3D p1( static_cast<float>( triangle->xAt( 0 ) ), static_cast<float>( triangle->yAt( 0 ) ), static_cast<float>( triangle->zAt( 0 ) ) );
780 const QVector3D p2( static_cast<float>( triangle->xAt( 1 ) ), static_cast<float>( triangle->yAt( 1 ) ), static_cast<float>( triangle->zAt( 1 ) ) );
781 const QVector3D p3( static_cast<float>( triangle->xAt( 2 ) ), static_cast<float>( triangle->yAt( 2 ) ), static_cast<float>( triangle->zAt( 2 ) ) );
782 const std::array<QVector3D, 3> points = { { p1, p2, p3 } };
783
784 addTriangleVertices( points, pNormal, extrusionHeight, &base, &extrusionOrigin, false );
785
786 if ( mAddBackFaces )
787 {
788 addTriangleVertices( points, pNormal, extrusionHeight, &base, &extrusionOrigin, true );
789 }
790
791 if ( extrusionHeight != 0 && buildFloor )
792 {
793 addTriangleVertices( points, pNormal, 0, &base, &extrusionOrigin, false );
794 if ( mAddBackFaces )
795 {
796 addTriangleVertices( points, pNormal, 0, &base, &extrusionOrigin, true );
797 }
798 }
799 }
800 else // we need to triangulate the polygon
801 {
802 if ( _minimum_distance_between_coordinates( *polygonNew ) < 0.001 )
803 {
804 // when the distances between coordinates of input points are very small,
805 // the triangulation likes to crash on numerical errors - when the distances are ~ 1e-5
806 // Assuming that the coordinates should be in a projected CRS, we should be able
807 // to simplify geometries that may cause problems and avoid possible crashes
808 const QgsGeometry polygonSimplified = QgsGeometry( polygonNew->clone() ).simplify( 0.001 );
809 if ( polygonSimplified.isNull() )
810 {
811 mError = QObject::tr( "geometry simplification failed - skipping" );
812 return;
813 }
814 const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet() );
815 if ( !polygonSimplifiedData || _minimum_distance_between_coordinates( *polygonSimplifiedData ) < 0.001 )
816 {
817 // Failed to fix that. It could be a really tiny geometry... or maybe they gave us
818 // geometry in unprojected lat/lon coordinates
819 mError = QObject::tr( "geometry's coordinates are too close to each other and simplification failed - skipping" );
820 return;
821 }
822 else
823 {
824 polygonNew.reset( polygonSimplifiedData->clone() );
825 }
826 }
827
828 // run triangulation and write vertices to the output data array
829 try
830 {
831 std::vector<QVector3D> trianglePoints;
832 // NOLINTBEGIN(bugprone-branch-clone)
833 switch ( mTriangulationAlgorithm )
834 {
836 trianglePoints = generateConstrainedDelaunayTriangles( polygonNew.get() );
837 break;
839 trianglePoints = generateEarcutTriangles( polygonNew.get() );
840 break;
841 }
842 // NOLINTEND(bugprone-branch-clone)
843
844 if ( trianglePoints.empty() )
845 {
846 mError = QObject::tr( "Failed to triangulate polygon." );
847 return;
848 }
849
850 Q_ASSERT( trianglePoints.size() % 3 == 0 );
851
852 mData.reserve( mData.size() + trianglePoints.size() * 3 * ( stride() / sizeof( float ) ) );
853
854 for ( size_t i = 0; i < trianglePoints.size(); i += 3 )
855 {
856 const QVector3D p1 = trianglePoints[ i + 0 ];
857 const QVector3D p2 = trianglePoints[ i + 1 ];
858 const QVector3D p3 = trianglePoints[ i + 2 ];
859 const std::array<QVector3D, 3> points = { { p1, p2, p3 } };
860
861 addTriangleVertices( points, pNormal, extrusionHeight, &base, &extrusionOrigin, false );
862
863 if ( mAddBackFaces )
864 {
865 addTriangleVertices( points, pNormal, extrusionHeight, &base, &extrusionOrigin, true );
866 }
867
868 if ( extrusionHeight != 0 && buildFloor )
869 {
870 addTriangleVertices( points, pNormal, 0, &base, &extrusionOrigin, true );
871 if ( mAddBackFaces )
872 {
873 addTriangleVertices( points, pNormal, 0, &base, &extrusionOrigin, false );
874 }
875 }
876 }
877 }
878 catch ( std::runtime_error &err )
879 {
880 mError = err.what();
881 }
882 catch ( ... )
883 {
884 mError = QObject::tr( "An unknown error occurred" );
885 }
886 }
887 }
888
889 // add walls if extrusion is enabled
890 if ( extrusionHeight != 0 && buildWalls )
891 {
892 _makeWalls( *exterior, false, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOrigin.x(), mOrigin.y(), mOrigin.z(), mTextureRotation, mOutputZUp );
893
894 for ( int i = 0; i < polygon.numInteriorRings(); ++i )
895 _makeWalls( *qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ), true, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOrigin.x(), mOrigin.y(), mOrigin.z(), mTextureRotation, mOutputZUp );
896 }
897}
898
900{
901 if ( mData.size() == 0 )
902 return 0;
903
904 return mData.size() / ( stride() / sizeof( float ) );
905}
906
907std::unique_ptr<QgsMultiPolygon> QgsTessellator::asMultiPolygon() const
908{
909 auto mp = std::make_unique< QgsMultiPolygon >();
910 const auto nVals = mData.size();
911 mp->reserve( nVals / 9 );
912 for ( auto i = decltype( nVals ) {0}; i + 8 < nVals; i += 9 )
913 {
914 if ( mOutputZUp )
915 {
916 const QgsPoint p1( mData[i + 0], mData[i + 1], mData[i + 2] );
917 const QgsPoint p2( mData[i + 3], mData[i + 4], mData[i + 5] );
918 const QgsPoint p3( mData[i + 6], mData[i + 7], mData[i + 8] );
919 mp->addGeometry( new QgsTriangle( p1, p2, p3 ) );
920 }
921 else
922 {
923 // tessellator geometry is x, z, -y
924 const QgsPoint p1( mData[i + 0], -mData[i + 2], mData[i + 1] );
925 const QgsPoint p2( mData[i + 3], -mData[i + 5], mData[i + 4] );
926 const QgsPoint p3( mData[i + 6], -mData[i + 8], mData[i + 7] );
927 mp->addGeometry( new QgsTriangle( p1, p2, p3 ) );
928 }
929 }
930 return mp;
931}
@ CounterClockwise
Counter-clockwise direction.
Definition qgis.h:3493
@ Clockwise
Clockwise direction.
Definition qgis.h:3492
TriangulationAlgorithm
Triangulation algorithms.
Definition qgis.h:6442
QFlags< ExtrusionFace > ExtrusionFaces
Tessellator extrusion face types.
Definition qgis.h:6434
@ PointZ
PointZ.
Definition qgis.h:299
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:36
Qgis::AngularDirection orientation() const
Returns the curve's orientation, e.g.
Definition qgscurve.cpp:287
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:53
void setY(double y)
Sets the point's y-coordinate.
Definition qgspoint.h:341
void setX(double x)
Sets the point's x-coordinate.
Definition qgspoint.h:330
double z
Definition qgspoint.h:58
double x
Definition qgspoint.h:56
void setZ(double z)
Sets the point's z-coordinate.
Definition qgspoint.h:354
double y
Definition qgspoint.h:57
Polygon geometry type.
Definition qgspolygon.h:37
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.
double xMinimum
double yMinimum
std::unique_ptr< QgsMultiPolygon > asMultiPolygon() const
Returns the triangulation as a multipolygon geometry.
float textureRotation() const
Returns the rotation of texture UV coordinates (in degrees).
void setTextureRotation(float rotation)
Sets the rotation of texture UV coordinates (in degrees).
void setOrigin(const QgsVector3D &origin)
Sets the origin point of the map.
void setTriangulationAlgorithm(Qgis::TriangulationAlgorithm algorithm)
Sets the triangulation algorithm.
int stride() const
Returns size of one vertex entry in bytes.
void setBounds(const QgsRectangle &bounds)
Sets scaling and the bounds of the input geometry coordinates.
void addPolygon(const QgsPolygon &polygon, float extrusionHeight)
Tessellates a triangle and adds its vertex entries to the output data array.
QgsVector3D origin() const
Returns the origin point of the map.
void setInvertNormals(bool invertNormals)
Sets whether normals should be inverted (true) or not (false).
void setBackFacesEnabled(bool addBackFaces)
Sets whether back faces should be added to the output data (true) or not (false).
int dataVerticesCount() const
Returns the number of vertices stored in the output data array.
void setExtrusionFaces(Qgis::ExtrusionFaces faces)
Sets which faces should be generated during extrusion.
void setInputZValueIgnored(bool ignore)
Sets whether Z values from the input geometries are ignored (true) or not (false).
void setAddTextureUVs(bool addTextureUVs)
Sets whether texture UV coordinates should be added to the output data (true) or not (false).
void setAddNormals(bool addNormals)
Sets whether normals should be added to the output data (true) or not (false).
Triangle geometry type.
Definition qgstriangle.h:33
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
static Q_INVOKABLE bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into allowing algorithms to be written in pure substantial changes are required in order to port existing x Processing algorithms for QGIS x The most significant changes are outlined not GeoAlgorithm For algorithms which operate on features one by consider subclassing the QgsProcessingFeatureBasedAlgorithm class This class allows much of the boilerplate code for looping over features from a vector layer to be bypassed and instead requires implementation of a processFeature method Ensure that your algorithm(or algorithm 's parent class) implements the new pure virtual createInstance(self) call
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6900
T qgsgeometry_cast(QgsAbstractGeometry *geom)
double _round_coord(double x)
double _minimum_distance_between_coordinates(const QgsPolygon &polygon)
std::size_t operator()(const std::pair< float, float > pair) const