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 )
48 const float dx = x1 - x0;
49 const float dy = -( y1 - y0 );
52 QVector3D vn( -dy, 0, dx );
61 QVector<double> textureCoordinates;
62 textureCoordinates.reserve( 12 );
64 if ( fabsf( dy ) <= fabsf( dx ) )
95 textureCoordinates.push_back( u0 );
96 textureCoordinates.push_back( v0 );
98 textureCoordinates.push_back( u1 );
99 textureCoordinates.push_back( v1 );
101 textureCoordinates.push_back( u2 );
102 textureCoordinates.push_back( v2 );
104 textureCoordinates.push_back( u2 );
105 textureCoordinates.push_back( v2 );
107 textureCoordinates.push_back( u1 );
108 textureCoordinates.push_back( v1 );
110 textureCoordinates.push_back( u3 );
111 textureCoordinates.push_back( v3 );
113 for (
int i = 0; i < textureCoordinates.size(); i += 2 )
115 const std::pair<float, float> rotated = rotateCoords( textureCoordinates[i], textureCoordinates[i + 1], 0, 0, textureRotation );
116 textureCoordinates[i] = rotated.first;
117 textureCoordinates[i + 1] = rotated.second;
122 data << x0 << z0 + height << -y0;
124 data << vn.x() << vn.y() << vn.z();
125 if ( addTextureCoords )
126 data << textureCoordinates[0] << textureCoordinates[1];
128 data << x1 << z1 + height << -y1;
130 data << vn.x() << vn.y() << vn.z();
131 if ( addTextureCoords )
132 data << textureCoordinates[2] << textureCoordinates[3];
134 data << x0 << z0 << -y0;
136 data << vn.x() << vn.y() << vn.z();
137 if ( addTextureCoords )
138 data << textureCoordinates[4] << textureCoordinates[5];
142 data << x0 << z0 << -y0;
144 data << vn.x() << vn.y() << vn.z();
145 if ( addTextureCoords )
146 data << textureCoordinates[6] << textureCoordinates[7];
148 data << x1 << z1 + height << -y1;
150 data << vn.x() << vn.y() << vn.z();
151 if ( addTextureCoords )
152 data << textureCoordinates[8] << textureCoordinates[9];
154 data << x1 << z1 << -y1;
156 data << vn.x() << vn.y() << vn.z();
157 if ( addTextureCoords )
158 data << textureCoordinates[10] << textureCoordinates[11];
163 bool addTextureCoords,
int facade,
float textureRotation )
164 : mOriginX( originX )
165 , mOriginY( originY )
166 , mAddNormals( addNormals )
167 , mInvertNormals( invertNormals )
168 , mAddBackFaces( addBackFaces )
169 , mAddTextureCoords( addTextureCoords )
171 , mTessellatedFacade( facade )
172 , mTextureRotation( textureRotation )
178 bool addTextureCoords,
int facade,
float textureRotation )
180 , mOriginX( mBounds.xMinimum() )
181 , mOriginY( mBounds.yMinimum() )
182 , mAddNormals( addNormals )
183 , mInvertNormals( invertNormals )
184 , mAddBackFaces( addBackFaces )
185 , mAddTextureCoords( addTextureCoords )
187 , mTessellatedFacade( facade )
188 , mTextureRotation( textureRotation )
193void QgsTessellator::init()
195 mStride = 3 *
sizeof( float );
197 mStride += 3 *
sizeof( float );
198 if ( mAddTextureCoords )
199 mStride += 2 *
sizeof( float );
202static bool _isRingCounterClockWise(
const QgsCurve &ring )
209 for (
int i = 1; i < count + 1; ++i )
211 ring.
pointAt( i % count, pt, vt );
212 a += ptPrev.
x() * pt.
y() - ptPrev.
y() * pt.
x();
218static void _makeWalls(
const QgsLineString &ring,
bool ccw,
float extrusionHeight, QVector<float> &data,
219 bool addNormals,
bool addTextureCoords,
double originX,
double originY,
float textureRotation )
224 const bool is_counter_clockwise = _isRingCounterClockWise( ring );
228 for (
int i = 1; i < ring.
numPoints(); ++i )
230 pt = ring.
pointN( is_counter_clockwise == ccw ? i : ring.
numPoints() - i - 1 );
231 float x0 = ptPrev.
x() - originX, y0 = ptPrev.
y() - originY;
232 float x1 = pt.
x() - originX, y1 = pt.
y() - originY;
233 const float z0 = std::isnan( ptPrev.
z() ) ? 0 : ptPrev.
z();
234 const float z1 = std::isnan( pt.
z() ) ? 0 : pt.
z();
237 make_quad( x0, y0, z0, x1, y1, z1, extrusionHeight, data, addNormals, addTextureCoords, textureRotation );
242static QVector3D _calculateNormal(
const QgsLineString *curve,
double originX,
double originY,
bool invertNormal )
247 return QVector3D( 0, 0, 1 );
255 for (
int i = 1; i < curve->
numPoints(); i++ )
258 if ( pt1.
z() != pt2.
z() )
265 return QVector3D( 0, 0, 1 );
272 double nx = 0, ny = 0, nz = 0;
276 pt1.
setX( pt1.
x() - originX );
277 pt1.
setY( pt1.
y() - originY );
278 for (
int i = 1; i < curve->
numPoints(); i++ )
281 pt2.
setX( pt2.
x() - originX );
282 pt2.
setY( pt2.
y() - originY );
284 if ( std::isnan( pt1.
z() ) || std::isnan( pt2.
z() ) )
287 nx += ( pt1.
y() - pt2.
y() ) * ( pt1.
z() + pt2.
z() );
288 ny += ( pt1.
z() - pt2.
z() ) * ( pt1.
x() + pt2.
x() );
289 nz += ( pt1.
x() - pt2.
x() ) * ( pt1.
y() + pt2.
y() );
294 QVector3D normal( nx, ny, nz );
302static void _normalVectorToXYVectors(
const QVector3D &pNormal, QVector3D &pXVector, QVector3D &pYVector )
307 if ( pNormal.z() > 0.001 || pNormal.z() < -0.001 )
309 pXVector = QVector3D( 1, 0, -pNormal.x() / pNormal.z() );
311 else if ( pNormal.y() > 0.001 || pNormal.y() < -0.001 )
313 pXVector = QVector3D( 1, -pNormal.x() / pNormal.y(), 0 );
317 pXVector = QVector3D( -pNormal.y() / pNormal.x(), 1, 0 );
319 pXVector.normalize();
320 pYVector = QVector3D::normal( pNormal, pXVector );
325 std::size_t
operator()(
const std::pair<float, float> pair )
const
327 const std::size_t h1 = std::hash<float>()( pair.first );
328 const std::size_t h2 = std::hash<float>()( pair.second );
334static void _ringToPoly2tri(
const QgsLineString *ring, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> *zHash )
338 polyline.reserve( pCount );
340 const double *srcXData = ring->
xData();
341 const double *srcYData = ring->
yData();
342 const double *srcZData = ring->
zData();
343 std::unordered_set<std::pair<float, float>,
float_pair_hash> foundPoints;
345 for (
int i = 0; i < pCount - 1; ++i )
347 const float x = *srcXData++;
348 const float y = *srcYData++;
350 const auto res = foundPoints.insert( std::make_pair( x, y ) );
357 p2t::Point *pt2 =
new p2t::Point( x, y );
358 polyline.push_back( pt2 );
361 ( *zHash )[pt2] = *srcZData++;
369 const double exp = 1e10;
370 return round( x * exp ) / exp;
374static QgsCurve *_transform_ring_to_new_base(
const QgsLineString &curve,
const QgsPoint &pt0,
const QMatrix4x4 *toNewBase,
const float scale )
383 double *xData = x.data();
384 double *yData = y.data();
385 double *zData = z.data();
387 const double *srcXData = curve.
xData();
388 const double *srcYData = curve.
yData();
389 const double *srcZData = curve.
is3D() ? curve.
zData() :
nullptr;
391 for (
int i = 0; i < count; ++i )
393 QVector4D v( *srcXData++ - pt0.
x(),
394 *srcYData++ - pt0.
y(),
395 srcZData ? *srcZData++ - pt0.
z() : 0,
398 v = toNewBase->map( v );
401 v.setX( v.x() * scale );
402 v.setY( v.y() * scale );
422static QgsPolygon *_transform_polygon_to_new_base(
const QgsPolygon &polygon,
const QgsPoint &pt0,
const QMatrix4x4 *toNewBase,
const float scale )
425 p->
setExteriorRing( _transform_ring_to_new_base( *qgsgeometry_cast< const QgsLineString * >( polygon.
exteriorRing() ), pt0, toNewBase, scale ) );
427 p->
addInteriorRing( _transform_ring_to_new_base( *qgsgeometry_cast< const QgsLineString * >( polygon.
interiorRing( i ) ), pt0, toNewBase, scale ) );
436 std::vector< const QgsLineString * > rings;
438 rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.
exteriorRing() ) );
440 rings.emplace_back( qgsgeometry_cast< const QgsLineString * >( polygon.
interiorRing( i ) ) );
445 if ( numPoints <= 1 )
448 const double *srcXData = ring->
xData();
449 const double *srcYData = ring->
yData();
450 double x0 = *srcXData++;
451 double y0 = *srcYData++;
452 for (
int i = 1; i < numPoints; ++i )
454 const double x1 = *srcXData++;
455 const double y1 = *srcYData++;
456 const double d = ( x0 - x1 ) * ( x0 - x1 ) + ( y0 - y1 ) * ( y0 - y1 );
464 return min_d != 1e20 ? std::sqrt( min_d ) : 1e20;
473 const QVector3D pNormal = !mNoZ ? _calculateNormal( exterior, mOriginX, mOriginY, mInvertNormals ) : QVector3D();
474 const int pCount = exterior->
numPoints();
478 float zMin = std::numeric_limits<float>::max();
479 float zMaxBase = -std::numeric_limits<float>::max();
480 float zMaxExtruded = -std::numeric_limits<float>::max();
482 const float scale = mBounds.
isNull() ? 1.0 : std::max( 10000.0 / mBounds.
width(), 10000.0 / mBounds.
height() );
484 std::unique_ptr<QMatrix4x4> toNewBase, toOldBase;
486 std::unique_ptr<QgsPolygon> polygonNew;
487 auto rotatePolygonToXYPlane = [&]()
489 if ( !mNoZ && pNormal != QVector3D( 0, 0, 1 ) )
493 QVector3D pXVector, pYVector;
494 _normalVectorToXYVectors( pNormal, pXVector, pYVector );
499 toNewBase.reset(
new QMatrix4x4(
500 pXVector.x(), pXVector.y(), pXVector.z(), 0,
501 pYVector.x(), pYVector.y(), pYVector.z(), 0,
502 pNormal.x(), pNormal.y(), pNormal.z(), 0,
506 toOldBase.reset(
new QMatrix4x4( toNewBase->transposed() ) );
515 polygonNew.reset( _transform_polygon_to_new_base( polygon, pt0, toNewBase.get(), scale ) );
521 const QVector3D upVector( 0, 0, 1 );
522 const float pNormalUpVectorDotProduct = QVector3D::dotProduct( upVector, pNormal );
523 const float radsBetwwenUpNormal = qAcos( pNormalUpVectorDotProduct );
525 const float detectionDelta = qDegreesToRadians( 10.0f );
527 if ( radsBetwwenUpNormal > M_PI_2 - detectionDelta && radsBetwwenUpNormal < M_PI_2 + detectionDelta ) facade = 1;
528 else if ( radsBetwwenUpNormal > - M_PI_2 - detectionDelta && radsBetwwenUpNormal < -M_PI_2 + detectionDelta ) facade = 1;
531 if ( pCount == 4 && polygon.
numInteriorRings() == 0 && ( mTessellatedFacade & facade ) )
534 if ( mAddTextureCoords )
536 rotatePolygonToXYPlane();
537 triangle = qgsgeometry_cast< QgsLineString * >( polygonNew->exteriorRing() );
538 Q_ASSERT( polygonNew->exteriorRing()->numPoints() >= 3 );
542 const double *xData = exterior->
xData();
543 const double *yData = exterior->
yData();
544 const double *zData = !mNoZ ? exterior->
zData() :
nullptr;
545 for (
int i = 0; i < 3; i++ )
547 const float z = !zData ? 0 : *zData;
552 if ( z > zMaxExtruded )
555 mData << *xData - mOriginX << z << - *yData + mOriginY;
557 mData << pNormal.x() << pNormal.z() << - pNormal.y();
558 if ( mAddTextureCoords )
560 std::pair<float, float> p( triangle->
xAt( i ), triangle->
yAt( i ) );
563 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
565 else if ( facade & 2 )
567 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
569 mData << p.first << p.second;
580 for (
int i = 2; i >= 0; i-- )
582 mData << exterior->
xAt( i ) - mOriginX << ( mNoZ ? 0 : exterior->
zAt( i ) ) << - exterior->
yAt( i ) + mOriginY;
584 mData << -pNormal.x() << -pNormal.z() << pNormal.y();
585 if ( mAddTextureCoords )
587 std::pair<float, float> p( triangle->
xAt( i ), triangle->
yAt( i ) );
590 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
592 else if ( facade & 2 )
594 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
596 mData << p.first << p.second;
601 else if ( mTessellatedFacade & facade )
604 rotatePolygonToXYPlane();
613 if ( polygonSimplified.
isNull() )
618 const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.
constGet() );
623 QgsMessageLog::logMessage( QObject::tr(
"geometry's coordinates are too close to each other and simplification failed - skipping" ), QObject::tr(
"3D" ) );
628 polygonNew.reset( polygonSimplifiedData->
clone() );
632 QList< std::vector<p2t::Point *> > polylinesToDelete;
633 QHash<p2t::Point *, float> z;
636 std::vector<p2t::Point *> polyline;
637 _ringToPoly2tri( qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() ), polyline, mNoZ ?
nullptr : &z );
638 polylinesToDelete << polyline;
640 std::unique_ptr<p2t::CDT> cdt(
new p2t::CDT( polyline ) );
643 for (
int i = 0; i < polygonNew->numInteriorRings(); ++i )
645 std::vector<p2t::Point *> holePolyline;
646 const QgsLineString *hole = qgsgeometry_cast< const QgsLineString *>( polygonNew->interiorRing( i ) );
648 _ringToPoly2tri( hole, holePolyline, mNoZ ?
nullptr : &z );
650 cdt->AddHole( holePolyline );
651 polylinesToDelete << holePolyline;
659 std::vector<p2t::Triangle *> triangles = cdt->GetTriangles();
661 mData.reserve( mData.size() + 3 * triangles.size() * (
stride() /
sizeof(
float ) ) );
662 for (
size_t i = 0; i < triangles.size(); ++i )
664 p2t::Triangle *t = triangles[i];
665 for (
int j = 0; j < 3; ++j )
667 p2t::Point *p = t->GetPoint( j );
668 QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
670 pt = *toOldBase * pt;
671 const double fx = ( pt.x() / scale ) - mOriginX + pt0.
x();
672 const double fy = ( pt.y() / scale ) - mOriginY + pt0.
y();
673 const double baseHeight = mNoZ ? 0 : ( pt.z() + pt0.
z() );
674 const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.
z() );
675 if ( baseHeight < zMin )
677 if ( baseHeight > zMaxBase )
678 zMaxBase = baseHeight;
679 if ( fz > zMaxExtruded )
682 mData << fx << fz << -fy;
684 mData << pNormal.x() << pNormal.z() << - pNormal.y();
685 if ( mAddTextureCoords )
687 const std::pair<float, float> pr = rotateCoords( p->x, p->y, 0.0f, 0.0f, mTextureRotation );
688 mData << pr.first << pr.second;
695 for (
int j = 2; j >= 0; --j )
697 p2t::Point *p = t->GetPoint( j );
698 QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
700 pt = *toOldBase * pt;
701 const double fx = ( pt.x() / scale ) - mOriginX + pt0.
x();
702 const double fy = ( pt.y() / scale ) - mOriginY + pt0.
y();
703 const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.
z() );
704 mData << fx << fz << -fy;
706 mData << -pNormal.x() << -pNormal.z() << pNormal.y();
707 if ( mAddTextureCoords )
709 const std::pair<float, float> pr = rotateCoords( p->x, p->y, 0.0f, 0.0f, mTextureRotation );
710 mData << pr.first << pr.second;
721 for (
int i = 0; i < polylinesToDelete.count(); ++i )
722 qDeleteAll( polylinesToDelete[i] );
726 if ( extrusionHeight != 0 && ( mTessellatedFacade & 1 ) )
728 _makeWalls( *exterior,
false, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation );
731 _makeWalls( *qgsgeometry_cast< const QgsLineString * >( polygon.
interiorRing( i ) ),
true, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation );
733 if ( zMaxBase + extrusionHeight > zMaxExtruded )
734 zMaxExtruded = zMaxBase + extrusionHeight;
739 if ( zMaxExtruded > mZMax )
740 mZMax = zMaxExtruded;
741 if ( zMaxBase > mZMax )
747 return mData.size() / (
stride() /
sizeof( float ) );
752 std::unique_ptr< QgsMultiPolygon > mp = std::make_unique< QgsMultiPolygon >();
753 const auto nVals = mData.size();
754 mp->reserve( nVals / 9 );
755 for (
auto i =
decltype( nVals ) {0}; i + 8 < nVals; i += 9 )
758 const QgsPoint p1( mData[i + 0], -mData[i + 2], mData[i + 1] );
759 const QgsPoint p2( mData[i + 3], -mData[i + 5], mData[i + 4] );
760 const QgsPoint p3( mData[i + 6], -mData[i + 8], mData[i + 7] );
VertexType
Types of vertex.
bool is3D() const SIP_HOLDGIL
Returns true if the geometry is 3D and contains a z-value.
Qgis::WkbType wkbType() const SIP_HOLDGIL
Returns the WKB type of the geometry.
const QgsCurve * interiorRing(int i) const SIP_HOLDGIL
Retrieves an interior ring from the curve polygon.
const QgsCurve * exteriorRing() const SIP_HOLDGIL
Returns the curve polygon's exterior ring.
int numInteriorRings() const SIP_HOLDGIL
Returns the number of interior rings contained with 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 SIP_HOLDGIL
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.
QgsPoint startPoint() const override SIP_HOLDGIL
Returns the starting point of the curve.
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.
int numPoints() const override SIP_HOLDGIL
Returns the number of points in the curve.
QgsPoint pointN(int i) const
Returns the specified point from inside the line string.
bool isEmpty() const override SIP_HOLDGIL
Returns true if the geometry is empty.
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.
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 setX(double x) SIP_HOLDGIL
Sets the point's x-coordinate.
void setY(double y) SIP_HOLDGIL
Sets the point's y-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.
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
double width() const SIP_HOLDGIL
Returns the width 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(Qgis::WkbType type) SIP_HOLDGIL
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