Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mesh editing delaunay refinement #59560

Merged
merged 19 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions python/PyQt6/core/auto_generated/mesh/qgsmesheditor.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,13 @@ Returns the count of valid vertices, that is non void vertices in the mesh
int maximumVerticesPerFace() const;
%Docstring
Returns the maximum count of vertices per face that the mesh can support
%End

void addVertexWithDelaunayRefinement( const QgsMeshVertex vertex, const double tolerance );
%Docstring
Add a vertex in a face with Delaunay refinement of neighboring faces

.. versionadded:: 3.42
%End

signals:
Expand Down
7 changes: 7 additions & 0 deletions python/core/auto_generated/mesh/qgsmesheditor.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,13 @@ Returns the count of valid vertices, that is non void vertices in the mesh
int maximumVerticesPerFace() const;
%Docstring
Returns the maximum count of vertices per face that the mesh can support
%End

void addVertexWithDelaunayRefinement( const QgsMeshVertex vertex, const double tolerance );
%Docstring
Add a vertex in a face with Delaunay refinement of neighboring faces

.. versionadded:: 3.42
%End

signals:
Expand Down
27 changes: 24 additions & 3 deletions src/app/mesh/qgsmaptooleditmeshframe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,20 +238,28 @@ QgsMeshEditDigitizingAction::QgsMeshEditDigitizingAction( QObject *parent )
int interpolateFromValue = settings.enumValue( QStringLiteral( "UI/Mesh/zValueFrom" ), PreferMeshThenZWidget );
mComboZValueType->setCurrentIndex( interpolateFromValue );

gLayout->addWidget( labelZValueType, 2, 0, 1, 3 );
gLayout->addWidget( mComboZValueType, 2, 3, 1, 1 );
mCheckBoxRefineNeighboringFaces = new QCheckBox( tr( "On add vertex in face refine neighboring faces" ) );
JanCaha marked this conversation as resolved.
Show resolved Hide resolved

bool refineNeighboringFaces = settings.value( QStringLiteral( "UI/Mesh/refineNeighboringFaces" ) ).toBool();
mCheckBoxRefineNeighboringFaces->setChecked( refineNeighboringFaces );

gLayout->addWidget( labelZValueType, 1, 0, 1, 1 );
gLayout->addWidget( mComboZValueType, 1, 1, 1, 1 );
gLayout->addWidget( mCheckBoxRefineNeighboringFaces, 2, 0, 1, 2 );

QWidget *w = new QWidget();
w->setLayout( gLayout );
setDefaultWidget( w );

connect( mCheckBoxRefineNeighboringFaces, &QCheckBox::toggled, this, &QgsMeshEditDigitizingAction::updateSettings );
}

void QgsMeshEditDigitizingAction::updateSettings()
{
QgsSettings settings;

settings.setEnumValue( QStringLiteral( "UI/Mesh/zValueFrom" ), static_cast<ZValueSource>( mComboZValueType->currentData().toInt() ) );
settings.setValue( QStringLiteral( "UI/Mesh/refineNeighboringFaces" ), mCheckBoxRefineNeighboringFaces->isChecked() );
}

QgsMeshEditDigitizingAction::ZValueSource QgsMeshEditDigitizingAction::zValueSourceType() const
Expand All @@ -264,6 +272,11 @@ void QgsMeshEditDigitizingAction::setZValueType( QgsMeshEditDigitizingAction::ZV
mComboZValueType->setCurrentIndex( mComboZValueType->findData( zValueSource ) );
}

bool QgsMeshEditDigitizingAction::refineNeighboringFaces() const
{
return mCheckBoxRefineNeighboringFaces->isChecked();
}

//
// QgsMapToolEditMeshFrame
//
Expand Down Expand Up @@ -2798,10 +2811,18 @@ void QgsMapToolEditMeshFrame::addVertex(
}

const QVector<QgsMeshVertex> points( 1, QgsMeshVertex( effectivePoint.x(), effectivePoint.y(), zValue ) );

