mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-08 00:06:51 -05:00
[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:
parent
a1002c4574
commit
6d4c995a28
@ -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
|
||||
};
|
||||
|
||||
|
||||
102
python/core/auto_generated/mesh/qgsmeshtracerenderer.sip.in
Normal file
102
python/core/auto_generated/mesh/qgsmeshtracerenderer.sip.in
Normal 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 *
|
||||
************************************************************************/
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -470,7 +470,7 @@ QgsMeshVectorRenderer *QgsMeshVectorRenderer::makeVectorRenderer( const QgsTrian
|
||||
context, size );
|
||||
break;
|
||||
case QgsMeshRendererVectorSettings::Streamlines:
|
||||
renderer = new QgsMeshVectorStreamLineRenderer(
|
||||
renderer = new QgsMeshVectorStreamlineRenderer(
|
||||
m,
|
||||
datasetVectorValues,
|
||||
scalarActiveFaceFlagValues,
|
||||
|
||||
@ -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 );
|
||||
|
||||
};
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
Loading…
x
Reference in New Issue
Block a user