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