if ( mCurrentEditor )
{
double tolerance = QgsTolerance::vertexSearchRadius( canvas()->mapSettings() );
mCurrentEditor->addVertices( points, tolerance );
if ( mWidgetActionDigitizing->refineNeighboringFaces() && mCurrentFaceIndex != -1 )
{
mCurrentEditor->addVertexWithDelaunayRefinement( points.first(), tolerance );
}
else
{
mCurrentEditor->addVertices( points, tolerance );
}
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/app/mesh/qgsmaptooleditmeshframe.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,19 @@ class QgsMeshEditDigitizingAction : public QWidgetAction
//! Returns type of z value obtaining
QgsMeshEditDigitizingAction::ZValueSource zValueSourceType() const;

//! Returns if neighboring faces should be refined when adding vertex inside mesh
bool refineNeighboringFaces() const;

void setZValueType( QgsMeshEditDigitizingAction::ZValueSource zValueSource );

private slots:
void updateSettings();

private:
QComboBox *mComboZValueType = nullptr;
QCheckBox *mCheckBoxRefineNeighboringFaces = nullptr;

friend class TestQgsMapToolEditMesh;
};

class APP_EXPORT QgsMapToolEditMeshFrame : public QgsMapToolAdvancedDigitizing
Expand Down
158 changes: 158 additions & 0 deletions src/core/mesh/qgsmesheditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,15 @@ QgsMeshEditingError QgsMeshEditor::removeFaces( const QList<int> &facesToRemove
return error;
}

void QgsMeshEditor::addVertexWithDelaunayRefinement( const QgsMeshVertex vertex, const double tolerance )
{
int triangleIndex = mTriangularMesh->faceIndexForPoint_v2( vertex );
if ( triangleIndex == -1 )
return;

mUndoStack->push( new QgsMeshLayerUndoCommandAddVertexInFaceWithDelaunayRefinement( this, vertex, tolerance ) );
}

bool QgsMeshEditor::edgeCanBeFlipped( int vertexIndex1, int vertexIndex2 ) const
{
return mTopologicalMesh.edgeCanBeFlipped( vertexIndex1, vertexIndex2 );
Expand Down Expand Up @@ -1482,3 +1491,152 @@ void QgsMeshLayerUndoCommandAdvancedEditing::redo()
mMeshEditor->applyEdit( edit );
}
}

QgsMeshLayerUndoCommandAddVertexInFaceWithDelaunayRefinement::QgsMeshLayerUndoCommandAddVertexInFaceWithDelaunayRefinement(
QgsMeshEditor *meshEditor,
const QgsMeshVertex vertex,
double tolerance )
: QgsMeshLayerUndoCommandMeshEdit( meshEditor )
, mVertex( vertex )
, mTolerance( tolerance )
{
setText( QObject::tr( "Add vertex inside face with Delaunay refinement" ) );
}

void QgsMeshLayerUndoCommandAddVertexInFaceWithDelaunayRefinement::redo()
{
if ( !mVertex.isEmpty() )
{
QgsMeshEditor::Edit edit;

mMeshEditor->applyAddVertex( edit, mVertex, mTolerance );
mEdits.append( edit );

QList<std::pair<int, int>> sharedEdges = innerEdges( triangularFaces( facesContainingVertex( mMeshEditor->topologicalMesh().mesh()->vertexCount() - 1 ) ) );

for ( std::pair<int, int> edge : sharedEdges )
{
if ( mMeshEditor->edgeCanBeFlipped( edge.first, edge.second ) && !mMeshEditor->topologicalMesh().delaunayConditionForEdge( edge.first, edge.second ) )
{
mMeshEditor->applyFlipEdge( edit, edge.first, edge.second );
mEdits.append( edit );
}
}

mVertex = QgsMeshVertex();
}
else
{
for ( QgsMeshEditor::Edit &edit : mEdits )
mMeshEditor->applyEdit( edit );
}
}

