[FEATURE] API for rendering frames for mesh vector dataset animation (particles) (#33110)

* [FEATURE] API for rendering frames for mesh vector dataset animation (particles) 

Adds a renderer to generate frames that represent particle traces in a vector field of a mesh layer. The renderer cannot be chosen in the current GUI, however crayfish plugin can use API to generate avi/gif files with nice animations representing the movement of (random) particles in the mesh layer vector field.
This commit is contained in:
Vincent Cloarec 2019-11-29 04:06:47 -04:00 committed by Peter Petrik
parent a1002c4574
commit 6d4c995a28
10 changed files with 1172 additions and 117 deletions

View File

@ -342,9 +342,9 @@ Represents a streamline renderer settings for vector datasets
enum Symbology
{
//! Displying vector dataset with arrows
//! Displaying vector dataset with arrows
Arrows,
//! Displying vector dataset with streamlines
//! Displaying vector dataset with streamlines
Streamlines
};

View File

@ -0,0 +1,102 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/mesh/qgsmeshtracerenderer.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsMeshVectorTraceRenderer
{
%Docstring
A wrapper for QgsMeshParticuleTracesField used to render the particles. Available for Python binding
.. versionadded:: 3.12
%End
%TypeHeaderCode
#include "qgsmeshtracerenderer.h"
%End
public:
QgsMeshVectorTraceRenderer( QgsMeshLayer *layer, const QgsRenderContext &rendererContext );
%Docstring
Constructor to use with Python binding
%End
QgsMeshVectorTraceRenderer( const QgsMeshVectorTraceRenderer &other );
%Docstring
Copy constructor
%End
~QgsMeshVectorTraceRenderer();
void seedRandomParticles( int count );
%Docstring
seeds particles in the vector fields
%End
QImage imageRendered();
%Docstring
Moves all the particles using frame per second (fps) to calculate the displacement
%End
void setFPS( int FPS );
%Docstring
Sets the number of frames per seconds that will be rendered
%End
void setMaxSpeedPixel( int max );
%Docstring
Sets the max number of pixels that can be go through by the particles in 1 second
%End
void setParticlesLifeTime( double particleLifeTime );
%Docstring
Sets maximum life time of particles in seconds
%End
void setParticlesColor( const QColor &c );
%Docstring
Sets colors of particle
%End
void setParticlesSize( double width );
%Docstring
Sets particle size
%End
void setTailFactor( double fct );
%Docstring
Sets the tail factor, used to adjust the length of the tail. 0 : minimum length, >1 increase the tail
%End
void setMinimumTailLength( int l );
%Docstring
Sets the minimum tail length
%End
void setTailPersitence( double p );
%Docstring
Sets the visual persistence of the tail
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/mesh/qgsmeshtracerenderer.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -385,6 +385,7 @@
%Include auto_generated/mesh/qgsmeshrenderersettings.sip
%Include auto_generated/mesh/qgsmeshspatialindex.sip
%Include auto_generated/mesh/qgsmeshtimesettings.sip
%Include auto_generated/mesh/qgsmeshtracerenderer.sip
%Include auto_generated/metadata/qgsabstractmetadatabase.sip
%Include auto_generated/metadata/qgslayermetadata.sip
%Include auto_generated/metadata/qgslayermetadataformatter.sip

View File

@ -323,9 +323,9 @@ class CORE_EXPORT QgsMeshRendererVectorSettings
*/
enum Symbology
{
//! Displying vector dataset with arrows
//! Displaying vector dataset with arrows
Arrows = 0,
//! Displying vector dataset with streamlines
//! Displaying vector dataset with streamlines
Streamlines
};

View File

@ -16,7 +16,7 @@
***************************************************************************/
#include "qgsmeshtracerenderer.h"
#include "qgsmeshlayerrenderer.h"
///@cond PRIVATE
QgsVector QgsMeshVectorValueInterpolator::vectorValue( const QgsPointXY &point ) const
@ -50,18 +50,50 @@ QgsVector QgsMeshVectorValueInterpolator::vectorValue( const QgsPointXY &point )
}
QgsMeshVectorValueInterpolatorFromVertex::QgsMeshVectorValueInterpolatorFromVertex( const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &datasetVectorValues ):
QgsMeshVectorValueInterpolator &QgsMeshVectorValueInterpolator::operator=( const QgsMeshVectorValueInterpolator &other )
{
mTriangularMesh = other.mTriangularMesh;
mDatasetValues = other.mDatasetValues;
mActiveFaceFlagValues = other.mActiveFaceFlagValues;
mFaceCache = other.mFaceCache;
mCacheFaceIndex = other.mCacheFaceIndex;
mUseScalarActiveFaceFlagValues = other.mUseScalarActiveFaceFlagValues;
return *this;
}
QgsMeshVectorValueInterpolatorFromVertex::
QgsMeshVectorValueInterpolatorFromVertex( const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &datasetVectorValues ):
QgsMeshVectorValueInterpolator( triangularMesh, datasetVectorValues )
{
}
QgsMeshVectorValueInterpolatorFromVertex::QgsMeshVectorValueInterpolatorFromVertex( const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &datasetVectorValues, const QgsMeshDataBlock &scalarActiveFaceFlagValues ):
QgsMeshVectorValueInterpolatorFromVertex::
QgsMeshVectorValueInterpolatorFromVertex( const QgsTriangularMesh &triangularMesh,
const QgsMeshDataBlock &datasetVectorValues,
const QgsMeshDataBlock &scalarActiveFaceFlagValues ):
QgsMeshVectorValueInterpolator( triangularMesh, datasetVectorValues, scalarActiveFaceFlagValues )
{
}
QgsMeshVectorValueInterpolatorFromVertex::QgsMeshVectorValueInterpolatorFromVertex( const QgsMeshVectorValueInterpolatorFromVertex &other ):
QgsMeshVectorValueInterpolator( other )
{}
QgsMeshVectorValueInterpolatorFromVertex *QgsMeshVectorValueInterpolatorFromVertex::clone()
{
return new QgsMeshVectorValueInterpolatorFromVertex( *this );
}
QgsMeshVectorValueInterpolatorFromVertex &QgsMeshVectorValueInterpolatorFromVertex::
operator=( const QgsMeshVectorValueInterpolatorFromVertex &other )
{
QgsMeshVectorValueInterpolator::operator=( other );
return ( *this );
}
QgsVector QgsMeshVectorValueInterpolatorFromVertex::interpolatedValuePrivate( int faceIndex, const QgsPointXY point ) const
{
QgsMeshFace face = mTriangularMesh.triangles().at( faceIndex );
@ -105,6 +137,15 @@ QgsMeshVectorValueInterpolator::QgsMeshVectorValueInterpolator( const QgsTriangu
mUseScalarActiveFaceFlagValues( true )
{}
QgsMeshVectorValueInterpolator::QgsMeshVectorValueInterpolator( const QgsMeshVectorValueInterpolator &other ):
mTriangularMesh( other.mTriangularMesh ),
mDatasetValues( other.mDatasetValues ),
mActiveFaceFlagValues( other.mActiveFaceFlagValues ),
mFaceCache( other.mFaceCache ),
mCacheFaceIndex( other.mCacheFaceIndex ),
mUseScalarActiveFaceFlagValues( other.mUseScalarActiveFaceFlagValues )
{}
void QgsMeshVectorValueInterpolator::updateCacheFaceIndex( const QgsPointXY &point ) const
{
if ( ! QgsMeshUtils::isInTriangleFace( point, mFaceCache, mTriangularMesh.vertices() ) )
@ -148,10 +189,10 @@ QgsPointXY QgsMeshStreamField::positionToMapCoordinates( const QPoint &pixelPosi
return mapPoint;
}
QgsMeshStreamField::QgsMeshStreamField( const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &dataSetVectorValues, const QgsMeshDataBlock &scalarActiveFaceFlagValues, const QgsRectangle &layerExtent, double magMax, bool dataIsOnVertices, QgsRenderContext &rendererContext, int resolution ):
QgsMeshStreamField::QgsMeshStreamField( const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &dataSetVectorValues, const QgsMeshDataBlock &scalarActiveFaceFlagValues, const QgsRectangle &layerExtent, double magnitudeMaximum, bool dataIsOnVertices, const QgsRenderContext &rendererContext, int resolution ):
mFieldResolution( resolution ),
mLayerExtent( layerExtent ),
mMagMax( magMax ),
mMagMax( magnitudeMaximum ),
mRenderContext( rendererContext )
{
if ( dataIsOnVertices )
@ -176,6 +217,36 @@ QgsMeshStreamField::QgsMeshStreamField( const QgsTriangularMesh &triangularMesh,
}
}
QgsMeshStreamField::QgsMeshStreamField( const QgsMeshStreamField &other ):
mFieldSize( other.mFieldSize ),
mFieldResolution( other.mFieldResolution ),
mPen( other.mPen ),
mTraceImage( other.mTraceImage ),
mMapToFieldPixel( other.mMapToFieldPixel ),
mPixelFillingCount( other.mPixelFillingCount ),
mMaxPixelFillingCount( other.mMaxPixelFillingCount ),
mLayerExtent( other.mLayerExtent ),
mMapExtent( other.mMapExtent ),
mFieldTopLeftInDeviceCoordinates( other.mFieldTopLeftInDeviceCoordinates ),
mValid( other.mValid ),
mMagMax( other.mMagMax ),
mPixelFillingDensity( other.mPixelFillingDensity ),
mMinMagFilter( other.mMinMagFilter ),
mMaxMagFilter( other.mMaxMagFilter ),
mRenderContext( other.mRenderContext ),
mMinimizeFieldSize( other.mMinimizeFieldSize )
{
mPainter = new QPainter( &mTraceImage );
mVectorValueInterpolator =
std::unique_ptr<QgsMeshVectorValueInterpolator>( other.mVectorValueInterpolator->clone() );
}
QgsMeshStreamField::~QgsMeshStreamField()
{
if ( mPainter )
delete mPainter;
}
void QgsMeshStreamField::updateSize( const QgsRenderContext &renderContext )
{
mMapExtent = renderContext.mapExtent();
@ -184,7 +255,6 @@ void QgsMeshStreamField::updateSize( const QgsRenderContext &renderContext )
try
{
layerExtent = renderContext.coordinateTransform().transform( mLayerExtent );
}
catch ( QgsCsException &cse )
{
@ -192,7 +262,12 @@ void QgsMeshStreamField::updateSize( const QgsRenderContext &renderContext )
layerExtent = mLayerExtent;
}
QgsRectangle interestZoneExtent = layerExtent.intersect( mMapExtent );
QgsRectangle interestZoneExtent;
if ( mMinimizeFieldSize )
interestZoneExtent = layerExtent.intersect( mMapExtent );
else
interestZoneExtent = mMapExtent;
if ( interestZoneExtent == QgsRectangle() )
{
mValid = false;
@ -202,8 +277,12 @@ void QgsMeshStreamField::updateSize( const QgsRenderContext &renderContext )
return;
}
QgsPointXY interestZoneTopLeft = deviceMapToPixel.transform( QgsPointXY( interestZoneExtent.xMinimum(), interestZoneExtent.yMaximum() ) );
QgsPointXY interestZoneBottomRight = deviceMapToPixel.transform( QgsPointXY( interestZoneExtent.xMaximum(), interestZoneExtent.yMinimum() ) );
QgsPointXY interestZoneTopLeft;
QgsPointXY interestZoneBottomRight;
interestZoneTopLeft = deviceMapToPixel.transform( QgsPointXY( interestZoneExtent.xMinimum(), interestZoneExtent.yMaximum() ) );
interestZoneBottomRight = deviceMapToPixel.transform( QgsPointXY( interestZoneExtent.xMaximum(), interestZoneExtent.yMinimum() ) );
mFieldTopLeftInDeviceCoordinates = interestZoneTopLeft.toQPointF().toPoint();
QPoint mFieldBottomRightInDeviceCoordinates = interestZoneBottomRight.toQPointF().toPoint();
@ -245,11 +324,6 @@ void QgsMeshStreamField::updateSize( const QgsRenderContext &renderContext )
fieldWidth,
fieldHeight, 0 );
mLayerPixelExtent.setTopLeft(
mMapToFieldPixel.transform( QgsPointXY( layerExtent.xMinimum(), layerExtent.yMaximum() ) ).toQPointF().toPoint() );
mLayerPixelExtent.setBottomRight(
mMapToFieldPixel.transform( QgsPointXY( layerExtent.xMaximum(), layerExtent.yMinimum() ) ).toQPointF().toPoint() );
initField();
mValid = true;
}
@ -327,7 +401,6 @@ void QgsMeshStreamField::addTracesOnMesh( const QgsTriangularMesh &mesh, const Q
void QgsMeshStreamField::addTrace( QPoint startPixel )
{
//This is where each traces are constructed
if ( !mPainter )
return;
@ -339,19 +412,18 @@ void QgsMeshStreamField::addTrace( QPoint startPixel )
mPainter->setPen( mPen );
bool end = false;
//position in the pixelField
double x1 = 0;
double y1 = 0;
std::list<QPair<QPoint, double>> chunkTrace;
std::list<QPair<QPoint, FieldData>> chunkTrace;
QPoint currentPixel = startPixel;
QgsVector vector;
FieldData data;
data.time = 1;
float dt = 0;
while ( !end )
while ( !mRenderContext.renderingStopped() )
{
QgsPointXY mapPosition = positionToMapCoordinates( currentPixel, QgsPointXY( x1, y1 ) );
vector = mVectorValueInterpolator->vectorValue( mapPosition ) ;
@ -359,26 +431,31 @@ void QgsMeshStreamField::addTrace( QPoint startPixel )
if ( std::isnan( vector.x() ) || std::isnan( vector.y() ) )
{
mPixelFillingCount++;
setChunkTrace( chunkTrace );
drawChunkTrace( chunkTrace );
break;
}
/* adimensional value : Vu=2 when the particule need dt=1 to go through a pixel
* The size of the side of a pixel is 2
/* nondimensional value : Vu=2 when the particle need dt=1 to go through a pixel with the mMagMax magnitude
* The nondimensional size of the side of a pixel is 2
*/
QgsVector vu = vector / mMagMax * 2;
double mag = vector.length();
data.magnitude = vector.length();
double Vx = vu.x();
double Vy = vu.y();
double Vu = mag / mMagMax * 2; //nondimensional vector magnitude
double Vu = data.magnitude / mMagMax * 2; //nondimensional vector magnitude
if ( qgsDoubleNear( Vu, 0 ) )
{
// no trace anymore
mPixelFillingCount++;
addPixelToChunkTrace( currentPixel, data, chunkTrace );
simplifyChunkTrace( chunkTrace );
setChunkTrace( chunkTrace );
drawChunkTrace( chunkTrace );
break;
}
//calculates where the particule will be after dt=1,
//calculates where the particle will be after dt=1,
QgsPointXY nextPosition = QgsPointXY( x1, y1 ) + vu;
int incX = 0;
int incY = 0;
@ -395,15 +472,21 @@ void QgsMeshStreamField::addTrace( QPoint startPixel )
if ( incX != 0 || incY != 0 )
{
//the particule leave the current pixel --> store pixels, calculate where the particule is and change the current pixel
chunkTrace.emplace_back( currentPixel, mag );
if ( chunkTrace.size() == 3 )
data.directionX = incX;
data.directionY = -incY;
//the particule leave the current pixel --> store pixels, calculates where the particle is and change the current pixel
if ( chunkTrace.empty() )
{
simplifyChunkTrace( chunkTrace );
storeInField( QPair<QPoint, FieldData>( currentPixel, data ) );
}
if ( addPixelToChunkTrace( currentPixel, data, chunkTrace ) )
{
setChunkTrace( chunkTrace );
drawChunkTrace( chunkTrace );
clearChunkTrace( chunkTrace );
}
dt = 1;
data.time = 1;
currentPixel += QPoint( incX, -incY );
x1 = nextPosition.x() - 2 * incX;
y1 = nextPosition.y() - 2 * incY;
@ -471,11 +554,12 @@ void QgsMeshStreamField::addTrace( QPoint startPixel )
double dy = y2 - y1;
double dl = sqrt( dx * dx + dy * dy );
dt += dl / Vu ; //adimensional time step : this the time needed to go to the border of the pixel
if ( dt > 10000 ) //Guard to prevent that the particle never leave the pixel
data.time += dl / Vu ; //adimensional time step : this the time needed to go to the border of the pixel
if ( data.time > 10000 ) //Guard to prevent that the particle never leave the pixel
{
mPixelFillingCount++;
addPixelToChunkTrace( currentPixel, data, chunkTrace );
setChunkTrace( chunkTrace );
drawChunkTrace( chunkTrace );
break;
}
x1 = x2;
@ -485,17 +569,16 @@ void QgsMeshStreamField::addTrace( QPoint startPixel )
//test if the new current pixel is already defined, if yes no need to continue
if ( isTraceExists( currentPixel ) )
{
chunkTrace.emplace_back( currentPixel, mag );
if ( chunkTrace.size() == 3 )
simplifyChunkTrace( chunkTrace );
//Set the pixel in the chunk before adding the current pixel because this pixel is already defined
setChunkTrace( chunkTrace );
addPixelToChunkTrace( currentPixel, data, chunkTrace );
drawChunkTrace( chunkTrace );
break;
}
if ( isTraceOutside( currentPixel ) )
{
if ( chunkTrace.size() == 3 )
simplifyChunkTrace( chunkTrace );
setChunkTrace( chunkTrace );
drawChunkTrace( chunkTrace );
break;
}
@ -519,7 +602,20 @@ QPointF QgsMeshStreamField::fieldToDevice( const QPoint &pixel ) const
return p;
}
void QgsMeshStreamField::initField()
bool QgsMeshStreamField::addPixelToChunkTrace( QPoint &pixel,
QgsMeshStreamField::FieldData &data,
std::list<QPair<QPoint, QgsMeshStreamField::FieldData> > &chunkTrace )
{
chunkTrace.emplace_back( pixel, data );
if ( chunkTrace.size() == 3 )
{
simplifyChunkTrace( chunkTrace );
return true;
}
return false;
}
void QgsMeshStreamlinesField::initField()
{
mField = QVector<bool>( mFieldSize.width() * mFieldSize.height(), false );
@ -534,53 +630,90 @@ void QgsMeshStreamField::initField()
mPainter->setPen( mPen );
}
void QgsMeshStreamField::storeInField( const QPoint &pixel )
QgsMeshStreamlinesField::QgsMeshStreamlinesField( const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &datasetVectorValues, const QgsMeshDataBlock &scalarActiveFaceFlagValues, const QgsRectangle &layerExtent, double magMax, bool dataIsOnVertices, QgsRenderContext &rendererContext ):
QgsMeshStreamField( triangularMesh, datasetVectorValues, scalarActiveFaceFlagValues, layerExtent, magMax, dataIsOnVertices, rendererContext )
{}
QgsMeshStreamlinesField::QgsMeshStreamlinesField( const QgsMeshStreamlinesField &other ):
QgsMeshStreamField( other ),
mField( other.mField )
{}
QgsMeshStreamlinesField &QgsMeshStreamlinesField::operator=( const QgsMeshStreamlinesField &other )
{
int i = pixel.x();
int j = pixel.y();
QgsMeshStreamField::operator=( other );
mField = other.mField;
return *this;
}
void QgsMeshStreamlinesField::storeInField( const QPair<QPoint, FieldData> pixelData )
{
int i = pixelData.first.x();
int j = pixelData.first.y();
if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
{
mField[j * mFieldSize.width() + i] = true;
}
}
void QgsMeshStreamField::drawChunkTrace( std::list<QPair<QPoint, double> > &chunkTrace )
void QgsMeshStreamField::setChunkTrace( std::list<QPair<QPoint, FieldData> > &chunkTrace )
{
auto p = chunkTrace.begin();
while ( p != chunkTrace.end() )
{
storeInField( ( *p ) );
mPixelFillingCount++;
++p;
}
}
void QgsMeshStreamlinesField::drawChunkTrace( const std::list<QPair<QPoint, QgsMeshStreamField::FieldData> > &chunkTrace )
{
auto p1 = chunkTrace.begin();
auto p2 = p1;
p2++;
while ( p2 != chunkTrace.end() )
{
storeInField( ( *p2 ).first );
if ( filterMag( ( *p1 ).second ) && filterMag( ( *p2 ).second ) )
if ( filterMag( ( *p1 ).second.magnitude ) && filterMag( ( *p2 ).second.magnitude ) )
mPainter->drawLine( fieldToDevice( ( *p1 ).first ), fieldToDevice( ( *p2 ).first ) );
mPixelFillingCount++;
auto p = p1;
p1++;
p2++;
chunkTrace.erase( p );
}
}
void QgsMeshStreamField::simplifyChunkTrace( std::list<QPair<QPoint, double> > &chunkTrace )
void QgsMeshStreamField::clearChunkTrace( std::list<QPair<QPoint, QgsMeshStreamField::FieldData> > &chunkTrace )
{
Q_ASSERT( chunkTrace.size() == 3 );
QPoint p1 = chunkTrace.front().first;
auto ip2 = chunkTrace.begin();
ip2++;
QPoint p2 = ( *( ip2 ) ).first;
QPoint p3 = chunkTrace.back().first;
QPoint v1 = p1 - p2;
QPoint v2 = p2 - p3;
if ( v1.x()*v2.x() + v1.y()*v2.y() == 0 )
chunkTrace.erase( ip2 );
auto one_before_end = std::prev( chunkTrace.end() );
chunkTrace.erase( chunkTrace.begin(), one_before_end );
}
bool QgsMeshStreamField::isTraceExists( const QPoint &pixel ) const
void QgsMeshStreamField::simplifyChunkTrace( std::list<QPair<QPoint, FieldData> > &shunkTrace )
{
if ( shunkTrace.size() != 3 )
return;
auto ip3 = shunkTrace.begin();
auto ip1 = ip3++;
auto ip2 = ip3++;
while ( ip3 != shunkTrace.end() && ip2 != shunkTrace.end() )
{
QPoint v1 = ( *ip1 ).first - ( *ip2 ).first;
QPoint v2 = ( *ip2 ).first - ( *ip3 ).first;
if ( v1.x()*v2.x() + v1.y()*v2.y() == 0 )
{
( *ip1 ).second.time += ( ( *ip2 ).second.time ) / 2;
( *ip3 ).second.time += ( ( *ip2 ).second.time ) / 2;
( *ip1 ).second.directionX += ( *ip2 ).second.directionX;
( *ip1 ).second.directionY += ( *ip2 ).second.directionY;
shunkTrace.erase( ip2 );
}
ip1 = ip3++;
ip2 = ip3++;
}
}
bool QgsMeshStreamlinesField::isTraceExists( const QPoint &pixel ) const
{
int i = pixel.x();
int j = pixel.y();
@ -590,7 +723,6 @@ bool QgsMeshStreamField::isTraceExists( const QPoint &pixel ) const
}
return false;
}
bool QgsMeshStreamField::isTraceOutside( const QPoint &pixel ) const
@ -604,6 +736,53 @@ bool QgsMeshStreamField::isTraceOutside( const QPoint &pixel ) const
return true;
}
void QgsMeshStreamField::setMinimizeFieldSize( bool minimizeFieldSize )
{
mMinimizeFieldSize = minimizeFieldSize;
}
QgsMeshStreamField &QgsMeshStreamField::operator=( const QgsMeshStreamField &other )
{
mFieldSize = other.mFieldSize ;
mFieldResolution = other.mFieldResolution;
mPen = other.mPen;
mTraceImage = other.mTraceImage ;
mMapToFieldPixel = other.mMapToFieldPixel ;
mPixelFillingCount = other.mPixelFillingCount ;
mMaxPixelFillingCount = other.mMaxPixelFillingCount ;
mLayerExtent = other.mLayerExtent ;
mMapExtent = other.mMapExtent;
mFieldTopLeftInDeviceCoordinates = other.mFieldTopLeftInDeviceCoordinates ;
mValid = other.mValid ;
mMagMax = other.mMagMax ;
mPixelFillingDensity = other.mPixelFillingDensity ;
mMinMagFilter = other.mMinMagFilter ;
mMaxMagFilter = other.mMaxMagFilter ;
mMinimizeFieldSize = other.mMinimizeFieldSize ;
mPainter = new QPainter( &mTraceImage );
mVectorValueInterpolator =
std::unique_ptr<QgsMeshVectorValueInterpolator>( other.mVectorValueInterpolator->clone() );
if ( mPainter )
delete mPainter;
mPainter = new QPainter( &mTraceImage );
return ( *this );
}
void QgsMeshStreamField::initImage()
{
if ( mPainter )
delete mPainter;
mTraceImage = QImage( mFieldSize * mFieldResolution, QImage::Format_ARGB32 );
mTraceImage.fill( 0X00000000 );
mPainter = new QPainter( &mTraceImage );
mPainter->setRenderHint( QPainter::Antialiasing, true );
mPainter->setPen( mPen );
}
bool QgsMeshStreamField::filterMag( double value ) const
{
return ( mMinMagFilter < 0 || value > mMinMagFilter ) && ( mMaxMagFilter < 0 || value < mMaxMagFilter );
@ -644,6 +823,21 @@ QgsMeshVectorValueInterpolatorFromFace::QgsMeshVectorValueInterpolatorFromFace(
QgsMeshVectorValueInterpolator( triangularMesh, datasetVectorValues, scalarActiveFaceFlagValues )
{}
QgsMeshVectorValueInterpolatorFromFace::QgsMeshVectorValueInterpolatorFromFace( const QgsMeshVectorValueInterpolatorFromFace &other ):
QgsMeshVectorValueInterpolator( other )
{}
QgsMeshVectorValueInterpolatorFromFace *QgsMeshVectorValueInterpolatorFromFace::clone()
{
return new QgsMeshVectorValueInterpolatorFromFace( *this );
}
QgsMeshVectorValueInterpolatorFromFace &QgsMeshVectorValueInterpolatorFromFace::operator=( const QgsMeshVectorValueInterpolatorFromFace &other )
{
QgsMeshVectorValueInterpolator::operator=( other );
return ( *this );
}
QgsVector QgsMeshVectorValueInterpolatorFromFace::interpolatedValuePrivate( int faceIndex, const QgsPointXY point ) const
{
QgsMeshFace face = mTriangularMesh.triangles().at( faceIndex );
@ -663,10 +857,10 @@ QgsVector QgsMeshVectorValueInterpolatorFromFace::interpolatedValuePrivate( int
point );
}
QgsMeshVectorStreamLineRenderer::QgsMeshVectorStreamLineRenderer( const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &dataSetVectorValues, const QgsMeshDataBlock &scalarActiveFaceFlagValues, bool dataIsOnVertices, const QgsMeshRendererVectorSettings &settings, QgsRenderContext &rendererContext, const QgsRectangle &layerExtent, double magMax ):
QgsMeshVectorStreamlineRenderer::QgsMeshVectorStreamlineRenderer( const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &dataSetVectorValues, const QgsMeshDataBlock &scalarActiveFaceFlagValues, bool dataIsOnVertices, const QgsMeshRendererVectorSettings &settings, QgsRenderContext &rendererContext, const QgsRectangle &layerExtent, double magMax ):
mRendererContext( rendererContext )
{
mStreamLineField.reset( new QgsMeshStreamField( triangularMesh,
mStreamLineField.reset( new QgsMeshStreamlinesField( triangularMesh,
dataSetVectorValues,
scalarActiveFaceFlagValues,
layerExtent,
@ -695,11 +889,470 @@ QgsMeshVectorStreamLineRenderer::QgsMeshVectorStreamLineRenderer( const QgsTrian
}
}
void QgsMeshVectorStreamLineRenderer::draw()
void QgsMeshVectorStreamlineRenderer::draw()
{
if ( mRendererContext.renderingStopped() )
return;
mRendererContext.painter()->drawImage( mStreamLineField->topLeft(), mStreamLineField->image() );
}
QgsMeshParticleTracesField::QgsMeshParticleTracesField( const QgsTriangularMesh &triangularMesh, const QgsMeshDataBlock &datasetVectorValues, const QgsMeshDataBlock &scalarActiveFaceFlagValues, const QgsRectangle &layerExtent, double magMax, bool dataIsOnVertices, const QgsRenderContext &rendererContext ):
QgsMeshStreamField( triangularMesh, datasetVectorValues, scalarActiveFaceFlagValues, layerExtent, magMax, dataIsOnVertices, rendererContext )
{
std::srand( uint( std::time( nullptr ) ) );
mPen.setCapStyle( Qt::RoundCap );
}
QgsMeshParticleTracesField::QgsMeshParticleTracesField( const QgsMeshParticleTracesField &other ):
QgsMeshStreamField( other ),
mTimeField( other.mTimeField ),
mDirectionField( other.mDirectionField ),
mParticles( other.mParticles ),
mStumpImage( other.mStumpImage ),
mTimeStep( other.mTimeStep ),
mParticlesLifeTime( other.mParticlesLifeTime ),
mParticlesCount( other.mParticlesCount ),
mTailFactor( other.mTailFactor ),
mParticleColor( other.mParticleColor ),
mParticleSize( other.mParticleSize ),
mStumpFactor( other.mStumpFactor )
{}
void QgsMeshParticleTracesField::addParticle( const QPoint &startPoint, double lifeTime )
{
addTrace( startPoint );
if ( time( startPoint ) > 0 )
{
QgsMeshTraceParticle p;
p.lifeTime = lifeTime;
p.position = startPoint;
mParticles.append( p );
}
}
void QgsMeshParticleTracesField::addParticleXY( const QgsPointXY &startPoint, double lifeTime )
{
addParticle( mMapToFieldPixel.transform( startPoint ).toQPointF().toPoint(), lifeTime );
}
void QgsMeshParticleTracesField::moveParticles()
{
stump();
for ( auto &p : mParticles )
{
double spentTime = p.remainingTime; //adjust with the past remaining time
size_t countAdded = 0;
while ( spentTime < mTimeStep && p.lifeTime > 0 )
{
double timeToSpend = double( time( p.position ) );
if ( timeToSpend > 0 )
{
p.lifeTime -= timeToSpend;
spentTime += timeToSpend;
QPoint dir = direction( p.position );
if ( p.lifeTime > 0 )
{
p.position += dir;
p.tail.emplace_back( p.position );
countAdded++;
}
else
{
break;
}
}
else
{
p.lifeTime = -1;
break;
}
}
if ( p.lifeTime <= 0 )
{
// the particle is not alive anymore
p.lifeTime = 0;
p.tail.clear();
}
else
{
p.remainingTime = spentTime - mTimeStep;
while ( int( p.tail.size() ) > mMinTailLength && p.tail.size() > countAdded * mTailFactor )
p.tail.erase( p.tail.begin() );
drawParticleTrace( p );
}
}
//remove empty (dead particles)
int i = 0;
while ( i < mParticles.count() )
{
if ( mParticles.at( i ).tail.size() == 0 )
mParticles.removeAt( i );
else
++i;
}
//add new particles if needed
if ( mParticles.count() < mParticlesCount )
addRandomParticles();
}
void QgsMeshParticleTracesField::addRandomParticles()
{
if ( !isValid() )
return;
int count = mParticlesCount - mParticles.count();
for ( int i = 0; i < count; ++i )
{
int xRandom = 1 + std::rand() / int( ( RAND_MAX + 1u ) / uint( mFieldSize.width() ) ) ;
int yRandom = 1 + std::rand() / int ( ( RAND_MAX + 1u ) / uint( mFieldSize.height() ) ) ;
double lifeTime = ( std::rand() / ( ( RAND_MAX + 1u ) / mParticlesLifeTime ) );
addParticle( QPoint( xRandom, yRandom ), lifeTime );
}
}
void QgsMeshParticleTracesField::storeInField( const QPair<QPoint, QgsMeshStreamField::FieldData> pixelData )
{
int i = pixelData.first.x();
int j = pixelData.first.y();
if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
{
mTimeField[j * mFieldSize.width() + i] = pixelData.second.time;
int d = pixelData.second.directionX + 2 + ( pixelData.second.directionY + 1 ) * 3;
mDirectionField[j * mFieldSize.width() + i] = static_cast<char>( d );
}
}
void QgsMeshParticleTracesField::initField()
{
mTimeField = QVector<float>( mFieldSize.width() * mFieldSize.height(), -1 );
mDirectionField = QVector<char>( mFieldSize.width() * mFieldSize.height(), static_cast<char>( int( 0 ) ) );
initImage();
mStumpImage = QImage( mFieldSize * mFieldResolution, QImage::Format_ARGB32 );
mStumpImage.fill( QColor( 0, 0, 0, mStumpFactor ) ); //alpha=0 -> no persitence, alpha=255 -> total persistence
}
bool QgsMeshParticleTracesField::isTraceExists( const QPoint &pixel ) const
{
int i = pixel.x();
int j = pixel.y();
if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
{
return mTimeField[j * mFieldSize.width() + i] >= 0;
}
return false;
}
void QgsMeshParticleTracesField::setMinTailLength( int minTailLength )
{
mMinTailLength = minTailLength;
}
QgsMeshParticleTracesField &QgsMeshParticleTracesField::operator=( const QgsMeshParticleTracesField &other )
{
QgsMeshStreamField::operator=( other );
mTimeField = other.mTimeField;
mDirectionField = other.mDirectionField;
mParticles = other.mParticles;
mStumpImage = other.mStumpImage;
mTimeStep = other.mTimeStep;
mParticlesLifeTime = other.mParticlesLifeTime;
mParticlesCount = other.mParticlesCount;
mTailFactor = other.mTailFactor;
mParticleColor = other.mParticleColor;
mParticleSize = other.mParticleSize;
mStumpFactor = other.mStumpFactor;
return ( *this );
}
void QgsMeshParticleTracesField::setTailFactor( double tailFactor )
{
mTailFactor = tailFactor;
}
void QgsMeshParticleTracesField::setParticleSize( double particleSize )
{
mParticleSize = particleSize;
}
void QgsMeshParticleTracesField::setParticleColor( const QColor &particleColor )
{
mParticleColor = particleColor;
}
void QgsMeshParticleTracesField::setTimeStep( double timeStep )
{
mTimeStep = timeStep;
}
void QgsMeshParticleTracesField::setParticlesLifeTime( double particlesLifeTime )
{
mParticlesLifeTime = particlesLifeTime;
}
QImage QgsMeshParticleTracesField::imageRendered() const
{
return mTraceImage;
}
void QgsMeshParticleTracesField::stump()
{
mPainter->save();
mPainter->setCompositionMode( QPainter::CompositionMode_DestinationIn );
mPainter->drawImage( QPoint( 0, 0 ), mStumpImage );
mPainter->restore();
}
void QgsMeshParticleTracesField::setStumpFactor( int sf )
{
mStumpFactor = sf;
mStumpImage = QImage( mFieldSize * mFieldResolution, QImage::Format_ARGB32 );
mStumpImage.fill( QColor( 0, 0, 0, mStumpFactor ) );
}
QPoint QgsMeshParticleTracesField::direction( QPoint position ) const
{
int i = position.x();
int j = position.y();
if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
{
int dir = static_cast<int>( mDirectionField[j * mFieldSize.width() + i] );
if ( dir != 0 && dir < 10 )
return QPoint( ( dir - 1 ) % 3 - 1, ( dir - 1 ) / 3 - 1 );
}
return QPoint( 0, 0 );
}
float QgsMeshParticleTracesField::time( QPoint position ) const
{
int i = position.x();
int j = position.y();
if ( i >= 0 && i < mFieldSize.width() && j >= 0 && j < mFieldSize.height() )
{
return mTimeField[j * mFieldSize.width() + i];
}
return -1;
}
void QgsMeshParticleTracesField::drawParticleTrace( const QgsMeshTraceParticle &particle )
{
const std::list<QPoint> &tail = particle.tail;
if ( tail.size() == 0 )
return;
double iniWidth = mParticleSize;
double finWidth = 0;
size_t pixelCount = tail.size();
QColor traceColor = mParticleColor;
double transparency = sin( M_PI * particle.lifeTime / mParticlesLifeTime );
traceColor.setAlphaF( transparency );
mPen.setColor( traceColor );
double dw;
if ( pixelCount > 1 )
dw = ( iniWidth - finWidth ) / ( pixelCount );
else
dw = 0;
auto ip1 = std::prev( tail.end() );
auto ip2 = std::prev( ip1 );
int i = 0;
while ( ip1 != tail.begin() )
{
QPointF p1 = fieldToDevice( ( *ip1 ) );
QPointF p2 = fieldToDevice( ( *ip2 ) );
mPen.setWidthF( iniWidth - i * dw );
mPainter->setPen( mPen );
mPainter->drawLine( p1, p2 );
ip1--;
ip2--;
++i;
}
}
void QgsMeshParticleTracesField::setParticlesCount( int particlesCount )
{
mParticlesCount = particlesCount;
}
QgsMeshVectorTraceRenderer::QgsMeshVectorTraceRenderer(
const QgsTriangularMesh &triangularMesh,
const QgsMeshDataBlock &dataSetVectorValues,
const QgsMeshDataBlock &scalarActiveFaceFlagValues,
bool dataIsOnVertices,
const QgsRenderContext &rendererContext,
const QgsRectangle &layerExtent, double magMax ):
mRendererContext( rendererContext )
{
mParticleField = std::unique_ptr<QgsMeshParticleTracesField>( new QgsMeshParticleTracesField( triangularMesh,
dataSetVectorValues,
scalarActiveFaceFlagValues,
layerExtent,
magMax,
dataIsOnVertices,
rendererContext ) ) ;
mParticleField->updateSize( rendererContext ) ;
}
QgsMeshVectorTraceRenderer::QgsMeshVectorTraceRenderer( QgsMeshLayer *layer, const QgsRenderContext &rendererContext ):
mRendererContext( rendererContext )
{
if ( !layer->triangularMesh() )
layer->reload();
QgsMeshDataBlock vectorDatasetValues;
QgsMeshDataBlock scalarActiveFaceFlagValues;
bool vectorDataOnVertices;
double magMax;
// Find out if we can use cache up to date. If yes, use it and return
const int datasetGroupCount = layer->dataProvider()->datasetGroupCount();
QgsMeshDatasetIndex datasetIndex = layer->rendererSettings().activeVectorDataset();
QgsMeshLayerRendererCache *cache = layer->rendererCache();
if ( ( cache->mDatasetGroupsCount == datasetGroupCount ) &&
( cache->mActiveVectorDatasetIndex == datasetIndex ) )
{
vectorDatasetValues = cache->mVectorDatasetValues;
scalarActiveFaceFlagValues = cache->mScalarActiveFaceFlagValues;
magMax = cache->mVectorDatasetMagMaximum;
vectorDataOnVertices = cache->mVectorDataOnVertices;
}
else
{
const QgsMeshDatasetGroupMetadata metadata =
layer->dataProvider()->datasetGroupMetadata( layer->rendererSettings().activeVectorDataset() );
magMax = metadata.maximum();
vectorDataOnVertices = metadata.dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices;
int count;
if ( vectorDataOnVertices )
count = layer->nativeMesh()->vertices.count();
else
count = layer->nativeMesh()->faces.count();
vectorDatasetValues = layer->dataProvider()->datasetValues(
datasetIndex,
0,
count );
scalarActiveFaceFlagValues = layer->dataProvider()->areFacesActive(
datasetIndex,
0,
layer->nativeMesh()->faces.count() );
}
mParticleField = std::unique_ptr<QgsMeshParticleTracesField>( new QgsMeshParticleTracesField( ( *layer->triangularMesh() ),
vectorDatasetValues,
scalarActiveFaceFlagValues,
layer->extent(),
magMax,
vectorDataOnVertices,
rendererContext ) ) ;
mParticleField->setMinimizeFieldSize( false );
mParticleField->updateSize( mRendererContext );
}
QgsMeshVectorTraceRenderer::QgsMeshVectorTraceRenderer( const QgsMeshVectorTraceRenderer &other ):
mRendererContext( other.mRendererContext ),
mFPS( other.mFPS ),
mVpixMax( other.mVpixMax ),
mParticleLifeTime( other.mParticleLifeTime )
{
mParticleField = std::unique_ptr<QgsMeshParticleTracesField>(
new QgsMeshParticleTracesField( *other.mParticleField ) );
}
QgsMeshVectorTraceRenderer::~QgsMeshVectorTraceRenderer() = default;
void QgsMeshVectorTraceRenderer::seedRandomParticles( int count )
{
mParticleField->setParticlesCount( count );
mParticleField->addRandomParticles();
}
QImage QgsMeshVectorTraceRenderer::imageRendered()
{
mParticleField->moveParticles();
return mParticleField->image();
}
void QgsMeshVectorTraceRenderer::setFPS( int FPS )
{
if ( FPS > 0 )
mFPS = FPS;
else
mFPS = 1;
updateFieldParameter();
}
void QgsMeshVectorTraceRenderer::setMaxSpeedPixel( int max )
{
mVpixMax = max;
updateFieldParameter();
}
void QgsMeshVectorTraceRenderer::setParticlesLifeTime( double particleLifeTime )
{
mParticleLifeTime = particleLifeTime;
updateFieldParameter();
}
void QgsMeshVectorTraceRenderer::setParticlesColor( const QColor &c )
{
mParticleField->setParticleColor( c );
}
void QgsMeshVectorTraceRenderer::setParticlesSize( double width )
{
mParticleField->setParticleSize( width );
}
void QgsMeshVectorTraceRenderer::setTailFactor( double fct )
{
mParticleField->setTailFactor( fct );
}
void QgsMeshVectorTraceRenderer::setMinimumTailLength( int l )
{
mParticleField->setMinTailLength( l );
}
void QgsMeshVectorTraceRenderer::setTailPersitence( double p )
{
if ( p < 0 )
p = 0;
if ( p > 1 )
p = 1;
mParticleField->setStumpFactor( int( 255 * p ) );
}
QgsMeshVectorTraceRenderer &QgsMeshVectorTraceRenderer::operator=( const QgsMeshVectorTraceRenderer &other )
{
mParticleField.reset( new QgsMeshParticleTracesField( *mParticleField ) );
const_cast<QgsRenderContext &>( mRendererContext ) = other.mRendererContext;
mFPS = other.mFPS;
mVpixMax = other.mVpixMax;
mParticleLifeTime = other.mParticleLifeTime;
return ( *this );
}
void QgsMeshVectorTraceRenderer::updateFieldParameter()
{
double fieldTimeStep = mVpixMax / mFPS;
double fieldLifeTime = mParticleLifeTime * mFPS * fieldTimeStep;
mParticleField->setTimeStep( fieldTimeStep );
mParticleField->setParticlesLifeTime( fieldLifeTime );
}
///@endcond

View File

@ -18,7 +18,6 @@
#ifndef QGSMESHTRACERENDERER_H
#define QGSMESHTRACERENDERER_H
#define SIP_NO_FILE
#include <QVector>
#include <QSize>
@ -33,6 +32,8 @@
///@cond PRIVATE
#ifndef SIP_RUN
/**
* \ingroup core
*
@ -52,13 +53,22 @@ class QgsMeshVectorValueInterpolator
QgsMeshVectorValueInterpolator( const QgsTriangularMesh &triangularMesh,
const QgsMeshDataBlock &datasetVectorValues,
const QgsMeshDataBlock &scalarActiveFaceFlagValues );
//! Copy constructor
QgsMeshVectorValueInterpolator( const QgsMeshVectorValueInterpolator &other );
//! Clone
virtual QgsMeshVectorValueInterpolator *clone() = 0;
//! Destructor
virtual ~QgsMeshVectorValueInterpolator() = default;
//! Returns the interpolated vector
virtual QgsVector vectorValue( const QgsPointXY &point ) const;
//! Assignment operator
QgsMeshVectorValueInterpolator &operator=( const QgsMeshVectorValueInterpolator &other );
protected:
void updateCacheFaceIndex( const QgsPointXY &point ) const;
@ -97,6 +107,15 @@ class QgsMeshVectorValueInterpolatorFromVertex: public QgsMeshVectorValueInterpo
const QgsMeshDataBlock &datasetVectorValues,
const QgsMeshDataBlock &scalarActiveFaceFlagValues );
//! Copy constructor
QgsMeshVectorValueInterpolatorFromVertex( const QgsMeshVectorValueInterpolatorFromVertex &other );
//! Clone the instance
virtual QgsMeshVectorValueInterpolatorFromVertex *clone() override;
//! Assignment operator
QgsMeshVectorValueInterpolatorFromVertex &operator=( const QgsMeshVectorValueInterpolatorFromVertex &other );
private:
QgsVector interpolatedValuePrivate( int faceIndex, const QgsPointXY point ) const override;
};
@ -121,6 +140,15 @@ class QgsMeshVectorValueInterpolatorFromFace: public QgsMeshVectorValueInterpola
const QgsMeshDataBlock &datasetVectorValues,
const QgsMeshDataBlock &scalarActiveFaceFlagValues );
//! Copy constructor
QgsMeshVectorValueInterpolatorFromFace( const QgsMeshVectorValueInterpolatorFromFace &other );
//! Clone the instance
virtual QgsMeshVectorValueInterpolatorFromFace *clone() override;
//! Assignment operator
QgsMeshVectorValueInterpolatorFromFace &operator=( const QgsMeshVectorValueInterpolatorFromFace &other );
private:
QgsVector interpolatedValuePrivate( int faceIndex, const QgsPointXY point ) const override;
};
@ -128,7 +156,7 @@ class QgsMeshVectorValueInterpolatorFromFace: public QgsMeshVectorValueInterpola
/**
* \ingroup core
*
* Abstract class used to handle information about stream field;
* Abstract class used to handle information about stream field
*
* \note not available in Python bindings
* \since QGIS 3.12
@ -136,22 +164,29 @@ class QgsMeshVectorValueInterpolatorFromFace: public QgsMeshVectorValueInterpola
class QgsMeshStreamField
{
public:
struct FieldData
{
double magnitude;
float time;
int directionX;
int directionY;
};
//! Constructor
QgsMeshStreamField( const QgsTriangularMesh &triangularMesh,
const QgsMeshDataBlock &dataSetVectorValues,
const QgsMeshDataBlock &scalarActiveFaceFlagValues,
const QgsRectangle &layerExtent,
double magMax, bool dataIsOnVertices, QgsRenderContext &rendererContext,
double magnitudeMaximum, bool dataIsOnVertices, const QgsRenderContext &rendererContext,
int resolution = 1 );
//! Destructor
virtual ~QgsMeshStreamField()
{
if ( mPainter )
delete mPainter;
}
//! Copy constructor
QgsMeshStreamField( const QgsMeshStreamField &other );
/*
//! Destructor
virtual ~QgsMeshStreamField();
/**
* Updates the size of the field and the QgsMapToPixel instance to retrieve map point
* from pixel in the field depending on the resolution of the device
* If the extent of renderer context and the resolution are not changed, do nothing
@ -159,7 +194,7 @@ class QgsMeshStreamField
*/
void updateSize( const QgsRenderContext &renderContext );
/*
/**
* Updates the size of the field and the QgsMapToPixel instance to retrieve map point
* from pixel in the field depending on the resolution of the device
*/
@ -216,49 +251,243 @@ class QgsMeshStreamField
//! Sets min/max filter
void setFilter( double min, double max );
private:
//! Sets if the size of the field has to be minimized of all the mesh is in the device
void setMinimizeFieldSize( bool minimizeFieldSize );
QgsPointXY positionToMapCoordinates( const QPoint &pixelPosition, const QgsPointXY &positionInPixel );
//! Assignment operator
QgsMeshStreamField &operator=( const QgsMeshStreamField &other );
protected:
void initImage();
QPointF fieldToDevice( const QPoint &pixel ) const;
void initField();
void storeInField( const QPoint &pixel );
void drawChunkTrace( std::list<QPair<QPoint, double>> &chunkTrace );
void simplifyChunkTrace( std::list<QPair<QPoint, double>> &chunkTrace );
bool isTraceExists( const QPoint &pixel ) const;
bool isTraceOutside( const QPoint &pixel ) const;
bool filterMag( double value ) const;
private:
QgsPointXY positionToMapCoordinates( const QPoint &pixelPosition, const QgsPointXY &positionInPixel );
bool addPixelToChunkTrace( QPoint &pixel,
QgsMeshStreamField::FieldData &data,
std::list<QPair<QPoint, QgsMeshStreamField::FieldData> > &chunkTrace );
void setChunkTrace( std::list<QPair<QPoint, FieldData>> &chunkTrace );
virtual void drawChunkTrace( const std::list<QPair<QPoint, FieldData>> &chunkTrace ) = 0;
void clearChunkTrace( std::list<QPair<QPoint, FieldData>> &chunkTrace );
virtual void storeInField( const QPair<QPoint, FieldData> pixelData ) = 0;
virtual void initField() = 0;
void simplifyChunkTrace( std::list<QPair<QPoint, FieldData>> &shunkTrace );
virtual bool isTraceExists( const QPoint &pixel ) const = 0;
bool isTraceOutside( const QPoint &pixel ) const;
protected:
QSize mFieldSize;
QRect mLayerPixelExtent;
QPainter *mPainter = nullptr;
int mFieldResolution = 1;
QPen mPen;
QImage mTraceImage;
QgsMapToPixel mMapToFieldPixel;
private:
int mPixelFillingCount = 0;
int mMaxPixelFillingCount = 0;
std::unique_ptr<QgsMeshVectorValueInterpolator> mVectorValueInterpolator;
QImage mTraceImage;
QVector<bool> mField;
QgsRectangle mLayerExtent;
QgsRectangle mMapExtent;
QgsMapToPixel mMapToFieldPixel;
QPen mPen;
QPainter *mPainter = nullptr;
QPoint mFieldTopLeftInDeviceCoordinates;
bool mValid = false;
double mMagMax = 0;
double mPixelFillingDensity;
double mMinMagFilter = -1;
double mMaxMagFilter = -1;
QgsRenderContext &mRenderContext; //keep the renderer context only to know if the renderer is stopped
const QgsRenderContext &mRenderContext; //keep the renderer context only to know if the renderer is stopped
bool mMinimizeFieldSize = true; //
};
class QgsMeshVectorStreamLineRenderer: public QgsMeshVectorRenderer
/**
* \ingroup core
*
* Class used to draw streamlines from vector field
*
* \note not available in Python bindings
* \since QGIS 3.12
*/
class QgsMeshStreamlinesField: public QgsMeshStreamField
{
public:
//! Constructor
QgsMeshVectorStreamLineRenderer( const QgsTriangularMesh &triangularMesh,
QgsMeshStreamlinesField( const QgsTriangularMesh &triangularMesh,
const QgsMeshDataBlock &datasetVectorValues,
const QgsMeshDataBlock &scalarActiveFaceFlagValues,
const QgsRectangle &layerExtent,
double magMax, bool dataIsOnVertices, QgsRenderContext &rendererContext );
//! Copy constructor
QgsMeshStreamlinesField( const QgsMeshStreamlinesField &other );
//! Assignment operator
QgsMeshStreamlinesField &operator=( const QgsMeshStreamlinesField &other );
private:
void storeInField( const QPair<QPoint, FieldData> pixelData ) override;
void initField() override;
bool isTraceExists( const QPoint &pixel ) const override;
void drawChunkTrace( const std::list<QPair<QPoint, FieldData> > &chunkTrace ) override;
QVector<bool> mField;
};
class QgsMeshParticleTracesField;
/**
* \ingroup core
*
* Used to simulation moving particle
*
* \note not available in Python bindings
* \since QGIS 3.12
*/
struct QgsMeshTraceParticle
{
double lifeTime = 0;
QPoint position;
std::list<QPoint> tail;
double remainingTime = 0; //time remaining to spend in the current pixel at the end of the time step
};
/**
* \ingroup core
*
* Class used to draw streamlines from vector field
*
* \note not available in Python bindings
* \since QGIS 3.12
*/
class QgsMeshParticleTracesField: public QgsMeshStreamField
{
public:
//! Constructor
QgsMeshParticleTracesField( const QgsTriangularMesh &triangularMesh,
const QgsMeshDataBlock &datasetVectorValues,
const QgsMeshDataBlock &scalarActiveFaceFlagValues,
const QgsRectangle &layerExtent,
double magMax,
bool dataIsOnVertices,
const QgsRenderContext &rendererContext );
//! Copy constructor
QgsMeshParticleTracesField( const QgsMeshParticleTracesField &other );
//! Adds a particle in the vector field from a start point (pixel) with a specified life time
void addParticle( const QPoint &startPoint, double lifeTime );
//! Adds a particle in the vector field from a start point (map point) with a specified life time
void addParticleXY( const QgsPointXY &startPoint, double lifeTime );
//! Adds particle randomly (position and life time
void addRandomParticles();
//! Moves all the particles with a displacement corresponding to a nondimensional time
void moveParticles();
//! Returns the current image of the particles
QImage imageRendered() const;
//! Sets the total number of particles generated randomly
void setParticlesCount( int particlesCount );
//! Sets the maximum life time (nondimensional) of particle generated
void setParticlesLifeTime( double particlesLifeTime );
//! Stumps particles image and leave a persistent effect
void stump();
/**
* Sets stump factor from 0 to 255 :
* 0, stump completely, no persistence
* 255, no stump, total persistence
*/
void setStumpFactor( int sf );
//! Sets the time step
void setTimeStep( double timeStep );
//! Sets tihe color of the particles
void setParticleColor( const QColor &particleColor );
//! Sets particles size
void setParticleSize( double particleSize );
//! Sets the tail factor
void setTailFactor( double tailFactor );
//! Sets the minimum tail length
void setMinTailLength( int minTailLength );
//! Assignment operator
QgsMeshParticleTracesField &operator=( const QgsMeshParticleTracesField &other );
private:
QPoint direction( QPoint position ) const;
float time( QPoint position ) const;
void drawParticleTrace( const QgsMeshTraceParticle &particle );
void storeInField( const QPair<QPoint, FieldData> pixelData ) override;
void initField() override;
bool isTraceExists( const QPoint &pixel ) const override;
void drawChunkTrace( const std::list<QPair<QPoint, FieldData>> &chunkTrace ) override {Q_UNUSED( chunkTrace );}
/* Nondimensional time
* This field store the time spent by the particle in the pixel
*
* This time is nondimensional and value 1 is equivalent to the time spent by the particle in a pixel
* for Vmax, the maximum magnitude of the vector field.
*
*/
QVector<float> mTimeField;
/*the direction for a pixel is defined with a char value
*
* 1 2 3
* 4 5 6
* 7 8 9
*
* convenient to retrieve the indexes of the next pixel from the direction d:
* Xnext= (d-1)%3-1
* Ynext = (d-1)/3-1
*
* and the direction is defined by :
* d=incX + 2 + (incY+1)*3
*/
QVector<char> mDirectionField;
QList<QgsMeshTraceParticle> mParticles;
QImage mStumpImage;
double mTimeStep = 200;
double mParticlesLifeTime = 5000;
int mParticlesCount = 1000;
double mTailFactor = 5;
int mMinTailLength = 3;
QColor mParticleColor = Qt::white;
double mParticleSize = 2.5;
int mStumpFactor = 50;
};
/**
* \ingroup core
*
* A class derived from QgsMeshVectorRenderer used to render the particles traces
*
* \note not available in Python bindings
* \since QGIS 3.12
*/
class QgsMeshVectorStreamlineRenderer: public QgsMeshVectorRenderer
{
public:
//!Constructor
QgsMeshVectorStreamlineRenderer( const QgsTriangularMesh &triangularMesh,
const QgsMeshDataBlock &dataSetVectorValues,
const QgsMeshDataBlock &scalarActiveFaceFlagValues,
bool dataIsOnVertices,
@ -267,7 +496,6 @@ class QgsMeshVectorStreamLineRenderer: public QgsMeshVectorRenderer
const QgsRectangle &layerExtent,
double magMax );
void draw() override;
private:
@ -275,6 +503,78 @@ class QgsMeshVectorStreamLineRenderer: public QgsMeshVectorRenderer
QgsRenderContext &mRendererContext;
};
#endif //SIP_RUN
///@endcond
/**
* \ingroup core
*
* A wrapper for QgsMeshParticuleTracesField used to render the particles. Available for Python binding
*
* \since QGIS 3.12
*/
class CORE_EXPORT QgsMeshVectorTraceRenderer
{
public:
//!Constructor to use from QgsMeshVectorRenderer
QgsMeshVectorTraceRenderer( const QgsTriangularMesh &triangularMesh,
const QgsMeshDataBlock &dataSetVectorValues,
const QgsMeshDataBlock &scalarActiveFaceFlagValues,
bool dataIsOnVertices,
const QgsRenderContext &rendererContext,
const QgsRectangle &layerExtent,
double magMax ) SIP_SKIP;
//!Constructor to use with Python binding
QgsMeshVectorTraceRenderer( QgsMeshLayer *layer, const QgsRenderContext &rendererContext );
//! Copy constructor
QgsMeshVectorTraceRenderer( const QgsMeshVectorTraceRenderer &other );
//! Destructor
~QgsMeshVectorTraceRenderer();
//! seeds particles in the vector fields
void seedRandomParticles( int count );
//! Moves all the particles using frame per second (fps) to calculate the displacement
QImage imageRendered();
//! Sets the number of frames per seconds that will be rendered
void setFPS( int FPS );
//! Sets the max number of pixels that can be go through by the particles in 1 second
void setMaxSpeedPixel( int max );
//! Sets maximum life time of particles in seconds
void setParticlesLifeTime( double particleLifeTime );
//! Sets colors of particle
void setParticlesColor( const QColor &c );
//! Sets particle size
void setParticlesSize( double width );
//! Sets the tail factor, used to adjust the length of the tail. 0 : minimum length, >1 increase the tail
void setTailFactor( double fct );
//! Sets the minimum tail length
void setMinimumTailLength( int l );
//! Sets the visual persistence of the tail
void setTailPersitence( double p );
//! Assignment operator
QgsMeshVectorTraceRenderer &operator=( const QgsMeshVectorTraceRenderer &other );
private:
std::unique_ptr<QgsMeshParticleTracesField> mParticleField;
const QgsRenderContext &mRendererContext;
int mFPS = 15; //frame per second of the output, used to calculate orher parameters of the field
int mVpixMax = 2000; //is the number of pixels that are going through for 1 s
double mParticleLifeTime = 5;
void updateFieldParameter();
};
#endif // QGSMESHTRACERENDERER_H

View File

@ -470,7 +470,7 @@ QgsMeshVectorRenderer *QgsMeshVectorRenderer::makeVectorRenderer( const QgsTrian
context, size );
break;
case QgsMeshRendererVectorSettings::Streamlines:
renderer = new QgsMeshVectorStreamLineRenderer(
renderer = new QgsMeshVectorStreamlineRenderer(
m,
datasetVectorValues,
scalarActiveFaceFlagValues,

View File

@ -153,8 +153,7 @@ namespace QgsMeshUtils
* Tests if point p is on the face defined with vertices
* \since QGIS 3.12
*/
bool isInTriangleFace( const QgsPointXY point, const QgsMeshFace &face, const QVector<QgsMeshVertex> &vertices )
;
bool isInTriangleFace( const QgsPointXY point, const QgsMeshFace &face, const QVector<QgsMeshVertex> &vertices );
};