QgsBox3d::QgsBox3d( const QgsPoint &p1, const QgsPoint &p2 ) : mBounds2d( p1.x(), p1.y(), p2.x(), p2.y() ) , mZmin( std::min( p1.z(), p2.z() ) ) , mZmax( std::max( p1.z(), p2.z() ) ) { mBounds2d.normalize(); }
static void _ringToPoly2tri( const QgsCurve *ring, const QgsPoint &ptFirst, const QMatrix4x4 &toNewBase, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> &zHash ) { QgsVertexId::VertexType vt; QgsPoint pt; const int pCount = ring->numPoints(); double x0 = ptFirst.x(), y0 = ptFirst.y(), z0 = ( std::isnan( ptFirst.z() ) ? 0 : ptFirst.z() ); polyline.reserve( pCount ); for ( int i = 0; i < pCount - 1; ++i ) { ring->pointAt( i, pt, vt ); QVector4D tempPt( pt.x() - x0, pt.y() - y0, std::isnan( pt.z() ) ? 0 : pt.z() - z0, 0 ); QVector4D newBasePt = toNewBase * tempPt; const float x = newBasePt.x(); const float y = newBasePt.y(); const float z = newBasePt.z(); const bool found = std::find_if( polyline.begin(), polyline.end(), [x, y]( p2t::Point *&p ) { return *p == p2t::Point( x, y ); } ) != polyline.end(); if ( found ) { continue; } p2t::Point *pt2 = new p2t::Point( x, y ); polyline.push_back( pt2 ); zHash[pt2] = z; } }
static void _makeWalls( const QgsCurve &ring, bool ccw, float extrusionHeight, QVector<float> &data, bool addNormals, double originX, double originY ) { // we need to find out orientation of the ring so that the triangles we generate // face the right direction // (for exterior we want clockwise order, for holes we want counter-clockwise order) bool is_counter_clockwise = _isRingCounterClockWise( ring ); QgsVertexId::VertexType vt; QgsPoint pt; QgsPoint ptPrev; ring.pointAt( is_counter_clockwise == ccw ? 0 : ring.numPoints() - 1, ptPrev, vt ); for ( int i = 1; i < ring.numPoints(); ++i ) { ring.pointAt( is_counter_clockwise == ccw ? i : ring.numPoints() - i - 1, pt, vt ); float x0 = ptPrev.x() - originX, y0 = ptPrev.y() - originY; float x1 = pt.x() - originX, y1 = pt.y() - originY; float z0 = ptPrev.z(); float z1 = pt.z(); // make a quad make_quad( x0, y0, z0, x1, y1, z1, extrusionHeight, data, addNormals ); ptPrev = pt; } }
bool QgsBox3d::contains( const QgsPoint &p ) const { if ( !mBounds2d.contains( QgsPointXY( p.x(), p.y() ) ) ) return false; if ( p.is3D() ) return mZmin <= p.z() && p.z() <= mZmax; else return true; }
bool QgsCurve::isClosed() const { if ( numPoints() == 0 ) return false; //don't consider M-coordinates when testing closedness QgsPoint start = startPoint(); QgsPoint end = endPoint(); bool closed = qgsDoubleNear( start.x(), end.x(), 1E-8 ) && qgsDoubleNear( start.y(), end.y(), 1E-8 ); if ( is3D() && closed ) closed &= qgsDoubleNear( start.z(), end.z(), 1E-8 ) || ( std::isnan( start.z() ) && std::isnan( end.z() ) ); return closed; }
bool QgsCircularString::insertVertex( QgsVertexId position, const QgsPoint &vertex ) { if ( position.vertex >= mX.size() || position.vertex < 1 ) { return false; } mX.insert( position.vertex, vertex.x() ); mY.insert( position.vertex, vertex.y() ); if ( is3D() ) { mZ.insert( position.vertex, vertex.z() ); } if ( isMeasure() ) { mM.insert( position.vertex, vertex.m() ); } bool vertexNrEven = ( position.vertex % 2 == 0 ); if ( vertexNrEven ) { insertVertexBetween( position.vertex - 2, position.vertex - 1, position.vertex ); } else { insertVertexBetween( position.vertex, position.vertex + 1, position.vertex - 1 ); } clearCache(); //set bounding box invalid return true; }
void QgsMapToolIdentify::closestVertexAttributes( const QgsAbstractGeometry &geometry, QgsVertexId vId, QgsMapLayer *layer, QMap< QString, QString > &derivedAttributes ) { QString str = QLocale::system().toString( vId.vertex + 1 ); derivedAttributes.insert( tr( "Closest vertex number" ), str ); QgsPoint closestPoint = geometry.vertexAt( vId ); QgsPointXY closestPointMapCoords = mCanvas->mapSettings().layerToMapCoordinates( layer, QgsPointXY( closestPoint.x(), closestPoint.y() ) ); derivedAttributes.insert( QStringLiteral( "Closest vertex X" ), formatXCoordinate( closestPointMapCoords ) ); derivedAttributes.insert( QStringLiteral( "Closest vertex Y" ), formatYCoordinate( closestPointMapCoords ) ); if ( closestPoint.is3D() ) { str = QLocale::system().toString( closestPoint.z(), 'g', 10 ); derivedAttributes.insert( QStringLiteral( "Closest vertex Z" ), str ); } if ( closestPoint.isMeasure() ) { str = QLocale::system().toString( closestPoint.m(), 'g', 10 ); derivedAttributes.insert( QStringLiteral( "Closest vertex M" ), str ); } if ( vId.type == QgsVertexId::CurveVertex ) { double radius, centerX, centerY; QgsVertexId vIdBefore = vId; --vIdBefore.vertex; QgsVertexId vIdAfter = vId; ++vIdAfter.vertex; QgsGeometryUtils::circleCenterRadius( geometry.vertexAt( vIdBefore ), geometry.vertexAt( vId ), geometry.vertexAt( vIdAfter ), radius, centerX, centerY ); derivedAttributes.insert( QStringLiteral( "Closest vertex radius" ), QLocale::system().toString( radius ) ); } }
static void _ringToPoly2tri( const QgsCurve *ring, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> &zHash ) { QgsVertexId::VertexType vt; QgsPoint pt; const int pCount = ring->numPoints(); polyline.reserve( pCount ); for ( int i = 0; i < pCount - 1; ++i ) { ring->pointAt( i, pt, vt ); const float x = pt.x(); const float y = pt.y(); const float z = pt.z(); const bool found = std::find_if( polyline.begin(), polyline.end(), [x, y]( p2t::Point *&p ) { return *p == p2t::Point( x, y ); } ) != polyline.end(); if ( found ) { continue; } p2t::Point *pt2 = new p2t::Point( x, y ); polyline.push_back( pt2 ); zHash[pt2] = z; } }
bool QgsLineString::insertVertex( QgsVertexId position, const QgsPoint &vertex ) { if ( position.vertex < 0 || position.vertex > mX.size() ) { return false; } if ( mWkbType == QgsWkbTypes::Unknown || mX.isEmpty() ) { setZMTypeFromSubGeometry( &vertex, QgsWkbTypes::LineString ); } mX.insert( position.vertex, vertex.x() ); mY.insert( position.vertex, vertex.y() ); if ( is3D() ) { mZ.insert( position.vertex, vertex.z() ); } if ( isMeasure() ) { mM.insert( position.vertex, vertex.m() ); } clearCache(); //set bounding box invalid return true; }
QgsLineString::QgsLineString( const QgsPoint &p1, const QgsPoint &p2 ) { mWkbType = QgsWkbTypes::LineString; mX.resize( 2 ); mX[ 0 ] = p1.x(); mX[ 1 ] = p2.x(); mY.resize( 2 ); mY[ 0 ] = p1.y(); mY[ 1 ] = p2.y(); if ( p1.is3D() ) { mWkbType = QgsWkbTypes::addZ( mWkbType ); mZ.resize( 2 ); mZ[ 0 ] = p1.z(); mZ[ 1 ] = p2.z(); } if ( p1.isMeasure() ) { mWkbType = QgsWkbTypes::addM( mWkbType ); mM.resize( 2 ); mM[ 0 ] = p1.m(); mM[ 1 ] = p2.m(); } }
void QgsLineString::addVertex( const QgsPoint &pt ) { if ( mWkbType == QgsWkbTypes::Unknown || mX.isEmpty() ) { setZMTypeFromSubGeometry( &pt, QgsWkbTypes::LineString ); } mX.append( pt.x() ); mY.append( pt.y() ); if ( is3D() ) { mZ.append( pt.z() ); } if ( isMeasure() ) { mM.append( pt.m() ); } clearCache(); //set bounding box invalid }
bool QgsLineString::moveVertex( QgsVertexId position, const QgsPoint &newPos ) { if ( position.vertex < 0 || position.vertex >= mX.size() ) { return false; } mX[position.vertex] = newPos.x(); mY[position.vertex] = newPos.y(); if ( is3D() && newPos.is3D() ) { mZ[position.vertex] = newPos.z(); } if ( isMeasure() && newPos.isMeasure() ) { mM[position.vertex] = newPos.m(); } clearCache(); //set bounding box invalid return true; }
QList<QVector3D> Qgs3DUtils::positions( const Qgs3DMapSettings &map, QgsVectorLayer *layer, const QgsFeatureRequest &request, Qgs3DTypes::AltitudeClamping altClamp ) { QList<QVector3D> positions; QgsFeature f; QgsFeatureIterator fi = layer->getFeatures( request ); while ( fi.nextFeature( f ) ) { if ( f.geometry().isNull() ) continue; const QgsAbstractGeometry *g = f.geometry().constGet(); for ( auto it = g->vertices_begin(); it != g->vertices_end(); ++it ) { QgsPoint pt = *it; float geomZ = 0; if ( pt.is3D() ) { geomZ = pt.z(); } float terrainZ = map.terrainGenerator()->heightAt( pt.x(), pt.y(), map ) * map.terrainVerticalScale(); float h; switch ( altClamp ) { case Qgs3DTypes::AltClampAbsolute: default: h = geomZ; break; case Qgs3DTypes::AltClampTerrain: h = terrainZ; break; case Qgs3DTypes::AltClampRelative: h = terrainZ + geomZ; break; } positions.append( QVector3D( pt.x() - map.origin().x(), h, -( pt.y() - map.origin().y() ) ) ); //qDebug() << positions.last(); } } return positions; }
float Qgs3DUtils::clampAltitude( const QgsPoint &p, Qgs3DTypes::AltitudeClamping altClamp, Qgs3DTypes::AltitudeBinding altBind, float height, const QgsPoint ¢roid, const Qgs3DMapSettings &map ) { float terrainZ = 0; if ( altClamp == Qgs3DTypes::AltClampRelative || altClamp == Qgs3DTypes::AltClampTerrain ) { QgsPointXY pt = altBind == Qgs3DTypes::AltBindVertex ? p : centroid; terrainZ = map.terrainGenerator()->heightAt( pt.x(), pt.y(), map ); } float geomZ = altClamp == Qgs3DTypes::AltClampAbsolute || altClamp == Qgs3DTypes::AltClampRelative ? p.z() : 0; float z = ( terrainZ + geomZ ) * map.terrainVerticalScale() + height; return z; }
void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeight ) { if ( _minimum_distance_between_coordinates( polygon ) < 0.001 ) { // when the distances between coordinates of input points are very small, // the triangulation likes to crash on numerical errors - when the distances are ~ 1e-5 // Assuming that the coordinates should be in a projected CRS, we should be able // to simplify geometries that may cause problems and avoid possible crashes QgsGeometry polygonSimplified = QgsGeometry( polygon.clone() ).simplify( 0.001 ); const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet() ); if ( _minimum_distance_between_coordinates( *polygonSimplifiedData ) < 0.001 ) { // Failed to fix that. It could be a really tiny geometry... or maybe they gave us // geometry in unprojected lat/lon coordinates QgsMessageLog::logMessage( "geometry's coordinates are too close to each other and simplification failed - skipping", "3D" ); } else { addPolygon( *polygonSimplifiedData, extrusionHeight ); } return; } if ( !_check_intersecting_rings( polygon ) ) { // skip the polygon - it would cause a crash inside poly2tri library QgsMessageLog::logMessage( "polygon rings intersect each other - skipping", "3D" ); return; } const QgsCurve *exterior = polygon.exteriorRing(); QList< std::vector<p2t::Point *> > polylinesToDelete; QHash<p2t::Point *, float> z; std::vector<p2t::Point *> polyline; const QVector3D pNormal = _calculateNormal( exterior, mOriginX, mOriginY ); const int pCount = exterior->numPoints(); // Polygon is a triangle if ( pCount == 4 ) { QgsPoint pt; QgsVertexId::VertexType vt; for ( int i = 0; i < 3; i++ ) { exterior->pointAt( i, pt, vt ); mData << pt.x() - mOriginX << pt.z() << - pt.y() + mOriginY; if ( mAddNormals ) mData << pNormal.x() << pNormal.z() << - pNormal.y(); } } else { if ( !qgsDoubleNear( pNormal.length(), 1, 0.001 ) ) return; // this should not happen - pNormal should be normalized to unit length QVector3D pXVector, pYVector; _normalVectorToXYVectors( pNormal, pXVector, pYVector ); // so now we have three orthogonal unit vectors defining new base // let's build transform matrix. We actually need just a 3x3 matrix, // but Qt does not have good support for it, so using 4x4 matrix instead. QMatrix4x4 toNewBase( pXVector.x(), pXVector.y(), pXVector.z(), 0, pYVector.x(), pYVector.y(), pYVector.z(), 0, pNormal.x(), pNormal.y(), pNormal.z(), 0, 0, 0, 0, 0 ); // our 3x3 matrix is orthogonal, so for inverse we only need to transpose it QMatrix4x4 toOldBase = toNewBase.transposed(); const QgsPoint ptFirst( exterior->startPoint() ); _ringToPoly2tri( exterior, ptFirst, toNewBase, polyline, z ); polylinesToDelete << polyline; // TODO: robustness (no nearly duplicate points, invalid geometries ...) double x0 = ptFirst.x(), y0 = ptFirst.y(), z0 = ( std::isnan( ptFirst.z() ) ? 0 : ptFirst.z() ); if ( polyline.size() == 3 && polygon.numInteriorRings() == 0 ) { for ( std::vector<p2t::Point *>::iterator it = polyline.begin(); it != polyline.end(); it++ ) { p2t::Point *p = *it; QVector4D ptInNewBase( p->x, p->y, z[p], 0 ); QVector4D nPoint = toOldBase * ptInNewBase; const double fx = nPoint.x() - mOriginX + x0; const double fy = nPoint.y() - mOriginY + y0; const double fz = nPoint.z() + extrusionHeight + z0; mData << fx << fz << -fy; if ( mAddNormals ) mData << pNormal.x() << pNormal.z() << - pNormal.y(); } } else if ( polyline.size() >= 3 ) { p2t::CDT *cdt = new p2t::CDT( polyline ); // polygon holes for ( int i = 0; i < polygon.numInteriorRings(); ++i ) { std::vector<p2t::Point *> holePolyline; const QgsCurve *hole = polygon.interiorRing( i ); _ringToPoly2tri( hole, ptFirst, toNewBase, holePolyline, z ); cdt->AddHole( holePolyline ); polylinesToDelete << holePolyline; } try { cdt->Triangulate(); std::vector<p2t::Triangle *> triangles = cdt->GetTriangles(); for ( size_t i = 0; i < triangles.size(); ++i ) { p2t::Triangle *t = triangles[i]; for ( int j = 0; j < 3; ++j ) { p2t::Point *p = t->GetPoint( j ); QVector4D ptInNewBase( p->x, p->y, z[p], 0 ); QVector4D nPoint = toOldBase * ptInNewBase; const double fx = nPoint.x() - mOriginX + x0; const double fy = nPoint.y() - mOriginY + y0; const double fz = nPoint.z() + extrusionHeight + z0; mData << fx << fz << -fy; if ( mAddNormals ) mData << pNormal.x() << pNormal.z() << - pNormal.y(); } } } catch ( ... ) { QgsMessageLog::logMessage( "Triangulation failed. Skipping polygon...", "3D" ); } delete cdt; } for ( int i = 0; i < polylinesToDelete.count(); ++i ) qDeleteAll( polylinesToDelete[i] ); } // add walls if extrusion is enabled if ( extrusionHeight != 0 ) { _makeWalls( *exterior, false, extrusionHeight, mData, mAddNormals, mOriginX, mOriginY ); for ( int i = 0; i < polygon.numInteriorRings(); ++i ) _makeWalls( *polygon.interiorRing( i ), true, extrusionHeight, mData, mAddNormals, mOriginX, mOriginY ); } }
void QgsMapToolDigitizeFeature::cadCanvasReleaseEvent( QgsMapMouseEvent *e ) { QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mLayer ); if ( !vlayer ) //if no given layer take the current from canvas vlayer = currentVectorLayer(); if ( !vlayer ) { notifyNotVectorLayer(); return; } QgsWkbTypes::Type layerWKBType = vlayer->wkbType(); QgsVectorDataProvider *provider = vlayer->dataProvider(); if ( !( provider->capabilities() & QgsVectorDataProvider::AddFeatures ) ) { emit messageEmitted( tr( "The data provider for this layer does not support the addition of features." ), Qgis::Warning ); return; } if ( !vlayer->isEditable() ) { notifyNotEditableLayer(); return; } // POINT CAPTURING if ( mode() == CapturePoint ) { if ( e->button() != Qt::LeftButton ) return; //check we only use this tool for point/multipoint layers if ( vlayer->geometryType() != QgsWkbTypes::PointGeometry && mCheckGeometryType ) { emit messageEmitted( tr( "Wrong editing tool, cannot apply the 'capture point' tool on this vector layer" ), Qgis::Warning ); return; } QgsPoint savePoint; //point in layer coordinates bool isMatchPointZ = false; try { QgsPoint fetchPoint; int res; res = fetchLayerPoint( e->mapPointMatch(), fetchPoint ); if ( QgsWkbTypes::hasZ( fetchPoint.wkbType() ) ) isMatchPointZ = true; if ( res == 0 ) { if ( isMatchPointZ ) savePoint = fetchPoint; else savePoint = QgsPoint( fetchPoint.x(), fetchPoint.y() ); } else { QgsPointXY layerPoint = toLayerCoordinates( vlayer, e->mapPoint() ); if ( isMatchPointZ ) savePoint = QgsPoint( QgsWkbTypes::PointZ, layerPoint.x(), layerPoint.y(), fetchPoint.z() ); else savePoint = QgsPoint( layerPoint.x(), layerPoint.y() ); } } catch ( QgsCsException &cse ) { Q_UNUSED( cse ); emit messageEmitted( tr( "Cannot transform the point to the layers coordinate system" ), Qgis::Warning ); return; } //only do the rest for provider with feature addition support //note that for the grass provider, this will return false since //grass provider has its own mechanism of feature addition if ( provider->capabilities() & QgsVectorDataProvider::AddFeatures ) { QgsFeature f( vlayer->fields(), 0 ); QgsGeometry g; if ( layerWKBType == QgsWkbTypes::Point ) { g = QgsGeometry( qgis::make_unique<QgsPoint>( savePoint ) ); } else if ( !QgsWkbTypes::isMultiType( layerWKBType ) && QgsWkbTypes::hasZ( layerWKBType ) ) { g = QgsGeometry( qgis::make_unique<QgsPoint>( savePoint.x(), savePoint.y(), isMatchPointZ ? savePoint.z() : defaultZValue() ) ); } else if ( QgsWkbTypes::isMultiType( layerWKBType ) && !QgsWkbTypes::hasZ( layerWKBType ) ) { g = QgsGeometry::fromMultiPointXY( QgsMultiPointXY() << savePoint ); } else if ( QgsWkbTypes::isMultiType( layerWKBType ) && QgsWkbTypes::hasZ( layerWKBType ) ) { QgsMultiPoint *mp = new QgsMultiPoint(); mp->addGeometry( new QgsPoint( QgsWkbTypes::PointZ, savePoint.x(), savePoint.y(), isMatchPointZ ? savePoint.z() : defaultZValue() ) ); g.set( mp ); } else { // if layer supports more types (mCheckGeometryType is false) g = QgsGeometry( qgis::make_unique<QgsPoint>( savePoint ) ); } if ( QgsWkbTypes::hasM( layerWKBType ) ) { g.get()->addMValue(); } f.setGeometry( g ); f.setValid( true ); digitized( f ); // we are done with digitizing for now so instruct advanced digitizing dock to reset its CAD points cadDockWidget()->clearPoints(); } } // LINE AND POLYGON CAPTURING else if ( mode() == CaptureLine || mode() == CapturePolygon ) { //check we only use the line tool for line/multiline layers if ( mode() == CaptureLine && vlayer->geometryType() != QgsWkbTypes::LineGeometry && mCheckGeometryType ) { emit messageEmitted( tr( "Wrong editing tool, cannot apply the 'capture line' tool on this vector layer" ), Qgis::Warning ); return; } //check we only use the polygon tool for polygon/multipolygon layers if ( mode() == CapturePolygon && vlayer->geometryType() != QgsWkbTypes::PolygonGeometry && mCheckGeometryType ) { emit messageEmitted( tr( "Wrong editing tool, cannot apply the 'capture polygon' tool on this vector layer" ), Qgis::Warning ); return; } //add point to list and to rubber band if ( e->button() == Qt::LeftButton ) { int error = addVertex( e->mapPoint(), e->mapPointMatch() ); if ( error == 1 ) { //current layer is not a vector layer return; } else if ( error == 2 ) { //problem with coordinate transformation emit messageEmitted( tr( "Cannot transform the point to the layers coordinate system" ), Qgis::Warning ); return; } startCapturing(); } else if ( e->button() == Qt::RightButton ) { // End of string deleteTempRubberBand(); //lines: bail out if there are not at least two vertices if ( mode() == CaptureLine && size() < 2 ) { stopCapturing(); return; } //polygons: bail out if there are not at least two vertices if ( mode() == CapturePolygon && size() < 3 ) { stopCapturing(); return; } if ( mode() == CapturePolygon ) { closePolygon(); } //create QgsFeature with wkb representation std::unique_ptr< QgsFeature > f( new QgsFeature( vlayer->fields(), 0 ) ); //does compoundcurve contain circular strings? //does provider support circular strings? bool hasCurvedSegments = captureCurve()->hasCurvedSegments(); bool providerSupportsCurvedSegments = vlayer->dataProvider()->capabilities() & QgsVectorDataProvider::CircularGeometries; QList<QgsPointLocator::Match> snappingMatchesList; QgsCurve *curveToAdd = nullptr; if ( hasCurvedSegments && providerSupportsCurvedSegments ) { curveToAdd = captureCurve()->clone(); } else { curveToAdd = captureCurve()->curveToLine(); snappingMatchesList = snappingMatches(); } if ( mode() == CaptureLine ) { QgsGeometry g( curveToAdd ); f->setGeometry( g ); } else { QgsCurvePolygon *poly = nullptr; if ( hasCurvedSegments && providerSupportsCurvedSegments ) { poly = new QgsCurvePolygon(); } else { poly = new QgsPolygon(); } poly->setExteriorRing( curveToAdd ); QgsGeometry g( poly ); f->setGeometry( g ); QgsGeometry featGeom = f->geometry(); int avoidIntersectionsReturn = featGeom.avoidIntersections( QgsProject::instance()->avoidIntersectionsLayers() ); f->setGeometry( featGeom ); if ( avoidIntersectionsReturn == 1 ) { //not a polygon type. Impossible to get there } if ( f->geometry().isEmpty() ) //avoid intersection might have removed the whole geometry { emit messageEmitted( tr( "The feature cannot be added because it's geometry collapsed due to intersection avoidance" ), Qgis::Critical ); stopCapturing(); return; } } f->setValid( true ); digitized( *f ); stopCapturing(); } } }
static QgsCurve *_transform_ring_to_new_base( const QgsCurve &curve, const QgsPoint &pt0, const QMatrix4x4 *toNewBase ) { int count = curve.numPoints(); QVector<QgsPoint> pts; pts.reserve( count ); QgsVertexId::VertexType vt; for ( int i = 0; i < count; ++i ) { QgsPoint pt; curve.pointAt( i, pt, vt ); QgsPoint pt2( QgsWkbTypes::PointZ, pt.x() - pt0.x(), pt.y() - pt0.y(), std::isnan( pt.z() ) ? 0 : pt.z() - pt0.z() ); QVector4D v( pt2.x(), pt2.y(), pt2.z(), 0 ); if ( toNewBase ) v = toNewBase->map( v ); // we also round coordinates before passing them to poly2tri triangulation in order to fix possible numerical // stability issues. We had crashes with nearly collinear points where one of the points was off by a tiny bit (e.g. by 1e-20). // See TestQgsTessellator::testIssue17745(). // // A hint for a similar issue: https://github.com/greenm01/poly2tri/issues/99 // // The collinear tests uses epsilon 1e-12. Seems rounding to 12 places you still // can get problems with this test when points are pretty much on a straight line. // I suggest you round to 10 decimals for stability and you can live with that // precision. pts << QgsPoint( QgsWkbTypes::PointZ, _round_coord( v.x() ), _round_coord( v.y() ), _round_coord( v.z() ) ); } return new QgsLineString( pts ); }
void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeight ) { const QgsCurve *exterior = polygon.exteriorRing(); const QVector3D pNormal = _calculateNormal( exterior, mOriginX, mOriginY, mInvertNormals ); const int pCount = exterior->numPoints(); if ( pCount == 4 && polygon.numInteriorRings() == 0 ) { // polygon is a triangle - write vertices to the output data array without triangulation QgsPoint pt; QgsVertexId::VertexType vt; for ( int i = 0; i < 3; i++ ) { exterior->pointAt( i, pt, vt ); mData << pt.x() - mOriginX << pt.z() << - pt.y() + mOriginY; if ( mAddNormals ) mData << pNormal.x() << pNormal.z() << - pNormal.y(); } if ( mAddBackFaces ) { // the same triangle with reversed order of coordinates and inverted normal for ( int i = 2; i >= 0; i-- ) { exterior->pointAt( i, pt, vt ); mData << pt.x() - mOriginX << pt.z() << - pt.y() + mOriginY; if ( mAddNormals ) mData << -pNormal.x() << -pNormal.z() << pNormal.y(); } } } else { if ( !qgsDoubleNear( pNormal.length(), 1, 0.001 ) ) return; // this should not happen - pNormal should be normalized to unit length std::unique_ptr<QMatrix4x4> toNewBase, toOldBase; if ( pNormal != QVector3D( 0, 0, 1 ) ) { // this is not a horizontal plane - need to reproject the polygon to a new base so that // we can do the triangulation in a plane QVector3D pXVector, pYVector; _normalVectorToXYVectors( pNormal, pXVector, pYVector ); // so now we have three orthogonal unit vectors defining new base // let's build transform matrix. We actually need just a 3x3 matrix, // but Qt does not have good support for it, so using 4x4 matrix instead. toNewBase.reset( new QMatrix4x4( pXVector.x(), pXVector.y(), pXVector.z(), 0, pYVector.x(), pYVector.y(), pYVector.z(), 0, pNormal.x(), pNormal.y(), pNormal.z(), 0, 0, 0, 0, 0 ) ); // our 3x3 matrix is orthogonal, so for inverse we only need to transpose it toOldBase.reset( new QMatrix4x4( toNewBase->transposed() ) ); } const QgsPoint ptStart( exterior->startPoint() ); const QgsPoint pt0( QgsWkbTypes::PointZ, ptStart.x(), ptStart.y(), std::isnan( ptStart.z() ) ? 0 : ptStart.z() ); // subtract ptFirst from geometry for better numerical stability in triangulation // and apply new 3D vector base if the polygon is not horizontal std::unique_ptr<QgsPolygon> polygonNew( _transform_polygon_to_new_base( polygon, pt0, toNewBase.get() ) ); if ( _minimum_distance_between_coordinates( *polygonNew ) < 0.001 ) { // when the distances between coordinates of input points are very small, // the triangulation likes to crash on numerical errors - when the distances are ~ 1e-5 // Assuming that the coordinates should be in a projected CRS, we should be able // to simplify geometries that may cause problems and avoid possible crashes QgsGeometry polygonSimplified = QgsGeometry( polygonNew->clone() ).simplify( 0.001 ); const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet() ); if ( _minimum_distance_between_coordinates( *polygonSimplifiedData ) < 0.001 ) { // Failed to fix that. It could be a really tiny geometry... or maybe they gave us // geometry in unprojected lat/lon coordinates QgsMessageLog::logMessage( QObject::tr( "geometry's coordinates are too close to each other and simplification failed - skipping" ), QObject::tr( "3D" ) ); return; } else { polygonNew.reset( polygonSimplifiedData->clone() ); } } if ( !_check_intersecting_rings( *polygonNew.get() ) ) { // skip the polygon - it would cause a crash inside poly2tri library QgsMessageLog::logMessage( QObject::tr( "polygon rings self-intersect or intersect each other - skipping" ), QObject::tr( "3D" ) ); return; } QList< std::vector<p2t::Point *> > polylinesToDelete; QHash<p2t::Point *, float> z; // polygon exterior std::vector<p2t::Point *> polyline; _ringToPoly2tri( polygonNew->exteriorRing(), polyline, z ); polylinesToDelete << polyline; std::unique_ptr<p2t::CDT> cdt( new p2t::CDT( polyline ) ); // polygon holes for ( int i = 0; i < polygonNew->numInteriorRings(); ++i ) { std::vector<p2t::Point *> holePolyline; const QgsCurve *hole = polygonNew->interiorRing( i ); _ringToPoly2tri( hole, holePolyline, z ); cdt->AddHole( holePolyline ); polylinesToDelete << holePolyline; } // run triangulation and write vertices to the output data array try { cdt->Triangulate(); std::vector<p2t::Triangle *> triangles = cdt->GetTriangles(); for ( size_t i = 0; i < triangles.size(); ++i ) { p2t::Triangle *t = triangles[i]; for ( int j = 0; j < 3; ++j ) { p2t::Point *p = t->GetPoint( j ); QVector4D pt( p->x, p->y, z[p], 0 ); if ( toOldBase ) pt = *toOldBase * pt; const double fx = pt.x() - mOriginX + pt0.x(); const double fy = pt.y() - mOriginY + pt0.y(); const double fz = pt.z() + extrusionHeight + pt0.z(); mData << fx << fz << -fy; if ( mAddNormals ) mData << pNormal.x() << pNormal.z() << - pNormal.y(); } if ( mAddBackFaces ) { // the same triangle with reversed order of coordinates and inverted normal for ( int j = 2; j >= 0; --j ) { p2t::Point *p = t->GetPoint( j ); QVector4D pt( p->x, p->y, z[p], 0 ); if ( toOldBase ) pt = *toOldBase * pt; const double fx = pt.x() - mOriginX + pt0.x(); const double fy = pt.y() - mOriginY + pt0.y(); const double fz = pt.z() + extrusionHeight + pt0.z(); mData << fx << fz << -fy; if ( mAddNormals ) mData << -pNormal.x() << -pNormal.z() << pNormal.y(); } } } } catch ( ... ) { QgsMessageLog::logMessage( QObject::tr( "Triangulation failed. Skipping polygon…" ), QObject::tr( "3D" ) ); } for ( int i = 0; i < polylinesToDelete.count(); ++i ) qDeleteAll( polylinesToDelete[i] ); } // add walls if extrusion is enabled if ( extrusionHeight != 0 ) { _makeWalls( *exterior, false, extrusionHeight, mData, mAddNormals, mOriginX, mOriginY ); for ( int i = 0; i < polygon.numInteriorRings(); ++i ) _makeWalls( *polygon.interiorRing( i ), true, extrusionHeight, mData, mAddNormals, mOriginX, mOriginY ); } }