QSet<int> QgsMeshLayerUndoCommandAddVertexInFaceWithDelaunayRefinement::facesContainingVertex( const int vertexId )
JanCaha marked this conversation as resolved.
Show resolved Hide resolved
{
QList<int> facesAroundVertex;

for ( int i = 0; i < mMeshEditor->topologicalMesh().mesh()->faceCount(); i++ )
{
QgsMeshFace face = mMeshEditor->topologicalMesh().mesh()->face( i );

if ( face.contains( vertexId ) )
{
facesAroundVertex.push_back( i );
}
}
JanCaha marked this conversation as resolved.
Show resolved Hide resolved

QSet<int> vertexIndexes;

for ( int faceIndex : facesAroundVertex )
{
QgsMeshFace face = mMeshEditor->topologicalMesh().mesh()->face( faceIndex );

for ( int i = 0; i < face.count(); i++ )
{
vertexIndexes.insert( face.at( i ) );
}
}

// faces that have at least one common vertex with newly added faces
QSet<int> selectedFaces;

for ( int i = 0; i < mMeshEditor->topologicalMesh().mesh()->faceCount(); i++ )
{
const QgsMeshFace face = mMeshEditor->topologicalMesh().mesh()->face( i );

for ( int j = 0; j < face.count(); j++ )
{
for ( int vertex : vertexIndexes )
{
if ( face.contains( vertex ) )
{
selectedFaces.insert( i );
break;
}
}
}
}
return selectedFaces;
}


QSet<int> QgsMeshLayerUndoCommandAddVertexInFaceWithDelaunayRefinement::triangularFaces( const QSet<int> &faces )
{
QSet<int> triangularFaces;

for ( int faceIndex : faces )
{
if ( mMeshEditor->topologicalMesh().mesh()->face( faceIndex ).count() == 3 )
{
triangularFaces.insert( faceIndex );
}
}

return triangularFaces;
}

QList<std::pair<int, int>> QgsMeshLayerUndoCommandAddVertexInFaceWithDelaunayRefinement::innerEdges( const QSet<int> &faces )
uclaros marked this conversation as resolved.
Show resolved Hide resolved
{
// edges and number of their occurrence in triangular faces
QMap<std::pair<int, int>, int> edges;

for ( int faceIndex : faces )
{
const QgsMeshFace face = mMeshEditor->topologicalMesh().mesh()->face( faceIndex );

for ( int i = 0; i < face.size(); i++ )
{
int next = i + 1;
if ( next == face.size() )
{
next = 0;
}

int minIndex = std::min( face.at( i ), face.at( next ) );
int maxIndex = std::max( face.at( i ), face.at( next ) );
std::pair<int, int> edge = std::pair<int, int>( minIndex, maxIndex );

int count = 1;
if ( edges.contains( edge ) )
{
count = edges.take( edge );
count++;
}

edges.insert( edge, count );
}
}

QList<std::pair<int, int>> sharedEdges;

for ( auto it = edges.begin(); it != edges.end(); it++ )
{
if ( it.value() == 2 )
{
sharedEdges.push_back( it.key() );
}
}

return sharedEdges;
}
30 changes: 30 additions & 0 deletions src/core/mesh/qgsmesheditor.h
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,13 @@ class CORE_EXPORT QgsMeshEditor : public QObject
//! Returns the maximum count of vertices per face that the mesh can support
int maximumVerticesPerFace() const;

/**
* Add a vertex in a face with Delaunay refinement of neighboring faces
JanCaha marked this conversation as resolved.
Show resolved Hide resolved
*
* \since QGIS 3.42
*/
void addVertexWithDelaunayRefinement( const QgsMeshVertex vertex, const double tolerance );
JanCaha marked this conversation as resolved.
Show resolved Hide resolved

signals:
//! Emitted when the mesh is edited
void meshEdited();
Expand Down Expand Up @@ -363,6 +370,7 @@ class CORE_EXPORT QgsMeshEditor : public QObject
friend class QgsMeshLayerUndoCommandFlipEdge;
friend class QgsMeshLayerUndoCommandMerge;
friend class QgsMeshLayerUndoCommandSplitFaces;
friend class QgsMeshLayerUndoCommandAddVertexInFaceWithDelaunayRefinement;

