QGIS API Documentation 3.43.0-Master (b60ef06885e)
qgsmeshtracerenderer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmeshtracerenderer.cpp
3 -------------------------
4 begin : November 2019
5 copyright : (C) 2019 by Vincent Cloarec
6 email : vcloarec at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
20#include "qgsrendercontext.h"
21#include "qgslinestring.h"
23#include "qgsmeshlayerutils.h"
24#include "qgsrastershader.h"
26
27#include <QPointer>
28
30
31#ifndef M_DEG2RAD
32#define M_DEG2RAD 0.0174532925
33#endif
34
35
36QgsVector QgsMeshVectorValueInterpolator::vectorValue( const QgsPointXY &point ) const
37{
38 if ( mCacheFaceIndex != -1 && mCacheFaceIndex < mTriangularMesh.triangles().count() )
39 {
40 QgsVector res = interpolatedValuePrivate( mCacheFaceIndex, point );
41 if ( isVectorValid( res ) )
42 {
43 activeFaceFilter( res, mCacheFaceIndex );
44 return res;
45 }
46 }
47
48 //point is not on the face associated with mCacheIndex --> search for the face containing the point
49 QList<int> potentialFaceIndexes = mTriangularMesh.faceIndexesForRectangle( QgsRectangle( point, point ) );
50 mCacheFaceIndex = -1;
51 for ( const int faceIndex : potentialFaceIndexes )
52 {
53 QgsVector res = interpolatedValuePrivate( faceIndex, point );
54 if ( isVectorValid( res ) )
55 {
56 mCacheFaceIndex = faceIndex;
57 activeFaceFilter( res, mCacheFaceIndex );
58 return res;
59 }
60 }
61
62 //--> no face found return non valid vector
63 return ( QgsVector( std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN() ) );
64
65}
66
67QgsMeshVectorValueInterpolator &QgsMeshVectorValueInterpolator::operator=( const QgsMeshVectorValueInterpolator &other )
68{
69 mTriangularMesh = other.mTriangularMesh;
70 mDatasetValues = other.mDatasetValues;
71 mActiveFaceFlagValues = other.mActiveFaceFlagValues;
72 mFaceCache = other.mFaceCache;
73 mCacheFaceIndex = other.mCacheFaceIndex;
74 mUseScalarActiveFaceFlagValues = other.mUseScalarActiveFaceFlagValues;
75
76 return *this;
77}
78
79QgsMeshVectorValueInterpolatorFromVertex::
80QgsMeshVectorValueInterpolatorFromVertex( const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &datasetVectorValues )
81 : QgsMeshVectorValueInterpolator( triangularMesh, datasetVectorValues )
82{
83
84}
85
86QgsMeshVectorValueInterpolatorFromVertex::
87QgsMeshVectorValueInterpolatorFromVertex( const QgsTriangularMesh &triangularMesh,
88 const QgsMeshDataBlock &datasetVectorValues,
89 const QgsMeshDataBlock &scalarActiveFaceFlagValues )
90 : QgsMeshVectorValueInterpolator( triangularMesh, datasetVectorValues, scalarActiveFaceFlagValues )
91{
92
93}
94
95QgsMeshVectorValueInterpolatorFromVertex::QgsMeshVectorValueInterpolatorFromVertex( const QgsMeshVectorValueInterpolatorFromVertex &other ):
96 QgsMeshVectorValueInterpolator( other )
97{}
98
99QgsMeshVectorValueInterpolatorFromVertex *QgsMeshVectorValueInterpolatorFromVertex::clone()
100{
101 return new QgsMeshVectorValueInterpolatorFromVertex( *this );
102}
103
104QgsMeshVectorValueInterpolatorFromVertex &QgsMeshVectorValueInterpolatorFromVertex::
105operator=( const QgsMeshVectorValueInterpolatorFromVertex &other )
106{
107 QgsMeshVectorValueInterpolator::operator=( other );
108 return ( *this );
109}
110
111QgsVector QgsMeshVectorValueInterpolatorFromVertex::interpolatedValuePrivate( int faceIndex, const QgsPointXY point ) const
112{
113 QgsMeshFace face = mTriangularMesh.triangles().at( faceIndex );
114
115 QgsPoint p1 = mTriangularMesh.vertices().at( face.at( 0 ) );
116 QgsPoint p2 = mTriangularMesh.vertices().at( face.at( 1 ) );
117 QgsPoint p3 = mTriangularMesh.vertices().at( face.at( 2 ) );
118
119 QgsVector v1 = QgsVector( mDatasetValues.value( face.at( 0 ) ).x(),
120 mDatasetValues.value( face.at( 0 ) ).y() );
121
122 QgsVector v2 = QgsVector( mDatasetValues.value( face.at( 1 ) ).x(),
123 mDatasetValues.value( face.at( 1 ) ).y() );
124
125 QgsVector v3 = QgsVector( mDatasetValues.value( face.at( 2 ) ).x(),
126 mDatasetValues.value( face.at( 2 ) ).y() );
127
128 return QgsMeshLayerUtils::interpolateVectorFromVerticesData(
129 p1,
130 p2,
131 p3,
132 v1,
133 v2,
134 v3,
135 point );
136}
137
138QgsMeshVectorValueInterpolator::QgsMeshVectorValueInterpolator( const QgsTriangularMesh &triangularMesh,
139 const QgsMeshDataBlock &datasetVectorValues )
140 : mTriangularMesh( triangularMesh )
141 , mDatasetValues( datasetVectorValues )
142 , mUseScalarActiveFaceFlagValues( false )
143{}
144
145QgsMeshVectorValueInterpolator::QgsMeshVectorValueInterpolator( const QgsTriangularMesh &triangularMesh,
146 const QgsMeshDataBlock &datasetVectorValues,
147 const QgsMeshDataBlock &scalarActiveFaceFlagValues )
148 : mTriangularMesh( triangularMesh )
149 , mDatasetValues( datasetVectorValues )
150 , mActiveFaceFlagValues( scalarActiveFaceFlagValues )
151 , mUseScalarActiveFaceFlagValues( true )
152{}
153
154QgsMeshVectorValueInterpolator::QgsMeshVectorValueInterpolator( const QgsMeshVectorValueInterpolator &other )
155 : mTriangularMesh( other.mTriangularMesh )
156 , mDatasetValues( other.mDatasetValues )
157 , mActiveFaceFlagValues( other.mActiveFaceFlagValues )
158 , mFaceCache( other.mFaceCache )
159 , mCacheFaceIndex( other.mCacheFaceIndex )
160 , mUseScalarActiveFaceFlagValues( other.mUseScalarActiveFaceFlagValues )
161{}
162
163void QgsMeshVectorValueInterpolator::updateCacheFaceIndex( const QgsPointXY &point ) const
164{
165 if ( ! QgsMeshUtils::isInTriangleFace( point, mFaceCache, mTriangularMesh.vertices() ) )
166 {
167 mCacheFaceIndex = mTriangularMesh.faceIndexForPoint_v2( point );
168 }
169}
170
171bool QgsMeshVectorValueInterpolator::isVectorValid( const QgsVector &v ) const
172{
173 return !( std::isnan( v.x() ) || std::isnan( v.y() ) );
174
175}
176
177void QgsMeshVectorValueInterpolator::activeFaceFilter( QgsVector &vector, int faceIndex ) const
178{
179 if ( mUseScalarActiveFaceFlagValues && ! mActiveFaceFlagValues.active( mTriangularMesh.trianglesToNativeFaces()[faceIndex] ) )
180 vector = QgsVector( std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN() ) ;
181}
182
183QSize QgsMeshStreamField::size() const
184{
185 return mFieldSize;
186}
187
188QPoint QgsMeshStreamField::topLeft() const
189{
190 return mFieldTopLeftInDeviceCoordinates;
191}
192
193int QgsMeshStreamField::resolution() const
194{
195 return mFieldResolution;
196}
197
198QgsPointXY QgsMeshStreamField::positionToMapCoordinates( const QPoint &pixelPosition, const QgsPointXY &positionInPixel )
199{
200 QgsPointXY mapPoint = mMapToFieldPixel.toMapCoordinates( pixelPosition );
201 mapPoint = mapPoint + QgsVector( positionInPixel.x() * mMapToFieldPixel.mapUnitsPerPixel(),
202 positionInPixel.y() * mMapToFieldPixel.mapUnitsPerPixel() );
203 return mapPoint;
204}
205
206QgsMeshStreamField::QgsMeshStreamField(
207 const QgsTriangularMesh &triangularMesh,
208 const QgsMeshDataBlock &dataSetVectorValues,
209 const QgsMeshDataBlock &scalarActiveFaceFlagValues,
210 const QgsRectangle &layerExtent,
211 double magnitudeMaximum,
212 bool dataIsOnVertices,
213 const QgsRenderContext &rendererContext,
214 const QgsInterpolatedLineColor &vectorColoring,
215 int resolution )
216 : mFieldResolution( resolution )
217 , mVectorColoring( vectorColoring )
218 , mRenderContext( rendererContext )
219 , mLayerExtent( layerExtent )
220 , mMaximumMagnitude( magnitudeMaximum )
221{
222 if ( dataIsOnVertices )
223 {
224 if ( scalarActiveFaceFlagValues.isValid() )
225 mVectorValueInterpolator.reset( new QgsMeshVectorValueInterpolatorFromVertex(
226 triangularMesh,
227 dataSetVectorValues,
228 scalarActiveFaceFlagValues ) );
229 else
230 mVectorValueInterpolator.reset( new QgsMeshVectorValueInterpolatorFromVertex(
231 triangularMesh,
232 dataSetVectorValues ) );
233 }
234 else
235 {
236 if ( scalarActiveFaceFlagValues.isValid() )
237 mVectorValueInterpolator.reset( new QgsMeshVectorValueInterpolatorFromFace(
238 triangularMesh,
239 dataSetVectorValues,
240 scalarActiveFaceFlagValues ) );
241 else
242 mVectorValueInterpolator.reset( new QgsMeshVectorValueInterpolatorFromFace(
243 triangularMesh,
244 dataSetVectorValues ) );
245 }
246}
247
248QgsMeshStreamField::QgsMeshStreamField( const QgsMeshStreamField &other )
249 : mFieldSize( other.mFieldSize )
250 , mFieldResolution( other.mFieldResolution )
251 , mPen( other.mPen )
252 , mTraceImage( other.mTraceImage )
253 , mMapToFieldPixel( other.mMapToFieldPixel )
254 , mOutputExtent( other.mOutputExtent )
255 , mVectorColoring( other.mVectorColoring )
256 , mDirectionField( other.mDirectionField )
257 , mRenderContext( other.mRenderContext )
258 , mPixelFillingCount( other.mPixelFillingCount )
259 , mMaxPixelFillingCount( other.mMaxPixelFillingCount )
260 , mLayerExtent( other.mLayerExtent )
261 , mMapExtent( other.mMapExtent )
262 , mFieldTopLeftInDeviceCoordinates( other.mFieldTopLeftInDeviceCoordinates )
263 , mValid( other.mValid )
264 , mMaximumMagnitude( other.mMaximumMagnitude )
265 , mPixelFillingDensity( other.mPixelFillingDensity )
266 , mMinMagFilter( other.mMinMagFilter )
267 , mMaxMagFilter( other.mMaxMagFilter )
268 , mMinimizeFieldSize( other.mMinimizeFieldSize )
269{
270 mPainter.reset( new QPainter( &mTraceImage ) );
271 mVectorValueInterpolator =
272 std::unique_ptr<QgsMeshVectorValueInterpolator>( other.mVectorValueInterpolator->clone() );
273}
274
275QgsMeshStreamField::~QgsMeshStreamField()
276{
277 if ( mPainter )
278 mPainter->end();
279}
280
281void QgsMeshStreamField::updateSize( const QgsRenderContext &renderContext )
282{
283 mMapExtent = renderContext.mapExtent();
284 const QgsMapToPixel &deviceMapToPixel = renderContext.mapToPixel();
285 QgsRectangle layerExtent;
286 try
287 {
288 QgsCoordinateTransform extentTransform = renderContext.coordinateTransform();
289 extentTransform.setBallparkTransformsAreAppropriate( true );
290 layerExtent = extentTransform.transformBoundingBox( mLayerExtent );
291 }
292 catch ( QgsCsException &cse )
293 {
294 Q_UNUSED( cse )
295 //if the transform fails, consider the whole map
296 layerExtent = mMapExtent;
297 }
298
299 QgsRectangle interestZoneExtent;
300 if ( mMinimizeFieldSize )
301 interestZoneExtent = layerExtent.intersect( mMapExtent );
302 else
303 interestZoneExtent = mMapExtent;
304
305 if ( interestZoneExtent == QgsRectangle() )
306 {
307 mValid = false;
308 mFieldSize = QSize();
309 mFieldTopLeftInDeviceCoordinates = QPoint();
310 initField();
311 return;
312 }
313
314 QgsRectangle fieldInterestZoneInDeviceCoordinates = QgsMeshLayerUtils::boundingBoxToScreenRectangle( deviceMapToPixel, interestZoneExtent );
315 mFieldTopLeftInDeviceCoordinates =
316 QPoint( static_cast<int>( std::round( fieldInterestZoneInDeviceCoordinates.xMinimum() ) ),
317 static_cast<int>( std::round( fieldInterestZoneInDeviceCoordinates.yMinimum() ) ) );
318 int fieldWidthInDeviceCoordinate = int( fieldInterestZoneInDeviceCoordinates.width() );
319 int fieldHeightInDeviceCoordinate = int ( fieldInterestZoneInDeviceCoordinates.height() );
320
321 int fieldWidth = int( fieldWidthInDeviceCoordinate / mFieldResolution );
322 int fieldHeight = int( fieldHeightInDeviceCoordinate / mFieldResolution );
323
324 //increase the field size if this size is not adjusted to extent of zone of interest in device coordinates
325 if ( fieldWidthInDeviceCoordinate % mFieldResolution > 0 )
326 fieldWidth++;
327 if ( fieldHeightInDeviceCoordinate % mFieldResolution > 0 )
328 fieldHeight++;
329
330 if ( fieldWidth == 0 || fieldHeight == 0 )
331 {
332 mFieldSize = QSize();
333 mOutputExtent = QgsRectangle();
334 }
335 else
336 {
337 mFieldSize.setWidth( fieldWidth );
338 mFieldSize.setHeight( fieldHeight );
339 QgsPointXY pt1 = deviceMapToPixel.toMapCoordinates( mFieldTopLeftInDeviceCoordinates );
340 QgsPointXY pt2 = deviceMapToPixel.toMapCoordinates( mFieldTopLeftInDeviceCoordinates + QPoint( fieldWidth, fieldHeight ) );
341 QgsPointXY pt3 = deviceMapToPixel.toMapCoordinates( mFieldTopLeftInDeviceCoordinates + QPoint( 0, fieldHeight ) );
342 QgsPointXY pt4 = deviceMapToPixel.toMapCoordinates( mFieldTopLeftInDeviceCoordinates + QPoint( fieldWidth, 0 ) );
343
344 mOutputExtent = QgsRectangle( std::min( {pt1.x(), pt2.x(), pt3.x(), pt4.x()} ),
345 std::min( {pt1.y(), pt2.y(), pt3.y(), pt4.y()} ),
346 std::max( {pt1.x(), pt2.x(), pt3.x(), pt4.x()} ),
347 std::max( {pt1.y(), pt2.y(), pt3.y(), pt4.y()} ),
348 true );
349 }
350
351 double mapUnitPerFieldPixel;
352 if ( interestZoneExtent.width() > 0 )
353 mapUnitPerFieldPixel = deviceMapToPixel.mapUnitsPerPixel() * mFieldResolution * mFieldSize.width() /
354 ( fieldWidthInDeviceCoordinate / static_cast<double>( mFieldResolution ) ) ;
355 else
356 mapUnitPerFieldPixel = 1e-8;
357
358 int fieldRightDevice = mFieldTopLeftInDeviceCoordinates.x() + mFieldSize.width() * mFieldResolution;
359 int fieldBottomDevice = mFieldTopLeftInDeviceCoordinates.y() + mFieldSize.height() * mFieldResolution;
360 QgsPointXY fieldRightBottomMap = deviceMapToPixel.toMapCoordinates( fieldRightDevice, fieldBottomDevice );
361
362 int fieldTopDevice = mFieldTopLeftInDeviceCoordinates.x();
363 int fieldLeftDevice = mFieldTopLeftInDeviceCoordinates.y();
364 QgsPointXY fieldTopLeftMap = deviceMapToPixel.toMapCoordinates( fieldTopDevice, fieldLeftDevice );
365
366 double xc = ( fieldRightBottomMap.x() + fieldTopLeftMap.x() ) / 2;
367 double yc = ( fieldTopLeftMap.y() + fieldRightBottomMap.y() ) / 2;
368
369 mMapToFieldPixel = QgsMapToPixel( mapUnitPerFieldPixel,
370 xc,
371 yc,
372 fieldWidth,
373 fieldHeight,
374 deviceMapToPixel.mapRotation()
375 );
376
377 initField();
378 mValid = true;
379}
380
381void QgsMeshStreamField::updateSize( const QgsRenderContext &renderContext, int resolution )
382{
383 if ( renderContext.mapExtent() == mMapExtent && resolution == mFieldResolution )
384 return;
385 mFieldResolution = resolution;
386
387 updateSize( renderContext );
388}
389
390bool QgsMeshStreamField::isValid() const
391{
392 return mValid;
393}
394
395void QgsMeshStreamField::addTrace( QgsPointXY startPoint )
396{
397 addTrace( mMapToFieldPixel.transform( startPoint ).toQPointF().toPoint() );
398}
399
400
401void QgsMeshStreamField::addRandomTraces()
402{
403 if ( mMaximumMagnitude > 0 )
404 while ( ( mPixelFillingCount < mMaxPixelFillingCount ) &&
405 ( !mRenderContext.feedback() ||
406 !mRenderContext.feedback()->isCanceled() ||
407 !mRenderContext.renderingStopped() ) )
408 addRandomTrace();
409}
410
411void QgsMeshStreamField::addRandomTrace()
412{
413 if ( !mValid )
414 return;
415
416 int xRandom = 1 + std::rand() / int( ( RAND_MAX + 1u ) / uint( mFieldSize.width() ) ) ;
417 int yRandom = 1 + std::rand() / int ( ( RAND_MAX + 1u ) / uint( mFieldSize.height() ) ) ;
418 addTrace( QPoint( xRandom, yRandom ) );
419}
420
421void QgsMeshStreamField::addGriddedTraces( int dx, int dy )
422{
423 int i = 0 ;
424 while ( i < mFieldSize.width() && mRenderContext.feedback() && !mRenderContext.feedback()->isCanceled() )
425 {
426 int j = 0 ;
427 while ( j < mFieldSize.height() && mRenderContext.feedback() && !mRenderContext.feedback()->isCanceled() )
428 {
429 addTrace( QPoint( i, j ) );
430 j += dy;
431 }
432 i += dx;
433 }
434}
435
436void QgsMeshStreamField::addTracesOnMesh( const QgsTriangularMesh &mesh, const QgsRectangle &extent )
437{
438 QList<int> facesInExtent = mesh.faceIndexesForRectangle( extent );
439 QSet<int> vertices;
440 for ( auto f : std::as_const( facesInExtent ) )
441 {
442 auto face = mesh.triangles().at( f );
443 for ( auto i : std::as_const( face ) )
444 vertices.insert( i );
445 }
446
447 for ( auto i : std::as_const( vertices ) )
448 {
449 addTrace( mesh.vertices().at( i ) );
450 }
451}
452
453void QgsMeshStreamField::addTrace( QPoint startPixel )
454{
455 //This is where each traces are constructed
456 if ( !mPainter )
457 return;
458
459 if ( isTraceExists( startPixel ) || isTraceOutside( startPixel ) )
460 return;
461
462 if ( !mVectorValueInterpolator )
463 return;
464
465 if ( !( mMaximumMagnitude > 0 ) )
466 return;
467
468 mPainter->setPen( mPen );
469
470 //position in the pixelField
471 double x1 = 0;
472 double y1 = 0;
473
474 std::list<QPair<QPoint, FieldData>> chunkTrace;
475
476 QPoint currentPixel = startPixel;
477 QgsVector vector;
478 FieldData data;
479 data.time = 1;
480
481 while ( true )
482 {
483 QgsPointXY mapPosition = positionToMapCoordinates( currentPixel, QgsPointXY( x1, y1 ) );
484 vector = mVectorValueInterpolator->vectorValue( mapPosition ) ;
485
486 if ( std::isnan( vector.x() ) || std::isnan( vector.y() ) )
487 {
488 mPixelFillingCount++;
489 setChunkTrace( chunkTrace );
490 break;
491 }
492
493 /* nondimensional value : Vu=2 when the particle need dt=1 to go through a pixel with the mMagMax magnitude
494 * The nondimensional size of the side of a pixel is 2
495 */
496 vector = vector.rotateBy( -mMapToFieldPixel.mapRotation() * M_DEG2RAD );
497 QgsVector vu = vector / mMaximumMagnitude * 2;
498 data.magnitude = vector.length();
499
500 double Vx = vu.x();
501 double Vy = vu.y();
502 double Vu = data.magnitude / mMaximumMagnitude * 2; //nondimensional vector magnitude
503
504 if ( qgsDoubleNear( Vu, 0 ) )
505 {
506 // no trace anymore
507 addPixelToChunkTrace( currentPixel, data, chunkTrace );
508 simplifyChunkTrace( chunkTrace );
509 setChunkTrace( chunkTrace );
510 break;
511 }
512
513 //calculates where the particle will be after dt=1,
514 QgsPointXY nextPosition = QgsPointXY( x1, y1 ) + vu;
515 int incX = 0;
516 int incY = 0;
517 if ( nextPosition.x() > 1 )
518 incX = +1;
519 if ( nextPosition.x() < -1 )
520 incX = -1;
521 if ( nextPosition.y() > 1 )
522 incY = +1;
523 if ( nextPosition.y() < -1 )
524 incY = -1;
525
526 if ( incX != 0 || incY != 0 )
527 {
528 data.directionX = incX;
529 data.directionY = -incY;
530 //the particule leave the current pixel --> store pixels, calculates where the particle is and change the current pixel
531 if ( chunkTrace.empty() )
532 {
533 storeInField( QPair<QPoint, FieldData>( currentPixel, data ) );
534 }
535 if ( addPixelToChunkTrace( currentPixel, data, chunkTrace ) )
536 {
537 setChunkTrace( chunkTrace );
538 clearChunkTrace( chunkTrace );
539 }
540
541 data.time = 1;
542 currentPixel += QPoint( incX, -incY );
543 x1 = nextPosition.x() - 2 * incX;
544 y1 = nextPosition.y() - 2 * incY;
545 }
546 else
547 {
548 double x2, y2;
549 /*the particule still in the pixel --> "push" the position with the vector value to join a border
550 * and calculate the time spent to go to this border
551 */
552 if ( qgsDoubleNear( Vy, 0 ) )
553 {
554 y2 = y1;
555 if ( Vx > 0 )
556 incX = +1;
557 else
558 incX = -1;
559
560 x2 = incX ;
561 }
562 else if ( qgsDoubleNear( Vx, 0 ) )
563 {
564 x2 = x1;
565 if ( Vy > 0 )
566 incY = +1;
567 else
568 incY = -1;
569
570 y2 = incY ;
571 }
572 else
573 {
574 if ( Vy > 0 )
575 x2 = x1 + ( 1 - y1 ) * Vx / fabs( Vy ) ;
576 else
577 x2 = x1 + ( 1 + y1 ) * Vx / fabs( Vy ) ;
578 if ( Vx > 0 )
579 y2 = y1 + ( 1 - x1 ) * Vy / fabs( Vx ) ;
580 else
581 y2 = y1 + ( 1 + x1 ) * Vy / fabs( Vx ) ;
582
583 if ( x2 >= 1 )
584 x2 = 1;
585
586 if ( x2 <= -1 )
587 x2 = -1;
588
589 if ( y2 >= 1 )
590 y2 = 1;
591
592 if ( y2 <= -1 )
593 y2 = -1;
594
595 }
596
597 //calculate distance
598 double dx = x2 - x1;
599 double dy = y2 - y1;
600 double dl = sqrt( dx * dx + dy * dy );
601
602 data.time += static_cast<float>( dl / Vu ) ; //adimensional time step : this the time needed to go to the border of the pixel
603 if ( data.time > 10000 ) //Guard to prevent that the particle never leave the pixel
604 {
605 addPixelToChunkTrace( currentPixel, data, chunkTrace );
606 setChunkTrace( chunkTrace );
607 break;
608 }
609 x1 = x2;
610 y1 = y2;
611 }
612
613 //test if the new current pixel is already defined, if yes no need to continue
614 if ( isTraceExists( currentPixel ) )
615 {
616 //Set the pixel in the chunk before adding the current pixel because this pixel is already defined
617 setChunkTrace( chunkTrace );
618 addPixelToChunkTrace( currentPixel, data, chunkTrace );
619 break;
620 }
621
622 if ( isTraceOutside( currentPixel ) )
623 {
624 setChunkTrace( chunkTrace );
625 break;
626 }
627
628 if ( mRenderContext.feedback() && mRenderContext.feedback()->isCanceled() )
629 break;
630
631 if ( mRenderContext.renderingStopped() )
632 break;
633 }
634
635 drawTrace( startPixel );
636}
637
638void QgsMeshStreamField::setResolution( int width )
639{
640 mFieldResolution = width;
641}
642
643QSize QgsMeshStreamField::imageSize() const
644{
645 return mFieldSize * mFieldResolution;
646}
647
648QPointF QgsMeshStreamField::fieldToDevice( const QPoint &pixel ) const
649{
650 QPointF p( pixel );
651 p = mFieldResolution * p + QPointF( mFieldResolution - 1, mFieldResolution - 1 ) / 2;
652 return p;
653}
654
655bool QgsMeshStreamField::addPixelToChunkTrace( QPoint &pixel,
656 QgsMeshStreamField::FieldData &data,
657 std::list<QPair<QPoint, QgsMeshStreamField::FieldData> > &chunkTrace )
658{
659 chunkTrace.emplace_back( pixel, data );
660 if ( chunkTrace.size() == 3 )
661 {
662 simplifyChunkTrace( chunkTrace );
663 return true;
664 }
665 return false;
666}
667
668void QgsMeshStreamlinesField::initField()
669{
670 mField = QVector<bool>( mFieldSize.width() * mFieldSize.height(), false );
671 mDirectionField = QVector<unsigned char>( mFieldSize.width() * mFieldSize.height(), static_cast<unsigned char>( int( 0 ) ) );
672 initImage();
673}
674
675void QgsMeshStreamlinesField::initImage()
676{
677 mTraceImage = QImage();
678 switch ( mVectorColoring.coloringMethod() )
679 {
681 {
682 QSize imgSize = mFieldSize * mFieldResolution;
683 QgsRenderContext fieldContext = mRenderContext;
684
685 fieldContext.setMapToPixel( mMapToFieldPixel );
686 std::unique_ptr<QgsMeshLayerInterpolator> mScalarInterpolator(
687 new QgsMeshLayerInterpolator(
688 mTriangularMesh,
689 mMagValues,
690 mScalarActiveFaceFlagValues,
691 mDataType,
692 fieldContext,
693 imgSize ) );
694
696 sh->setRasterShaderFunction( new QgsColorRampShader( mVectorColoring.colorRampShader() ) ); // takes ownership of fcn
697 QgsSingleBandPseudoColorRenderer renderer( mScalarInterpolator.get(), 0, sh ); // takes ownership of sh
698 if ( imgSize.isValid() )
699 {
700 std::unique_ptr<QgsRasterBlock> bl( renderer.block( 0, mOutputExtent, imgSize.width(), imgSize.height(), mFeedBack ) );
701 mTraceImage = bl->image();
702 }
703 }
704 break;
706 {
707 mTraceImage = QImage( mFieldSize * mFieldResolution, QImage::Format_ARGB32_Premultiplied );
708 QColor col = mVectorColoring.singleColor();
709 mTraceImage.fill( col );
710 }
711 break;
712 }
713
714 if ( !mTraceImage.isNull() )
715 {
716 mPainter.reset( new QPainter( &mTraceImage ) );
717 mPainter->setRenderHint( QPainter::Antialiasing, true );
718
719 mDrawingTraceImage = QImage( mTraceImage.size(), QImage::Format_ARGB32_Premultiplied );
720 mDrawingTraceImage.fill( Qt::transparent );
721 mDrawingTracePainter.reset( new QPainter( &mDrawingTraceImage ) );
722 mDrawingTracePainter->setRenderHint( QPainter::Antialiasing, true );
723 }
724}
725
726void QgsMeshStreamField::clearChunkTrace( std::list<QPair<QPoint, QgsMeshStreamField::FieldData> > &chunkTrace )
727{
728 auto one_before_end = std::prev( chunkTrace.end() );
729 chunkTrace.erase( chunkTrace.begin(), one_before_end );
730}
731
732void QgsMeshStreamField::simplifyChunkTrace( std::list<QPair<QPoint, FieldData> > &chunkTrace )
733{
734 if ( chunkTrace.size() != 3 )
735 return;
736
737 auto ip3 = chunkTrace.begin();
738 auto ip1 = ip3++;
739 auto ip2 = ip3++;
740
741 while ( ip3 != chunkTrace.end() && ip2 != chunkTrace.end() )
742 {
743 QPoint v1 = ( *ip1 ).first - ( *ip2 ).first;
744 QPoint v2 = ( *ip2 ).first - ( *ip3 ).first;
745 if ( v1.x()*v2.x() + v1.y()*v2.y() == 0 )
746 {
747 ( *ip1 ).second.time += ( ( *ip2 ).second.time ) / 2;
748 ( *ip3 ).second.time += ( ( *ip2 ).second.time ) / 2;
749 ( *ip1 ).second.directionX += ( *ip2 ).second.directionX;
750 ( *ip1 ).second.directionY += ( *ip2 ).second.directionY;
751 chunkTrace.erase( ip2 );
752 }
753 ip1 = ip3++;
754 ip2 = ip3++;
755 }
756}
757
758QgsMeshStreamlinesField::QgsMeshStreamlinesField( const QgsTriangularMesh &triangularMesh,
759 const QgsMeshDataBlock &datasetVectorValues,
760 const QgsMeshDataBlock &scalarActiveFaceFlagValues,
761 const QgsRectangle &layerExtent,
762 double magMax,
763 bool dataIsOnVertices,
764 QgsRenderContext &rendererContext,
765 const QgsInterpolatedLineColor &vectorColoring )
766 : QgsMeshStreamField(
767 triangularMesh,
768 datasetVectorValues,
769 scalarActiveFaceFlagValues,
770 layerExtent,
771 magMax,
772 dataIsOnVertices,
773 rendererContext,
774 vectorColoring )
775 , mMagValues( QgsMeshLayerUtils::calculateMagnitudes( datasetVectorValues ) )
776{
777}
778
779QgsMeshStreamlinesField::QgsMeshStreamlinesField(
780 const QgsTriangularMesh &triangularMesh,
781 const QgsMeshDataBlock &datasetVectorValues,
782 const QgsMeshDataBlock &scalarActiveFaceFlagValues,
783 const QVector<double> &datasetMagValues,
784 const QgsRectangle &layerExtent,
785 QgsMeshLayerRendererFeedback *feedBack,
786 double magMax,
787 bool dataIsOnVertices,
788 QgsRenderContext &rendererContext,
789 const QgsInterpolatedLineColor &vectorColoring )
790 : QgsMeshStreamField(
791 triangularMesh,
792 datasetVectorValues,
793 scalarActiveFaceFlagValues,
794 layerExtent,
795 magMax,
796 dataIsOnVertices,
797 rendererContext,
798 vectorColoring )
799 , mTriangularMesh( triangularMesh )
800 , mMagValues( datasetMagValues )
801 , mScalarActiveFaceFlagValues( scalarActiveFaceFlagValues )
802 , mDataType( dataIsOnVertices ? QgsMeshDatasetGroupMetadata::DataOnVertices : QgsMeshDatasetGroupMetadata::DataOnFaces )
803 , mFeedBack( feedBack )
804{
805}
806
807void QgsMeshStreamlinesField::compose()
808{
809 if ( !mPainter )
810 return;
811 mPainter->setCompositionMode( QPainter::CompositionMode_DestinationIn );
812 mPainter->drawImage( 0, 0, mDrawingTraceImage );
813}
814
815void QgsMeshStreamlinesField::storeInField( const QPair<QPoint, FieldData> pixelData )
816{
817 int i = pixelData.first.x();
818 int j = pixelData.first.y();
819 if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
820 {
821 mField[j * mFieldSize.width() + i] = true;
822 int d = pixelData.second.directionX + 2 + ( pixelData.second.directionY + 1 ) * 3;
823 mDirectionField[j * mFieldSize.width() + i] = static_cast<unsigned char>( d );
824 }
825}
826
827void QgsMeshStreamField::setChunkTrace( std::list<QPair<QPoint, FieldData> > &chunkTrace )
828{
829 auto p = chunkTrace.begin();
830 while ( p != chunkTrace.end() )
831 {
832 storeInField( ( *p ) );
833 mPixelFillingCount++;
834 ++p;
835 }
836}
837
838void QgsMeshStreamlinesField::drawTrace( const QPoint &start ) const
839{
840 if ( !isTraceExists( start ) || isTraceOutside( start ) )
841 return;
842
843 if ( !mDrawingTracePainter )
844 return;
845
846 QPoint pt1 = start;
847 QPoint curPt = pt1;
848 int fieldWidth = mFieldSize.width();
849 QSet<QgsPointXY> path;
850 unsigned char dir = 0;
851 unsigned char prevDir = mDirectionField.at( pt1.y() * fieldWidth + pt1.x() );
852
853 QVector<double> xPoly;
854 QVector<double> yPoly;
855 QPointF devicePt = fieldToDevice( pt1 );
856 xPoly.append( devicePt.x() );
857 yPoly.append( devicePt.y() );
858
859 while ( isTraceExists( curPt ) && !isTraceOutside( curPt ) && !path.contains( curPt ) )
860 {
861 dir = mDirectionField.at( curPt.y() * fieldWidth + curPt.x() );
862 if ( dir == 5 ) //no direction, static pixel
863 break;
864
865 const QPoint curPtDir( ( dir - 1 ) % 3 - 1, ( dir - 1 ) / 3 - 1 );
866 const QPoint pt2 = curPt + curPtDir;
867
868 if ( dir != prevDir )
869 {
870 path.insert( curPt );
871 devicePt = fieldToDevice( curPt );
872 xPoly.append( devicePt.x() );
873 yPoly.append( devicePt.y() );
874 prevDir = dir;
875 }
876 curPt = pt2;
877 }
878
879 if ( ! isTraceExists( curPt ) || isTraceOutside( curPt ) )
880 {
881 // just add the last point
882 devicePt = fieldToDevice( curPt - QPoint( ( dir - 1 ) % 3 - 1, ( dir - 1 ) / 3 - 1 ) );
883 xPoly.append( devicePt.x() );
884 yPoly.append( devicePt.y() );
885 }
886
887 QgsGeometry geom( new QgsLineString( xPoly, yPoly ) );
888 geom = geom.simplify( 1.5 * mFieldResolution ).smooth( 1, 0.25, -1.0, 45 );
889 QPen pen = mPen;
890 pen.setColor( QColor( 0, 0, 0, 255 ) );
891 mDrawingTracePainter->setPen( pen );
892 mDrawingTracePainter->drawPolyline( geom.asQPolygonF() );
893}
894
895bool QgsMeshStreamlinesField::isTraceExists( const QPoint &pixel ) const
896{
897 int i = pixel.x();
898 int j = pixel.y();
899 if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
900 {
901 return mField[j * mFieldSize.width() + i];
902 }
903
904 return false;
905}
906
907bool QgsMeshStreamField::isTraceOutside( const QPoint &pixel ) const
908{
909 int i = pixel.x();
910 int j = pixel.y();
911
912 return !( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() );
913}
914
915void QgsMeshStreamField::setMinimizeFieldSize( bool minimizeFieldSize )
916{
917 mMinimizeFieldSize = minimizeFieldSize;
918}
919
920QgsMeshStreamField &QgsMeshStreamField::operator=( const QgsMeshStreamField &other )
921{
922 mFieldSize = other.mFieldSize ;
923 mFieldResolution = other.mFieldResolution;
924 mPen = other.mPen;
925 mTraceImage = other.mTraceImage ;
926 mMapToFieldPixel = other.mMapToFieldPixel ;
927 mOutputExtent = other.mOutputExtent;
928 mVectorColoring = other.mVectorColoring;
929 mDirectionField = other.mDirectionField;
930 mRenderContext = other.mRenderContext;
931 mPixelFillingCount = other.mPixelFillingCount ;
932 mMaxPixelFillingCount = other.mMaxPixelFillingCount ;
933 mLayerExtent = other.mLayerExtent ;
934 mMapExtent = other.mMapExtent;
935 mFieldTopLeftInDeviceCoordinates = other.mFieldTopLeftInDeviceCoordinates ;
936 mValid = other.mValid ;
937 mMaximumMagnitude = other.mMaximumMagnitude ;
938 mPixelFillingDensity = other.mPixelFillingDensity ;
939 mMinMagFilter = other.mMinMagFilter ;
940 mMaxMagFilter = other.mMaxMagFilter ;
941 mMinimizeFieldSize = other.mMinimizeFieldSize ;
942 mVectorValueInterpolator =
943 std::unique_ptr<QgsMeshVectorValueInterpolator>( other.mVectorValueInterpolator->clone() );
944
945 mPainter.reset( new QPainter( &mTraceImage ) );
946
947 return ( *this );
948}
949
950void QgsMeshStreamField::initImage()
951{
952 mTraceImage = QImage( mFieldSize * mFieldResolution, QImage::Format_ARGB32 );
953 if ( !mTraceImage.isNull() )
954 {
955 mTraceImage.fill( 0X00000000 );
956 mPainter.reset( new QPainter( &mTraceImage ) );
957 mPainter->setRenderHint( QPainter::Antialiasing, true );
958 mPainter->setPen( mPen );
959 }
960}
961
962bool QgsMeshStreamField::filterMag( double value ) const
963{
964 return ( mMinMagFilter < 0 || value > mMinMagFilter ) && ( mMaxMagFilter < 0 || value < mMaxMagFilter );
965}
966
967QImage QgsMeshStreamField::image() const
968{
969 if ( mTraceImage.isNull() )
970 return QImage();
971 return mTraceImage.scaled( mFieldSize * mFieldResolution, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
972}
973
974void QgsMeshStreamField::setPixelFillingDensity( double maxFilling )
975{
976 mPixelFillingDensity = maxFilling;
977 mMaxPixelFillingCount = int( mPixelFillingDensity * mFieldSize.width() * mFieldSize.height() );
978}
979
980void QgsMeshStreamField::setColor( QColor color )
981{
982 mPen.setColor( color );
983}
984
985void QgsMeshStreamField::setLineWidth( double width )
986{
987 mPen.setWidthF( width );
988}
989
990void QgsMeshStreamField::setFilter( double min, double max )
991{
992 mMinMagFilter = min;
993 mMaxMagFilter = max;
994}
995
996QgsMeshVectorValueInterpolatorFromFace::QgsMeshVectorValueInterpolatorFromFace( const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &datasetVectorValues ):
997 QgsMeshVectorValueInterpolator( triangularMesh, datasetVectorValues )
998{}
999
1000QgsMeshVectorValueInterpolatorFromFace::QgsMeshVectorValueInterpolatorFromFace( const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &datasetVectorValues, const QgsMeshDataBlock &scalarActiveFaceFlagValues ):
1001 QgsMeshVectorValueInterpolator( triangularMesh, datasetVectorValues, scalarActiveFaceFlagValues )
1002{}
1003
1004QgsMeshVectorValueInterpolatorFromFace::QgsMeshVectorValueInterpolatorFromFace( const QgsMeshVectorValueInterpolatorFromFace &other ):
1005 QgsMeshVectorValueInterpolator( other )
1006{}
1007
1008QgsMeshVectorValueInterpolatorFromFace *QgsMeshVectorValueInterpolatorFromFace::clone()
1009{
1010 return new QgsMeshVectorValueInterpolatorFromFace( *this );
1011}
1012
1013QgsMeshVectorValueInterpolatorFromFace &QgsMeshVectorValueInterpolatorFromFace::operator=( const QgsMeshVectorValueInterpolatorFromFace &other )
1014{
1015 QgsMeshVectorValueInterpolator::operator=( other );
1016 return ( *this );
1017}
1018
1019QgsVector QgsMeshVectorValueInterpolatorFromFace::interpolatedValuePrivate( int faceIndex, const QgsPointXY point ) const
1020{
1021 QgsMeshFace face = mTriangularMesh.triangles().at( faceIndex );
1022
1023 QgsPoint p1 = mTriangularMesh.vertices().at( face.at( 0 ) );
1024 QgsPoint p2 = mTriangularMesh.vertices().at( face.at( 1 ) );
1025 QgsPoint p3 = mTriangularMesh.vertices().at( face.at( 2 ) );
1026
1027 QgsVector vect = QgsVector( mDatasetValues.value( mTriangularMesh.trianglesToNativeFaces().at( faceIndex ) ).x(),
1028 mDatasetValues.value( mTriangularMesh.trianglesToNativeFaces().at( faceIndex ) ).y() );
1029
1030 return QgsMeshLayerUtils::interpolateVectorFromFacesData(
1031 p1,
1032 p2,
1033 p3,
1034 vect,
1035 point );
1036}
1037
1038QgsMeshVectorStreamlineRenderer::QgsMeshVectorStreamlineRenderer(
1039 const QgsTriangularMesh &triangularMesh,
1040 const QgsMeshDataBlock &dataSetVectorValues,
1041 const QgsMeshDataBlock &scalarActiveFaceFlagValues,
1042 bool dataIsOnVertices,
1043 const QgsMeshRendererVectorSettings &settings,
1044 QgsRenderContext &rendererContext,
1045 const QgsRectangle &layerExtent, double magMax ):
1046 QgsMeshVectorStreamlineRenderer(
1047 triangularMesh,
1048 dataSetVectorValues,
1049 scalarActiveFaceFlagValues,
1050 QgsMeshLayerUtils::calculateMagnitudes( dataSetVectorValues ),
1051 dataIsOnVertices,
1052 settings, rendererContext,
1053 layerExtent,
1054 nullptr,
1055 magMax )
1056{}
1057
1058QgsMeshVectorStreamlineRenderer::QgsMeshVectorStreamlineRenderer( const QgsTriangularMesh &triangularMesh,
1059 const QgsMeshDataBlock &dataSetVectorValues,
1060 const QgsMeshDataBlock &scalarActiveFaceFlagValues,
1061 const QVector<double> &datasetMagValues,
1062 bool dataIsOnVertices,
1063 const QgsMeshRendererVectorSettings &settings,
1064 QgsRenderContext &rendererContext,
1065 const QgsRectangle &layerExtent, QgsMeshLayerRendererFeedback *feedBack,
1066 double magMax ):
1067 mRendererContext( rendererContext )
1068{
1069 mStreamlineField.reset(
1070 new QgsMeshStreamlinesField(
1071 triangularMesh,
1072 dataSetVectorValues,
1073 scalarActiveFaceFlagValues,
1074 datasetMagValues,
1075 layerExtent,
1076 feedBack,
1077 magMax,
1078 dataIsOnVertices,
1079 rendererContext,
1080 settings.vectorStrokeColoring() ) );
1081
1082 mStreamlineField->updateSize( rendererContext );
1083 mStreamlineField->setPixelFillingDensity( settings.streamLinesSettings().seedingDensity() );
1084 mStreamlineField->setLineWidth( rendererContext.convertToPainterUnits( settings.lineWidth(),
1086 mStreamlineField->setColor( settings.color() );
1087
1088 mStreamlineField->setFilter( settings.filterMin(), settings.filterMax() );
1089
1090 switch ( settings.streamLinesSettings().seedingMethod() )
1091 {
1093 if ( settings.isOnUserDefinedGrid() )
1094 mStreamlineField->addGriddedTraces( settings.userGridCellWidth(), settings.userGridCellHeight() );
1095 else
1096 mStreamlineField->addTracesOnMesh( triangularMesh, rendererContext.mapExtent() );
1097 break;
1099 mStreamlineField->addRandomTraces();
1100 break;
1101 }
1102}
1103
1104void QgsMeshVectorStreamlineRenderer::draw()
1105{
1106 if ( mRendererContext.renderingStopped() )
1107 return;
1108 mStreamlineField->compose();
1109 mRendererContext.painter()->drawImage( mStreamlineField->topLeft(), mStreamlineField->image() );
1110}
1111
1112QgsMeshParticleTracesField::QgsMeshParticleTracesField( const QgsTriangularMesh &triangularMesh,
1113 const QgsMeshDataBlock &datasetVectorValues,
1114 const QgsMeshDataBlock &scalarActiveFaceFlagValues,
1115 const QgsRectangle &layerExtent,
1116 double magMax,
1117 bool dataIsOnVertices,
1118 const QgsRenderContext &rendererContext,
1119 const QgsInterpolatedLineColor &vectorColoring ):
1120 QgsMeshStreamField( triangularMesh,
1121 datasetVectorValues,
1122 scalarActiveFaceFlagValues,
1123 layerExtent,
1124 magMax,
1125 dataIsOnVertices,
1126 rendererContext,
1127 vectorColoring )
1128{
1129 std::srand( uint( ::time( nullptr ) ) );
1130 mPen.setCapStyle( Qt::RoundCap );
1131}
1132
1133QgsMeshParticleTracesField::QgsMeshParticleTracesField( const QgsMeshParticleTracesField &other )
1134 : QgsMeshStreamField( other )
1135 , mTimeField( other.mTimeField )
1136 , mMagnitudeField( other.mMagnitudeField )
1137 , mParticles( other.mParticles )
1138 , mStumpImage( other.mStumpImage )
1139 , mTimeStep( other.mTimeStep )
1140 , mParticlesLifeTime( other.mParticlesLifeTime )
1141 , mParticlesCount( other.mParticlesCount )
1142 , mTailFactor( other.mTailFactor )
1143 , mMinTailLength( other.mMinTailLength )
1144 , mParticleColor( other.mParticleColor )
1145 , mParticleSize( other.mParticleSize )
1146 , mStumpFactor( other.mStumpFactor )
1147 , mStumpParticleWithLifeTime( other.mStumpParticleWithLifeTime )
1148{}
1149
1150void QgsMeshParticleTracesField::addParticle( const QPoint &startPoint, double lifeTime )
1151{
1152 addTrace( startPoint );
1153 if ( time( startPoint ) > 0 )
1154 {
1155 QgsMeshTraceParticle p;
1156 p.lifeTime = lifeTime;
1157 p.position = startPoint;
1158 mParticles.append( p );
1159 }
1160
1161}
1162
1163void QgsMeshParticleTracesField::addParticleXY( const QgsPointXY &startPoint, double lifeTime )
1164{
1165 addParticle( mMapToFieldPixel.transform( startPoint ).toQPointF().toPoint(), lifeTime );
1166}
1167
1168void QgsMeshParticleTracesField::moveParticles()
1169{
1170 stump();
1171 for ( auto &p : mParticles )
1172 {
1173 double spentTime = p.remainingTime; //adjust with the past remaining time
1174 size_t countAdded = 0;
1175 while ( spentTime < mTimeStep && p.lifeTime > 0 )
1176 {
1177 double timeToSpend = double( time( p.position ) );
1178 if ( timeToSpend > 0 )
1179 {
1180 p.lifeTime -= timeToSpend;
1181 spentTime += timeToSpend;
1182 QPoint dir = direction( p.position );
1183 if ( p.lifeTime > 0 )
1184 {
1185 p.position += dir;
1186 p.tail.emplace_back( p.position );
1187 countAdded++;
1188 }
1189 else
1190 {
1191 break;
1192 }
1193 }
1194 else
1195 {
1196 p.lifeTime = -1;
1197 break;
1198 }
1199 }
1200
1201 if ( p.lifeTime <= 0 )
1202 {
1203 // the particle is not alive anymore
1204 p.lifeTime = 0;
1205 p.tail.clear();
1206 }
1207 else
1208 {
1209 p.remainingTime = spentTime - mTimeStep;
1210 while ( static_cast<int>( p.tail.size() ) > mMinTailLength &&
1211 static_cast<double>( p.tail.size() ) > ( static_cast<double>( countAdded ) * mTailFactor ) )
1212 p.tail.erase( p.tail.begin() );
1213 drawParticleTrace( p );
1214 }
1215 }
1216
1217 //remove empty (dead particles)
1218 int i = 0;
1219 while ( i < mParticles.count() )
1220 {
1221 if ( mParticles.at( i ).tail.size() == 0 )
1222 mParticles.removeAt( i );
1223 else
1224 ++i;
1225 }
1226
1227 //add new particles if needed
1228 if ( mParticles.count() < mParticlesCount )
1229 addRandomParticles();
1230}
1231
1232void QgsMeshParticleTracesField::addRandomParticles()
1233{
1234 if ( !isValid() )
1235 return;
1236
1237 if ( mParticlesCount < 0 ) //for tests, add one particle on the center of the map
1238 {
1239 addParticleXY( QgsPointXY( mMapToFieldPixel.xCenter(), mMapToFieldPixel.yCenter() ), mParticlesLifeTime );
1240 return;
1241 }
1242
1243 int count = mParticlesCount - mParticles.count();
1244
1245 for ( int i = 0; i < count; ++i )
1246 {
1247 int xRandom = 1 + std::rand() / int( ( RAND_MAX + 1u ) / uint( mFieldSize.width() ) ) ;
1248 int yRandom = 1 + std::rand() / int ( ( RAND_MAX + 1u ) / uint( mFieldSize.height() ) ) ;
1249 double lifeTime = ( std::rand() / ( ( RAND_MAX + 1u ) / mParticlesLifeTime ) );
1250 addParticle( QPoint( xRandom, yRandom ), lifeTime );
1251 }
1252}
1253
1254void QgsMeshParticleTracesField::storeInField( const QPair<QPoint, QgsMeshStreamField::FieldData> pixelData )
1255{
1256 int i = pixelData.first.x();
1257 int j = pixelData.first.y();
1258 if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
1259 {
1260 mTimeField[j * mFieldSize.width() + i] = pixelData.second.time;
1261 int d = pixelData.second.directionX + 2 + ( pixelData.second.directionY + 1 ) * 3;
1262 mDirectionField[j * mFieldSize.width() + i] = static_cast<unsigned char>( d );
1263 mMagnitudeField[j * mFieldSize.width() + i] = static_cast<float>( pixelData.second.magnitude );
1264 }
1265}
1266
1267void QgsMeshParticleTracesField::initField()
1268{
1269 mTimeField = QVector<float>( mFieldSize.width() * mFieldSize.height(), -1 );
1270 mDirectionField = QVector<unsigned char>( mFieldSize.width() * mFieldSize.height(), static_cast<unsigned char>( int( 0 ) ) );
1271 mMagnitudeField = QVector<float>( mFieldSize.width() * mFieldSize.height(), 0 );
1272 initImage();
1273 mStumpImage = QImage( mFieldSize * mFieldResolution, QImage::Format_ARGB32 );
1274 mStumpImage.fill( QColor( 0, 0, 0, mStumpFactor ) ); //alpha=0 -> no persitence, alpha=255 -> total persistence
1275}
1276
1277bool QgsMeshParticleTracesField::isTraceExists( const QPoint &pixel ) const
1278{
1279 int i = pixel.x();
1280 int j = pixel.y();
1281 if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
1282 {
1283 return mTimeField[j * mFieldSize.width() + i] >= 0;
1284 }
1285
1286 return false;
1287}
1288
1289void QgsMeshParticleTracesField::setStumpParticleWithLifeTime( bool stumpParticleWithLifeTime )
1290{
1291 mStumpParticleWithLifeTime = stumpParticleWithLifeTime;
1292}
1293
1294void QgsMeshParticleTracesField::setParticlesColor( const QColor &c )
1295{
1296 mVectorColoring.setColor( c );
1297}
1298
1299QgsMeshParticleTracesField &QgsMeshParticleTracesField::operator=( const QgsMeshParticleTracesField &other )
1300{
1301 QgsMeshStreamField::operator=( other );
1302 mTimeField = other.mTimeField;
1303 mMagnitudeField = other.mMagnitudeField;
1304 mDirectionField = other.mDirectionField;
1305 mParticles = other.mParticles;
1306 mStumpImage = other.mStumpImage;
1307 mTimeStep = other.mTimeStep;
1308 mParticlesLifeTime = other.mParticlesLifeTime;
1309 mParticlesCount = other.mParticlesCount;
1310 mMinTailLength = other.mMinTailLength;
1311 mTailFactor = other.mTailFactor;
1312 mParticleColor = other.mParticleColor;
1313 mParticleSize = other.mParticleSize;
1314 mStumpFactor = other.mStumpFactor;
1315 mStumpParticleWithLifeTime = other.mStumpParticleWithLifeTime;
1316
1317 return ( *this );
1318}
1319
1320void QgsMeshParticleTracesField::setMinTailLength( int minTailLength )
1321{
1322 mMinTailLength = minTailLength;
1323}
1324
1325void QgsMeshParticleTracesField::setTailFactor( double tailFactor )
1326{
1327 mTailFactor = tailFactor;
1328}
1329
1330void QgsMeshParticleTracesField::setParticleSize( double particleSize )
1331{
1332 mParticleSize = particleSize;
1333}
1334
1335void QgsMeshParticleTracesField::setTimeStep( double timeStep )
1336{
1337 mTimeStep = timeStep;
1338}
1339
1340void QgsMeshParticleTracesField::setParticlesLifeTime( double particlesLifeTime )
1341{
1342 mParticlesLifeTime = particlesLifeTime;
1343}
1344
1345QImage QgsMeshParticleTracesField::imageRendered() const
1346{
1347 return mTraceImage;
1348}
1349
1350void QgsMeshParticleTracesField::stump()
1351{
1352 if ( !mPainter )
1353 return;
1354 QgsScopedQPainterState painterState( mPainter.get() );
1355 mPainter->setCompositionMode( QPainter::CompositionMode_DestinationIn );
1356 mPainter->drawImage( QPoint( 0, 0 ), mStumpImage );
1357}
1358
1359void QgsMeshParticleTracesField::setStumpFactor( int sf )
1360{
1361 mStumpFactor = sf;
1362 mStumpImage = QImage( mFieldSize * mFieldResolution, QImage::Format_ARGB32 );
1363 mStumpImage.fill( QColor( 0, 0, 0, mStumpFactor ) );
1364}
1365
1366QPoint QgsMeshParticleTracesField::direction( QPoint position ) const
1367{
1368 int i = position.x();
1369 int j = position.y();
1370 if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
1371 {
1372 int dir = static_cast<int>( mDirectionField[j * mFieldSize.width() + i] );
1373 if ( dir != 0 && dir < 10 )
1374 return QPoint( ( dir - 1 ) % 3 - 1, ( dir - 1 ) / 3 - 1 );
1375 }
1376 return QPoint( 0, 0 );
1377}
1378
1379float QgsMeshParticleTracesField::time( QPoint position ) const
1380{
1381 int i = position.x();
1382 int j = position.y();
1383 if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
1384 {
1385 return mTimeField[j * mFieldSize.width() + i];
1386 }
1387 return -1;
1388}
1389
1390float QgsMeshParticleTracesField::magnitude( QPoint position ) const
1391{
1392 int i = position.x();
1393 int j = position.y();
1394 if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
1395 {
1396 return mMagnitudeField[j * mFieldSize.width() + i];
1397 }
1398 return -1;
1399}
1400
1401void QgsMeshParticleTracesField::drawParticleTrace( const QgsMeshTraceParticle &particle )
1402{
1403 if ( !mPainter )
1404 return;
1405 const std::list<QPoint> &tail = particle.tail;
1406 if ( tail.size() == 0 )
1407 return;
1408 double iniWidth = mParticleSize;
1409
1410 size_t pixelCount = tail.size();
1411
1412 double transparency = 1;
1413 if ( mStumpParticleWithLifeTime )
1414 transparency = sin( M_PI * particle.lifeTime / mParticlesLifeTime );
1415
1416 double dw;
1417 if ( pixelCount > 1 )
1418 dw = iniWidth / static_cast<double>( pixelCount );
1419 else
1420 dw = 0;
1421
1422 auto ip1 = std::prev( tail.end() );
1423 auto ip2 = std::prev( ip1 );
1424 int i = 0;
1425 while ( ip1 != tail.begin() )
1426 {
1427 QPointF p1 = fieldToDevice( ( *ip1 ) );
1428 QPointF p2 = fieldToDevice( ( *ip2 ) );
1429 QColor traceColor = mVectorColoring.color( magnitude( *ip1 ) );
1430 traceColor.setAlphaF( traceColor.alphaF()*transparency );
1431 mPen.setColor( traceColor );
1432 mPen.setWidthF( iniWidth - i * dw );
1433 mPainter->setPen( mPen );
1434 mPainter->drawLine( p1, p2 );
1435 ip1--;
1436 ip2--;
1437 ++i;
1438 }
1439}
1440
1441void QgsMeshParticleTracesField::setParticlesCount( int particlesCount )
1442{
1443 mParticlesCount = particlesCount;
1444}
1445
1447 const QgsMeshDataBlock &dataSetVectorValues,
1448 const QgsMeshDataBlock &scalarActiveFaceFlagValues,
1449 bool dataIsOnVertices,
1450 const QgsRenderContext &rendererContext,
1451 const QgsRectangle &layerExtent,
1452 double magMax,
1453 const QgsMeshRendererVectorSettings &vectorSettings )
1454 : mParticleField( new QgsMeshParticleTracesField(
1455 triangularMesh,
1456 dataSetVectorValues,
1457 scalarActiveFaceFlagValues,
1458 layerExtent,
1459 magMax,
1460 dataIsOnVertices,
1461 rendererContext,
1462 vectorSettings.vectorStrokeColoring() ) )
1463 , mRendererContext( rendererContext )
1464{
1465 mParticleField->updateSize( rendererContext ) ;
1466}
1467
1469 mRendererContext( rendererContext )
1470{
1471 if ( !layer->triangularMesh() )
1472 layer->reload();
1473
1474 QgsMeshDataBlock vectorDatasetValues;
1475 QgsMeshDataBlock scalarActiveFaceFlagValues;
1476 bool vectorDataOnVertices;
1477 double magMax;
1478
1479 QgsMeshDatasetIndex datasetIndex = layer->activeVectorDatasetAtTime( rendererContext.temporalRange() );
1480
1481 // Find out if we can use cache up to date. If yes, use it and return
1482 int datasetGroupCount = layer->dataProvider()->datasetGroupCount();
1483 const QgsMeshRendererVectorSettings vectorSettings = layer->rendererSettings().vectorSettings( datasetIndex.group() );
1484 QgsMeshLayerRendererCache *cache = layer->rendererCache();
1485
1486 if ( ( cache->mDatasetGroupsCount == datasetGroupCount ) &&
1487 ( cache->mActiveVectorDatasetIndex == datasetIndex ) )
1488 {
1489 vectorDatasetValues = cache->mVectorDatasetValues;
1490 scalarActiveFaceFlagValues = cache->mScalarActiveFaceFlagValues;
1491 magMax = cache->mVectorDatasetMagMaximum;
1492 vectorDataOnVertices = cache->mVectorDataType == QgsMeshDatasetGroupMetadata::DataOnVertices;
1493 }
1494 else
1495 {
1496 const QgsMeshDatasetGroupMetadata metadata =
1497 layer->dataProvider()->datasetGroupMetadata( datasetIndex.group() );
1498 magMax = metadata.maximum();
1499 vectorDataOnVertices = metadata.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices;
1500
1501 int count;
1502 if ( vectorDataOnVertices )
1503 count = layer->nativeMesh()->vertices.count();
1504 else
1505 count = layer->nativeMesh()->faces.count();
1506
1507 vectorDatasetValues = QgsMeshLayerUtils::datasetValues( layer, datasetIndex, 0, count );
1508
1509 scalarActiveFaceFlagValues = layer->dataProvider()->areFacesActive(
1510 datasetIndex,
1511 0,
1512 layer->nativeMesh()->faces.count() );
1513 }
1514
1515 mParticleField = std::unique_ptr<QgsMeshParticleTracesField>( new QgsMeshParticleTracesField( ( *layer->triangularMesh() ),
1516 vectorDatasetValues,
1517 scalarActiveFaceFlagValues,
1518 layer->extent(),
1519 magMax,
1520 vectorDataOnVertices,
1521 rendererContext,
1522 vectorSettings.vectorStrokeColoring() ) ) ;
1523
1524 mParticleField->setMinimizeFieldSize( false );
1525 mParticleField->updateSize( mRendererContext );
1526}
1527
1529 : mParticleField( new QgsMeshParticleTracesField( *other.mParticleField ) )
1530 , mRendererContext( other.mRendererContext )
1531 , mFPS( other.mFPS )
1532 , mVpixMax( other.mVpixMax )
1533 , mParticleLifeTime( other.mParticleLifeTime )
1534
1535{
1536
1537}
1538
1539
1541{
1542 mParticleField->setParticlesCount( count );
1543 mParticleField->addRandomParticles();
1544}
1545
1547{
1548 mParticleField->moveParticles();
1549 return mParticleField->image();
1550}
1551
1553{
1554 if ( FPS > 0 )
1555 mFPS = FPS;
1556 else
1557 mFPS = 1;
1558
1559 updateFieldParameter();
1560}
1561
1563{
1564 mVpixMax = max;
1565 updateFieldParameter();
1566}
1567
1569{
1570 mParticleLifeTime = particleLifeTime;
1571 updateFieldParameter();
1572}
1573
1575{
1576 mParticleField->setParticlesColor( c );
1577}
1578
1580{
1581 mParticleField->setParticleSize( width );
1582}
1583
1585{
1586 mParticleField->setTailFactor( fct );
1587}
1588
1590{
1591 mParticleField->setMinTailLength( l );
1592}
1593
1595{
1596 if ( p < 0 )
1597 p = 0;
1598 if ( p > 1 )
1599 p = 1;
1600 mParticleField->setStumpFactor( int( 255 * p ) );
1601}
1602
1604{
1605 mParticleField.reset( new QgsMeshParticleTracesField( *( other.mParticleField ) ) );
1606 const_cast<QgsRenderContext &>( mRendererContext ) = other.mRendererContext;
1607 mFPS = other.mFPS;
1608 mVpixMax = other.mVpixMax;
1609 mParticleLifeTime = other.mParticleLifeTime;
1610
1611 return ( *this );
1612}
1613
1614void QgsMeshVectorTraceAnimationGenerator::updateFieldParameter()
1615{
1616 double fieldTimeStep = mVpixMax / static_cast<double>( mFPS );
1617 double fieldLifeTime = mParticleLifeTime * mFPS * fieldTimeStep;
1618 mParticleField->setTimeStep( fieldTimeStep );
1619 mParticleField->setParticlesLifeTime( fieldLifeTime );
1620}
1621
1622QgsMeshVectorTraceRenderer::QgsMeshVectorTraceRenderer(
1623 const QgsTriangularMesh &triangularMesh,
1624 const QgsMeshDataBlock &dataSetVectorValues,
1625 const QgsMeshDataBlock &scalarActiveFaceFlagValues,
1626 bool dataIsOnVertices,
1627 const QgsMeshRendererVectorSettings &settings,
1628 QgsRenderContext &rendererContext,
1629 const QgsRectangle &layerExtent,
1630 double magMax )
1631 : mParticleField( new QgsMeshParticleTracesField(
1632 triangularMesh,
1633 dataSetVectorValues,
1634 scalarActiveFaceFlagValues,
1635 layerExtent,
1636 magMax,
1637 dataIsOnVertices,
1638 rendererContext,
1639 settings.vectorStrokeColoring() ) )
1640 , mRendererContext( rendererContext )
1641{
1642
1643 mParticleField->updateSize( rendererContext ) ;
1644
1645 mParticleField->setParticleSize( rendererContext.convertToPainterUnits(
1647 mParticleField->setParticlesCount( settings.tracesSettings().particlesCount() );
1648 mParticleField->setTailFactor( 1 );
1649 mParticleField->setStumpParticleWithLifeTime( false );
1650 mParticleField->setTimeStep( rendererContext.convertToPainterUnits( settings.tracesSettings().maximumTailLength(),
1651 settings.tracesSettings().maximumTailLengthUnit() ) ); //as the particles go through 1 pix for dt=1 and Vmax
1652 mParticleField->addRandomParticles();
1653 mParticleField->moveParticles();
1654}
1655
1656void QgsMeshVectorTraceRenderer::draw()
1657{
1658 if ( mRendererContext.renderingStopped() )
1659 return;
1660 mRendererContext.painter()->drawImage( mParticleField->topLeft(), mParticleField->image() );
1661}
1662
@ Millimeters
Millimeters.
QgsVertexIterator vertices() const
Returns a read-only, Java-style iterator for traversal of vertices of all the geometry,...
A ramp shader will color a raster pixel based on a list of values ranges in a ramp.
Handles coordinate transforms between two coordinate systems.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const
Transforms a rectangle from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
A geometry is the spatial representation of a feature.
Defines color interpolation for rendering mesh datasets.
@ ColorRamp
Render with a color ramp.
@ SingleColor
Render with a single color.
Line string geometry type, with support for z-dimension and m-values.
Perform transforms between map coordinates and device coordinates.
double mapUnitsPerPixel() const
Returns the current map units per pixel.
QgsPointXY toMapCoordinates(int x, int y) const
Transforms device coordinates to map (world) coordinates.
double mapRotation() const
Returns the current map rotation in degrees (clockwise).
A block of integers/doubles from a mesh dataset.
bool isValid() const
Whether the block is valid.
A collection of dataset group metadata such as whether the data is vector or scalar,...
DataType dataType() const
Returns whether dataset group data is defined on vertices or faces or volumes.
double maximum() const
Returns maximum scalar value/vector magnitude present for whole dataset group.
@ DataOnVertices
Data is defined on vertices.
An index that identifies the dataset group (e.g.
int group() const
Returns a group index.
virtual QgsMeshDatasetGroupMetadata datasetGroupMetadata(int groupIndex) const =0
Returns dataset group metadata.
virtual QgsMeshDataBlock areFacesActive(QgsMeshDatasetIndex index, int faceIndex, int count) const =0
Returns whether the faces are active for particular dataset.
virtual int datasetGroupCount() const =0
Returns number of datasets groups loaded.
Represents a mesh layer supporting display of data on structured or unstructured meshes.
QgsRectangle extent() const override
Returns the extent of the layer.
QgsMeshRendererSettings rendererSettings() const
Returns renderer settings.
QgsMeshDatasetIndex activeVectorDatasetAtTime(const QgsDateTimeRange &timeRange, int group=-1) const
Returns dataset index from active vector group depending on the time range If the temporal properties...
void reload() override
Synchronises with changes in the datasource.
QgsMesh * nativeMesh()
Returns native mesh (nullptr before rendering or calling to updateMesh)
QgsMeshDataProvider * dataProvider() override
Returns the layer's data provider, it may be nullptr.
QgsTriangularMesh * triangularMesh(double minimumTriangleSize=0) const
Returns triangular mesh (nullptr before rendering or calling to updateMesh).
QgsMeshLayerRendererCache * rendererCache()
Returns native mesh (nullptr before rendering)
QgsMeshRendererVectorSettings vectorSettings(int groupIndex) const
Returns renderer settings.
Represents a renderer settings for vector datasets.
int userGridCellWidth() const
Returns width in pixels of user grid cell.
QgsMeshRendererVectorTracesSettings tracesSettings() const
Returns settings for vector rendered with traces.
QColor color() const
Returns color used for drawing arrows.
int userGridCellHeight() const
Returns height in pixels of user grid cell.
double lineWidth() const
Returns line width of the arrow (in millimeters)
double filterMax() const
Returns filter value for vector magnitudes.
QgsInterpolatedLineColor vectorStrokeColoring() const
Returns the stroke coloring used to render vector datasets.
bool isOnUserDefinedGrid() const
Returns whether vectors are drawn on user-defined grid.
double filterMin() const
Returns filter value for vector magnitudes.
QgsMeshRendererVectorStreamlineSettings streamLinesSettings() const
Returns settings for vector rendered with streamlines.
SeedingStartPointsMethod seedingMethod() const
Returns the method used for seeding start points of strealines.
@ Random
Seeds start points randomly on the mesh.
@ MeshGridded
Seeds start points on the vertices mesh or user regular grid.
double seedingDensity() const
Returns the density used for seeding start points.
Qgis::RenderUnit maximumTailLengthUnit() const
Returns the maximum tail length unit.
double maximumTailLength() const
Returns the maximum tail length.
int particlesCount() const
Returns particles count.
static bool isInTriangleFace(const QgsPointXY point, const QgsMeshFace &face, const QVector< QgsMeshVertex > &vertices)
Tests if point p is on the face defined with vertices.
A wrapper for QgsMeshParticuleTracesField used to render the particles.
void setParticlesLifeTime(double particleLifeTime)
Sets maximum life time of particles in seconds.
QgsMeshVectorTraceAnimationGenerator & operator=(const QgsMeshVectorTraceAnimationGenerator &other)
void setMinimumTailLength(int l)
Sets the minimum tail length.
void setTailPersitence(double p)
Sets the visual persistence of the tail.
void setParticlesColor(const QColor &c)
Sets colors of particle.
QImage imageRendered()
Moves all the particles using frame per second (fps) to calculate the displacement and return the ren...
void setTailFactor(double fct)
Sets the tail factor, used to adjust the length of the tail. 0 : minimum length, >1 increase the tail...
void setFPS(int FPS)
Sets the number of frames per seconds that will be rendered.
QgsMeshVectorTraceAnimationGenerator(const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &dataSetVectorValues, const QgsMeshDataBlock &scalarActiveFaceFlagValues, bool dataIsOnVertices, const QgsRenderContext &rendererContext, const QgsRectangle &layerExtent, double magMax, const QgsMeshRendererVectorSettings &vectorSettings)
Constructor to use from QgsMeshVectorRenderer.
void setParticlesSize(double width)
Sets particle size in px.
void setMaxSpeedPixel(int max)
Sets the max number of pixels that can be go through by the particles in 1 second.
void seedRandomParticles(int count)
seeds particles in the vector fields
Represents a 2D point.
Definition qgspointxy.h:60
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
Interface for all raster shaders.
void setRasterShaderFunction(QgsRasterShaderFunction *function)
A public method that allows the user to set their own shader function.
A rectangle specified with double values.
double xMinimum
double yMinimum
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Contains information about the context of a rendering operation.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QgsRectangle mapExtent() const
Returns the original extent of the map being rendered.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
void setMapToPixel(const QgsMapToPixel &mtp)
Sets the context's map to pixel transform, which transforms between map coordinates and device coordi...
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
Scoped object for saving and restoring a QPainter object's state.
Raster renderer pipe for single band pseudocolor.
const QgsDateTimeRange & temporalRange() const
Returns the datetime range for the object.
A triangular/derived mesh with vertices in map coordinates.
const QVector< QgsMeshFace > & triangles() const
Returns triangles.
const QVector< QgsMeshVertex > & vertices() const
Returns vertices in map coordinate system.
QList< int > faceIndexesForRectangle(const QgsRectangle &rectangle) const
Finds indexes of triangles intersecting given bounding box It uses spatial indexing.
Represent a 2-dimensional vector.
Definition qgsvector.h:30
double y() const
Returns the vector's y-component.
Definition qgsvector.h:152
QgsVector rotateBy(double rot) const
Rotates the vector by a specified angle.
Definition qgsvector.cpp:21
double x() const
Returns the vector's x-component.
Definition qgsvector.h:143
double length() const
Returns the length of the vector.
Definition qgsvector.h:124
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6286
#define M_DEG2RAD
QVector< int > QgsMeshFace
List of vertex indexes.
QVector< QgsMeshVertex > vertices
QVector< QgsMeshFace > faces