19#include <unordered_set>
36static std::pair<float, float> rotateCoords(
float x,
float y,
float origin_x,
float origin_y,
float r )
38 r = qDegreesToRadians( r );
39 float x0 = x - origin_x, y0 = y - origin_y;
43 const float x1 = origin_x + x0 * qCos( r ) - y0 * qSin( r );
44 const float y1 = origin_y + x0 * qSin( r ) + y0 * qCos( r );
45 return std::make_pair( x1, y1 );
48static 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 )
50 const float dx = x1 - x0;
51 const float dy = y1 - y0;
54 QVector3D vn = zUp ? QVector3D( -dy, dx, 0 ) : QVector3D( -dy, 0, -dx );
62 QVector<double> textureCoordinates;
63 textureCoordinates.reserve( 12 );
65 if ( fabsf( dy ) <= fabsf( dx ) )
96 textureCoordinates.push_back( u0 );
97 textureCoordinates.push_back( v0 );
99 textureCoordinates.push_back( u1 );
100 textureCoordinates.push_back( v1 );
102 textureCoordinates.push_back( u2 );
103 textureCoordinates.push_back( v2 );
105 textureCoordinates.push_back( u2 );
106 textureCoordinates.push_back( v2 );
108 textureCoordinates.push_back( u1 );
109 textureCoordinates.push_back( v1 );
111 textureCoordinates.push_back( u3 );
112 textureCoordinates.push_back( v3 );
114 for (
int i = 0; i < textureCoordinates.size(); i += 2 )
116 const std::pair<float, float> rotated = rotateCoords( textureCoordinates[i], textureCoordinates[i + 1], 0, 0, textureRotation );
117 textureCoordinates[i] = rotated.first;
118 textureCoordinates[i + 1] = rotated.second;
124 data << x0 << y0 << z0 + height;
126 data << x0 << z0 + height << -y0;
128 data << vn.x() << vn.y() << vn.z();
129 if ( addTextureCoords )
130 data << textureCoordinates[0] << textureCoordinates[1];
133 data << x1 << y1 << z1 + height;
135 data << x1 << z1 + height << -y1;
137 data << vn.x() << vn.y() << vn.z();
138 if ( addTextureCoords )
139 data << textureCoordinates[2] << textureCoordinates[3];
142 data << x0 << y0 << z0;
144 data << x0 << z0 << -y0;
146 data << vn.x() << vn.y() << vn.z();
147 if ( addTextureCoords )
148 data << textureCoordinates[4] << textureCoordinates[5];
153 data << x0 << y0 << z0;
155 data << x0 << z0 << -y0;
157 data << vn.x() << vn.y() << vn.z();
158 if ( addTextureCoords )
159 data << textureCoordinates[6] << textureCoordinates[7];
162 data << x1 << y1 << z1 + height;
164 data << x1 << z1 + height << -y1;
166 data << vn.x() << vn.y() << vn.z();
167 if ( addTextureCoords )
168 data << textureCoordinates[8] << textureCoordinates[9];
171 data << x1 << y1 << z1;
173 data << x1 << z1 << -y1;
175 data << vn.x() << vn.y() << vn.z();
176 if ( addTextureCoords )
177 data << textureCoordinates[10] << textureCoordinates[11];
188 setExtrusionFacesLegacy( facade );
199 setExtrusionFacesLegacy( facade );
216 mScale = bounds.
isNull() ? 1.0 : std::max( 10000.0 / bounds.
width(), 10000.0 / bounds.
height() );
221 mInputZValueIgnored = ignore;
226 mExtrusionFaces = faces;
229void QgsTessellator::setExtrusionFacesLegacy(
int facade )
255 mTextureRotation = rotation;
260 mAddBackFaces = addBackFaces;
265 mInvertNormals = invertNormals;
270 mAddNormals = addNormals;
276 mAddTextureCoords = addTextureUVs;
280void QgsTessellator::updateStride()
282 mStride = 3 *
sizeof( float );
284 mStride += 3 *
sizeof( float );
285 if ( mAddTextureCoords )
286 mStride += 2 *
sizeof( float );
289static void _makeWalls(
const QgsLineString &ring,
bool ccw,
float extrusionHeight, QVector<float> &data,
290 bool addNormals,
bool addTextureCoords,
double originX,
double originY,
double originZ,
float textureRotation,
bool zUp )
299 for (
int i = 1; i < ring.
numPoints(); ++i )
301 pt = ring.
pointN( is_counter_clockwise == ccw ? i : ring.
numPoints() - i - 1 );
302 const double x0 = ptPrev.
x() - originX, y0 = ptPrev.
y() - originY;
303 const double x1 = pt.
x() - originX, y1 = pt.
y() - originY;
304 const double z0 = std::isnan( ptPrev.
z() ) ? 0.0 : ptPrev.
z() - originZ;
305 const double z1 = std::isnan( pt.
z() ) ? 0.0 : pt.
z() - originZ;
308 make_quad(
static_cast<float>( x0 ),
static_cast<float>( y0 ),
static_cast<float>( z0 ),
static_cast<float>( x1 ),
static_cast<float>( y1 ),
static_cast<float>( z1 ), extrusionHeight, data, addNormals, addTextureCoords, textureRotation, zUp );
313static QVector3D calculateNormal(
const QgsLineString *curve,
double originX,
double originY,
double originZ,
bool invertNormal,
float extrusionHeight )
318 if ( extrusionHeight != 0 )
319 return QVector3D( 0, 0, 1 );
322 float orientation = 1.f;
324 orientation = -orientation;
326 orientation = -orientation;
327 return QVector3D( 0, 0, orientation );
335 for (
int i = 1; i < curve->
numPoints(); i++ )
338 if ( pt1.
z() != pt2.
z() )
344 if ( sameZ && extrusionHeight != 0 )
345 return QVector3D( 0, 0, 1 );
352 double nx = 0, ny = 0, nz = 0;
355 pt1.
setX( pt1.
x() - originX );
356 pt1.
setY( pt1.
y() - originY );
357 pt1.
setZ( std::isnan( pt1.
z() ) ? 0.0 : pt1.
z() - originZ );
358 for (
int i = 1; i < curve->
numPoints(); i++ )
361 pt2.
setX( pt2.
x() - originX );
362 pt2.
setY( pt2.
y() - originY );
363 pt2.
setZ( std::isnan( pt2.
z() ) ? 0.0 : pt2.
z() - originZ );
365 nx += ( pt1.
y() - pt2.
y() ) * ( pt1.
z() + pt2.
z() );
366 ny += ( pt1.
z() - pt2.
z() ) * ( pt1.
x() + pt2.
x() );
367 nz += ( pt1.
x() - pt2.
x() ) * ( pt1.
y() + pt2.
y() );
372 QVector3D normal( nx, ny, nz );
380static void _normalVectorToXYVectors(
const QVector3D &pNormal, QVector3D &pXVector, QVector3D &pYVector )
385 if ( pNormal.z() > 0.001 || pNormal.z() < -0.001 )
387 pXVector = QVector3D( 1, 0, -pNormal.x() / pNormal.z() );
389 else if ( pNormal.y() > 0.001 || pNormal.y() < -0.001 )
391 pXVector = QVector3D( 1, -pNormal.x() / pNormal.y(), 0 );
395 pXVector = QVector3D( -pNormal.y() / pNormal.x(), 1, 0 );
397 pXVector.normalize();
398 pYVector = QVector3D::normal( pNormal, pXVector );
403 std::size_t
operator()(
const std::pair<float, float> pair )
const
405 const std::size_t h1 = std::hash<float>()( pair.first );
406 const std::size_t h2 = std::hash<float>()( pair.second );
412static void _ringToPoly2tri(
const QgsLineString *ring, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> *zHash )
416 polyline.reserve( pCount );
418 const double *srcXData = ring->
xData();
419 const double *srcYData = ring->
yData();
420 const double *srcZData = ring->
zData();
421 std::unordered_set<std::pair<float, float>,
float_pair_hash> foundPoints;
423 for (
int i = 0; i < pCount - 1; ++i )
425 const float x = *srcXData++;
426 const float y = *srcYData++;
428 const auto res = foundPoints.insert( std::make_pair( x, y ) );
435 p2t::Point *pt2 =
new p2t::Point( x, y );
436 polyline.push_back( pt2 );
439 ( *zHash )[pt2] = *srcZData++;
447 const double exp = 1e10;
448 return round( x * exp ) / exp;
452static QgsCurve *_transform_ring_to_new_base(
const QgsLineString &curve,
const QgsPoint &pt0,
const QMatrix4x4 *toNewBase,
const float scale )
461 double *xData = x.data();
462 double *yData = y.data();
463 double *zData = z.data();
465 const double *srcXData = curve.
xData();
466 const double *srcYData = curve.
yData();
467 const double *srcZData = curve.
is3D() ? curve.
zData() :
nullptr;
469 for (
int i = 0; i < count; ++i )
471 QVector4D v( *srcXData++ - pt0.
x(),
472 *srcYData++ - pt0.
y(),
473 srcZData ? *srcZData++ - pt0.
z() : 0,
476 v = toNewBase->map( v );
479 v.setX( v.x() * scale );
480 v.setY( v.y() * scale );
500static QgsPolygon *_transform_polygon_to_new_base(
const QgsPolygon &polygon,
const QgsPoint &pt0,
const QMatrix4x4 *toNewBase,
const float scale )
514 std::vector< const QgsLineString * > rings;
523 if ( numPoints <= 1 )
526 const double *srcXData = ring->
xData();
527 const double *srcYData = ring->
yData();
528 double x0 = *srcXData++;
529 double y0 = *srcYData++;
530 for (
int i = 1; i < numPoints; ++i )
532 const double x1 = *srcXData++;
533 const double y1 = *srcYData++;
542 return min_d != 1e20 ? std::sqrt( min_d ) : 1e20;
545void QgsTessellator::calculateBaseTransform(
const QVector3D &pNormal, QMatrix4x4 *base )
const
547 if ( !mInputZValueIgnored && pNormal != QVector3D( 0, 0, 1 ) )
551 QVector3D pXVector, pYVector;
552 _normalVectorToXYVectors( pNormal, pXVector, pYVector );
558 pXVector.x(), pXVector.y(), pXVector.z(), 0,
559 pYVector.x(), pYVector.y(), pYVector.z(), 0,
560 pNormal.x(), pNormal.y(), pNormal.z(), 0,
565 base->setToIdentity();
569void QgsTessellator::addTriangleVertices(
570 const std::array<QVector3D, 3> &points,
572 float extrusionHeight,
573 QMatrix4x4 *transformMatrix,
579 const QVector3D normal = reverse ? -pNormal : pNormal;
580 for (
int i = 0; i < 3; ++i )
582 const int index = reverse ? 2 - i : i;
585 QVector3D point = points[ index ];
586 const float z = mInputZValueIgnored ? 0.0f : point.z();
587 QVector4D pt( point.x(), point.y(), z, 0 );
589 pt = *transformMatrix * pt;
591 const double fx = pt.
x() - mOrigin.x() + originOffset->
x();
592 const double fy = pt.y() - mOrigin.y() + originOffset->
y();
593 const double baseHeight = mInputZValueIgnored ? 0 : pt.z() - mOrigin.z() + originOffset->
z();
594 const double fz = mInputZValueIgnored ? 0.0 : ( baseHeight + extrusionHeight );
596 if ( baseHeight < mZMin )
597 mZMin =
static_cast<float>( baseHeight );
598 if ( baseHeight > mZMax )
599 mZMax =
static_cast<float>( baseHeight );
601 mZMax =
static_cast<float>( fz );
606 mData << static_cast<float>( fx ) <<
static_cast<float>( fy ) <<
static_cast<float>( fz );
608 mData << normal.x() << normal.y() << normal.z();
612 mData << static_cast<float>( fx ) <<
static_cast<float>( fz ) <<
static_cast<float>( -fy );
614 mData << normal.x() << normal.z() << - normal.y();
618 if ( mAddTextureCoords )
620 const std::pair<float, float> pr = rotateCoords(
static_cast<float>( pt.x() ),
static_cast<float>( pt.y() ), 0.0f, 0.0f, mTextureRotation );
621 mData << pr.first << pr.second;
626std::vector<QVector3D> QgsTessellator::generateConstrainedDelaunayTriangles(
const QgsPolygon *polygonNew )
628 QList<std::vector<p2t::Point *>> polylinesToDelete;
629 QHash<p2t::Point *, float> z;
632 std::vector<p2t::Point *> polyline;
634 polylinesToDelete << polyline;
636 p2t::CDT cdt = p2t::CDT( polyline );
641 std::vector<p2t::Point *> holePolyline;
644 _ringToPoly2tri( hole, holePolyline, mInputZValueIgnored ?
nullptr : &z );
646 cdt.AddHole( holePolyline );
647 polylinesToDelete << holePolyline;
651 std::vector<p2t::Triangle *> triangles = cdt.GetTriangles();
653 std::vector<QVector3D> trianglePoints;
654 trianglePoints.reserve( triangles.size() * 3 );
656 for ( p2t::Triangle *t : triangles )
658 trianglePoints.emplace_back( t->GetPoint( 0 )->x / mScale, t->GetPoint( 0 )->y / mScale, z.value( t->GetPoint( 0 ) ) );
659 trianglePoints.emplace_back( t->GetPoint( 1 )->x / mScale, t->GetPoint( 1 )->y / mScale, z.value( t->GetPoint( 1 ) ) );
660 trianglePoints.emplace_back( t->GetPoint( 2 )->x / mScale, t->GetPoint( 2 )->y / mScale, z.value( t->GetPoint( 2 ) ) );
663 for (
int i = 0; i < polylinesToDelete.count(); ++i )
664 qDeleteAll( polylinesToDelete[i] );
666 return trianglePoints;
675 const QVector3D pNormal = !mInputZValueIgnored ? calculateNormal( exterior, mOrigin.x(), mOrigin.y(), mOrigin.z(), mInvertNormals, extrusionHeight ) : QVector3D();
676 const int pCount = exterior->
numPoints();
683 std::unique_ptr<QgsPolygon> polygonNew;
685 if ( !mInputZValueIgnored && !
qgsDoubleNear( pNormal.length(), 1, 0.001 ) )
692 if ( buildFloor || buildRoof )
694 calculateBaseTransform( pNormal, &base );
695 polygonNew.reset( _transform_polygon_to_new_base( polygon, extrusionOrigin, &base, mScale ) );
698 base = base.transposed();
702 Q_ASSERT( polygonNew->exteriorRing()->numPoints() >= 3 );
705 const QVector3D p1(
static_cast<float>( triangle->
xAt( 0 ) ),
static_cast<float>( triangle->
yAt( 0 ) ),
static_cast<float>( triangle->
zAt( 0 ) ) );
706 const QVector3D p2(
static_cast<float>( triangle->
xAt( 1 ) ),
static_cast<float>( triangle->
yAt( 1 ) ),
static_cast<float>( triangle->
zAt( 1 ) ) );
707 const QVector3D p3(
static_cast<float>( triangle->
xAt( 2 ) ),
static_cast<float>( triangle->
yAt( 2 ) ),
static_cast<float>( triangle->
zAt( 2 ) ) );
708 const std::array<QVector3D, 3> points = { { p1, p2, p3 } };
710 addTriangleVertices( points, pNormal, extrusionHeight, &base, &extrusionOrigin,
false );
714 addTriangleVertices( points, pNormal, extrusionHeight, &base, &extrusionOrigin,
true );
717 if ( extrusionHeight != 0 && buildFloor )
719 addTriangleVertices( points, pNormal, 0, &base, &extrusionOrigin,
false );
722 addTriangleVertices( points, pNormal, 0, &base, &extrusionOrigin,
true );
735 if ( polygonSimplified.
isNull() )
737 mError = QObject::tr(
"geometry simplification failed - skipping" );
745 mError = QObject::tr(
"geometry's coordinates are too close to each other and simplification failed - skipping" );
750 polygonNew.reset( polygonSimplifiedData->
clone() );
757 std::vector<QVector3D> trianglePoints = generateConstrainedDelaunayTriangles( polygonNew.get() );
759 Q_ASSERT( trianglePoints.size() % 3 == 0 );
761 mData.reserve( mData.size() + trianglePoints.size() * 3 * (
stride() /
sizeof(
float ) ) );
763 for (
size_t i = 0; i < trianglePoints.size(); i += 3 )
765 const QVector3D p1 = trianglePoints[ i + 0 ];
766 const QVector3D p2 = trianglePoints[ i + 1 ];
767 const QVector3D p3 = trianglePoints[ i + 2 ];
768 const std::array<QVector3D, 3> points = { { p1, p2, p3 } };
770 addTriangleVertices( points, pNormal, extrusionHeight, &base, &extrusionOrigin,
false );
774 addTriangleVertices( points, pNormal, extrusionHeight, &base, &extrusionOrigin,
true );
777 if ( extrusionHeight != 0 && buildFloor )
779 addTriangleVertices( points, pNormal, 0, &base, &extrusionOrigin,
true );
782 addTriangleVertices( points, pNormal, 0, &base, &extrusionOrigin,
false );
787 catch ( std::runtime_error &err )
793 mError = QObject::tr(
"An unknown error occurred" );
799 if ( extrusionHeight != 0 && buildWalls )
801 _makeWalls( *exterior,
false, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOrigin.x(), mOrigin.y(), mOrigin.z(), mTextureRotation, mOutputZUp );
804 _makeWalls( *
qgsgeometry_cast< const QgsLineString * >( polygon.
interiorRing( i ) ),
true, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOrigin.x(), mOrigin.y(), mOrigin.z(), mTextureRotation, mOutputZUp );
810 if ( mData.size() == 0 )
813 return mData.size() / (
stride() /
sizeof( float ) );
818 auto mp = std::make_unique< QgsMultiPolygon >();
819 const auto nVals = mData.size();
820 mp->reserve( nVals / 9 );
821 for (
auto i =
decltype( nVals ) {0}; i + 8 < nVals; i += 9 )
825 const QgsPoint p1( mData[i + 0], mData[i + 1], mData[i + 2] );
826 const QgsPoint p2( mData[i + 3], mData[i + 4], mData[i + 5] );
827 const QgsPoint p3( mData[i + 6], mData[i + 7], mData[i + 8] );
833 const QgsPoint p1( mData[i + 0], -mData[i + 2], mData[i + 1] );
834 const QgsPoint p2( mData[i + 3], -mData[i + 5], mData[i + 4] );
835 const QgsPoint p3( mData[i + 6], -mData[i + 8], mData[i + 7] );
@ CounterClockwise
Counter-clockwise direction.
@ Clockwise
Clockwise direction.
QFlags< ExtrusionFace > ExtrusionFaces
Tessellator extrusion face types.
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 setZ(double z)
Sets the point's z-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.
float textureRotation() const
Returns the rotation of texture UV coordinates (in degrees).
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.
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.
void setInvertNormals(bool invertNormals)
Sets whether normals should be inverted (true) or not (false).
void setBackFacesEnabled(bool addBackFaces)
Sets whether back faces should be added to the output data (true) or not (false).
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 (true) or not (false).
void setAddNormals(bool addNormals)
Sets whether normals should be added to the output data (true) or not (false).
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
static Q_INVOKABLE 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).
T qgsgeometry_cast(QgsAbstractGeometry *geom)
double _round_coord(double x)
double _minimum_distance_between_coordinates(const QgsPolygon &polygon)
std::size_t operator()(const std::pair< float, float > pair) const