friend class QgsMeshLayerUndoCommandAdvancedEditing;
};
Expand Down Expand Up @@ -652,7 +660,29 @@ class QgsMeshLayerUndoCommandAdvancedEditing : public QgsMeshLayerUndoCommandMes
};


/**
* \ingroup core
*
* \brief Class for undo/redo command for adding vertex to face with Delaunay Refiment of faces surrounding
*
* \since QGIS 3.42
*/
class QgsMeshLayerUndoCommandAddVertexInFaceWithDelaunayRefinement: public QgsMeshLayerUndoCommandMeshEdit
{
public:

//! Constructor with the associated \a meshEditor and indexes \a vertex and \a tolerance
QgsMeshLayerUndoCommandAddVertexInFaceWithDelaunayRefinement( QgsMeshEditor *meshEditor, const QgsMeshVertex vertex, double tolerance );
JanCaha marked this conversation as resolved.
Show resolved Hide resolved

void redo() override;
private:
QSet<int> facesContainingVertex( const int vertexId );
QSet<int> triangularFaces( const QSet<int> &faces );
QList<std::pair<int, int>> innerEdges( const QSet<int> &faces );

QgsMeshVertex mVertex;
double mTolerance;
};

#endif //SIP_RUN

Expand Down
39 changes: 39 additions & 0 deletions src/core/mesh/qgstopologicalmesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "qgsmesheditor.h"
#include "qgsmessagelog.h"
#include "qgsgeometryutils.h"
#include "qgscircle.h"

#include <poly2tri.h>
#include <QSet>
Expand Down Expand Up @@ -2584,3 +2585,41 @@ QgsTopologicalMesh::Changes QgsTopologicalMesh::changeXYValue( const QList<int>

return changes;
}

bool QgsTopologicalMesh::delaunayConditionForEdge( int vertexIndex1, int vertexIndex2 )
{
int faceIndex1;
int faceIndex2;
int oppositeVertexFace1;
int oppositeVertexFace2;
int supposedOppositeVertexFace1;
int supposedoppositeVertexFace2;

bool result = eitherSideFacesAndVertices(
vertexIndex1,
vertexIndex2,
faceIndex1,
faceIndex2,
oppositeVertexFace1,
supposedoppositeVertexFace2,
supposedOppositeVertexFace1,
oppositeVertexFace2 );

if ( ! result )
return false;

const QgsMeshFace face1 = mMesh->face( faceIndex1 );
const QgsMeshFace face2 = mMesh->face( faceIndex2 );

QgsCircle circle = QgsCircle::from3Points( mMesh->vertex( face1.at( 0 ) ),
mMesh->vertex( face1.at( 1 ) ),
mMesh->vertex( face1.at( 2 ) ) );
bool circle1ContainsPoint = circle.contains( mMesh->vertex( supposedoppositeVertexFace2 ) );

circle = QgsCircle::from3Points( mMesh->vertex( face2.at( 0 ) ),
mMesh->vertex( face2.at( 1 ) ),
mMesh->vertex( face2.at( 2 ) ) );
bool circle2ContainsPoint = circle.contains( mMesh->vertex( supposedOppositeVertexFace1 ) );

return !( circle1ContainsPoint || circle2ContainsPoint );
}
8 changes: 8 additions & 0 deletions src/core/mesh/qgstopologicalmesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,14 @@ class CORE_EXPORT QgsTopologicalMesh
*/
Changes flipEdge( int vertexIndex1, int vertexIndex2 );

/**
* Check if Delaunay condition holds for given edge
* returns TRUE if delaunay condition holds FALSE otherwise
*
* \since QGIS 3.42
*/
bool delaunayConditionForEdge( int vertexIndex1, int vertexIndex2 );

/**
* Returns TRUE if faces separated by vertices with indexes \a vertexIndex1 and \a vertexIndex2 can be merged
*/
Expand Down
Loading
Loading