QGIS API Documentation 3.99.0-Master (a8f284845db)
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
49void QgsTessellator::addExtrusionWallQuad( const QVector3D &pt1, const QVector3D &pt2, float height )
50{
51 const float dx = pt2.x() - pt1.x();
52 const float dy = pt2.y() - pt1.y();
53
54 // perpendicular vector in plane to [x,y] is [-y,x]
55 QVector3D vn = mOutputZUp ? 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 = pt1.x();
70 v0 = pt1.z() + height;
71
72 u1 = pt2.x();
73 v1 = pt2.z() + height;
74
75 u2 = pt1.x();
76 v2 = pt1.z();
77
78 u3 = pt2.x();
79 v3 = pt2.z();
80 }
81 else
82 {
83 // consider y and z as the texture coowallsTextureRotationrdinates
84 u0 = -pt1.y();
85 v0 = pt1.z() + height;
86
87 u1 = -pt2.y();
88 v1 = pt2.z() + height;
89
90 u2 = -pt1.y();
91 v2 = pt1.z();
92
93 u3 = -pt2.y();
94 v3 = pt2.z();
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( u3 );
107 textureCoordinates.push_back( v3 );
108
109 for ( int i = 0; i < textureCoordinates.size(); i += 2 )
110 {
111 const std::pair<float, float> rotated = rotateCoords( textureCoordinates[i], textureCoordinates[i + 1], 0, 0, mTextureRotation );
112 textureCoordinates[i] = rotated.first;
113 textureCoordinates[i + 1] = rotated.second;
114 }
115
116 // triangle 1 vertex 1
117 mIndexBuffer << uniqueVertexCount();
118 if ( mOutputZUp )
119 mData << pt1.x() << pt1.y() << pt1.z() + height;
120 else
121 mData << pt1.x() << pt1.z() + height << -pt1.y();
122 if ( mAddNormals )
123 mData << vn.x() << vn.y() << vn.z();
124 if ( mAddTextureCoords )
125 mData << textureCoordinates[0] << textureCoordinates[1];
126
127 // triangle 1 vertex 2
128 mIndexBuffer << uniqueVertexCount();
129 if ( mOutputZUp )
130 mData << pt2.x() << pt2.y() << pt2.z() + height;
131 else
132 mData << pt2.x() << pt2.z() + height << -pt2.y();
133 if ( mAddNormals )
134 mData << vn.x() << vn.y() << vn.z();
135 if ( mAddTextureCoords )
136 mData << textureCoordinates[2] << textureCoordinates[3];
137
138 // triangle 1 vertex 3
139 mIndexBuffer << uniqueVertexCount();
140 if ( mOutputZUp )
141 mData << pt1.x() << pt1.y() << pt1.z();
142 else
143 mData << pt1.x() << pt1.z() << -pt1.y();
144 if ( mAddNormals )
145 mData << vn.x() << vn.y() << vn.z();
146 if ( mAddTextureCoords )
147 mData << textureCoordinates[4] << textureCoordinates[5];
148
149 // triangle 2 vertex 1
150 mIndexBuffer << uniqueVertexCount() - 1;
151
152 // triangle 2 vertex 2
153 mIndexBuffer << uniqueVertexCount() - 2;
154
155 // triangle 2 vertex 3
156 mIndexBuffer << uniqueVertexCount();
157 if ( mOutputZUp )
158 mData << pt2.x() << pt2.y() << pt2.z();
159 else
160 mData << pt2.x() << pt2.z() << -pt2.y();
161 if ( mAddNormals )
162 mData << vn.x() << vn.y() << vn.z();
163 if ( mAddTextureCoords )
164 mData << textureCoordinates[6] << textureCoordinates[7];
165}
166
168
169QgsTessellator::QgsTessellator( double originX, double originY, bool addNormals, bool invertNormals, bool addBackFaces, bool noZ,
170 bool addTextureCoords, int facade, float textureRotation )
171{
172 setOrigin( QgsVector3D( originX, originY, 0 ) );
173 setAddNormals( addNormals );
174 setInvertNormals( invertNormals );
175 setExtrusionFacesLegacy( facade );
176 setBackFacesEnabled( addBackFaces );
177 setAddTextureUVs( addTextureCoords );
180}
181
182QgsTessellator::QgsTessellator( const QgsRectangle &bounds, bool addNormals, bool invertNormals, bool addBackFaces, bool noZ,
183 bool addTextureCoords, int facade, float textureRotation )
184{
185 setAddTextureUVs( addTextureCoords );
186 setExtrusionFacesLegacy( facade );
187 setBounds( bounds );
188 setAddNormals( addNormals );
189 setInvertNormals( invertNormals );
190 setBackFacesEnabled( addBackFaces );
193}
194
196{
197 mOrigin = origin;
198}
199
201{
202 mOrigin = QgsVector3D( bounds.xMinimum(), bounds.yMinimum(), 0 );
203 mScale = bounds.isNull() ? 1.0 : std::max( 10000.0 / bounds.width(), 10000.0 / bounds.height() );
204}
205
207{
208 mInputZValueIgnored = ignore;
209}
210
212{
213 mExtrusionFaces = faces;
214}
215
216void QgsTessellator::setExtrusionFacesLegacy( int facade )
217{
218 switch ( facade )
219 {
220 case 0:
221 mExtrusionFaces = Qgis::ExtrusionFace::NoFace;
222 break;
223 case 1:
224 mExtrusionFaces = Qgis::ExtrusionFace::Walls;
225 break;
226 case 2:
227 mExtrusionFaces = Qgis::ExtrusionFace::Roof;
228 break;
229 case 3:
231 break;
232 case 7:
234 break;
235 default:
236 break;
237 }
238}
239
241{
242 mTextureRotation = rotation;
243}
244
245void QgsTessellator::setBackFacesEnabled( bool addBackFaces )
246{
247 mAddBackFaces = addBackFaces;
248}
249
250void QgsTessellator::setInvertNormals( bool invertNormals )
251{
252 mInvertNormals = invertNormals;
253}
254
255void QgsTessellator::setAddNormals( bool addNormals )
256{
257 mAddNormals = addNormals;
258 updateStride();
259}
260
261void QgsTessellator::setAddTextureUVs( bool addTextureUVs )
262{
263 mAddTextureCoords = addTextureUVs;
264 updateStride();
265}
266
271
272void QgsTessellator::updateStride()
273{
274 mStride = 3 * sizeof( float );
275 if ( mAddNormals )
276 mStride += 3 * sizeof( float );
277 if ( mAddTextureCoords )
278 mStride += 2 * sizeof( float );
279}
280
281void QgsTessellator::makeWalls( const QgsLineString &ring, bool ccw, float extrusionHeight )
282{
283 // we need to find out orientation of the ring so that the triangles we generate
284 // face the right direction
285 // (for exterior we want clockwise order, for holes we want counter-clockwise order)
286 const bool is_counter_clockwise = ring.orientation() == Qgis::AngularDirection::CounterClockwise;
287
288 QgsPoint pt;
289 QgsPoint ptPrev = ring.pointN( is_counter_clockwise == ccw ? 0 : ring.numPoints() - 1 );
290 for ( int i = 1; i < ring.numPoints(); ++i )
291 {
292 pt = ring.pointN( is_counter_clockwise == ccw ? i : ring.numPoints() - i - 1 );
293
294 const QVector3D pt1(
295 static_cast<float>( ptPrev.x() - mOrigin.x() ),
296 static_cast<float>( ptPrev.y() - mOrigin.y() ),
297 static_cast<float>( std::isnan( ptPrev.z() ) ? 0 : ptPrev.z() - mOrigin.z() ) );
298
299 const QVector3D pt2(
300 static_cast<float>( pt.x() - mOrigin.x() ),
301 static_cast<float>( pt.y() - mOrigin.y() ),
302 static_cast<float>( std::isnan( pt.z() ) ? 0 : pt.z() - mOrigin.z() ) );
303
304 // make a quad
305 addExtrusionWallQuad( pt1, pt2, extrusionHeight );
306 ptPrev = pt;
307 }
308}
309
310static QVector3D calculateNormal( const QgsLineString *curve, double originX, double originY, double originZ, bool invertNormal, float extrusionHeight )
311{
312 if ( !QgsWkbTypes::hasZ( curve->wkbType() ) )
313 {
314 // In case of extrusions, flat polygons always face up
315 if ( extrusionHeight != 0 )
316 return QVector3D( 0, 0, 1 );
317
318 // For non-extrusions, decide based on polygon winding order and invertNormal flag
319 float orientation = 1.f;
321 orientation = -orientation;
322 if ( invertNormal )
323 orientation = -orientation;
324 return QVector3D( 0, 0, orientation );
325 }
326
327 // often we have 3D coordinates, but Z is the same for all vertices
328 // if these flat polygons are extruded, we consider them up-facing regardless of winding order
329 bool sameZ = true;
330 QgsPoint pt1 = curve->pointN( 0 );
331 QgsPoint pt2;
332 for ( int i = 1; i < curve->numPoints(); i++ )
333 {
334 pt2 = curve->pointN( i );
335 if ( pt1.z() != pt2.z() )
336 {
337 sameZ = false;
338 break;
339 }
340 }
341 if ( sameZ && extrusionHeight != 0 )
342 return QVector3D( 0, 0, 1 );
343
344 // Calculate the polygon's normal vector, based on Newell's method
345 // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
346 //
347 // Order of vertices is important here as it determines the front/back face of the polygon
348
349 double nx = 0, ny = 0, nz = 0;
350
351 // shift points by the tessellator's origin - this does not affect normal calculation and it may save us from losing some precision
352 pt1.setX( pt1.x() - originX );
353 pt1.setY( pt1.y() - originY );
354 pt1.setZ( std::isnan( pt1.z() ) ? 0.0 : pt1.z() - originZ );
355 for ( int i = 1; i < curve->numPoints(); i++ )
356 {
357 pt2 = curve->pointN( i );
358 pt2.setX( pt2.x() - originX );
359 pt2.setY( pt2.y() - originY );
360 pt2.setZ( std::isnan( pt2.z() ) ? 0.0 : pt2.z() - originZ );
361
362 nx += ( pt1.y() - pt2.y() ) * ( pt1.z() + pt2.z() );
363 ny += ( pt1.z() - pt2.z() ) * ( pt1.x() + pt2.x() );
364 nz += ( pt1.x() - pt2.x() ) * ( pt1.y() + pt2.y() );
365
366 pt1 = pt2;
367 }
368
369 QVector3D normal( nx, ny, nz );
370 if ( invertNormal )
371 normal = -normal;
372 normal.normalize();
373 return normal;
374}
375
376
377static void normalVectorToXYVectors( const QVector3D &pNormal, QVector3D &pXVector, QVector3D &pYVector )
378{
379 // Here we define the two perpendicular vectors that define the local
380 // 2D space on the plane. They will act as axis for which we will
381 // calculate the projection coordinates of a 3D point to the plane.
382 if ( pNormal.z() > 0.001 || pNormal.z() < -0.001 )
383 {
384 pXVector = QVector3D( 1, 0, -pNormal.x() / pNormal.z() );
385 }
386 else if ( pNormal.y() > 0.001 || pNormal.y() < -0.001 )
387 {
388 pXVector = QVector3D( 1, -pNormal.x() / pNormal.y(), 0 );
389 }
390 else
391 {
392 pXVector = QVector3D( -pNormal.y() / pNormal.x(), 1, 0 );
393 }
394 pXVector.normalize();
395 pYVector = QVector3D::normal( pNormal, pXVector );
396}
397
399{
400 std::size_t operator()( const std::pair<float, float> pair ) const
401 {
402 const std::size_t h1 = std::hash<float>()( pair.first );
403 const std::size_t h2 = std::hash<float>()( pair.second );
404
405 return h1 ^ h2;
406 }
407};
408
409void ringToPoly2tri( const QgsLineString *ring, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> *zHash )
410{
411 const int pCount = ring->numPoints();
412
413 polyline.reserve( pCount );
414
415 const double *srcXData = ring->xData();
416 const double *srcYData = ring->yData();
417 const double *srcZData = ring->zData();
418 std::unordered_set<std::pair<float, float>, float_pair_hash> foundPoints;
419
420 for ( int i = 0; i < pCount - 1; ++i )
421 {
422 const float x = *srcXData++;
423 const float y = *srcYData++;
424
425 const auto res = foundPoints.insert( std::make_pair( x, y ) );
426 if ( !res.second )
427 {
428 // already used this point, don't add a second time
429 continue;
430 }
431
432 p2t::Point *pt2 = new p2t::Point( x, y );
433 polyline.push_back( pt2 );
434 if ( zHash )
435 {
436 ( *zHash )[pt2] = *srcZData++;
437 }
438 }
439}
440
441
442double roundCoord( double x )
443{
444 const double exp = 1e10; // round to 10 decimal digits
445 return round( x * exp ) / exp;
446}
447
448
449static QgsCurve *transformRingToNewBase( const QgsLineString &curve, const QgsPoint &pt0, const QMatrix4x4 *toNewBase, const float scale )
450{
451 const int count = curve.numPoints();
452 QVector<double> x;
453 QVector<double> y;
454 QVector<double> z;
455 x.resize( count );
456 y.resize( count );
457 z.resize( count );
458 double *xData = x.data();
459 double *yData = y.data();
460 double *zData = z.data();
461
462 const double *srcXData = curve.xData();
463 const double *srcYData = curve.yData();
464 const double *srcZData = curve.is3D() ? curve.zData() : nullptr;
465
466 for ( int i = 0; i < count; ++i )
467 {
468 QVector4D v( *srcXData++ - pt0.x(),
469 *srcYData++ - pt0.y(),
470 srcZData ? *srcZData++ - pt0.z() : 0,
471 0 );
472 if ( toNewBase )
473 v = toNewBase->map( v );
474
475 // scale coordinates
476 v.setX( v.x() * scale );
477 v.setY( v.y() * scale );
478
479 // we also round coordinates before passing them to poly2tri triangulation in order to fix possible numerical
480 // 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).
481 // See TestQgsTessellator::testIssue17745().
482 //
483 // A hint for a similar issue: https://github.com/greenm01/poly2tri/issues/99
484 //
485 // The collinear tests uses epsilon 1e-12. Seems rounding to 12 places you still
486 // can get problems with this test when points are pretty much on a straight line.
487 // I suggest you round to 10 decimals for stability and you can live with that
488 // precision.
489 *xData++ = roundCoord( v.x() );
490 *yData++ = roundCoord( v.y() );
491 *zData++ = roundCoord( v.z() );
492 }
493 return new QgsLineString( x, y, z );
494}
495
496
497QgsPolygon *transformPolygonToNewBase( const QgsPolygon &polygon, const QgsPoint &pt0, const QMatrix4x4 *toNewBase, const float scale )
498{
499 QgsPolygon *p = new QgsPolygon;
500 p->setExteriorRing( transformRingToNewBase( *qgsgeometry_cast< const QgsLineString * >( polygon.exteriorRing() ), pt0, toNewBase, scale ) );
501 for ( int i = 0; i < polygon.numInteriorRings(); ++i )
502 p->addInteriorRing( transformRingToNewBase( *qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ), pt0, toNewBase, scale ) );
503 return p;
504}
505
506
508{
509 double min_d = 1e20;
510
511 std::vector< const QgsLineString * > rings;
512 rings.reserve( 1 + polygon.numInteriorRings() );
513 rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.exteriorRing() ) );
514 for ( int i = 0; i < polygon.numInteriorRings(); ++i )
515 rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ) );
516
517 for ( const QgsLineString *ring : rings )
518 {
519 const int numPoints = ring->numPoints();
520 if ( numPoints <= 1 )
521 continue;
522
523 const double *srcXData = ring->xData();
524 const double *srcYData = ring->yData();
525 double x0 = *srcXData++;
526 double y0 = *srcYData++;
527 for ( int i = 1; i < numPoints; ++i )
528 {
529 const double x1 = *srcXData++;
530 const double y1 = *srcYData++;
531 const double d = QgsGeometryUtilsBase::sqrDistance2D( x0, y0, x1, y1 );
532 if ( d < min_d )
533 min_d = d;
534 x0 = x1;
535 y0 = y1;
536 }
537 }
538
539 return min_d != 1e20 ? std::sqrt( min_d ) : 1e20;
540}
541
543{
544 return QByteArray( reinterpret_cast<const char *>( mData.constData() ), sizeof( float ) * mData.size() );
545}
546
548{
549 return QByteArray( reinterpret_cast<const char *>( mIndexBuffer.constData() ), sizeof( uint32_t ) * mIndexBuffer.size() );
550}
551
552void QgsTessellator::calculateBaseTransform( const QVector3D &pNormal, QMatrix4x4 *base ) const
553{
554 if ( !mInputZValueIgnored && pNormal != QVector3D( 0, 0, 1 ) )
555 {
556 // this is not a horizontal plane - need to reproject to a new base so that
557 // we can do the triangulation in a plane
558 QVector3D pXVector, pYVector;
559 normalVectorToXYVectors( pNormal, pXVector, pYVector );
560
561 // so now we have three orthogonal unit vectors defining new base
562 // let's build transform matrix. We actually need just a 3x3 matrix,
563 // but Qt does not have good support for it, so using 4x4 matrix instead.
564 *base = QMatrix4x4(
565 pXVector.x(), pXVector.y(), pXVector.z(), 0,
566 pYVector.x(), pYVector.y(), pYVector.z(), 0,
567 pNormal.x(), pNormal.y(), pNormal.z(), 0,
568 0, 0, 0, 0 );
569 }
570 else
571 {
572 base->setToIdentity();
573 }
574}
575
576QVector3D QgsTessellator::applyTransformWithExtrusion(
577 const QVector3D point,
578 float extrusionHeight,
579 QMatrix4x4 *transformMatrix,
580 const QgsPoint *originOffset
581)
582{
583 const float z = mInputZValueIgnored ? 0.0f : point.z();
584 QVector4D pt( point.x(), point.y(), z, 0 );
585
586 pt = *transformMatrix * pt;
587
588 const double fx = pt.x() - mOrigin.x() + originOffset->x();
589 const double fy = pt.y() - mOrigin.y() + originOffset->y();
590 const double baseHeight = mInputZValueIgnored ? 0 : pt.z() - mOrigin.z() + originOffset->z();
591 const double fz = mInputZValueIgnored ? 0.0 : ( baseHeight + extrusionHeight );
592
593 if ( baseHeight < mZMin )
594 mZMin = static_cast<float>( baseHeight );
595 if ( baseHeight > mZMax )
596 mZMax = static_cast<float>( baseHeight );
597 if ( fz > mZMax )
598 mZMax = static_cast<float>( fz );
599
600 // NOLINTBEGIN(bugprone-branch-clone);
601 if ( mOutputZUp )
602 return QVector3D( static_cast<float>( fx ), static_cast<float>( fy ), static_cast<float>( fz ) );
603 else
604 return QVector3D( static_cast<float>( fx ), static_cast<float>( fz ), static_cast<float>( -fy ) );
605 // NOLINTEND(bugprone-branch-clone)
606}
607
608
609void QgsTessellator::ringToEarcutPoints( const QgsLineString *ring, std::vector<std::array<double, 2>> &polyline, QHash<std::array<double, 2>*, float> *zHash )
610{
611 const int pCount = ring->numPoints();
612
613 polyline.reserve( pCount );
614
615 const double *srcXData = ring->xData();
616 const double *srcYData = ring->yData();
617 const double *srcZData = ring->zData();
618
619 // earcut handles duplicates, we do not need to remove them here
620 for ( int i = 0; i < pCount - 1; ++i )
621 {
622 const float x = static_cast<float>( *srcXData++ );
623 const float y = static_cast<float>( *srcYData++ );
624
625 std::array<double, 2> pt = { x, y };
626 polyline.push_back( pt );
627
628 if ( zHash && srcZData )
629 {
630 ( *zHash )[ &pt ] = *srcZData++;
631 }
632 }
633}
634
635std::vector<QVector3D> QgsTessellator::generateConstrainedDelaunayTriangles( const QgsPolygon *polygonNew )
636{
637 QList<std::vector<p2t::Point *>> polylinesToDelete;
638 QHash<p2t::Point *, float> z;
639
640 // polygon exterior
641 std::vector<p2t::Point *> polyline;
642 ringToPoly2tri( qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() ), polyline, mInputZValueIgnored ? nullptr : &z );
643 polylinesToDelete << polyline;
644
645 p2t::CDT cdt = p2t::CDT( polyline );
646
647 // polygon holes
648 for ( int i = 0; i < polygonNew->numInteriorRings(); ++i )
649 {
650 std::vector<p2t::Point *> holePolyline;
651 const QgsLineString *hole = qgsgeometry_cast< const QgsLineString *>( polygonNew->interiorRing( i ) );
652
653 ringToPoly2tri( hole, holePolyline, mInputZValueIgnored ? nullptr : &z );
654
655 cdt.AddHole( holePolyline );
656 polylinesToDelete << holePolyline;
657 }
658
659 cdt.Triangulate();
660 std::vector<p2t::Triangle *> triangles = cdt.GetTriangles();
661
662 std::vector<QVector3D> trianglePoints;
663 trianglePoints.reserve( triangles.size() * 3 );
664
665 for ( p2t::Triangle *t : triangles )
666 {
667 trianglePoints.emplace_back( t->GetPoint( 0 )->x / mScale, t->GetPoint( 0 )->y / mScale, z.value( t->GetPoint( 0 ) ) );
668 trianglePoints.emplace_back( t->GetPoint( 1 )->x / mScale, t->GetPoint( 1 )->y / mScale, z.value( t->GetPoint( 1 ) ) );
669 trianglePoints.emplace_back( t->GetPoint( 2 )->x / mScale, t->GetPoint( 2 )->y / mScale, z.value( t->GetPoint( 2 ) ) );
670 }
671
672 for ( int i = 0; i < polylinesToDelete.count(); ++i )
673 qDeleteAll( polylinesToDelete[i] );
674
675 return trianglePoints;
676}
677
678std::vector<QVector3D> QgsTessellator::generateEarcutTriangles( const QgsPolygon *polygonNew )
679{
680 QHash<std::array<double, 2>*, float> z;
681 std::vector<std::vector<std::array<double, 2>>> rings;
682 std::vector<std::array<double, 2>> polyline;
683
684 ringToEarcutPoints( qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() ), polyline, mInputZValueIgnored ? nullptr : &z );
685 rings.push_back( polyline );
686
687 for ( int i = 0; i < polygonNew->numInteriorRings(); ++i )
688 {
689 std::vector<std::array<double, 2>> holePolyline;
690 ringToEarcutPoints( qgsgeometry_cast<const QgsLineString *>( polygonNew->interiorRing( i ) ), holePolyline, mInputZValueIgnored ? nullptr : &z );
691 rings.push_back( holePolyline );
692 }
693
694 std::vector<std::array<double, 2>> points;
695 for ( const auto &ring : rings )
696 {
697 points.insert( points.end(), ring.begin(), ring.end() );
698 }
699
700 std::vector<uint32_t> indices = mapbox::earcut<uint32_t>( rings );
701 std::vector<QVector3D> trianglePoints;
702 trianglePoints.reserve( points.size() );
703
704 for ( size_t i = 0; i < indices.size(); i++ )
705 {
706 uint32_t vertexIndex = indices[ i ];
707 std::array<double, 2> vertex = points[ vertexIndex ];
708
709 double x = vertex[ 0 ];
710 double y = vertex[ 1 ];
711
712 float zValue = z.value( &vertex, 0.0f );
713
714 trianglePoints.emplace_back( x / mScale, y / mScale, zValue );
715 }
716
717 return trianglePoints;
718}
719
720void QgsTessellator::addVertex( const QVector3D &point, const QVector3D &normal, float extrusionHeight, QMatrix4x4 *transformMatrix, const QgsPoint *originOffset, QHash<VertexPoint, unsigned int> *vertexBuffer, const size_t &vertexBufferOffset )
721{
722 const QVector3D pt = applyTransformWithExtrusion( point, extrusionHeight, transformMatrix, originOffset );
723 const VertexPoint vertex( pt, normal );
724 if ( vertexBuffer->contains( vertex ) )
725 {
726 unsigned int index = vertexBuffer->value( vertex );
727 mIndexBuffer << vertexBufferOffset + index;
728 }
729 else
730 {
731 unsigned int index = vertexBuffer->size();
732 vertexBuffer->insert( vertex, index );
733 mIndexBuffer << vertexBufferOffset + index;
734
735 mData << pt.x() << pt.y() << pt.z();
736 if ( mAddNormals )
737 {
738 mData << normal.x() << normal.y() << normal.z();
739 }
740 if ( mAddTextureCoords )
741 {
742 const std::pair<float, float> pr = rotateCoords( static_cast<float>( point.x() ), static_cast<float>( point.y() ), 0.0f, 0.0f, mTextureRotation );
743 mData << pr.first << pr.second;
744 }
745 }
746}
747
748void QgsTessellator::addVertex( const QVector3D &point, const QVector3D &normal, float extrusionHeight, QMatrix4x4 *transformMatrix, const QgsPoint *originOffset )
749{
750 const QVector3D pt = applyTransformWithExtrusion( point, extrusionHeight, transformMatrix, originOffset );
751 mIndexBuffer << uniqueVertexCount();
752 mData << pt.x() << pt.y() << pt.z();
753 if ( mAddNormals )
754 {
755 mData << normal.x() << normal.y() << normal.z();
756 }
757 if ( mAddTextureCoords )
758 {
759 const std::pair<float, float> pr = rotateCoords( static_cast<float>( point.x() ), static_cast<float>( point.y() ), 0.0f, 0.0f, mTextureRotation );
760 mData << pr.first << pr.second;
761 }
762}
763
764void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeight )
765{
767 if ( !exterior )
768 return;
769
770 const QVector3D pNormal = !mInputZValueIgnored ? calculateNormal( exterior, mOrigin.x(), mOrigin.y(), mOrigin.z(), mInvertNormals, extrusionHeight ) : QVector3D();
771 const int pCount = exterior->numPoints();
772 if ( pCount == 0 )
773 return;
774
775 QMatrix4x4 base; // identity matrix by default
776 const QgsPoint ptStart( exterior->startPoint() );
777 const QgsPoint extrusionOrigin( Qgis::WkbType::PointZ, ptStart.x(), ptStart.y(), std::isnan( ptStart.z() ) ? 0 : ptStart.z() );
778 std::unique_ptr<QgsPolygon> polygonNew;
779
780 if ( !mInputZValueIgnored && !qgsDoubleNear( pNormal.length(), 1, 0.001 ) )
781 return; // this should not happen - pNormal should be normalized to unit length
782
783 const bool buildWalls = mExtrusionFaces.testFlag( Qgis::ExtrusionFace::Walls );
784 const bool buildFloor = mExtrusionFaces.testFlag( Qgis::ExtrusionFace::Floor );
785 const bool buildRoof = mExtrusionFaces.testFlag( Qgis::ExtrusionFace::Roof );
786
787 if ( buildFloor || buildRoof )
788 {
789 calculateBaseTransform( pNormal, &base );
790 polygonNew.reset( transformPolygonToNewBase( polygon, extrusionOrigin, &base, mScale ) );
791
792 QVector3D normal;
793 if ( !mOutputZUp )
794 {
795 normal = QVector3D( pNormal.x(), pNormal.z(), - pNormal.y() );
796 }
797 else
798 {
799 normal = pNormal;
800 }
801 // our 3x3 matrix is orthogonal, so for inverse we only need to transpose it
802 base = base.transposed();
803
804 if ( pCount == 4 && polygon.numInteriorRings() == 0 )
805 {
806 Q_ASSERT( polygonNew->exteriorRing()->numPoints() >= 3 );
807
808 const QgsLineString *triangle = qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() );
809 const QVector3D p1( static_cast<float>( triangle->xAt( 0 ) ), static_cast<float>( triangle->yAt( 0 ) ), static_cast<float>( triangle->zAt( 0 ) ) );
810 const QVector3D p2( static_cast<float>( triangle->xAt( 1 ) ), static_cast<float>( triangle->yAt( 1 ) ), static_cast<float>( triangle->zAt( 1 ) ) );
811 const QVector3D p3( static_cast<float>( triangle->xAt( 2 ) ), static_cast<float>( triangle->yAt( 2 ) ), static_cast<float>( triangle->zAt( 2 ) ) );
812 std::array<QVector3D, 3> points = { p1, p2, p3 };
813
814 for ( const QVector3D &point : points )
815 {
816 addVertex( point, normal, extrusionHeight, &base, &extrusionOrigin );
817 }
818
819 if ( mAddBackFaces )
820 {
821 for ( size_t i = points.size(); i-- > 0; )
822 {
823 const QVector3D &point = points[ i ];
824 addVertex( point, -normal, extrusionHeight, &base, &extrusionOrigin );
825 }
826 }
827
828 if ( extrusionHeight != 0 && buildFloor )
829 {
830 for ( const QVector3D &point : points )
831 {
832 addVertex( point, normal, 0.0, &base, &extrusionOrigin );
833 }
834
835 if ( mAddBackFaces )
836 {
837 for ( size_t i = points.size(); i-- > 0; )
838 {
839 const QVector3D &point = points[ i ];
840 addVertex( point, -normal, 0.0, &base, &extrusionOrigin );
841 }
842 }
843 }
844 }
845 else // we need to triangulate the polygon
846 {
847 if ( minimumDistanceBetweenCoordinates( *polygonNew ) < 0.001 )
848 {
849 // when the distances between coordinates of input points are very small,
850 // the triangulation likes to crash on numerical errors - when the distances are ~ 1e-5
851 // Assuming that the coordinates should be in a projected CRS, we should be able
852 // to simplify geometries that may cause problems and avoid possible crashes
853 const QgsGeometry polygonSimplified = QgsGeometry( polygonNew->clone() ).simplify( 0.001 );
854 if ( polygonSimplified.isNull() )
855 {
856 mError = QObject::tr( "geometry simplification failed - skipping" );
857 return;
858 }
859 const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet() );
860 if ( !polygonSimplifiedData || minimumDistanceBetweenCoordinates( *polygonSimplifiedData ) < 0.001 )
861 {
862 // Failed to fix that. It could be a really tiny geometry... or maybe they gave us
863 // geometry in unprojected lat/lon coordinates
864 mError = QObject::tr( "geometry's coordinates are too close to each other and simplification failed - skipping" );
865 return;
866 }
867 else
868 {
869 polygonNew.reset( polygonSimplifiedData->clone() );
870 }
871 }
872
873 // run triangulation and write vertices to the output data array
874 try
875 {
876 std::vector<QVector3D> trianglePoints;
877 switch ( mTriangulationAlgorithm )
878 {
880 trianglePoints = generateConstrainedDelaunayTriangles( polygonNew.get() );
881 break;
883 trianglePoints = generateEarcutTriangles( polygonNew.get() );
884 break;
885 }
886
887 if ( trianglePoints.empty() )
888 {
889 mError = QObject::tr( "Failed to triangulate polygon." );
890 return;
891 }
892
893 Q_ASSERT( trianglePoints.size() % 3 == 0 );
894
895 mData.reserve( mData.size() + trianglePoints.size() * 3 * ( stride() / sizeof( float ) ) );
896
897 const size_t vertexBufferSize = uniqueVertexCount();
898 QHash<VertexPoint, unsigned int> vertexBuffer;
899 for ( size_t i = 0; i < trianglePoints.size(); i += 3 )
900 {
901 const std::array<QVector3D, 3> points = { trianglePoints[ i + 0 ], trianglePoints[ i + 1 ], trianglePoints[ i + 2 ] };
902
903 // roof
904 for ( const QVector3D &point : points )
905 {
906 addVertex( point, normal, extrusionHeight, &base, &extrusionOrigin, &vertexBuffer, vertexBufferSize );
907 }
908
909 if ( mAddBackFaces )
910 {
911 for ( size_t i = points.size(); i-- > 0; )
912 {
913 const QVector3D &point = points[ i ];
914 addVertex( point, -normal, extrusionHeight, &base, &extrusionOrigin, &vertexBuffer, vertexBufferSize );
915 }
916 }
917
918 if ( extrusionHeight != 0 && buildFloor )
919 {
920 for ( const QVector3D &point : points )
921 {
922 addVertex( point, normal, 0.0, &base, &extrusionOrigin, &vertexBuffer, vertexBufferSize );
923 }
924
925 if ( mAddBackFaces )
926 {
927 for ( size_t i = points.size(); i-- > 0; )
928 {
929 const QVector3D &point = points[ i ];
930 addVertex( point, -normal, 0.0, &base, &extrusionOrigin, &vertexBuffer, vertexBufferSize );
931 }
932 }
933 }
934 }
935 }
936 catch ( std::runtime_error &err )
937 {
938 mError = err.what();
939 }
940 catch ( ... )
941 {
942 mError = QObject::tr( "An unknown error occurred" );
943 }
944 }
945 }
946
947 // add walls if extrusion is enabled
948 if ( extrusionHeight != 0 && buildWalls )
949 {
950 makeWalls( *exterior, false, extrusionHeight );
951 for ( int i = 0; i < polygon.numInteriorRings(); ++i )
952 makeWalls( *qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ), true, extrusionHeight );
953 }
954}
955
957{
958 return mIndexBuffer.size();
959}
960
961std::unique_ptr<QgsMultiPolygon> QgsTessellator::asMultiPolygon() const
962{
963 auto mp = std::make_unique< QgsMultiPolygon >();
964 const size_t nVals = mIndexBuffer.size();
965
966 Q_ASSERT( nVals % 3 == 0 );
967
968 mp->reserve( nVals / 3 );
969 const size_t noOfElements = stride() / sizeof( float );
970
971 for ( size_t i = 0; i + 2 < nVals; i += 3 )
972 {
973 const uint32_t index1 = mIndexBuffer[ i ] * noOfElements;
974 const uint32_t index2 = mIndexBuffer[ i + 1 ] * noOfElements;
975 const uint32_t index3 = mIndexBuffer[ i + 2 ] * noOfElements;
976
977 if ( mOutputZUp )
978 {
979 const QgsPoint p1( mData[ index1 ], mData[ index1 + 1 ], mData[ index1 + 2 ] );
980 const QgsPoint p2( mData[ index2 ], mData[ index2 + 1 ], mData[ index2 + 2 ] );
981 const QgsPoint p3( mData[ index3 ], mData[ index3 + 1 ], mData[ index3 + 2 ] );
982 mp->addGeometry( new QgsTriangle( p1, p2, p3 ) );
983 }
984 else
985 {
986 // tessellator geometry is x, z, -y
987 const QgsPoint p1( mData[ index1 ], -mData[ index1 + 2 ], mData[ index1 + 1 ] );
988 const QgsPoint p2( mData[ index2 ], -mData[ index2 + 2 ], mData[ index2 + 1 ] );
989 const QgsPoint p3( mData[ index3 ], -mData[ index3 + 2 ], mData[ index3 + 1 ] );
990 mp->addGeometry( new QgsTriangle( p1, p2, p3 ) );
991 }
992 }
993
994 return mp;
995}
996
997QVector<float> QgsTessellator::data() const
998{
999 const size_t n = mIndexBuffer.size();
1000 if ( n == 0 )
1001 return QVector<float>();
1002
1003 QVector<float> tData;
1004 size_t noOfElements = stride() / sizeof( float );
1005 tData.reserve( n * noOfElements );
1006
1007 for ( auto &index : mIndexBuffer )
1008 {
1009 for ( size_t i = 0; i < noOfElements; i++ )
1010 {
1011 tData << mData[ index * noOfElements + i ];
1012 }
1013 }
1014
1015 return tData;
1016}
1017
1019{
1020 if ( mData.size() == 0 )
1021 return 0;
1022
1023 return mData.size() / ( stride() / sizeof( float ) );
1024}
@ CounterClockwise
Counter-clockwise direction.
Definition qgis.h:3520
@ Clockwise
Clockwise direction.
Definition qgis.h:3519
TriangulationAlgorithm
Triangulation algorithms.
Definition qgis.h:6492
QFlags< ExtrusionFace > ExtrusionFaces
Tessellator extrusion face types.
Definition qgis.h:6484
@ PointZ
PointZ.
Definition qgis.h:310
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:370
void setX(double x)
Sets the point's x-coordinate.
Definition qgspoint.h:359
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:383
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.
Q_DECL_DEPRECATED QVector< float > data() const
Returns array of triangle vertex data.
QByteArray vertexBuffer() const
Returns vertex buffer for the generated points.
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.
int uniqueVertexCount() const
Returns unique vertex count.
QByteArray indexBuffer() const
Returns index buffer for the generated points.
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:6950
T qgsgeometry_cast(QgsAbstractGeometry *geom)
double minimumDistanceBetweenCoordinates(const QgsPolygon &polygon)
void ringToPoly2tri(const QgsLineString *ring, std::vector< p2t::Point * > &polyline, QHash< p2t::Point *, float > *zHash)
QgsPolygon * transformPolygonToNewBase(const QgsPolygon &polygon, const QgsPoint &pt0, const QMatrix4x4 *toNewBase, const float scale)
double roundCoord(double x)
std::size_t operator()(const std::pair< float, float > pair) const