35#include <unordered_set>
37static std::pair<float, float> rotateCoords(
float x,
float y,
float origin_x,
float origin_y,
float r )
39 r = qDegreesToRadians( r );
40 float x0 = x - origin_x, y0 = y - origin_y;
44 const float x1 = origin_x + x0 * qCos( r ) - y0 * qSin( r );
45 const float y1 = origin_y + x0 * qSin( r ) + y0 * qCos( r );
46 return std::make_pair( x1, y1 );
49static 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 )
51 const float dx = x1 - x0;
52 const float dy = -( y1 - y0 );
55 QVector3D vn( -dy, 0, dx );
64 QVector<double> textureCoordinates;
65 textureCoordinates.reserve( 12 );
67 if ( fabsf( dy ) <= fabsf( dx ) )
98 textureCoordinates.push_back( u0 );
99 textureCoordinates.push_back( v0 );
101 textureCoordinates.push_back( u1 );
102 textureCoordinates.push_back( v1 );
104 textureCoordinates.push_back( u2 );
105 textureCoordinates.push_back( v2 );
107 textureCoordinates.push_back( u2 );
108 textureCoordinates.push_back( v2 );
110 textureCoordinates.push_back( u1 );
111 textureCoordinates.push_back( v1 );
113 textureCoordinates.push_back( u3 );
114 textureCoordinates.push_back( v3 );
116 for (
int i = 0; i < textureCoordinates.size(); i += 2 )
118 const std::pair<float, float> rotated = rotateCoords( textureCoordinates[i], textureCoordinates[i + 1], 0, 0, textureRotation );
119 textureCoordinates[i] = rotated.first;
120 textureCoordinates[i + 1] = rotated.second;
125 data << x0 << z0 + height << -y0;
127 data << vn.x() << vn.y() << vn.z();
128 if ( addTextureCoords )
129 data << textureCoordinates[0] << textureCoordinates[1];
131 data << x1 << z1 + height << -y1;
133 data << vn.x() << vn.y() << vn.z();
134 if ( addTextureCoords )
135 data << textureCoordinates[2] << textureCoordinates[3];
137 data << x0 << z0 << -y0;
139 data << vn.x() << vn.y() << vn.z();
140 if ( addTextureCoords )
141 data << textureCoordinates[4] << textureCoordinates[5];
145 data << x0 << z0 << -y0;
147 data << vn.x() << vn.y() << vn.z();
148 if ( addTextureCoords )
149 data << textureCoordinates[6] << textureCoordinates[7];
151 data << x1 << z1 + height << -y1;
153 data << vn.x() << vn.y() << vn.z();
154 if ( addTextureCoords )
155 data << textureCoordinates[8] << textureCoordinates[9];
157 data << x1 << z1 << -y1;
159 data << vn.x() << vn.y() << vn.z();
160 if ( addTextureCoords )
161 data << textureCoordinates[10] << textureCoordinates[11];
166 bool addTextureCoords,
int facade,
float textureRotation )
167 : mOriginX( originX )
168 , mOriginY( originY )
169 , mAddNormals( addNormals )
170 , mInvertNormals( invertNormals )
171 , mAddBackFaces( addBackFaces )
172 , mAddTextureCoords( addTextureCoords )
174 , mTessellatedFacade( facade )
175 , mTextureRotation( textureRotation )
181 bool addTextureCoords,
int facade,
float textureRotation )
183 , mOriginX( mBounds.xMinimum() )
184 , mOriginY( mBounds.yMinimum() )
185 , mAddNormals( addNormals )
186 , mInvertNormals( invertNormals )
187 , mAddBackFaces( addBackFaces )
188 , mAddTextureCoords( addTextureCoords )
190 , mTessellatedFacade( facade )
191 , mTextureRotation( textureRotation )
196void QgsTessellator::init()
198 mStride = 3 *
sizeof( float );
200 mStride += 3 *
sizeof( float );
201 if ( mAddTextureCoords )
202 mStride += 2 *
sizeof( float );
205static bool _isRingCounterClockWise(
const QgsCurve &ring )
212 for (
int i = 1; i < count + 1; ++i )
214 ring.
pointAt( i % count, pt, vt );
215 a += ptPrev.
x() * pt.
y() - ptPrev.
y() * pt.
x();
221static void _makeWalls(
const QgsLineString &ring,
bool ccw,
float extrusionHeight, QVector<float> &data,
222 bool addNormals,
bool addTextureCoords,
double originX,
double originY,
float textureRotation )
227 const bool is_counter_clockwise = _isRingCounterClockWise( ring );
230 QgsPoint ptPrev = ring.
pointN( is_counter_clockwise == ccw ? 0 : ring.numPoints() - 1 );
231 for (
int i = 1; i < ring.
numPoints(); ++i )
233 pt = ring.
pointN( is_counter_clockwise == ccw ? i : ring.numPoints() - i - 1 );
234 float x0 = ptPrev.
x() - originX, y0 = ptPrev.
y() - originY;
235 float x1 = pt.
x() - originX, y1 = pt.
y() - originY;
236 const float z0 = std::isnan( ptPrev.
z() ) ? 0 : ptPrev.
z();
237 const float z1 = std::isnan( pt.
z() ) ? 0 : pt.
z();
240 make_quad( x0, y0, z0, x1, y1, z1, extrusionHeight, data, addNormals, addTextureCoords, textureRotation );
245static QVector3D _calculateNormal(
const QgsLineString *curve,
double originX,
double originY,
bool invertNormal )
250 return QVector3D( 0, 0, 1 );
258 for (
int i = 1; i < curve->
numPoints(); i++ )
261 if ( pt1.
z() != pt2.
z() )
268 return QVector3D( 0, 0, 1 );
275 double nx = 0, ny = 0, nz = 0;
279 pt1.
setX( pt1.
x() - originX );
280 pt1.
setY( pt1.
y() - originY );
281 for (
int i = 1; i < curve->
numPoints(); i++ )
284 pt2.
setX( pt2.
x() - originX );
285 pt2.
setY( pt2.
y() - originY );
287 if ( std::isnan( pt1.
z() ) || std::isnan( pt2.
z() ) )
290 nx += ( pt1.
y() - pt2.
y() ) * ( pt1.
z() + pt2.
z() );
291 ny += ( pt1.
z() - pt2.
z() ) * ( pt1.
x() + pt2.
x() );
292 nz += ( pt1.
x() - pt2.
x() ) * ( pt1.
y() + pt2.
y() );
297 QVector3D normal( nx, ny, nz );
305static void _normalVectorToXYVectors(
const QVector3D &pNormal, QVector3D &pXVector, QVector3D &pYVector )
310 if ( pNormal.z() > 0.001 || pNormal.z() < -0.001 )
312 pXVector = QVector3D( 1, 0, -pNormal.x() / pNormal.z() );
314 else if ( pNormal.y() > 0.001 || pNormal.y() < -0.001 )
316 pXVector = QVector3D( 1, -pNormal.x() / pNormal.y(), 0 );
320 pXVector = QVector3D( -pNormal.y() / pNormal.x(), 1, 0 );
322 pXVector.normalize();
323 pYVector = QVector3D::normal( pNormal, pXVector );
328 std::size_t
operator()(
const std::pair<float, float> pair )
const
330 const std::size_t h1 = std::hash<float>()( pair.first );
331 const std::size_t h2 = std::hash<float>()( pair.second );
337static void _ringToPoly2tri(
const QgsLineString *ring, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> *zHash )
341 polyline.reserve( pCount );
343 const double *srcXData = ring->
xData();
344 const double *srcYData = ring->
yData();
345 const double *srcZData = ring->
zData();
346 std::unordered_set<std::pair<float, float>,
float_pair_hash> foundPoints;
348 for (
int i = 0; i < pCount - 1; ++i )
350 const float x = *srcXData++;
351 const float y = *srcYData++;
353 const auto res = foundPoints.insert( std::make_pair( x, y ) );
360 p2t::Point *pt2 =
new p2t::Point( x, y );
361 polyline.push_back( pt2 );
364 ( *zHash )[pt2] = *srcZData++;
372 const double exp = 1e10;
373 return round( x * exp ) / exp;
377static QgsCurve *_transform_ring_to_new_base(
const QgsLineString &curve,
const QgsPoint &pt0,
const QMatrix4x4 *toNewBase,
const float scale )
386 double *xData = x.data();
387 double *yData = y.data();
388 double *zData = z.data();
390 const double *srcXData = curve.
xData();
391 const double *srcYData = curve.
yData();
392 const double *srcZData = curve.
is3D() ? curve.
zData() :
nullptr;
394 for (
int i = 0; i < count; ++i )
396 QVector4D v( *srcXData++ - pt0.
x(),
397 *srcYData++ - pt0.
y(),
398 srcZData ? *srcZData++ - pt0.
z() : 0,
401 v = toNewBase->map( v );
404 v.setX( v.x() * scale );
405 v.setY( v.y() * scale );
425static QgsPolygon *_transform_polygon_to_new_base(
const QgsPolygon &polygon,
const QgsPoint &pt0,
const QMatrix4x4 *toNewBase,
const float scale )
428 p->
setExteriorRing( _transform_ring_to_new_base( *qgsgeometry_cast< const QgsLineString * >( polygon.
exteriorRing() ), pt0, toNewBase, scale ) );
430 p->
addInteriorRing( _transform_ring_to_new_base( *qgsgeometry_cast< const QgsLineString * >( polygon.
interiorRing( i ) ), pt0, toNewBase, scale ) );
439 std::vector< const QgsLineString * > rings;
441 rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.
exteriorRing() ) );
443 rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.
interiorRing( i ) ) );
448 if ( numPoints <= 1 )
451 const double *srcXData = ring->
xData();
452 const double *srcYData = ring->
yData();
453 double x0 = *srcXData++;
454 double y0 = *srcYData++;
455 for (
int i = 1; i < numPoints; ++i )
457 const double x1 = *srcXData++;
458 const double y1 = *srcYData++;
459 const double d = ( x0 - x1 ) * ( x0 - x1 ) + ( y0 - y1 ) * ( y0 - y1 );
467 return min_d != 1e20 ? std::sqrt( min_d ) : 1e20;
476 const QVector3D pNormal = !mNoZ ? _calculateNormal( exterior, mOriginX, mOriginY, mInvertNormals ) : QVector3D();
477 const int pCount = exterior->
numPoints();
481 float zMin = std::numeric_limits<float>::max();
482 float zMaxBase = -std::numeric_limits<float>::max();
483 float zMaxExtruded = -std::numeric_limits<float>::max();
485 const float scale = mBounds.
isNull() ? 1.0 : std::max( 10000.0 / mBounds.
width(), 10000.0 / mBounds.
height() );
487 std::unique_ptr<QMatrix4x4> toNewBase, toOldBase;
489 std::unique_ptr<QgsPolygon> polygonNew;
490 auto rotatePolygonToXYPlane = [&]()
492 if ( !mNoZ && pNormal != QVector3D( 0, 0, 1 ) )
496 QVector3D pXVector, pYVector;
497 _normalVectorToXYVectors( pNormal, pXVector, pYVector );
502 toNewBase.reset(
new QMatrix4x4(
503 pXVector.x(), pXVector.y(), pXVector.z(), 0,
504 pYVector.x(), pYVector.y(), pYVector.z(), 0,
505 pNormal.x(), pNormal.y(), pNormal.z(), 0,
509 toOldBase.reset(
new QMatrix4x4( toNewBase->transposed() ) );
518 polygonNew.reset( _transform_polygon_to_new_base( polygon, pt0, toNewBase.get(), scale ) );
524 const QVector3D upVector( 0, 0, 1 );
525 const float pNormalUpVectorDotProduct = QVector3D::dotProduct( upVector, pNormal );
526 const float radsBetwwenUpNormal = qAcos( pNormalUpVectorDotProduct );
528 const float detectionDelta = qDegreesToRadians( 10.0f );
530 if ( radsBetwwenUpNormal > M_PI_2 - detectionDelta && radsBetwwenUpNormal < M_PI_2 + detectionDelta ) facade = 1;
531 else if ( radsBetwwenUpNormal > - M_PI_2 - detectionDelta && radsBetwwenUpNormal < -M_PI_2 + detectionDelta ) facade = 1;
534 if ( pCount == 4 && polygon.
numInteriorRings() == 0 && ( mTessellatedFacade & facade ) )
537 if ( mAddTextureCoords )
539 rotatePolygonToXYPlane();
540 triangle = qgsgeometry_cast< QgsLineString * >( polygonNew->exteriorRing() );
541 Q_ASSERT( polygonNew->exteriorRing()->numPoints() >= 3 );
545 const double *xData = exterior->
xData();
546 const double *yData = exterior->
yData();
547 const double *zData = !mNoZ ? exterior->
zData() :
nullptr;
548 for (
int i = 0; i < 3; i++ )
550 const float z = !zData ? 0 : *zData;
555 if ( z > zMaxExtruded )
558 mData << *xData - mOriginX << z << - *yData + mOriginY;
560 mData << pNormal.x() << pNormal.z() << - pNormal.y();
561 if ( mAddTextureCoords )
563 std::pair<float, float> p( triangle->
xAt( i ), triangle->
yAt( i ) );
566 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
568 else if ( facade & 2 )
570 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
572 mData << p.first << p.second;
583 for (
int i = 2; i >= 0; i-- )
585 mData << exterior->
xAt( i ) - mOriginX << ( mNoZ ? 0 : exterior->
zAt( i ) ) << - exterior->
yAt( i ) + mOriginY;
587 mData << -pNormal.x() << -pNormal.z() << pNormal.y();
588 if ( mAddTextureCoords )
590 std::pair<float, float> p( triangle->
xAt( i ), triangle->
yAt( i ) );
593 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
595 else if ( facade & 2 )
597 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
599 mData << p.first << p.second;
604 else if ( mTessellatedFacade & facade )
607 rotatePolygonToXYPlane();
616 if ( polygonSimplified.
isNull() )
621 const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.
constGet() );
626 QgsMessageLog::logMessage( QObject::tr(
"geometry's coordinates are too close to each other and simplification failed - skipping" ), QObject::tr(
"3D" ) );
631 polygonNew.reset( polygonSimplifiedData->
clone() );
635 QList< std::vector<p2t::Point *> > polylinesToDelete;
636 QHash<p2t::Point *, float> z;
639 std::vector<p2t::Point *> polyline;
640 _ringToPoly2tri( qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() ), polyline, mNoZ ?
nullptr : &z );
641 polylinesToDelete << polyline;
643 std::unique_ptr<p2t::CDT> cdt(
new p2t::CDT( polyline ) );
646 for (
int i = 0; i < polygonNew->numInteriorRings(); ++i )
648 std::vector<p2t::Point *> holePolyline;
649 const QgsLineString *hole = qgsgeometry_cast< const QgsLineString *>( polygonNew->interiorRing( i ) );
651 _ringToPoly2tri( hole, holePolyline, mNoZ ?
nullptr : &z );
653 cdt->AddHole( holePolyline );
654 polylinesToDelete << holePolyline;
662 std::vector<p2t::Triangle *> triangles = cdt->GetTriangles();
664 mData.reserve( mData.size() + 3 * triangles.size() * (
stride() /
sizeof( float ) ) );
665 for (
size_t i = 0; i < triangles.size(); ++i )
667 p2t::Triangle *t = triangles[i];
668 for (
int j = 0; j < 3; ++j )
670 p2t::Point *p = t->GetPoint( j );
671 QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
673 pt = *toOldBase * pt;
674 const double fx = ( pt.x() / scale ) - mOriginX + pt0.
x();
675 const double fy = ( pt.y() / scale ) - mOriginY + pt0.
y();
676 const double baseHeight = mNoZ ? 0 : ( pt.z() + pt0.
z() );
677 const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.
z() );
678 if ( baseHeight < zMin )
680 if ( baseHeight > zMaxBase )
681 zMaxBase = baseHeight;
682 if ( fz > zMaxExtruded )
685 mData << fx << fz << -fy;
687 mData << pNormal.x() << pNormal.z() << - pNormal.y();
688 if ( mAddTextureCoords )
690 const std::pair<float, float> pr = rotateCoords( p->x, p->y, 0.0f, 0.0f, mTextureRotation );
691 mData << pr.first << pr.second;
698 for (
int j = 2; j >= 0; --j )
700 p2t::Point *p = t->GetPoint( j );
701 QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
703 pt = *toOldBase * pt;
704 const double fx = ( pt.x() / scale ) - mOriginX + pt0.
x();
705 const double fy = ( pt.y() / scale ) - mOriginY + pt0.
y();
706 const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.
z() );
707 mData << fx << fz << -fy;
709 mData << -pNormal.x() << -pNormal.z() << pNormal.y();
710 if ( mAddTextureCoords )
712 const std::pair<float, float> pr = rotateCoords( p->x, p->y, 0.0f, 0.0f, mTextureRotation );
713 mData << pr.first << pr.second;
724 for (
int i = 0; i < polylinesToDelete.count(); ++i )
725 qDeleteAll( polylinesToDelete[i] );
729 if ( extrusionHeight != 0 && ( mTessellatedFacade & 1 ) )
731 _makeWalls( *exterior,
false, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation );
734 _makeWalls( *qgsgeometry_cast< const QgsLineString * >( polygon.
interiorRing( i ) ),
true, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation );
736 if ( zMaxBase + extrusionHeight > zMaxExtruded )
737 zMaxExtruded = zMaxBase + extrusionHeight;
742 if ( zMaxExtruded > mZMax )
743 mZMax = zMaxExtruded;
744 if ( zMaxBase > mZMax )
750 return mData.size() / (
stride() /
sizeof( float ) );
755 std::unique_ptr< QgsMultiPolygon > mp = std::make_unique< QgsMultiPolygon >();
756 const auto nVals = mData.size();
757 mp->reserve( nVals / 9 );
758 for (
auto i =
decltype( nVals ) {0}; i + 8 < nVals; i += 9 )
761 const QgsPoint p1( mData[i + 0], -mData[i + 2], mData[i + 1] );
762 const QgsPoint p2( mData[i + 3], -mData[i + 5], mData[i + 4] );
763 const QgsPoint p3( mData[i + 6], -mData[i + 8], mData[i + 7] );
VertexType
Types of vertex.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
QgsWkbTypes::Type 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.
virtual int numPoints() const =0
Returns the number of points in the curve.
virtual bool pointAt(int node, QgsPoint &point, Qgis::VertexType &type) const =0
Returns the point and vertex id of a point within the curve.
A geometry is the spatial representation of a feature.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsGeometry simplify(double tolerance) const
Returns a simplified version of this geometry using a specified tolerance value.
Line string geometry type, with support for z-dimension and m-values.
const double * yData() const
Returns a const pointer to the y vertex data.
const double * xData() const
Returns a const pointer to the x vertex data.
const double * zData() const
Returns a const pointer to the z vertex data, or nullptr if the linestring does not have z values.
QgsPoint startPoint() const override
Returns the starting point of the curve.
bool isEmpty() const override
Returns true if the geometry is empty.
int numPoints() const override
Returns the number of points in the curve.
QgsPoint pointN(int i) const
Returns the specified point from inside the line string.
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
double zAt(int index) const
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.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
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.
double width() const
Returns the width of the rectangle.
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
double height() const
Returns the height of the rectangle.
std::unique_ptr< QgsMultiPolygon > asMultiPolygon() const
Returns the triangulation as a multipolygon geometry.
QgsTessellator(double originX, double originY, bool addNormals, bool invertNormals=false, bool addBackFaces=false, bool noZ=false, bool addTextureCoords=false, int facade=3, float textureRotation=0.0f)
Creates tessellator with a specified origin point of the world (in map coordinates)
int stride() const
Returns size of one vertex entry in bytes.
void addPolygon(const QgsPolygon &polygon, float extrusionHeight)
Tessellates a triangle and adds its vertex entries to the output data array.
int dataVerticesCount() const
Returns the number of vertices stored in the output data array.
static bool hasZ(Type 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