32#include <unordered_set>
34static std::pair<float, float> rotateCoords(
float x,
float y,
float origin_x,
float origin_y,
float r )
36 r = qDegreesToRadians( r );
37 float x0 = x - origin_x, y0 = y - origin_y;
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 );
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 )
48 const float dx = x1 - x0;
49 const float dy = y1 - y0;
52 QVector3D vn = zUp ? QVector3D( -dy, dx, 0 ) : QVector3D( -dy, 0, -dx );
60 QVector<double> textureCoordinates;
61 textureCoordinates.reserve( 12 );
63 if ( fabsf( dy ) <= fabsf( dx ) )
94 textureCoordinates.push_back( u0 );
95 textureCoordinates.push_back( v0 );
97 textureCoordinates.push_back( u1 );
98 textureCoordinates.push_back( v1 );
100 textureCoordinates.push_back( u2 );
101 textureCoordinates.push_back( v2 );
103 textureCoordinates.push_back( u2 );
104 textureCoordinates.push_back( v2 );
106 textureCoordinates.push_back( u1 );
107 textureCoordinates.push_back( v1 );
109 textureCoordinates.push_back( u3 );
110 textureCoordinates.push_back( v3 );
112 for (
int i = 0; i < textureCoordinates.size(); i += 2 )
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;
122 data << x0 << y0 << z0 + height;
124 data << x0 << z0 + height << -y0;
126 data << vn.x() << vn.y() << vn.z();
127 if ( addTextureCoords )
128 data << textureCoordinates[0] << textureCoordinates[1];
131 data << x1 << y1 << z1 + height;
133 data << x1 << z1 + height << -y1;
135 data << vn.x() << vn.y() << vn.z();
136 if ( addTextureCoords )
137 data << textureCoordinates[2] << textureCoordinates[3];
140 data << x0 << y0 << z0;
142 data << x0 << z0 << -y0;
144 data << vn.x() << vn.y() << vn.z();
145 if ( addTextureCoords )
146 data << textureCoordinates[4] << textureCoordinates[5];
151 data << x0 << y0 << z0;
153 data << x0 << z0 << -y0;
155 data << vn.x() << vn.y() << vn.z();
156 if ( addTextureCoords )
157 data << textureCoordinates[6] << textureCoordinates[7];
160 data << x1 << y1 << z1 + height;
162 data << x1 << z1 + height << -y1;
164 data << vn.x() << vn.y() << vn.z();
165 if ( addTextureCoords )
166 data << textureCoordinates[8] << textureCoordinates[9];
169 data << x1 << y1 << z1;
171 data << x1 << z1 << -y1;
173 data << vn.x() << vn.y() << vn.z();
174 if ( addTextureCoords )
175 data << textureCoordinates[10] << textureCoordinates[11];
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 )
188 , mTessellatedFacade( facade )
189 , mTextureRotation( textureRotation )
195 bool addTextureCoords,
int facade,
float textureRotation )
197 , mOriginX( mBounds.xMinimum() )
198 , mOriginY( mBounds.yMinimum() )
199 , mAddNormals( addNormals )
200 , mInvertNormals( invertNormals )
201 , mAddBackFaces( addBackFaces )
202 , mAddTextureCoords( addTextureCoords )
204 , mTessellatedFacade( facade )
205 , mTextureRotation( textureRotation )
210void QgsTessellator::init()
212 mStride = 3 *
sizeof( float );
214 mStride += 3 *
sizeof( float );
215 if ( mAddTextureCoords )
216 mStride += 2 *
sizeof( float );
219static void _makeWalls(
const QgsLineString &ring,
bool ccw,
float extrusionHeight, QVector<float> &data,
220 bool addNormals,
bool addTextureCoords,
double originX,
double originY,
float textureRotation,
bool zUp )
228 QgsPoint ptPrev = ring.
pointN( is_counter_clockwise == ccw ? 0 : ring.numPoints() - 1 );
229 for (
int i = 1; i < ring.
numPoints(); ++i )
231 pt = ring.
pointN( is_counter_clockwise == ccw ? i : ring.numPoints() - i - 1 );
232 float x0 = ptPrev.
x() - originX, y0 = ptPrev.
y() - originY;
233 float x1 = pt.
x() - originX, y1 = pt.
y() - originY;
234 const float z0 = std::isnan( ptPrev.
z() ) ? 0 : ptPrev.
z();
235 const float z1 = std::isnan( pt.
z() ) ? 0 : pt.
z();
238 make_quad( x0, y0, z0, x1, y1, z1, extrusionHeight, data, addNormals, addTextureCoords, textureRotation, zUp );
243static QVector3D _calculateNormal(
const QgsLineString *curve,
double originX,
double originY,
bool invertNormal,
float extrusionHeight )
248 if ( extrusionHeight != 0 )
249 return QVector3D( 0, 0, 1 );
252 float orientation = 1.f;
254 orientation = -orientation;
256 orientation = -orientation;
257 return QVector3D( 0, 0, orientation );
265 for (
int i = 1; i < curve->
numPoints(); i++ )
268 if ( pt1.
z() != pt2.
z() )
274 if ( sameZ && extrusionHeight != 0 )
275 return QVector3D( 0, 0, 1 );
282 double nx = 0, ny = 0, nz = 0;
285 pt1.
setX( pt1.
x() - originX );
286 pt1.
setY( pt1.
y() - originY );
287 for (
int i = 1; i < curve->
numPoints(); i++ )
290 pt2.
setX( pt2.
x() - originX );
291 pt2.
setY( pt2.
y() - originY );
293 if ( std::isnan( pt1.
z() ) || std::isnan( pt2.
z() ) )
296 nx += ( pt1.
y() - pt2.
y() ) * ( pt1.
z() + pt2.
z() );
297 ny += ( pt1.
z() - pt2.
z() ) * ( pt1.
x() + pt2.
x() );
298 nz += ( pt1.
x() - pt2.
x() ) * ( pt1.
y() + pt2.
y() );
303 QVector3D normal( nx, ny, nz );
311static void _normalVectorToXYVectors(
const QVector3D &pNormal, QVector3D &pXVector, QVector3D &pYVector )
316 if ( pNormal.z() > 0.001 || pNormal.z() < -0.001 )
318 pXVector = QVector3D( 1, 0, -pNormal.x() / pNormal.z() );
320 else if ( pNormal.y() > 0.001 || pNormal.y() < -0.001 )
322 pXVector = QVector3D( 1, -pNormal.x() / pNormal.y(), 0 );
326 pXVector = QVector3D( -pNormal.y() / pNormal.x(), 1, 0 );
328 pXVector.normalize();
329 pYVector = QVector3D::normal( pNormal, pXVector );
334 std::size_t
operator()(
const std::pair<float, float> pair )
const
336 const std::size_t h1 = std::hash<float>()( pair.first );
337 const std::size_t h2 = std::hash<float>()( pair.second );
334 std::size_t
operator()(
const std::pair<float, float> pair )
const {
…}
343static void _ringToPoly2tri(
const QgsLineString *ring, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> *zHash )
347 polyline.reserve( pCount );
349 const double *srcXData = ring->
xData();
350 const double *srcYData = ring->
yData();
351 const double *srcZData = ring->
zData();
352 std::unordered_set<std::pair<float, float>,
float_pair_hash> foundPoints;
354 for (
int i = 0; i < pCount - 1; ++i )
356 const float x = *srcXData++;
357 const float y = *srcYData++;
359 const auto res = foundPoints.insert( std::make_pair( x, y ) );
366 p2t::Point *pt2 =
new p2t::Point( x, y );
367 polyline.push_back( pt2 );
370 ( *zHash )[pt2] = *srcZData++;
378 const double exp = 1e10;
379 return round( x * exp ) / exp;
383static QgsCurve *_transform_ring_to_new_base(
const QgsLineString &curve,
const QgsPoint &pt0,
const QMatrix4x4 *toNewBase,
const float scale )
392 double *xData = x.data();
393 double *yData = y.data();
394 double *zData = z.data();
396 const double *srcXData = curve.
xData();
397 const double *srcYData = curve.
yData();
398 const double *srcZData = curve.
is3D() ? curve.
zData() :
nullptr;
400 for (
int i = 0; i < count; ++i )
402 QVector4D v( *srcXData++ - pt0.
x(),
403 *srcYData++ - pt0.
y(),
404 srcZData ? *srcZData++ - pt0.
z() : 0,
407 v = toNewBase->map( v );
410 v.setX( v.x() * scale );
411 v.setY( v.y() * scale );
431static QgsPolygon *_transform_polygon_to_new_base(
const QgsPolygon &polygon,
const QgsPoint &pt0,
const QMatrix4x4 *toNewBase,
const float scale )
434 p->
setExteriorRing( _transform_ring_to_new_base( *qgsgeometry_cast< const QgsLineString * >( polygon.
exteriorRing() ), pt0, toNewBase, scale ) );
436 p->
addInteriorRing( _transform_ring_to_new_base( *qgsgeometry_cast< const QgsLineString * >( polygon.
interiorRing( i ) ), pt0, toNewBase, scale ) );
445 std::vector< const QgsLineString * > rings;
447 rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.
exteriorRing() ) );
449 rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.
interiorRing( i ) ) );
454 if ( numPoints <= 1 )
457 const double *srcXData = ring->
xData();
458 const double *srcYData = ring->
yData();
459 double x0 = *srcXData++;
460 double y0 = *srcYData++;
461 for (
int i = 1; i < numPoints; ++i )
463 const double x1 = *srcXData++;
464 const double y1 = *srcYData++;
473 return min_d != 1e20 ? std::sqrt( min_d ) : 1e20;
482 const QVector3D pNormal = !mNoZ ? _calculateNormal( exterior, mOriginX, mOriginY, mInvertNormals, extrusionHeight ) : QVector3D();
483 const int pCount = exterior->
numPoints();
487 float zMin = std::numeric_limits<float>::max();
488 float zMaxBase = -std::numeric_limits<float>::max();
489 float zMaxExtruded = -std::numeric_limits<float>::max();
491 const float scale = mBounds.
isNull() ? 1.0 : std::max( 10000.0 / mBounds.
width(), 10000.0 / mBounds.
height() );
493 std::unique_ptr<QMatrix4x4> toNewBase, toOldBase;
495 std::unique_ptr<QgsPolygon> polygonNew;
496 auto rotatePolygonToXYPlane = [&]()
498 if ( !mNoZ && pNormal != QVector3D( 0, 0, 1 ) )
502 QVector3D pXVector, pYVector;
503 _normalVectorToXYVectors( pNormal, pXVector, pYVector );
508 toNewBase.reset(
new QMatrix4x4(
509 pXVector.x(), pXVector.y(), pXVector.z(), 0,
510 pYVector.x(), pYVector.y(), pYVector.z(), 0,
511 pNormal.x(), pNormal.y(), pNormal.z(), 0,
515 toOldBase.reset(
new QMatrix4x4( toNewBase->transposed() ) );
524 polygonNew.reset( _transform_polygon_to_new_base( polygon, pt0, toNewBase.get(), scale ) );
530 const QVector3D upVector( 0, 0, 1 );
531 const float pNormalUpVectorDotProduct = QVector3D::dotProduct( upVector, pNormal );
532 const float radsBetweenUpNormal =
static_cast<float>( qAcos( pNormalUpVectorDotProduct ) );
534 const float detectionDelta = qDegreesToRadians( 10.0f );
536 if ( ( radsBetweenUpNormal > M_PI_2 - detectionDelta && radsBetweenUpNormal < M_PI_2 + detectionDelta )
537 || ( radsBetweenUpNormal > - M_PI_2 - detectionDelta && radsBetweenUpNormal < -M_PI_2 + detectionDelta ) )
541 if ( pCount == 4 && polygon.
numInteriorRings() == 0 && ( mTessellatedFacade & facade ) )
544 if ( mAddTextureCoords )
546 rotatePolygonToXYPlane();
547 triangle = qgsgeometry_cast< QgsLineString * >( polygonNew->exteriorRing() );
548 Q_ASSERT( polygonNew->exteriorRing()->numPoints() >= 3 );
552 const double *xData = exterior->
xData();
553 const double *yData = exterior->
yData();
554 const double *zData = !mNoZ ? exterior->
zData() :
nullptr;
555 for (
int i = 0; i < 3; i++ )
557 const float baseHeight = !zData || mNoZ ? 0.0f :
static_cast<float>( * zData );
558 const float z = mNoZ ? 0.0f : ( baseHeight + extrusionHeight );
559 if ( baseHeight < zMin )
561 if ( baseHeight > zMaxBase )
562 zMaxBase = baseHeight;
563 if ( z > zMaxExtruded )
568 mData << static_cast<float>( *xData - mOriginX ) <<
static_cast<float>( *yData - mOriginY ) << z;
570 mData << pNormal.x() << pNormal.y() << pNormal.z();
574 mData << static_cast<float>( *xData - mOriginX ) << z << static_cast<float>( - *yData + mOriginY );
576 mData << pNormal.x() << pNormal.z() << - pNormal.y();
579 if ( mAddTextureCoords )
581 std::pair<float, float> p( triangle->
xAt( i ), triangle->
yAt( i ) );
584 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
586 else if ( facade & 2 )
588 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
590 mData << p.first << p.second;
601 for (
int i = 2; i >= 0; i-- )
605 mData << static_cast<float>( exterior->
xAt( i ) - mOriginX )
606 <<
static_cast<float>( exterior->
yAt( i ) - mOriginY )
607 <<
static_cast<float>( mNoZ ? 0 : exterior->
zAt( i ) );
609 mData << -pNormal.x() << -pNormal.y() << -pNormal.z();
613 mData << static_cast<float>( exterior->
xAt( i ) - mOriginX )
614 <<
static_cast<float>( mNoZ ? 0 : exterior->
zAt( i ) )
615 <<
static_cast<float>( - exterior->
yAt( i ) + mOriginY );
617 mData << -pNormal.x() << -pNormal.z() << pNormal.y();
620 if ( mAddTextureCoords )
622 std::pair<float, float> p( triangle->
xAt( i ), triangle->
yAt( i ) );
625 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
627 else if ( facade & 2 )
629 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
631 mData << p.first << p.second;
636 else if ( mTessellatedFacade & facade )
639 rotatePolygonToXYPlane();
648 if ( polygonSimplified.
isNull() )
650 mError = QObject::tr(
"geometry simplification failed - skipping" );
653 const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.
constGet() );
658 mError = QObject::tr(
"geometry's coordinates are too close to each other and simplification failed - skipping" );
663 polygonNew.reset( polygonSimplifiedData->
clone() );
667 QList< std::vector<p2t::Point *> > polylinesToDelete;
668 QHash<p2t::Point *, float> z;
671 std::vector<p2t::Point *> polyline;
672 _ringToPoly2tri( qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() ), polyline, mNoZ ?
nullptr : &z );
673 polylinesToDelete << polyline;
675 auto cdt = std::make_unique<p2t::CDT>( polyline );
678 for (
int i = 0; i < polygonNew->numInteriorRings(); ++i )
680 std::vector<p2t::Point *> holePolyline;
681 const QgsLineString *hole = qgsgeometry_cast< const QgsLineString *>( polygonNew->interiorRing( i ) );
683 _ringToPoly2tri( hole, holePolyline, mNoZ ?
nullptr : &z );
685 cdt->AddHole( holePolyline );
686 polylinesToDelete << holePolyline;
694 std::vector<p2t::Triangle *> triangles = cdt->GetTriangles();
696 mData.reserve( mData.size() + 3 * triangles.size() * (
stride() /
sizeof( float ) ) );
697 for (
size_t i = 0; i < triangles.size(); ++i )
699 p2t::Triangle *t = triangles[i];
700 for (
int j = 0; j < 3; ++j )
702 p2t::Point *p = t->GetPoint( j );
703 QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
705 pt = *toOldBase * pt;
706 const double fx = ( pt.x() / scale ) - mOriginX + pt0.
x();
707 const double fy = ( pt.y() / scale ) - mOriginY + pt0.
y();
708 const double baseHeight = mNoZ ? 0 : ( pt.z() + pt0.
z() );
709 const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.
z() );
710 if ( baseHeight < zMin )
712 if ( baseHeight > zMaxBase )
713 zMaxBase = baseHeight;
714 if ( fz > zMaxExtruded )
719 mData << static_cast<float>( fx ) <<
static_cast<float>( fy ) <<
static_cast<float>( fz );
721 mData << pNormal.x() << pNormal.y() << pNormal.z();
725 mData << static_cast<float>( fx ) <<
static_cast<float>( fz ) <<
static_cast<float>( -fy );
727 mData << pNormal.x() << pNormal.z() << - pNormal.y();
730 if ( mAddTextureCoords )
732 const std::pair<float, float> pr = rotateCoords( p->x, p->y, 0.0f, 0.0f, mTextureRotation );
733 mData << pr.first << pr.second;
740 for (
int j = 2; j >= 0; --j )
742 p2t::Point *p = t->GetPoint( j );
743 QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
745 pt = *toOldBase * pt;
746 const double fx = ( pt.x() / scale ) - mOriginX + pt0.
x();
747 const double fy = ( pt.y() / scale ) - mOriginY + pt0.
y();
748 const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.
z() );
752 mData << static_cast<float>( fx ) <<
static_cast<float>( fy ) <<
static_cast<float>( fz );
754 mData << -pNormal.x() << -pNormal.y() << -pNormal.z();
758 mData << static_cast<float>( fx ) <<
static_cast<float>( fz ) <<
static_cast<float>( -fy );
760 mData << -pNormal.x() << -pNormal.z() << pNormal.y();
763 if ( mAddTextureCoords )
765 const std::pair<float, float> pr = rotateCoords( p->x, p->y, 0.0f, 0.0f, mTextureRotation );
766 mData << pr.first << pr.second;
772 catch ( std::runtime_error &err )
778 mError = QObject::tr(
"An unknown error occurred" );
781 for (
int i = 0; i < polylinesToDelete.count(); ++i )
782 qDeleteAll( polylinesToDelete[i] );
786 if ( extrusionHeight != 0 && ( mTessellatedFacade & 1 ) )
788 _makeWalls( *exterior,
false, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation, mOutputZUp );
791 _makeWalls( *qgsgeometry_cast< const QgsLineString * >( polygon.
interiorRing( i ) ),
true, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation, mOutputZUp );
793 if ( zMaxBase + extrusionHeight > zMaxExtruded )
794 zMaxExtruded = zMaxBase + extrusionHeight;
799 if ( zMaxExtruded > mZMax )
800 mZMax = zMaxExtruded;
801 if ( zMaxBase > mZMax )
807 return mData.size() / (
stride() /
sizeof( float ) );
812 auto mp = std::make_unique< QgsMultiPolygon >();
813 const auto nVals = mData.size();
814 mp->reserve( nVals / 9 );
815 for (
auto i =
decltype( nVals ) {0}; i + 8 < nVals; i += 9 )
819 const QgsPoint p1( mData[i + 0], mData[i + 1], mData[i + 2] );
820 const QgsPoint p2( mData[i + 3], mData[i + 4], mData[i + 5] );
821 const QgsPoint p3( mData[i + 6], mData[i + 7], mData[i + 8] );
827 const QgsPoint p1( mData[i + 0], -mData[i + 2], mData[i + 1] );
828 const QgsPoint p2( mData[i + 3], -mData[i + 5], mData[i + 4] );
829 const QgsPoint p3( mData[i + 6], -mData[i + 8], mData[i + 7] );
@ CounterClockwise
Counter-clockwise direction.
@ Clockwise
Clockwise direction.
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.
Qgis::AngularDirection orientation() const
Returns the curve's orientation, e.g.
static double sqrDistance2D(double x1, double y1, double x2, double y2)
Returns the squared 2D distance between (x1, y1) and (x2, y2).
A geometry is the spatial representation of a feature.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsGeometry simplify(double tolerance) const
Returns a simplified version of this geometry using a specified tolerance value.
Line string geometry type, with support for z-dimension and m-values.
const double * yData() const
Returns a const pointer to the y vertex data.
const double * xData() const
Returns a const pointer to the x vertex data.
const double * zData() const
Returns a const pointer to the z vertex data, or nullptr if the linestring does not have z values.
QgsPoint startPoint() const override
Returns the starting point of the curve.
int numPoints() const override
Returns the number of points in the curve.
QgsPoint pointN(int i) const
Returns the specified point from inside the line string.
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
double zAt(int index) const override
Returns the z-coordinate of the specified node in the line string.
double xAt(int index) const override
Returns the x-coordinate of the specified node in the line string.
Point geometry type, with support for z-dimension and m-values.
void setY(double y)
Sets the point's y-coordinate.
void setX(double x)
Sets the point's x-coordinate.
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.
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.
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)
double _round_coord(double x)
double _minimum_distance_between_coordinates(const QgsPolygon &polygon)
std::size_t operator()(const std::pair< float, float > pair) const