1 /***************************************************************************
2                          qgsmesh3dmaterial.cpp
3                          -------------------------
4     begin                : january 2020
5     copyright            : (C) 2020 by Vincent Cloarec
6     email                : vcloarec at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "qgsmesh3dmaterial_p.h"
19 
20 #include <Qt3DRender/QEffect>
21 #include <Qt3DRender/QGraphicsApiFilter>
22 #include <Qt3DRender/QParameter>
23 #include <Qt3DRender/QTexture>
24 
25 #include <QUrl>
26 #include <QVector2D>
27 #include <QVector3D>
28 #include <QVector4D>
29 #include <Qt3DRender/QBuffer>
30 #include <QByteArray>
31 
32 #include "qgsmeshlayer.h"
33 #include "qgsmeshlayerutils.h"
34 #include "qgstriangularmesh.h"
35 
36 class ColorRampTextureGenerator: public Qt3DRender::QTextureImageDataGenerator
37 {
38 
39   public:
ColorRampTextureGenerator(const QgsColorRampShader & colorRampShader,double verticalScale=1)40     ColorRampTextureGenerator( const QgsColorRampShader &colorRampShader, double verticalScale = 1 ):
41       mColorRampShader( colorRampShader ),
42       mVerticalScale( verticalScale )
43     {}
44 
45   public:
operator ()()46     Qt3DRender::QTextureImageDataPtr operator()() override
47     {
48       Qt3DRender::QTextureImageDataPtr dataPtr = Qt3DRender::QTextureImageDataPtr::create();
49       dataPtr->setFormat( QOpenGLTexture::RGBA32F );
50       dataPtr->setTarget( QOpenGLTexture::Target1D );
51       dataPtr->setPixelFormat( QOpenGLTexture::RGBA );
52       dataPtr->setPixelType( QOpenGLTexture::Float32 );
53 
54       QByteArray data;
55       QList<QgsColorRampShader::ColorRampItem> colorItemList = mColorRampShader.colorRampItemList();
56       int size = colorItemList.count() ;
57 
58       dataPtr->setWidth( size );
59       dataPtr->setHeight( 1 );
60       dataPtr->setDepth( 1 );
61       dataPtr->setFaces( 1 );
62       dataPtr->setLayers( 1 );
63       dataPtr->setMipLevels( 1 );
64 
65       for ( int i = 0; i < colorItemList.count(); ++i )
66       {
67         float mag = float( colorItemList.at( i ).value * mVerticalScale );
68 
69         QColor color = colorItemList.at( i ).color;
70         float rf = float( color.redF() );
71         float gf = float( color.greenF() );
72         float bf = float( color.blueF() );
73 
74         data.append( reinterpret_cast<const char *>( &mag ), sizeof( float ) );
75         data.append( reinterpret_cast<const char *>( &rf ), sizeof( float ) );
76         data.append( reinterpret_cast<const char *>( &gf ), sizeof( float ) );
77         data.append( reinterpret_cast<const char *>( &bf ), sizeof( float ) );
78 
79       }
80 
81       dataPtr->setData( data, sizeof( float ) ); //size is the size of the type, here float
82 
83       return dataPtr;
84     }
85 
operator ==(const Qt3DRender::QTextureImageDataGenerator & other) const86     bool operator ==( const Qt3DRender::QTextureImageDataGenerator &other ) const override
87     {
88       const ColorRampTextureGenerator *otherFunctor = functor_cast<ColorRampTextureGenerator>( &other );
89       if ( !otherFunctor )
90         return false;
91 
92       QgsColorRampShader otherColorRampShader = otherFunctor->mColorRampShader;
93 
94       if ( mColorRampShader.colorRampItemList().count() != otherColorRampShader.colorRampItemList().count() ||
95            mColorRampShader.classificationMode() != otherColorRampShader.classificationMode() ||
96            mColorRampShader.colorRampType() != otherColorRampShader.colorRampType() )
97       {
98         return false;
99       }
100 
101       QList<QgsColorRampShader::ColorRampItem> colorItemList = mColorRampShader.colorRampItemList();
102       QList<QgsColorRampShader::ColorRampItem> otherColorItemList = otherColorRampShader.colorRampItemList();
103       for ( int i = 0; i < colorItemList.count(); ++i )
104       {
105         const QColor color = colorItemList.at( i ).color;
106         const QColor otherColor = otherColorItemList.at( i ).color;
107         double value = colorItemList.at( i ).value;
108         double otherValue = otherColorItemList.at( i ).value;
109         if ( color != otherColor ||
110              ( !std::isnan( value ) && !std::isnan( otherValue ) && colorItemList.at( i ).value != otherColorItemList.at( i ).value ) ||
111              ( std::isnan( value ) != std::isnan( otherValue ) ) )
112           return false;
113       }
114 
115       return true;
116     }
117 
118     QT3D_FUNCTOR( ColorRampTextureGenerator )
119 
120   private:
121     QgsColorRampShader mColorRampShader;
122     double mVerticalScale = 1;
123 };
124 
125 
126 class ColorRampTexture: public Qt3DRender::QAbstractTextureImage
127 {
128   public:
ColorRampTexture(const QgsColorRampShader & colorRampShader,double verticalScale=1,Qt3DCore::QNode * parent=nullptr)129     ColorRampTexture( const QgsColorRampShader &colorRampShader, double verticalScale = 1, Qt3DCore::QNode *parent = nullptr ):
130       Qt3DRender::QAbstractTextureImage( parent ),
131       mColorRampShader( colorRampShader ),
132       mVerticalScale( verticalScale )
133     {
134 
135     }
136     // QAbstractTextureImage interface
137   protected:
dataGenerator() const138     Qt3DRender::QTextureImageDataGeneratorPtr dataGenerator() const override
139     {
140       return Qt3DRender::QTextureImageDataGeneratorPtr( new ColorRampTextureGenerator( mColorRampShader, mVerticalScale ) );
141     }
142 
143   private:
144     QgsColorRampShader mColorRampShader;
145     double mVerticalScale = 1;
146 };
147 
148 
149 class ArrowsTextureGenerator: public Qt3DRender::QTextureImageDataGenerator
150 {
151   public:
ArrowsTextureGenerator(const QVector<QgsVector> & vectors,const QSize & size,bool fixedSize,double maxVectorLength)152     ArrowsTextureGenerator( const QVector<QgsVector> &vectors, const QSize &size, bool fixedSize, double maxVectorLength ):
153       mVectors( vectors ), mSize( size ), mFixedSize( fixedSize ), mMaxVectorLength( maxVectorLength )
154     {}
155 
operator ()()156     Qt3DRender::QTextureImageDataPtr operator()() override
157     {
158       Qt3DRender::QTextureImageDataPtr dataPtr = Qt3DRender::QTextureImageDataPtr::create();
159       dataPtr->setFormat( QOpenGLTexture::RG32F );
160       dataPtr->setTarget( QOpenGLTexture::Target2D );
161       dataPtr->setPixelFormat( QOpenGLTexture::RG );
162       dataPtr->setPixelType( QOpenGLTexture::Float32 );
163 
164       QByteArray data;
165 
166       dataPtr->setWidth( mSize.width() );
167       dataPtr->setHeight( mSize.height() );
168       dataPtr->setDepth( 1 );
169       dataPtr->setFaces( 1 );
170       dataPtr->setLayers( 1 );
171       dataPtr->setMipLevels( 1 );
172 
173       if ( mSize.isValid() )
174       {
175         data.resize( 2 * mSize.width()*mSize.height()*sizeof( float ) );
176         float *fptr = reinterpret_cast<float *>( data.data() );
177         for ( int i = 0; i < mSize.width()*mSize.height(); ++i )
178         {
179           if ( mFixedSize )
180             *fptr++ = 1;
181           else
182             *fptr++ = mVectors.at( i ).length() / mMaxVectorLength;
183 
184           *fptr++ = mVectors.at( i ).angle();
185         }
186       }
187 
188       dataPtr->setData( data, sizeof( float ) ); //size is the size of the type, here float
189       return dataPtr;
190     }
191 
operator ==(const Qt3DRender::QTextureImageDataGenerator & other) const192     bool operator ==( const Qt3DRender::QTextureImageDataGenerator &other ) const override
193     {
194       const ArrowsTextureGenerator *otherFunctor = functor_cast<ArrowsTextureGenerator>( &other );
195       if ( !otherFunctor )
196         return false;
197 
198       return ( otherFunctor->mVectors == mVectors &&
199                otherFunctor->mSize == mSize &&
200                otherFunctor->mFixedSize == mFixedSize );
201     }
202 
203   private:
204     const QVector<QgsVector> mVectors;
205     const QSize mSize;
206     const bool mFixedSize;
207     const double mMaxVectorLength;
208 
209     QT3D_FUNCTOR( ArrowsTextureGenerator )
210 };
211 
212 
213 class ArrowsGridTexture: public Qt3DRender::QAbstractTextureImage
214 {
215   public:
ArrowsGridTexture(const QVector<QgsVector> & vectors,const QSize & size,bool fixedSize,double maxVectorLength)216     ArrowsGridTexture( const QVector<QgsVector> &vectors, const QSize &size, bool fixedSize, double maxVectorLength ):
217       mVectors( vectors ), mSize( size ), mFixedSize( fixedSize ), mMaxVectorLength( maxVectorLength )
218     {}
219 
220   protected:
dataGenerator() const221     Qt3DRender::QTextureImageDataGeneratorPtr dataGenerator() const override
222     {
223       return Qt3DRender::QTextureImageDataGeneratorPtr( new ArrowsTextureGenerator( mVectors, mSize, mFixedSize, mMaxVectorLength ) );
224     }
225 
226   private:
227     const QVector<QgsVector> mVectors;
228     const QSize mSize;
229     const bool mFixedSize;
230     const double mMaxVectorLength;
231 };
232 
233 
QgsMesh3dMaterial(QgsMeshLayer * layer,const QgsDateTimeRange & timeRange,const QgsVector3D & origin,const QgsMesh3DSymbol * symbol,MagnitudeType magnitudeType)234 QgsMesh3dMaterial::QgsMesh3dMaterial( QgsMeshLayer *layer,
235                                       const QgsDateTimeRange &timeRange,
236                                       const QgsVector3D &origin,
237                                       const QgsMesh3DSymbol *symbol,
238                                       MagnitudeType magnitudeType )
239   : mSymbol( symbol->clone() )
240   , mMagnitudeType( magnitudeType )
241   , mOrigin( origin )
242 {
243   Qt3DRender::QEffect *eff = new Qt3DRender::QEffect( this );
244 
245   configure();
246 
247   // this method has to be called even if there isn't arrows (terrain) because it configures the parameter of shaders
248   // If al the parameters ("uniform" in shaders) are not defined in QGis, the shaders it happens the sahder doesn't work (depend on hardware?)
249   configureArrows( layer, timeRange );
250 
251   eff->addTechnique( mTechnique );
252   setEffect( eff );
253 }
254 
configure()255 void QgsMesh3dMaterial::configure()
256 {
257   // Create the texture to pass the color ramp
258   Qt3DRender::QTexture1D *colorRampTexture = nullptr;
259   if ( mSymbol->colorRampShader().colorRampItemList().count() > 0 )
260   {
261     colorRampTexture = new Qt3DRender::QTexture1D( this );
262     switch ( mMagnitudeType )
263     {
264       case QgsMesh3dMaterial::ZValue:
265         // if the color shading is done with the Z value of vertices, the color ramp has to be adapted with vertical scale
266         colorRampTexture->addTextureImage( new ColorRampTexture( mSymbol->colorRampShader(), mSymbol->verticalScale() ) );
267         break;
268       case QgsMesh3dMaterial::ScalarDataSet:
269         // if the color shading is done with scalar dataset, no vertical scale to use
270         colorRampTexture->addTextureImage( new ColorRampTexture( mSymbol->colorRampShader(), 1 ) );
271         break;
272     }
273 
274     colorRampTexture->setMinificationFilter( Qt3DRender::QTexture1D::Linear );
275     colorRampTexture->setMagnificationFilter( Qt3DRender::QTexture1D::Linear );
276   }
277 
278   // Create and configure technique
279   mTechnique = new Qt3DRender::QTechnique();
280   mTechnique->graphicsApiFilter()->setApi( Qt3DRender::QGraphicsApiFilter::OpenGL );
281   mTechnique->graphicsApiFilter()->setProfile( Qt3DRender::QGraphicsApiFilter::CoreProfile );
282   mTechnique->graphicsApiFilter()->setMajorVersion( 3 );
283   mTechnique->graphicsApiFilter()->setMinorVersion( 3 );
284   Qt3DRender::QFilterKey *filterKey = new Qt3DRender::QFilterKey();
285   filterKey->setName( QStringLiteral( "renderingStyle" ) );
286   filterKey->setValue( QStringLiteral( "forward" ) );
287   mTechnique->addFilterKey( filterKey );
288 
289   Qt3DRender::QRenderPass *renderPass = new Qt3DRender::QRenderPass();
290   Qt3DRender::QShaderProgram *shaderProgram = new Qt3DRender::QShaderProgram();
291 
292   //Load shader programs
293   QUrl urlVert( QStringLiteral( "qrc:/shaders/mesh/mesh.vert" ) );
294   shaderProgram->setShaderCode( Qt3DRender::QShaderProgram::Vertex, shaderProgram->loadSource( urlVert ) );
295   QUrl urlGeom( QStringLiteral( "qrc:/shaders/mesh/mesh.geom" ) );
296   shaderProgram->setShaderCode( Qt3DRender::QShaderProgram::Geometry, shaderProgram->loadSource( urlGeom ) );
297   QUrl urlFrag( QStringLiteral( "qrc:/shaders/mesh/mesh.frag" ) );
298   shaderProgram->setShaderCode( Qt3DRender::QShaderProgram::Fragment, shaderProgram->loadSource( urlFrag ) );
299 
300   renderPass->setShaderProgram( shaderProgram );
301   mTechnique->addRenderPass( renderPass );
302 
303   // Parameters
304   mTechnique->addParameter( new Qt3DRender::QParameter( "flatTriangles", ( !mSymbol->smoothedTriangles() ) ) );
305   QColor wireframecolor = mSymbol->wireframeLineColor();
306   mTechnique->addParameter( new Qt3DRender::QParameter( "lineWidth", float( mSymbol->wireframeLineWidth() ) ) );
307   mTechnique->addParameter( new Qt3DRender::QParameter( "lineColor", QVector4D( wireframecolor.redF(), wireframecolor.greenF(), wireframecolor.blueF(), 1.0f ) ) );
308   mTechnique->addParameter( new Qt3DRender::QParameter( "wireframeEnabled", mSymbol->wireframeEnabled() ) );
309   mTechnique->addParameter( new Qt3DRender::QParameter( "textureType", int( mSymbol->renderingStyle() ) ) );
310   if ( colorRampTexture )
311     mTechnique->addParameter( new Qt3DRender::QParameter( "colorRampTexture", colorRampTexture ) ) ;
312   mTechnique->addParameter( new Qt3DRender::QParameter( "colorRampCount", mSymbol->colorRampShader().colorRampItemList().count() ) );
313   int colorRampType = mSymbol->colorRampShader().colorRampType();
314   mTechnique->addParameter( new Qt3DRender::QParameter( "colorRampType", colorRampType ) );
315   QColor meshColor = mSymbol->singleMeshColor();
316   mTechnique->addParameter( new Qt3DRender::QParameter( "meshColor", QVector4D( meshColor.redF(), meshColor.greenF(), meshColor.blueF(), 1.0f ) ) );
317   mTechnique->addParameter( new Qt3DRender::QParameter( "isScalarMagnitude", ( mMagnitudeType == QgsMesh3dMaterial::ScalarDataSet ) ) );
318 }
319 
configureArrows(QgsMeshLayer * layer,const QgsDateTimeRange & timeRange)320 void QgsMesh3dMaterial::configureArrows( QgsMeshLayer *layer, const QgsDateTimeRange &timeRange )
321 {
322   QgsMeshDatasetIndex datasetIndex;
323   QColor arrowsColor;
324   QgsMeshDatasetGroupMetadata meta;
325 
326   if ( layer )
327     datasetIndex = layer->activeVectorDatasetAtTime( timeRange );
328 
329   QVector<QgsVector> vectors;
330   QSize gridSize;
331   QgsPointXY minCorner;
332   std::unique_ptr< Qt3DRender::QParameter > arrowsEnabledParameter = qgis::make_unique< Qt3DRender::QParameter >( "arrowsEnabled", nullptr );
333   if ( !layer || mMagnitudeType != MagnitudeType::ScalarDataSet || !mSymbol->arrowsEnabled() || meta.isScalar() || !datasetIndex.isValid() )
334     arrowsEnabledParameter->setValue( false );
335   else
336   {
337     meta = layer->datasetGroupMetadata( datasetIndex );
338     arrowsColor = layer->rendererSettings().vectorSettings( datasetIndex.group() ).color();
339     arrowsEnabledParameter->setValue( true );
340     int maxSize = mSymbol->maximumTextureSize();
341     // construct grid
342     QgsRectangle gridExtent = layer->triangularMesh()->extent();
343     gridSize = QSize( maxSize, maxSize );
344     double xSpacing = mSymbol->arrowsSpacing();
345     double ySpacing = mSymbol->arrowsSpacing();
346     // check the size of the grid and adjust the spacing if needed
347     int desiredXSize = int( gridExtent.width() / xSpacing );
348     if ( desiredXSize > maxSize )
349       xSpacing = gridExtent.width() / maxSize;
350     else
351       gridSize.setWidth( desiredXSize );
352 
353     int desiredYSize = int( gridExtent.height() / ySpacing );
354     if ( desiredYSize > maxSize )
355       ySpacing = gridExtent.height() / maxSize;
356     else
357       gridSize.setHeight( desiredYSize );
358 
359     double xMin = gridExtent.xMinimum() + xSpacing / 2;
360     double yMin = gridExtent.yMinimum() + ySpacing / 2;
361     minCorner = QgsPointXY( xMin, yMin );
362 
363     vectors = QgsMeshLayerUtils::griddedVectorValues(
364                 layer,
365                 datasetIndex,
366                 xSpacing,
367                 ySpacing,
368                 gridSize,
369                 minCorner );
370 
371     if ( vectors.isEmpty() )
372       return;
373   }
374 
375   mTechnique->addParameter( arrowsEnabledParameter.release() )  ;
376 
377   Qt3DRender::QTexture2D *arrowsGridTexture = new Qt3DRender::QTexture2D( this );
378   arrowsGridTexture->addTextureImage( new ArrowsGridTexture( vectors, gridSize, mSymbol->arrowsFixedSize(), meta.maximum() ) );
379   arrowsGridTexture->setMinificationFilter( Qt3DRender::QTexture2D::Nearest );
380   arrowsGridTexture->setMagnificationFilter( Qt3DRender::QTexture2D::Nearest );
381 
382   Qt3DRender::QTexture2D *arrowTexture = new Qt3DRender::QTexture2D( this );
383   Qt3DRender::QTextureImage *arrowTextureImage = new Qt3DRender::QTextureImage();
384   arrowTextureImage->setSource( QStringLiteral( "qrc:/textures/arrow.png" ) );
385   arrowTexture->addTextureImage( arrowTextureImage );
386   arrowTexture->setMinificationFilter( Qt3DRender::QTexture2D::Nearest );
387   arrowTexture->setMagnificationFilter( Qt3DRender::QTexture2D::Nearest );
388   mTechnique->addParameter( new Qt3DRender::QParameter( "arrowsColor", QVector4D( arrowsColor.redF(), arrowsColor.greenF(), arrowsColor.blueF(), 1.0f ) ) ) ;
389   mTechnique->addParameter( new Qt3DRender::QParameter( "arrowsSpacing", float( mSymbol->arrowsSpacing() ) ) ) ;
390   mTechnique->addParameter( new Qt3DRender::QParameter( "arrowTexture", arrowTexture ) );
391   mTechnique->addParameter( new Qt3DRender::QParameter( "arrowsGridTexture", arrowsGridTexture ) ) ;
392   mTechnique->addParameter( new Qt3DRender::QParameter( "arrowsMinCorner", QVector2D( minCorner.x() - mOrigin.x(), -minCorner.y() + mOrigin.y() ) ) ) ;
393 }
394