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