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 );
227 QgsPoint ptPrev = ring.
pointN( is_counter_clockwise == ccw ? 0 : ring.numPoints() - 1 );
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 radsBetweenUpNormal =
static_cast<float>( qAcos( pNormalUpVectorDotProduct ) );
525 const float detectionDelta = qDegreesToRadians( 10.0f );
527 if ( ( radsBetweenUpNormal > M_PI_2 - detectionDelta && radsBetweenUpNormal < M_PI_2 + detectionDelta )
528 || ( radsBetweenUpNormal > - M_PI_2 - detectionDelta && radsBetweenUpNormal < -M_PI_2 + detectionDelta ) )
532 if ( pCount == 4 && polygon.
numInteriorRings() == 0 && ( mTessellatedFacade & facade ) )
535 if ( mAddTextureCoords )
537 rotatePolygonToXYPlane();
538 triangle = qgsgeometry_cast< QgsLineString * >( polygonNew->exteriorRing() );
539 Q_ASSERT( polygonNew->exteriorRing()->numPoints() >= 3 );
543 const double *xData = exterior->
xData();
544 const double *yData = exterior->
yData();
545 const double *zData = !mNoZ ? exterior->
zData() :
nullptr;
546 for (
int i = 0; i < 3; i++ )
548 const float baseHeight = !zData || mNoZ ? 0.0f :
static_cast<float>( * zData );
549 const float z = mNoZ ? 0.0f : ( baseHeight + extrusionHeight );
550 if ( baseHeight < zMin )
552 if ( baseHeight > zMaxBase )
553 zMaxBase = baseHeight;
554 if ( z > zMaxExtruded )
557 mData << *xData - mOriginX << z << - *yData + mOriginY;
559 mData << pNormal.x() << pNormal.z() << - pNormal.y();
560 if ( mAddTextureCoords )
562 std::pair<float, float> p( triangle->
xAt( i ), triangle->
yAt( i ) );
565 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
567 else if ( facade & 2 )
569 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
571 mData << p.first << p.second;
582 for (
int i = 2; i >= 0; i-- )
584 mData << exterior->
xAt( i ) - mOriginX << ( mNoZ ? 0 : exterior->
zAt( i ) ) << - exterior->
yAt( i ) + mOriginY;
586 mData << -pNormal.x() << -pNormal.z() << pNormal.y();
587 if ( mAddTextureCoords )
589 std::pair<float, float> p( triangle->
xAt( i ), triangle->
yAt( i ) );
592 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
594 else if ( facade & 2 )
596 p = rotateCoords( p.first, p.second, 0.0f, 0.0f, mTextureRotation );
598 mData << p.first << p.second;
603 else if ( mTessellatedFacade & facade )
606 rotatePolygonToXYPlane();
615 if ( polygonSimplified.
isNull() )
620 const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.
constGet() );
625 QgsMessageLog::logMessage( QObject::tr(
"geometry's coordinates are too close to each other and simplification failed - skipping" ), QObject::tr(
"3D" ) );
630 polygonNew.reset( polygonSimplifiedData->
clone() );
634 QList< std::vector<p2t::Point *> > polylinesToDelete;
635 QHash<p2t::Point *, float> z;
638 std::vector<p2t::Point *> polyline;
639 _ringToPoly2tri( qgsgeometry_cast< const QgsLineString * >( polygonNew->exteriorRing() ), polyline, mNoZ ?
nullptr : &z );
640 polylinesToDelete << polyline;
642 std::unique_ptr<p2t::CDT> cdt(
new p2t::CDT( polyline ) );
645 for (
int i = 0; i < polygonNew->numInteriorRings(); ++i )
647 std::vector<p2t::Point *> holePolyline;
648 const QgsLineString *hole = qgsgeometry_cast< const QgsLineString *>( polygonNew->interiorRing( i ) );
650 _ringToPoly2tri( hole, holePolyline, mNoZ ?
nullptr : &z );
652 cdt->AddHole( holePolyline );
653 polylinesToDelete << holePolyline;
661 std::vector<p2t::Triangle *> triangles = cdt->GetTriangles();
663 mData.reserve( mData.size() + 3 * triangles.size() * (
stride() /
sizeof( float ) ) );
664 for (
size_t i = 0; i < triangles.size(); ++i )
666 p2t::Triangle *t = triangles[i];
667 for (
int j = 0; j < 3; ++j )
669 p2t::Point *p = t->GetPoint( j );
670 QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
672 pt = *toOldBase * pt;
673 const double fx = ( pt.x() / scale ) - mOriginX + pt0.
x();
674 const double fy = ( pt.y() / scale ) - mOriginY + pt0.
y();
675 const double baseHeight = mNoZ ? 0 : ( pt.z() + pt0.
z() );
676 const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.
z() );
677 if ( baseHeight < zMin )
679 if ( baseHeight > zMaxBase )
680 zMaxBase = baseHeight;
681 if ( fz > zMaxExtruded )
684 mData << fx << fz << -fy;
686 mData << pNormal.x() << pNormal.z() << - pNormal.y();
687 if ( mAddTextureCoords )
689 const std::pair<float, float> pr = rotateCoords( p->x, p->y, 0.0f, 0.0f, mTextureRotation );
690 mData << pr.first << pr.second;
697 for (
int j = 2; j >= 0; --j )
699 p2t::Point *p = t->GetPoint( j );
700 QVector4D pt( p->x, p->y, mNoZ ? 0 : z[p], 0 );
702 pt = *toOldBase * pt;
703 const double fx = ( pt.x() / scale ) - mOriginX + pt0.
x();
704 const double fy = ( pt.y() / scale ) - mOriginY + pt0.
y();
705 const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.
z() );
706 mData << fx << fz << -fy;
708 mData << -pNormal.x() << -pNormal.z() << pNormal.y();
709 if ( mAddTextureCoords )
711 const std::pair<float, float> pr = rotateCoords( p->x, p->y, 0.0f, 0.0f, mTextureRotation );
712 mData << pr.first << pr.second;
723 for (
int i = 0; i < polylinesToDelete.count(); ++i )
724 qDeleteAll( polylinesToDelete[i] );
728 if ( extrusionHeight != 0 && ( mTessellatedFacade & 1 ) )
730 _makeWalls( *exterior,
false, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation );
733 _makeWalls( *qgsgeometry_cast< const QgsLineString * >( polygon.
interiorRing( i ) ),
true, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation );
735 if ( zMaxBase + extrusionHeight > zMaxExtruded )
736 zMaxExtruded = zMaxBase + extrusionHeight;
741 if ( zMaxExtruded > mZMax )
742 mZMax = zMaxExtruded;
743 if ( zMaxBase > mZMax )
749 return mData.size() / (
stride() /
sizeof( float ) );
754 std::unique_ptr< QgsMultiPolygon > mp = std::make_unique< QgsMultiPolygon >();
755 const auto nVals = mData.size();
756 mp->reserve( nVals / 9 );
757 for (
auto i =
decltype( nVals ) {0}; i + 8 < nVals; i += 9 )
760 const QgsPoint p1( mData[i + 0], -mData[i + 2], mData[i + 1] );
761 const QgsPoint p2( mData[i + 3], -mData[i + 5], mData[i + 4] );
762 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.
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.
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 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 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 (holding no spatial information).
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(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