QGIS API Documentation 4.1.0-Master (01362494303)
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 QgsTessellator::ringToEarcutPoints( const QgsLineString *ring, std::vector<std::array<double, 2>> &polyline, QHash<std::array<double, 2> *, float> *zHash )
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 float x = static_cast<float>( *srcXData++ );
572 const float y = static_cast<float>( *srcYData++ );
573
574 std::array<double, 2> pt = { x, y };
575 polyline.push_back( pt );
576
577 if ( zHash && srcZData )
578 {
579 ( *zHash )[&pt] = *srcZData++;
580 }
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 QHash<std::array<double, 2> *, float> z;
630 std::vector<std::vector<std::array<double, 2>>> rings;
631 std::vector<std::array<double, 2>> polyline;
632
633 ringToEarcutPoints( qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() ), polyline, mInputZValueIgnored ? nullptr : &z );
634 rings.push_back( polyline );
635
636 for ( int i = 0; i < polygonNew->numInteriorRings(); ++i )
637 {
638 std::vector<std::array<double, 2>> holePolyline;
639 ringToEarcutPoints( qgsgeometry_cast<const QgsLineString *>( polygonNew->interiorRing( i ) ), holePolyline, mInputZValueIgnored ? nullptr : &z );
640 rings.push_back( holePolyline );
641 }
642
643 std::vector<std::array<double, 2>> points;
644 for ( const auto &ring : rings )
645 {
646 points.insert( points.end(), ring.begin(), ring.end() );
647 }
648
649 std::vector<uint32_t> indices = mapbox::earcut<uint32_t>( rings );
650 std::vector<QVector3D> trianglePoints;
651 trianglePoints.reserve( points.size() );
652
653 for ( size_t i = 0; i < indices.size(); i++ )
654 {
655 uint32_t vertexIndex = indices[i];
656 std::array<double, 2> vertex = points[vertexIndex];
657
658 double x = vertex[0];
659 double y = vertex[1];
660
661 float zValue = z.value( &vertex, 0.0f );
662
663 trianglePoints.emplace_back( x / mScale, y / mScale, zValue );
664 }
665
666 return trianglePoints;
667}
668
669void QgsTessellator::addVertex(
670 const QVector3D &point,
671 const QVector3D &normal,
672 const QVector4D &tangent,
673 float extrusionHeight,
674 QMatrix4x4 *transformMatrix,
675 const QgsPoint *originOffset,
676 QHash<VertexPoint, unsigned int> *vertexBuffer,
677 const size_t &vertexBufferOffset,
678 bool isFloor
679)
680{
681 const QVector3D pt = applyTransformWithExtrusion( point, extrusionHeight, transformMatrix, originOffset );
682 const VertexPoint vertex( pt, normal, tangent );
683 if ( vertexBuffer->contains( vertex ) )
684 {
685 unsigned int index = vertexBuffer->value( vertex );
686 mIndexBuffer << vertexBufferOffset + index;
687 }
688 else
689 {
690 unsigned int index = vertexBuffer->size();
691 vertexBuffer->insert( vertex, index );
692 mIndexBuffer << vertexBufferOffset + index;
693
694 mData << pt.x() << pt.y() << pt.z();
695 if ( mAddNormals )
696 {
697 mData << normal.x() << normal.y() << normal.z();
698 }
699 if ( mAddTangents )
700 {
701 mData << tangent.x() << tangent.y() << tangent.z() << tangent.w();
702 }
703 if ( mAddTextureCoords )
704 {
705 // flip y coordinate -- source texture images will have increasing y from top-to-bottom,
706 // but 3d textures need to increase from bottom-to-top
707 float u = pt.x();
708 float v = -pt.y();
709
710 if ( isFloor )
711 {
712 u = -u;
713 }
714
715 mData << u << v;
716 }
717 }
718}
719
720void QgsTessellator::addVertex( const QVector3D &point, const QVector3D &normal, const QVector4D &tangent, float extrusionHeight, QMatrix4x4 *transformMatrix, const QgsPoint *originOffset, bool isFloor )
721{
722 const QVector3D pt = applyTransformWithExtrusion( point, extrusionHeight, transformMatrix, originOffset );
723 mIndexBuffer << uniqueVertexCount();
724 mData << pt.x() << pt.y() << pt.z();
725 if ( mAddNormals )
726 {
727 mData << normal.x() << normal.y() << normal.z();
728 }
729 if ( mAddTangents )
730 {
731 mData << tangent.x() << tangent.y() << tangent.z() << tangent.w();
732 }
733 if ( mAddTextureCoords )
734 {
735 // flip y coordinate -- source texture images will have increasing y from top-to-bottom,
736 // but 3d textures need to increase from bottom-to-top
737 float u = pt.x();
738 float v = -pt.y();
739
740 if ( isFloor )
741 {
742 u = -u;
743 }
744
745 mData << u << v;
746 }
747}
748
749void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeight )
750{
752 if ( !exterior )
753 return;
754
755 const QVector3D pNormal = !mInputZValueIgnored ? calculateNormal( exterior, mOrigin.x(), mOrigin.y(), mOrigin.z(), mInvertNormals, extrusionHeight ) : QVector3D();
756 // calculate the tangent for the flat polygon (roof and floor)
757 const QVector4D frontTangent( 1.0f, 0.0f, 0.0f, 1.0f );
758 const QVector4D floorFrontTangent( -1.0f, 0.0f, 0.0f, 1.0f );
759 // back face tangent
760 const QVector4D backTangent( frontTangent.x(), frontTangent.y(), frontTangent.z(), -1.0f );
761 const QVector4D floorBackTangent( floorFrontTangent.x(), floorFrontTangent.y(), floorFrontTangent.z(), -1.0f );
762
763 const int pCount = exterior->numPoints();
764 if ( pCount == 0 )
765 return;
766
767 QMatrix4x4 base; // identity matrix by default
768 const QgsPoint ptStart( exterior->startPoint() );
769 const QgsPoint extrusionOrigin( Qgis::WkbType::PointZ, ptStart.x(), ptStart.y(), std::isnan( ptStart.z() ) ? 0 : ptStart.z() );
770 std::unique_ptr<QgsPolygon> polygonNew;
771
772 if ( !mInputZValueIgnored && !qgsDoubleNear( pNormal.length(), 1, 0.001 ) )
773 return; // this should not happen - pNormal should be normalized to unit length
774
775 const bool buildWalls = mExtrusionFaces.testFlag( Qgis::ExtrusionFace::Walls );
776 const bool buildFloor = mExtrusionFaces.testFlag( Qgis::ExtrusionFace::Floor );
777 const bool buildRoof = mExtrusionFaces.testFlag( Qgis::ExtrusionFace::Roof );
778
779 if ( buildFloor || buildRoof )
780 {
781 calculateBaseTransform( pNormal, &base );
782 polygonNew.reset( transformPolygonToNewBase( polygon, extrusionOrigin, &base, mScale ) );
783
784 QVector3D normal = pNormal;
785 // our 3x3 matrix is orthogonal, so for inverse we only need to transpose it
786 base = base.transposed();
787
788 if ( pCount == 4 && polygon.numInteriorRings() == 0 )
789 {
790 Q_ASSERT( polygonNew->exteriorRing()->numPoints() >= 3 );
791
792 const QgsLineString *triangle = qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() );
793 const QVector3D p1( static_cast<float>( triangle->xAt( 0 ) ), static_cast<float>( triangle->yAt( 0 ) ), static_cast<float>( triangle->zAt( 0 ) ) );
794 const QVector3D p2( static_cast<float>( triangle->xAt( 1 ) ), static_cast<float>( triangle->yAt( 1 ) ), static_cast<float>( triangle->zAt( 1 ) ) );
795 const QVector3D p3( static_cast<float>( triangle->xAt( 2 ) ), static_cast<float>( triangle->yAt( 2 ) ), static_cast<float>( triangle->zAt( 2 ) ) );
796 std::array<QVector3D, 3> points = { p1, p2, p3 };
797
798 for ( const QVector3D &point : points )
799 {
800 addVertex( point, normal, frontTangent, extrusionHeight, &base, &extrusionOrigin );
801 }
802
803 if ( mAddBackFaces )
804 {
805 for ( size_t i = points.size(); i-- > 0; )
806 {
807 const QVector3D &point = points[i];
808 addVertex( point, -normal, backTangent, extrusionHeight, &base, &extrusionOrigin );
809 }
810 }
811
812 if ( extrusionHeight != 0 && buildFloor )
813 {
814 for ( const QVector3D &point : points )
815 {
816 addVertex( point, normal, floorFrontTangent, 0.0, &base, &extrusionOrigin, true );
817 }
818
819 if ( mAddBackFaces )
820 {
821 for ( size_t i = points.size(); i-- > 0; )
822 {
823 const QVector3D &point = points[i];
824 addVertex( point, -normal, floorBackTangent, 0.0, &base, &extrusionOrigin, true );
825 }
826 }
827 }
828 }
829 else // we need to triangulate the polygon
830 {
831 if ( minimumDistanceBetweenCoordinates( *polygonNew ) < 0.001 )
832 {
833 // when the distances between coordinates of input points are very small,
834 // the triangulation likes to crash on numerical errors - when the distances are ~ 1e-5
835 // Assuming that the coordinates should be in a projected CRS, we should be able
836 // to simplify geometries that may cause problems and avoid possible crashes
837 const QgsGeometry polygonSimplified = QgsGeometry( polygonNew->clone() ).simplify( 0.001 );
838 if ( polygonSimplified.isNull() )
839 {
840 mError = QObject::tr( "geometry simplification failed - skipping" );
841 return;
842 }
843 const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet() );
844 if ( !polygonSimplifiedData || minimumDistanceBetweenCoordinates( *polygonSimplifiedData ) < 0.001 )
845 {
846 // Failed to fix that. It could be a really tiny geometry... or maybe they gave us
847 // geometry in unprojected lat/lon coordinates
848 mError = QObject::tr( "geometry's coordinates are too close to each other and simplification failed - skipping" );
849 return;
850 }
851 else
852 {
853 polygonNew.reset( polygonSimplifiedData->clone() );
854 }
855 }
856
857 // run triangulation and write vertices to the output data array
858 try
859 {
860 std::vector<QVector3D> trianglePoints;
861 switch ( mTriangulationAlgorithm )
862 {
864 trianglePoints = generateConstrainedDelaunayTriangles( polygonNew.get() );
865 break;
867 trianglePoints = generateEarcutTriangles( polygonNew.get() );
868 break;
869 }
870
871 if ( trianglePoints.empty() )
872 {
873 mError = QObject::tr( "Failed to triangulate polygon." );
874 return;
875 }
876
877 Q_ASSERT( trianglePoints.size() % 3 == 0 );
878
879 mData.reserve( mData.size() + trianglePoints.size() * 3 * ( stride() / sizeof( float ) ) );
880
881 const size_t vertexBufferSize = uniqueVertexCount();
882 QHash<VertexPoint, unsigned int> vertexBuffer;
883 for ( size_t i = 0; i < trianglePoints.size(); i += 3 )
884 {
885 const std::array<QVector3D, 3> points = { trianglePoints[i + 0], trianglePoints[i + 1], trianglePoints[i + 2] };
886
887 // roof
888 for ( const QVector3D &point : points )
889 {
890 addVertex( point, normal, frontTangent, extrusionHeight, &base, &extrusionOrigin, &vertexBuffer, vertexBufferSize );
891 }
892
893 if ( mAddBackFaces )
894 {
895 for ( size_t i = points.size(); i-- > 0; )
896 {
897 const QVector3D &point = points[i];
898 addVertex( point, -normal, backTangent, extrusionHeight, &base, &extrusionOrigin, &vertexBuffer, vertexBufferSize );
899 }
900 }
901
902 if ( extrusionHeight != 0 && buildFloor )
903 {
904 for ( const QVector3D &point : points )
905 {
906 addVertex( point, normal, floorFrontTangent, 0.0, &base, &extrusionOrigin, &vertexBuffer, vertexBufferSize, true );
907 }
908
909 if ( mAddBackFaces )
910 {
911 for ( size_t i = points.size(); i-- > 0; )
912 {
913 const QVector3D &point = points[i];
914 addVertex( point, -normal, floorBackTangent, 0.0, &base, &extrusionOrigin, &vertexBuffer, vertexBufferSize, true );
915 }
916 }
917 }
918 }
919 }
920 catch ( std::runtime_error &err )
921 {
922 mError = err.what();
923 }
924 catch ( ... )
925 {
926 mError = QObject::tr( "An unknown error occurred" );
927 }
928 }
929 }
930
931 // add walls if extrusion is enabled
932 if ( extrusionHeight != 0 && buildWalls )
933 {
934 makeWalls( *exterior, false, extrusionHeight );
935 for ( int i = 0; i < polygon.numInteriorRings(); ++i )
936 makeWalls( *qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ), true, extrusionHeight );
937 }
938}
939
941{
942 return mIndexBuffer.size();
943}
944
945std::unique_ptr<QgsMultiPolygon> QgsTessellator::asMultiPolygon() const
946{
947 auto mp = std::make_unique< QgsMultiPolygon >();
948 const size_t nVals = mIndexBuffer.size();
949
950 Q_ASSERT( nVals % 3 == 0 );
951
952 mp->reserve( nVals / 3 );
953 const size_t noOfElements = stride() / sizeof( float );
954
955 for ( size_t i = 0; i + 2 < nVals; i += 3 )
956 {
957 const uint32_t index1 = mIndexBuffer[i] * noOfElements;
958 const uint32_t index2 = mIndexBuffer[i + 1] * noOfElements;
959 const uint32_t index3 = mIndexBuffer[i + 2] * noOfElements;
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
967 return mp;
968}
969
970QVector<float> QgsTessellator::data() const
971{
972 const size_t n = mIndexBuffer.size();
973 if ( n == 0 )
974 return QVector<float>();
975
976 QVector<float> tData;
977 size_t noOfElements = stride() / sizeof( float );
978 tData.reserve( n * noOfElements );
979
980 for ( auto &index : mIndexBuffer )
981 {
982 for ( size_t i = 0; i < noOfElements; i++ )
983 {
984 tData << mData[index * noOfElements + i];
985 }
986 }
987
988 return tData;
989}
990
992{
993 if ( mData.size() == 0 )
994 return 0;
995
996 return mData.size() / ( stride() / sizeof( float ) );
997}
@ CounterClockwise
Counter-clockwise direction.
Definition qgis.h:3615
@ Clockwise
Clockwise direction.
Definition qgis.h:3614
TriangulationAlgorithm
Triangulation algorithms.
Definition qgis.h:6678
QFlags< ExtrusionFace > ExtrusionFaces
Tessellator extrusion face types.
Definition qgis.h:6670
@ 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, 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:7122
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 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