QGIS API Documentation  3.23.0-Master (4fd2f04bd0)
qgstriangularmesh.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgstriangularmesh.cpp
3  ---------------------
4  begin : April 2018
5  copyright : (C) 2018 by Peter Petrik
6  email : zilolv at gmail dot com
7  ***************************************************************************/
8
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17
18 #include <memory>
19 #include <QList>
20 #include "qgspolygon.h"
21 #include "qgslinestring.h"
22 #include "qgstriangularmesh.h"
23 #include "qgsrendercontext.h"
24 #include "qgscoordinatetransform.h"
25 #include "qgsgeometry.h"
26 #include "qgsrectangle.h"
27 #include "qgslogger.h"
28 #include "qgsmeshspatialindex.h"
29 #include "qgsmeshlayerutils.h"
30 #include "meshOptimizer/meshoptimizer.h"
31
32 static void ENP_centroid_step( const QPolygonF &pX, double &cx, double &cy, double &signedArea, int i, int i1 )
33 {
34  double x0 = 0.0; // Current vertex X
35  double y0 = 0.0; // Current vertex Y
36  double x1 = 0.0; // Next vertex X
37  double y1 = 0.0; // Next vertex Y
38  double a = 0.0; // Partial signed area
39
40  x0 = pX[i].x();
41  y0 = pX[i].y();
42  x1 = pX[i1].x();
43  y1 = pX[i1].y();
44  a = x0 * y1 - x1 * y0;
45  signedArea += a;
46  cx += ( x0 + x1 ) * a;
47  cy += ( y0 + y1 ) * a;
48 }
49
50 static void ENP_centroid( const QPolygonF &pX, double &cx, double &cy )
51 {
52  // http://stackoverflow.com/questions/2792443/finding-the-centroid-of-a-polygon/2792459#2792459
53  cx = 0;
54  cy = 0;
55
56  if ( pX.isEmpty() )
57  return;
58
59  double signedArea = 0.0;
60
61  const QPointF &pt0 = pX.first();
62  QPolygonF localPolygon( pX.count() );
63  for ( int i = 0; i < pX.count(); ++i )
64  localPolygon[i] = pX.at( i ) - pt0;
65
66  // For all vertices except last
67  int i = 0;
68  for ( ; i < localPolygon.size() - 1; ++i )
69  {
70  ENP_centroid_step( localPolygon, cx, cy, signedArea, i, i + 1 );
71  }
72  // Do last vertex separately to avoid performing an expensive
73  // modulus operation in each iteration.
74  ENP_centroid_step( localPolygon, cx, cy, signedArea, i, 0 );
75
76  signedArea *= 0.5;
77  cx /= ( 6.0 * signedArea );
78  cy /= ( 6.0 * signedArea );
79
80  cx = cx + pt0.x();
81  cy = cy + pt0.y();
82 }
83
84 static void triangulateFaces( const QgsMeshFace &face,
85  int nativeIndex,
86  QVector<QgsMeshFace> &destinationFaces,
87  QVector<int> &triangularToNative,
88  const QgsMesh &verticesMeshSource )
89 {
90  int vertexCount = face.size();
91  if ( vertexCount < 3 )
92  return;
93
94  while ( vertexCount > 3 )
95  {
96  // clip one ear from last 2 and first vertex
97  const QgsMeshFace ear = { face[vertexCount - 2], face[vertexCount - 1], face[0] };
98  if ( !( std::isnan( verticesMeshSource.vertex( ear[0] ).x() ) ||
99  std::isnan( verticesMeshSource.vertex( ear[1] ).x() ) ||
100  std::isnan( verticesMeshSource.vertex( ear[2] ).x() ) ) )
101  {
102  destinationFaces.push_back( ear );
103  triangularToNative.push_back( nativeIndex );
104  }
105  --vertexCount;
106  }
107
108  const QgsMeshFace triangle = { face[1], face[2], face[0] };
109  if ( !( std::isnan( verticesMeshSource.vertex( triangle[0] ).x() ) ||
110  std::isnan( verticesMeshSource.vertex( triangle[1] ).x() ) ||
111  std::isnan( verticesMeshSource.vertex( triangle[2] ).x() ) ) )
112  {
113  destinationFaces.push_back( triangle );
114  triangularToNative.push_back( nativeIndex );
115  }
116 }
117
118 void QgsTriangularMesh::triangulate( const QgsMeshFace &face, int nativeIndex )
119 {
120  triangulateFaces( face, nativeIndex, mTriangularMesh.faces, mTrianglesToNativeFaces, mTriangularMesh );
121 }
122
123 QgsMeshVertex QgsTriangularMesh::transformVertex( const QgsMeshVertex &vertex, Qgis::TransformDirection direction ) const
124 {
125  QgsMeshVertex transformedVertex = vertex;
126
127  if ( mCoordinateTransform.isValid() )
128  {
129  try
130  {
131  transformedVertex.transform( mCoordinateTransform, direction );
132  }
133  catch ( QgsCsException &cse )
134  {
135  Q_UNUSED( cse )
136  QgsDebugMsg( QStringLiteral( "Caught CRS exception %1" ).arg( cse.what() ) );
137  transformedVertex = QgsMeshVertex();
138  }
139  }
140
141  return transformedVertex;
142 }
143
144 QgsMeshVertex QgsTriangularMesh::calculateCentroid( const QgsMeshFace &nativeFace )
145 {
146  return QgsMeshUtils::centroid( nativeFace, mTriangularMesh.vertices );
147 }
148
150 {
151  return mAverageTriangleSize;
152 }
153
156
157 bool QgsTriangularMesh::update( QgsMesh *nativeMesh, const QgsCoordinateTransform &transform )
158 {
159  Q_ASSERT( nativeMesh );
160
161  bool needUpdateVerticesCoordinates = mTriangularMesh.vertices.size() != nativeMesh->vertices.size() ||
162  ( ( mCoordinateTransform.isValid() || transform.isValid() ) &&
163  ( mCoordinateTransform.sourceCrs() != transform.sourceCrs() ||
164  mCoordinateTransform.destinationCrs() != transform.destinationCrs() ||
165  mCoordinateTransform.isValid() != transform.isValid() ) ) ;
166
167  bool needUpdateFrame = mTriangularMesh.vertices.size() != nativeMesh->vertices.size() ||
168  mNativeMeshFaceCentroids.size() != nativeMesh->faces.size() ||
169  mTriangularMesh.faces.size() < nativeMesh->faces.size() ||
170  mTriangularMesh.edges.size() != nativeMesh->edges.size();
171
172
173  // FIND OUT IF UPDATE IS NEEDED
174  if ( ! needUpdateVerticesCoordinates && !needUpdateFrame )
175  return false;
176
177  // CLEAN-UP
178  mTriangularMesh.vertices.clear();
179  if ( needUpdateFrame )
180  {
181  mTriangularMesh.faces.clear();
182  mTriangularMesh.edges.clear();
183  mEdgesToNativeEdges.clear();
184  mTrianglesToNativeFaces.clear();
185  }
186
187  // TRANSFORM VERTICES
188  mCoordinateTransform = transform;
189  mTriangularMesh.vertices.resize( nativeMesh->vertices.size() );
190  mExtent.setMinimal();
191  for ( int i = 0; i < nativeMesh->vertices.size(); ++i )
192  {
193  mTriangularMesh.vertices[i] = nativeToTriangularCoordinates( nativeMesh->vertices.at( i ) );
194  mExtent.include( mTriangularMesh.vertices.at( i ) );
195  }
196
197  if ( needUpdateFrame )
198  {
199  // CREATE TRIANGULAR MESH
200  for ( int i = 0; i < nativeMesh->faces.size(); ++i )
201  {
202  const QgsMeshFace &face = nativeMesh->faces.at( i ) ;
203  triangulate( face, i );
204  }
205  }
206
207  // CALCULATE CENTROIDS
208  mNativeMeshFaceCentroids.resize( nativeMesh->faces.size() );
209  for ( int i = 0; i < nativeMesh->faces.size(); ++i )
210  {
211  const QgsMeshFace &face = nativeMesh->faces.at( i ) ;
212  mNativeMeshFaceCentroids[i] = calculateCentroid( face );
213  }
214
215  // CALCULATE SPATIAL INDEX
216  mSpatialFaceIndex = QgsMeshSpatialIndex( mTriangularMesh, nullptr, QgsMesh::ElementType::Face );
217
218  if ( needUpdateFrame )
219  {
220  // SET ALL TRIANGLE CCW AND COMPUTE AVERAGE SIZE
221  finalizeTriangles();
222  }
223
224  // CREATE EDGES
225  // remove all edges with invalid vertices
226  if ( needUpdateFrame )
227  {
228  const QVector<QgsMeshEdge> edges = nativeMesh->edges;
229  for ( int nativeIndex = 0; nativeIndex < edges.size(); ++nativeIndex )
230  {
231  const QgsMeshEdge &edge = edges.at( nativeIndex );
232  if ( !( std::isnan( mTriangularMesh.vertex( edge.first ).x() ) ||
233  std::isnan( mTriangularMesh.vertex( edge.second ).x() ) ) )
234  {
235  mTriangularMesh.edges.push_back( edge );
236  mEdgesToNativeEdges.push_back( nativeIndex );
237  }
238  }
239  }
240
241  // CALCULATE CENTROIDS
242  mNativeMeshEdgeCentroids.resize( nativeMesh->edgeCount() );
243  for ( int i = 0; i < nativeMesh->edgeCount(); ++i )
244  {
245  const QgsMeshEdge &edge = nativeMesh->edges.at( i ) ;
246  const QgsPoint &a = mTriangularMesh.vertices[edge.first];
247  const QgsPoint &b = mTriangularMesh.vertices[edge.second];
248  mNativeMeshEdgeCentroids[i] = QgsMeshVertex( ( a.x() + b.x() ) / 2.0, ( a.y() + b.y() ) / 2.0, ( a.z() + b.z() ) / 2.0 );
249  }
250
251  // CALCULATE SPATIAL INDEX
252  mSpatialEdgeIndex = QgsMeshSpatialIndex( mTriangularMesh, nullptr, QgsMesh::ElementType::Edge );
253
254  return true;
255 }
256
257 void QgsTriangularMesh::finalizeTriangles()
258 {
259  mAverageTriangleSize = 0;
260  for ( int i = 0; i < mTriangularMesh.faceCount(); ++i )
261  {
262  QgsMeshFace &face = mTriangularMesh.faces[i];
263
264  const QgsMeshVertex &v0 = mTriangularMesh.vertex( face[0] );
265  const QgsMeshVertex &v1 = mTriangularMesh.vertex( face[1] );
266  const QgsMeshVertex &v2 = mTriangularMesh.vertex( face[2] );
267
268  QgsRectangle bbox = QgsMeshLayerUtils::triangleBoundingBox( v0, v1, v2 );
269
270  mAverageTriangleSize += std::fmax( bbox.width(), bbox.height() );
271
272  QgsMeshUtils::setCounterClockwise( face, v0, v1, v2 );
273  }
274  mAverageTriangleSize /= mTriangularMesh.faceCount();
275 }
276
278 {
279  return transformVertex( vertex, Qgis::TransformDirection::Forward );
280 }
281
283 {
284  return transformVertex( vertex, Qgis::TransformDirection::Reverse );
285 }
286
288 {
290  if ( !mCoordinateTransform.isShortCircuited() )
291  {
292  try
293  {
294  QgsCoordinateTransform extentTransform = mCoordinateTransform;
295  extentTransform.setBallparkTransformsAreAppropriate( true );
296  nativeExtent = extentTransform.transformBoundingBox( extent(), Qgis::TransformDirection::Reverse );
297  }
298  catch ( QgsCsException &cse )
299  {
300  Q_UNUSED( cse )
301  QgsDebugMsg( QStringLiteral( "Caught CRS exception %1" ).arg( cse.what() ) );
302  }
303  }
304  else
305  nativeExtent = extent();
306
307  return nativeExtent;
308 }
309
311 {
312  if ( !mIsExtentValid )
313  {
314  mExtent.setMinimal();
315  for ( int i = 0; i < mTriangularMesh.vertices.size(); ++i )
316  if ( !mTriangularMesh.vertices.at( i ).isEmpty() )
317  mExtent.include( mTriangularMesh.vertices.at( i ) );
318
319  mIsExtentValid = true;
320  }
321  return mExtent;
322 }
323
325 {
326  return mLod;
327 }
328
330 {
331  switch ( type )
332  {
333  case QgsMesh::ElementType::Vertex:
334  return mTriangularMesh.vertexCount() != 0;
335  case QgsMesh::ElementType::Edge:
336  return mTriangularMesh.edgeCount() != 0;
337  case QgsMesh::ElementType::Face:
338  return mTriangularMesh.faceCount() != 0;
339  }
340
341  return false;
342 }
343
344 void QgsTriangularMesh::addVertex( const QgsMeshVertex &vertex )
345 {
346  QgsMeshVertex vertexInTriangularCoordinates = nativeToTriangularCoordinates( vertex );
347  mTriangularMesh.vertices.append( vertexInTriangularCoordinates );
348  if ( !vertexInTriangularCoordinates.isEmpty() )
349  mExtent.include( vertexInTriangularCoordinates );
350 }
351
352 const QVector<QgsMeshVertex> &QgsTriangularMesh::vertices() const
353 {
354  return mTriangularMesh.vertices;
355 }
356
357 const QVector<QgsMeshFace> &QgsTriangularMesh::triangles() const
358 {
359  return mTriangularMesh.faces;
360 }
361
362 const QVector<QgsMeshEdge> &QgsTriangularMesh::edges() const
363 {
364  return mTriangularMesh.edges;
365 }
366
367 const QVector<QgsMeshVertex> &QgsTriangularMesh::centroids() const
368 {
369  return faceCentroids();
370 }
371
372 const QVector<QgsMeshVertex> &QgsTriangularMesh::faceCentroids() const
373 {
374  return mNativeMeshFaceCentroids;
375 }
376
377 const QVector<QgsMeshVertex> &QgsTriangularMesh::edgeCentroids() const
378 {
379  return mNativeMeshEdgeCentroids;
380 }
381
382 const QVector<int> &QgsTriangularMesh::trianglesToNativeFaces() const
383 {
384  return mTrianglesToNativeFaces;
385 }
386
387 const QVector<int> &QgsTriangularMesh::edgesToNativeEdges() const
388 {
389  return mEdgesToNativeEdges;
390 }
391
393 {
394  QgsPointXY mapPoint;
395  if ( mCoordinateTransform.isValid() )
396  {
397  try
398  {
399  mapPoint = mCoordinateTransform.transform( point );
400  }
401  catch ( QgsCsException &cse )
402  {
403  Q_UNUSED( cse )
404  QgsDebugMsg( QStringLiteral( "Caught CRS exception %1" ).arg( cse.what() ) );
405  mapPoint = point;
406  }
407  }
408  else
409  mapPoint = point;
410
411  return point;
412 }
413
415 {
416  const QList<int> faceIndexes = mSpatialFaceIndex.intersects( QgsRectangle( point, point ) );
417  for ( const int faceIndex : faceIndexes )
418  {
419  const QgsMeshFace &face = mTriangularMesh.faces.at( faceIndex );
420  const QgsGeometry geom = QgsMeshUtils::toGeometry( face, mTriangularMesh.vertices );
421  if ( geom.contains( &point ) )
422  return faceIndex;
423  }
424  return -1;
425 }
426
428 {
429  int triangleIndex = faceIndexForPoint_v2( point );
430  if ( triangleIndex == -1 )
431  return -1;
432
433  if ( triangleIndex < mTrianglesToNativeFaces.count() )
434  return mTrianglesToNativeFaces.at( triangleIndex );
435
436  return -1;
437 }
438
440 {
441  QSet<int> concernedFaceIndex = QgsMeshUtils::nativeFacesFromTriangles(
442  faceIndexesForRectangle( rectangle ),
444  return concernedFaceIndex.values();
445 }
446
448 {
449  const QList<int> faceIndexes = mSpatialFaceIndex.intersects( QgsRectangle( point, point ) );
450
451  for ( const int faceIndex : faceIndexes )
452  {
453  const QgsMeshFace &face = mTriangularMesh.faces.at( faceIndex );
454  if ( QgsMeshUtils::isInTriangleFace( point, face, mTriangularMesh.vertices ) )
455  return faceIndex;
456  }
457  return -1;
458 }
459
460 QList<int> QgsTriangularMesh::faceIndexesForRectangle( const QgsRectangle &rectangle ) const
461 {
462  return mSpatialFaceIndex.intersects( rectangle );
463 }
464
465 QList<int> QgsTriangularMesh::edgeIndexesForRectangle( const QgsRectangle &rectangle ) const
466 {
467  return mSpatialEdgeIndex.intersects( rectangle );
468 }
469
470 QVector<QVector3D> QgsTriangularMesh::vertexNormals( float vertScale ) const
471 {
472  QVector<QVector3D> normales( vertices().count(), QVector3D( 0, 0, 0 ) );
473
474  for ( const auto &face : triangles() )
475  {
476  if ( face.isEmpty() )
477  continue;
478
479  for ( int i = 0; i < 3; i++ )
480  {
481  int index1( face.at( i ) );
482  int index2( face.at( ( i + 1 ) % 3 ) );
483  int index3( face.at( ( i + 2 ) % 3 ) );
484
485  const QgsMeshVertex &vert( vertices().at( index1 ) );
486  const QgsMeshVertex &otherVert1( vertices().at( index2 ) );
487  const QgsMeshVertex &otherVert2( vertices().at( index3 ) );
488
489  QVector3D v1( float( otherVert1.x() - vert.x() ), float( otherVert1.y() - vert.y() ), vertScale * float( otherVert1.z() - vert.z() ) );
490  QVector3D v2( float( otherVert2.x() - vert.x() ), float( otherVert2.y() - vert.y() ), vertScale * float( otherVert2.z() - vert.z() ) );
491
492  normales[index1] += QVector3D::crossProduct( v1, v2 );
493  }
494  }
495  return normales;
496 }
497
498 QVector<QgsTriangularMesh *> QgsTriangularMesh::simplifyMesh( double reductionFactor, int minimumTrianglesCount ) const
499 {
500  QVector<QgsTriangularMesh *> simplifiedMeshes;
501
502  if ( mTriangularMesh.edgeCount() != 0 )
503  return simplifiedMeshes;
504
505  if ( !( reductionFactor > 1 ) )
506  return simplifiedMeshes;
507
508  size_t verticesCount = size_t( mTriangularMesh.vertices.count() );
509
510  unsigned int baseIndexCount = mTriangularMesh.faceCount() * 3;
511
512  QVector<unsigned int> indexes( mTriangularMesh.faces.count() * 3 );
513  for ( int i = 0; i < mTriangularMesh.faceCount(); ++i )
514  {
515  const QgsMeshFace &f = mTriangularMesh.face( i );
516  for ( int j = 0; j < 3; ++j )
517  indexes[i * 3 + j] = f.at( j );
518  }
519
520  QVector<float> vertices( mTriangularMesh.vertices.count() * 3 );
521  for ( int i = 0; i < mTriangularMesh.vertices.count(); ++i )
522  {
523  const QgsMeshVertex &v = mTriangularMesh.vertex( i );
524  vertices[i * 3] = v.x() ;
525  vertices[i * 3 + 1] = v.y() ;
526  vertices[i * 3 + 2] = v.z() ;
527  }
528
529  int path = 0;
530  while ( true )
531  {
532  QgsTriangularMesh *simplifiedMesh = new QgsTriangularMesh( *this );
533  size_t maxNumberOfIndexes = baseIndexCount / pow( reductionFactor, path + 1 );
534
535  if ( indexes.size() <= int( maxNumberOfIndexes ) )
536  break;
537
538  QVector<unsigned int> returnIndexes( indexes.size() );
539  //returned size could be different than goal size but not than the input indexes count
540  size_t size = meshopt_simplifySloppy(
541  returnIndexes.data(),
542  indexes.data(),
543  indexes.size(),
544  vertices.data(),
545  verticesCount,
546  sizeof( float ) * 3,
547  maxNumberOfIndexes );
548
549
550  returnIndexes.resize( size );
551
552  if ( size == 0 || int( size ) >= indexes.size() )
553  {
554  QgsDebugMsg( QStringLiteral( "Mesh simplification failed after %1 path" ).arg( path + 1 ) );
555  break;
556  }
557
558  QgsMesh newMesh;
559  newMesh.vertices = mTriangularMesh.vertices;
560
561  newMesh.faces.resize( returnIndexes.size() / 3 );
562  for ( int i = 0; i < newMesh.faces.size(); ++i )
563  {
564  QgsMeshFace f( 3 );
565  for ( size_t j = 0; j < 3 ; ++j )
566  f[j] = returnIndexes.at( i * 3 + j ) ;
567  newMesh.faces[i ] = f;
568  }
569
570  simplifiedMesh->mTriangularMesh = newMesh;
571  simplifiedMesh->mSpatialFaceIndex = QgsMeshSpatialIndex( simplifiedMesh->mTriangularMesh );
572  simplifiedMesh->finalizeTriangles();
573  simplifiedMeshes.push_back( simplifiedMesh );
574
575  QgsDebugMsg( QStringLiteral( "Simplified mesh created with %1 triangles" ).arg( newMesh.faceCount() ) );
576
577  simplifiedMesh->mTrianglesToNativeFaces = QVector<int>( simplifiedMesh->triangles().count(), 0 );
578  for ( int i = 0; i < simplifiedMesh->mTrianglesToNativeFaces.count(); ++i )
579  {
580  QgsMeshFace triangle = simplifiedMesh->triangles().at( i );
581  double x = 0;
582  double y = 0;
583  for ( size_t j = 0; j < 3 ; ++j )
584  {
585  x += mTriangularMesh.vertex( triangle[j] ).x();
586  y += mTriangularMesh.vertex( triangle[j] ).y();
587  }
588  x /= 3;
589  y /= 3;
590  QgsPoint centroid( x, y );
591  int indexInBaseMesh = faceIndexForPoint_v2( centroid );
592
593  if ( indexInBaseMesh == -1 )
594  {
595  // sometime the centroid of simplified mesh could be outside the base mesh,
596  // so try with vertices of the simplified triangle
597  int j = 0;
598  while ( indexInBaseMesh == -1 && j < 3 )
599  indexInBaseMesh = faceIndexForPoint_v2( mTriangularMesh.vertex( triangle[j++] ) );
600  }
601
602  if ( indexInBaseMesh > -1 && indexInBaseMesh < mTrianglesToNativeFaces.count() )
603  simplifiedMesh->mTrianglesToNativeFaces[i] = mTrianglesToNativeFaces[indexInBaseMesh];
604  }
605
606  simplifiedMesh->mLod = path + 1;
607  simplifiedMesh->mBaseTriangularMesh = this;
608
609  if ( simplifiedMesh->triangles().count() < minimumTrianglesCount )
610  break;
611
612  indexes = returnIndexes;
613  ++path;
614  }
615
616  return simplifiedMeshes;
617 }
618
619 std::unique_ptr< QgsPolygon > QgsMeshUtils::toPolygon( const QgsMeshFace &face, const QVector<QgsMeshVertex> &vertices )
620 {
621  QVector<QgsPoint> ring;
622  for ( int j = 0; j < face.size(); ++j )
623  {
624  int vertexId = face[j];
625  Q_ASSERT( vertexId < vertices.size() );
626  const QgsPoint &vertex = vertices[vertexId];
627  ring.append( vertex );
628  }
629  std::unique_ptr< QgsPolygon > polygon = std::make_unique< QgsPolygon >();
630  polygon->setExteriorRing( new QgsLineString( ring ) );
631  return polygon;
632 }
633
634 QgsGeometry QgsMeshUtils::toGeometry( const QgsMeshFace &face, const QVector<QgsMeshVertex> &vertices )
635 {
636  return QgsGeometry( QgsMeshUtils::toPolygon( face, vertices ) );
637 }
638
639 static QSet<int> _nativeElementsFromElements( const QList<int> &indexes, const QVector<int> &elementToNativeElements )
640 {
641  QSet<int> nativeElements;
642  for ( const int index : indexes )
643  {
644  if ( index < elementToNativeElements.count() )
645  {
646  const int nativeIndex = elementToNativeElements[index];
647  nativeElements.insert( nativeIndex );
648  }
649  }
650  return nativeElements;
651 }
652
653 QSet<int> QgsMeshUtils::nativeFacesFromTriangles( const QList<int> &triangleIndexes, const QVector<int> &trianglesToNativeFaces )
654 {
655  return _nativeElementsFromElements( triangleIndexes, trianglesToNativeFaces );
656 }
657
658 QSet<int> QgsMeshUtils::nativeEdgesFromEdges( const QList<int> &edgesIndexes, const QVector<int> &edgesToNativeEdges )
659 {
660  return _nativeElementsFromElements( edgesIndexes, edgesToNativeEdges );
661 }
662
663
664 static double _isLeft2D( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p )
665 {
666  return ( p2.x() - p1.x() ) * ( p.y() - p1.y() ) - ( p.x() - p1.x() ) * ( p2.y() - p1.y() );
667 }
668
669 static bool _isInTriangle2D( const QgsPoint &p, const QVector<QgsMeshVertex> &triangle )
670 {
671  return ( ( _isLeft2D( triangle[2], triangle[0], p ) * _isLeft2D( triangle[2], triangle[0], triangle[1] ) >= 0 )
672  && ( _isLeft2D( triangle[0], triangle[1], p ) * _isLeft2D( triangle[0], triangle[1], triangle[2] ) >= 0 )
673  && ( _isLeft2D( triangle[2], triangle[1], p ) * _isLeft2D( triangle[2], triangle[1], triangle[0] ) >= 0 ) );
674 }
675
676 bool QgsMeshUtils::isInTriangleFace( const QgsPointXY point, const QgsMeshFace &face, const QVector<QgsMeshVertex> &vertices )
677 {
678  if ( face.count() != 3 )
679  return false;
680
681  QVector<QgsMeshVertex> triangle( 3 );
682  for ( int i = 0; i < 3; ++i )
683  {
684  if ( face[i] > vertices.count() )
685  return false;
686  triangle[i] = vertices[face[i]];
687  }
688
689  const QgsPoint p( point.x(), point.y() );
690
691  return _isInTriangle2D( p, triangle );
692 }
693
694 QSet<int> QgsMeshUtils::nativeVerticesFromTriangles( const QList<int> &triangleIndexes, const QVector<QgsMeshFace> &triangles )
695 {
696  QSet<int> uniqueVertices;
697  for ( int triangleIndex : triangleIndexes )
698  {
699  const QgsMeshFace triangle = triangles[triangleIndex];
700  for ( int i : triangle )
701  {
702  uniqueVertices.insert( i );
703  }
704  }
705  return uniqueVertices;
706 }
707
708 QSet<int> QgsMeshUtils::nativeVerticesFromEdges( const QList<int> &edgesIndexes, const QVector<QgsMeshEdge> &edges )
709 {
710  QSet<int> uniqueVertices;
711  for ( int edgeIndex : edgesIndexes )
712  {
713  const QgsMeshEdge edge = edges[edgeIndex];
714  uniqueVertices.insert( edge.first );
715  uniqueVertices.insert( edge.second );
716  }
717  return uniqueVertices;
718 }
719
721 {
722  //if necessary defined removes triangles index
723  if ( changes.mRemovedTriangleIndexes.isEmpty() && !changes.mNativeFaceIndexesToRemove.isEmpty() )
724  {
725  for ( int nf = 0; nf < changes.mNativeFaceIndexesToRemove.count(); ++nf )
726  {
727  int nativeIndex = changes.mNativeFaceIndexesToRemove.at( nf );
728  const QgsMeshFace &nativeFace = changes.mNativeFacesToRemove.at( nf );
729  Q_ASSERT( !nativeFace.isEmpty() );
730
731  QgsRectangle nativeFaceExtent( mTriangularMesh.vertex( nativeFace.at( 0 ) ), mTriangularMesh.vertex( nativeFace.at( 0 ) ) );
732  for ( int i = 1; i < nativeFace.count(); ++i )
733  {
734  const QgsMeshVertex &triangularVertex = mTriangularMesh.vertex( nativeFace.at( i ) );
735  nativeFaceExtent.include( triangularVertex );
736  }
737
738  QList<int> concernedTriangle = faceIndexesForRectangle( nativeFaceExtent );
739  //Remove only those corresponding to the native face
740  for ( int i = 0; i < concernedTriangle.count(); ++i )
741  {
742  int triangleIndex = concernedTriangle.at( i );
743  if ( mTrianglesToNativeFaces.at( triangleIndex ) == nativeIndex )
744  changes.mRemovedTriangleIndexes.append( triangleIndex );
745  }
746  }
747  }
748
749  if ( changes.mOldZValue.isEmpty() && !changes.mNewZValue.isEmpty() )
750  {
751  changes.mOldZValue.reserve( changes.mNewZValue.count() );
752  for ( int i = 0; i < changes.mNewZValue.count(); ++i )
753  changes.mOldZValue.append( mTriangularMesh.vertices.at( changes.mChangedVerticesCoordinates.at( i ) ).z() );
754  }
755
756  if ( changes.mTriangleIndexesGeometryChanged.isEmpty() && !changes.mNativeFaceIndexesGeometryChanged.isEmpty() )
757  {
758  for ( int i = 0; i < changes.mNativeFaceIndexesGeometryChanged.count(); ++i )
759  {
760  const QgsMeshFace &nativeFace = changes.mNativeFacesGeometryChanged.at( i );
761  if ( nativeFace.count() < 2 )
762  continue;
763  QgsRectangle bbox( mTriangularMesh.vertices.at( nativeFace.at( 0 ) ), mTriangularMesh.vertices.at( nativeFace.at( 1 ) ) );
764
765  for ( int i = 2; i < nativeFace.count(); ++i )
766  bbox.include( mTriangularMesh.vertices.at( nativeFace.at( i ) ) );
767
768  QList<int> triangleIndexes = faceIndexesForRectangle( bbox );
769  int pos = 0;
770  while ( pos < triangleIndexes.count() )
771  {
772  if ( trianglesToNativeFaces().at( triangleIndexes.at( pos ) ) !=
773  changes.mNativeFaceIndexesGeometryChanged.at( i ) )
774  triangleIndexes.removeAt( pos );
775  else
776  ++pos;
777  }
778  changes.mTriangleIndexesGeometryChanged.append( triangleIndexes );
779  }
780  }
781
783  for ( const QgsMeshVertex &vertex : std::as_const( changes.mAddedVertices ) )
785
788  {
790  int firstNewNativeFacesIndex = mNativeMeshFaceCentroids.count();
791  for ( int i = 0; i < changes.mNativeFacesToAdd.count(); ++i )
792  {
793  const QgsMeshFace &nativeFace = changes.mNativeFacesToAdd.at( i );
794  triangulate( nativeFace, firstNewNativeFacesIndex + i );
795  mNativeMeshFaceCentroids.append( calculateCentroid( nativeFace ) );
796  }
797
798  for ( int i = changes.mTrianglesAddedStartIndex; i < mTriangularMesh.faceCount(); ++i )
800  }
801
802  // remove faces
803  for ( int i = 0; i < changes.mRemovedTriangleIndexes.count(); ++i )
804  {
805  int triangleIndex = changes.mRemovedTriangleIndexes.at( i );
806  mTrianglesToNativeFaces[triangleIndex] = -1;
807  mSpatialFaceIndex.removeFace( triangleIndex, mTriangularMesh );
808  mTriangularMesh.faces[triangleIndex] = QgsMeshFace();
809  }
810
811  for ( int i = 0; i < changes.mNativeFaceIndexesToRemove.count(); ++i )
812  mNativeMeshFaceCentroids[changes.mNativeFaceIndexesToRemove.at( i )] = QgsMeshVertex();
813
814  // remove vertices
815  for ( int i = 0; i < changes.mVerticesIndexesToRemove.count(); ++i )
816  mTriangularMesh.vertices[changes.mVerticesIndexesToRemove.at( i )] = QgsMeshVertex();
817
818  if ( !changes.mVerticesIndexesToRemove.isEmpty() )
819  mIsExtentValid = false;
820
821  // change Z value
822  for ( int i = 0; i < changes.mNewZValue.count(); ++i )
823  {
824  int vertexIndex = changes.mChangedVerticesCoordinates.at( i );
825  mTriangularMesh.vertices[vertexIndex].setZ( changes.mNewZValue.at( i ) );
826  }
827
828  //remove outdated spatial index
829  for ( const int triangleIndex : std::as_const( changes.mTriangleIndexesGeometryChanged ) )
830  mSpatialFaceIndex.removeFace( triangleIndex, mTriangularMesh );
831
832  // change (X,Y) of vertices
833  for ( int i = 0; i < changes.mNewXYValue.count(); ++i )
834  {
835  const QgsPointXY &nativeCoordinates = changes.mNewXYValue.at( i );
836  const QgsMeshVertex nativeVertex( nativeCoordinates.x(),
837  nativeCoordinates.y(),
838  mTriangularMesh.vertices.at( changes.mChangedVerticesCoordinates.at( i ) ).z() );
839
840  mTriangularMesh.vertices[changes.mChangedVerticesCoordinates.at( i )] = nativeToTriangularCoordinates( nativeVertex );
841  }
842
843  //restore spatial undex
844  for ( const int triangleIndex : std::as_const( changes.mTriangleIndexesGeometryChanged ) )
846
847  //update native faces
848  for ( int i = 0; i < changes.mNativeFaceIndexesGeometryChanged.count(); ++i )
849  mNativeMeshFaceCentroids[changes.mNativeFaceIndexesGeometryChanged.at( i )] = calculateCentroid( changes.mNativeFacesGeometryChanged.at( i ) );
850 }
851
853 {
856  {
857  for ( int i = changes.mTrianglesAddedStartIndex; i < mTriangularMesh.faceCount(); ++i )
858  mSpatialFaceIndex.removeFace( i, mTriangularMesh );
859
860  int initialNativeFacesCount = mNativeMeshFaceCentroids.count() - changes.mNativeFacesToAdd.count();
861
864  mNativeMeshFaceCentroids.resize( initialNativeFacesCount );
865  }
866
867  int initialVerticesCount = mTriangularMesh.vertices.count() - changes.mAddedVertices.count();
868  mTriangularMesh.vertices.resize( initialVerticesCount );
869
871  mIsExtentValid = false;
872
873  // for each vertex to remove we need to update the vertices with the native vertex
874  for ( const int i : std::as_const( changes.mVerticesIndexesToRemove ) )
875  mTriangularMesh.vertices[i] = nativeToTriangularCoordinates( nativeMesh.vertex( i ) );
876
877  if ( !changes.mVerticesIndexesToRemove.isEmpty() )
878  mIsExtentValid = false;
879
880  // reverse removed faces
881  QVector<QgsMeshFace> restoredTriangles;
882  QVector<int> restoredTriangularToNative;
883  for ( int i = 0; i < changes.mNativeFacesToRemove.count(); ++i )
884  {
885  const QgsMeshFace &nativeFace = changes.mNativeFacesToRemove.at( i );
886  triangulateFaces( nativeFace,
887  changes.mNativeFaceIndexesToRemove.at( i ),
888  restoredTriangles,
889  restoredTriangularToNative,
890  mTriangularMesh );
891  mNativeMeshFaceCentroids[changes.mNativeFaceIndexesToRemove.at( i )] = calculateCentroid( nativeFace );
892  }
893  for ( int i = 0; i < changes.mRemovedTriangleIndexes.count(); ++i )
894  {
895  int triangleIndex = changes.mRemovedTriangleIndexes.at( i );
896  mTriangularMesh.faces[triangleIndex] = restoredTriangles.at( i );
898  mTrianglesToNativeFaces[triangleIndex] = restoredTriangularToNative.at( i );
899  }
900
901  // reverse Z value
902  for ( int i = 0; i < changes.mOldZValue.count(); ++i )
903  {
904  int vertexIndex = changes.mChangedVerticesCoordinates.at( i );
905  mTriangularMesh.vertices[vertexIndex].setZ( changes.mOldZValue.at( i ) );
906  }
907
908  //remove outdated spatial index
909  for ( const int triangleIndex : std::as_const( changes.mTriangleIndexesGeometryChanged ) )
910  mSpatialFaceIndex.removeFace( triangleIndex, mTriangularMesh );
911
912  // reverse (X,Y) of vertices
913  for ( int i = 0; i < changes.mOldXYValue.count(); ++i )
914  {
915  const QgsPointXY &nativeCoordinates = changes.mOldXYValue.at( i );
916  const QgsMeshVertex nativeVertex( nativeCoordinates.x(),
917  nativeCoordinates.y(),
918  mTriangularMesh.vertices.at( changes.mChangedVerticesCoordinates.at( i ) ).z() );
919
920  mTriangularMesh.vertices[changes.mChangedVerticesCoordinates.at( i )] = nativeToTriangularCoordinates( nativeVertex );
921  }
922
923  //restore spatial undex
924  for ( const int triangleIndex : std::as_const( changes.mTriangleIndexesGeometryChanged ) )
926
927  //update native faces
928  for ( int i = 0; i < changes.mNativeFaceIndexesGeometryChanged.count(); ++i )
929  mNativeMeshFaceCentroids[changes.mNativeFaceIndexesGeometryChanged.at( i )] = calculateCentroid( changes.mNativeFacesGeometryChanged.at( i ) );
930 }
931
933  const QgsMesh &nativeMesh )
934 {
936  mVerticesIndexesToRemove = topologicalChanges.verticesToRemoveIndexes();
938  mNativeFacesToRemove = topologicalChanges.removedFaces();
939  mNativeFaceIndexesToRemove = topologicalChanges.removedFaceIndexes();
940  mChangedVerticesCoordinates = topologicalChanges.changedCoordinatesVerticesIndexes();
941  mNewZValue = topologicalChanges.newVerticesZValues();
942  mNewXYValue = topologicalChanges.newVerticesXYValues();
943  mOldXYValue = topologicalChanges.oldVerticesXYValues();
944
945  mNativeFaceIndexesGeometryChanged = topologicalChanges.nativeFacesIndexesGeometryChanged();
946  mNativeFacesGeometryChanged.resize( mNativeFaceIndexesGeometryChanged.count() );
947  for ( int i = 0; i < mNativeFaceIndexesGeometryChanged.count(); ++i )
948  mNativeFacesGeometryChanged[i] = nativeMesh.face( mNativeFaceIndexesGeometryChanged.at( i ) );
949 }
950
951 QgsMeshVertex QgsMeshUtils::centroid( const QgsMeshFace &face, const QVector<QgsMeshVertex> &vertices )
952 {
953  QVector<QPointF> points( face.size() );
954  for ( int j = 0; j < face.size(); ++j )
955  {
956  int index = face.at( j );
957  const QgsMeshVertex &vertex = vertices.at( index ); // we need vertices in map coordinate
958  points[j] = vertex.toQPointF();
959  }
960  QPolygonF poly( points );
961  double cx, cy;
962  ENP_centroid( poly, cx, cy );
963  return QgsMeshVertex( cx, cy );
964 }
965
967 {
968  //To have consistent clock wise orientation of triangles which is necessary for 3D rendering
969  //Check the clock wise, and if it is not counter clock wise, swap indexes to make the oientation counter clock wise
970  double ux = v1.x() - v0.x();
971  double uy = v1.y() - v0.y();
972  double vx = v2.x() - v0.x();
973  double vy = v2.y() - v0.y();
974
975  double crossProduct = ux * vy - uy * vx;
976  if ( crossProduct < 0 ) //CW -->change the orientation
977  {
978  std::swap( triangle[1], triangle[2] );
979  }
980 }
TransformDirection
Indicates the direction (forward or inverse) of a transform.
Definition: qgis.h:910
Class for doing transforms between two map coordinate systems.
QgsCoordinateReferenceSystem sourceCrs() const
Returns the source coordinate reference system, which the transform will transform coordinates from.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
bool isShortCircuited() const
Returns true if the transform short circuits because the source and destination are equivalent.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system, which the transform will transform coordinates t...
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
QString what() const
Definition: qgsexception.h:48
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:125
bool contains(const QgsPointXY *p) const
Returns true if the geometry contains the point p.
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:44
A spatial index for QgsMeshFace or QgsMeshEdge objects.
QList< int > intersects(const QgsRectangle &rectangle) const
Returns a list of face ids with a bounding box which intersects the specified rectangle.
void addFace(int faceIndex, const QgsMesh &mesh)
Adds a face with faceIndex from the mesh in the spatial index.
void removeFace(int faceIndex, const QgsMesh &mesh)
Removes a face with faceIndex from the mesh in the spatial index.
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Definition: qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
void transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection d=Qgis::TransformDirection::Forward, bool transformZ=false) override SIP_THROW(QgsCsException)
Transforms the geometry using a coordinate transform.
Definition: qgspoint.cpp:382
QPointF toQPointF() const SIP_HOLDGIL
Returns the point as a QPointF.
Definition: qgspoint.h:331
bool isEmpty() const override SIP_HOLDGIL
Returns true if the geometry is empty.
Definition: qgspoint.cpp:767
Definition: qgspoint.h:52
double z
Definition: qgspoint.h:54
double y
Definition: qgspoint.h:53
A rectangle specified with double values.
Definition: qgsrectangle.h:42
void include(const QgsPointXY &p)
Updates the rectangle to include the specified point.
Definition: qgsrectangle.h:307
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
void setMinimal() SIP_HOLDGIL
Set a rectangle so that min corner is at max and max corner is at min.
Definition: qgsrectangle.h:172
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
Class that contains topological differences between two states of a topological mesh,...
QVector< QgsMeshFace > removedFaces() const
Returns the faces that are removed with this changes.
Returns the added vertices with this changes.
QList< int > changedCoordinatesVerticesIndexes() const
Returns the indexes of vertices that have changed coordinates.
QList< int > removedFaceIndexes() const
Returns the indexes of the faces that are removed with this changes.
QList< double > newVerticesZValues() const
Returns the new Z values of vertices that have changed their coordinates.
Returns the face that are added with this changes.
QList< QgsPointXY > oldVerticesXYValues() const
Returns the old (X,Y) values of vertices that have changed their coordinates.
QList< QgsPointXY > newVerticesXYValues() const
Returns the new (X,Y) values of vertices that have changed their coordinates.
QList< int > nativeFacesIndexesGeometryChanged() const
Returns a list of the native face indexes that have a geometry changed.
QList< int > verticesToRemoveIndexes() const
Returns the indexes of vertices to remove.
The Changes class is used to make changes of the triangular and to keep traces of this changes If a C...
Changes()=default
Default constructor, no changes.
Triangular/Derived Mesh is mesh with vertices in map coordinates.
bool update(QgsMesh *nativeMesh, const QgsCoordinateTransform &transform=QgsCoordinateTransform())
Constructs triangular mesh from layer's native mesh and transform to destination CRS.
const QVector< QgsMeshVertex > & edgeCentroids() const
Returns centroids of the native edges in map CRS.
const QVector< QgsMeshFace > & triangles() const
Returns triangles.
QVector< QgsTriangularMesh * > simplifyMesh(double reductionFactor, int minimumTrianglesCount=10) const
Returns simplified meshes.
QgsRectangle nativeExtent()
Returns the extent of the mesh in the native mesh coordinates system, returns empty extent if the tra...
QgsPointXY transformFromLayerToTrianglesCoordinates(const QgsPointXY &point) const
Transforms a point from layer coordinates system to triangular Mesh coordinates system.
QgsTriangularMesh()
Ctor.
int levelOfDetail() const
Returns the corresponding index of level of detail on which this mesh is associated.
QgsRectangle extent() const
Returns the extent of the triangular mesh in map coordinates.
int faceIndexForPoint(const QgsPointXY &point) const
Finds index of triangle at given point It uses spatial indexing.
int nativeFaceIndexForPoint(const QgsPointXY &point) const
Finds index of native face at given point It uses spatial indexing.
double averageTriangleSize() const
Returns the average size of triangles in map unit.
void reverseChanges(const Changes &changes, const QgsMesh &nativeMesh)
Reverses the changes on the triangular mesh (see Changes)
void applyChanges(const Changes &changes)
Applies the changes on the triangular mesh (see Changes)
QList< int > edgeIndexesForRectangle(const QgsRectangle &rectangle) const
Finds indexes of edges intersecting given bounding box It uses spatial indexing.
Q_DECL_DEPRECATED const QVector< QgsMeshVertex > & centroids() const
Returns centroids of the native faces in map CRS.
const QVector< QgsMeshVertex > & vertices() const
Returns vertices in map coordinate system.
const QVector< QgsMeshEdge > & edges() const
Returns edges.
bool contains(const QgsMesh::ElementType &type) const
Returns whether the mesh contains mesh elements of given type.
QgsMeshVertex triangularToNativeCoordinates(const QgsMeshVertex &vertex) const
Transforms the vertex from triangular mesh coordinates system to native coordinates system.
QVector< QVector3D > vertexNormals(float vertScale) const
Calculates and returns normale vector on each vertex that is part of any face.
QgsMeshVertex nativeToTriangularCoordinates(const QgsMeshVertex &vertex) const
Transforms the vertex from native coordinates system to triangular mesh coordinates system.
~QgsTriangularMesh()
Dtor.
const QVector< QgsMeshVertex > & faceCentroids() const
Returns centroids of the native faces in map CRS.
QList< int > nativeFaceIndexForRectangle(const QgsRectangle &rectangle) const
Finds indexes of native faces which bounding boxes intersect given bounding box It uses spatial index...
const QVector< int > & trianglesToNativeFaces() const
Returns mapping between triangles and original faces.
const QVector< int > & edgesToNativeEdges() const
Returns mapping between edges and original edges.
QList< int > faceIndexesForRectangle(const QgsRectangle &rectangle) const
Finds indexes of triangles intersecting given bounding box It uses spatial indexing.
int faceIndexForPoint_v2(const QgsPointXY &point) const
Finds index of triangle at given point It uses spatial indexing and don't use geos to be faster.
bool isInTriangleFace(const QgsPointXY point, const QgsMeshFace &face, const QVector< QgsMeshVertex > &vertices)
Tests if point p is on the face defined with vertices.
CORE_EXPORT QSet< int > nativeEdgesFromEdges(const QList< int > &edgesIndexes, const QVector< int > &edgesToNativeEdges)
Returns unique native faces indexes from list of triangle indexes.
CORE_EXPORT std::unique_ptr< QgsPolygon > toPolygon(const QgsMeshFace &face, const QVector< QgsMeshVertex > &vertices)
Returns face as polygon geometry, caller is responsible for delete.
CORE_EXPORT QgsGeometry toGeometry(const QgsMeshFace &face, const QVector< QgsMeshVertex > &vertices)
Returns face as polygon geometry.
void setCounterClockwise(QgsMeshFace &triangle, const QgsMeshVertex &v0, const QgsMeshVertex &v1, const QgsMeshVertex &v2)
Checks if the triangle is counter clockwise, if not sets it counter clockwise.
CORE_EXPORT QSet< int > nativeVerticesFromEdges(const QList< int > &edgesIndexes, const QVector< QgsMeshEdge > &edges)
Returns unique native faces indexes from list of vertices of triangles.
CORE_EXPORT QgsMeshVertex centroid(const QgsMeshFace &face, const QVector< QgsMeshVertex > &vertices)
Returns the centroid of the face.
CORE_EXPORT QSet< int > nativeVerticesFromTriangles(const QList< int > &triangleIndexes, const QVector< QgsMeshFace > &triangles)
Returns unique native vertex indexes from list of vertices of triangles.
CORE_EXPORT QSet< int > nativeFacesFromTriangles(const QList< int > &triangleIndexes, const QVector< int > &trianglesToNativeFaces)
Returns unique native faces indexes from list of triangle indexes.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QVector< int > QgsMeshFace
List of vertex indexes.
QPair< int, int > QgsMeshEdge
Edge is a straight line seqment between 2 points.
QgsPoint QgsMeshVertex
xyz coords of vertex
Mesh - vertices, edges and faces.
int vertexCount() const
Returns number of vertices.
QVector< QgsMeshVertex > vertices
QgsMeshFace face(int index) const
Returns a face at the index.
QVector< QgsMeshFace > faces
int faceCount() const
Returns number of faces.
ElementType
Defines type of mesh elements.
QgsMeshVertex vertex(int index) const
Returns a vertex at the index.
int edgeCount() const
Returns number of edge.
QVector< QgsMeshEdge > edges