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