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