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