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