1 /***************************************************************************
2   qgslayoutitem3dmap.cpp
3   --------------------------------------
4   Date                 : August 2018
5   Copyright            : (C) 2018 by Martin Dobias
6   Email                : wonder dot sk at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 #include "qgslayoutitem3dmap.h"
17 
18 #include "qgs3dmapscene.h"
19 #include "qgs3dutils.h"
20 #include "qgscameracontroller.h"
21 #include "qgslayout.h"
22 #include "qgslayoutmodel.h"
23 #include "qgslayoutitemregistry.h"
24 #include "qgsoffscreen3dengine.h"
25 #include "qgspostprocessingentity.h"
26 #include "qgsshadowrenderingframegraph.h"
27 #include "qgswindow3dengine.h"
28 
QgsLayoutItem3DMap(QgsLayout * layout)29 QgsLayoutItem3DMap::QgsLayoutItem3DMap( QgsLayout *layout )
30   : QgsLayoutItem( layout )
31 {
32   assignFreeId();
33 
34   connect( this, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutItem3DMap::onSizePositionChanged );
35 }
36 
37 QgsLayoutItem3DMap::~QgsLayoutItem3DMap() = default;
38 
39 
create(QgsLayout * layout)40 QgsLayoutItem3DMap *QgsLayoutItem3DMap::create( QgsLayout *layout )
41 {
42   return new QgsLayoutItem3DMap( layout );
43 }
44 
type() const45 int QgsLayoutItem3DMap::type() const
46 {
47   return QgsLayoutItemRegistry::Layout3DMap;
48 }
49 
icon() const50 QIcon QgsLayoutItem3DMap::icon() const
51 {
52   return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItem3DMap.svg" ) );
53 }
54 
assignFreeId()55 void QgsLayoutItem3DMap::assignFreeId()
56 {
57   if ( !mLayout )
58     return;
59 
60   QList<QgsLayoutItem3DMap *> mapsList;
61   mLayout->layoutItems( mapsList );
62 
63   int maxId = -1;
64   bool used = false;
65   for ( QgsLayoutItem3DMap *map : qgis::as_const( mapsList ) )
66   {
67     if ( map == this )
68       continue;
69 
70     if ( map->mMapId == mMapId )
71       used = true;
72 
73     maxId = std::max( maxId, map->mMapId );
74   }
75   if ( used )
76   {
77     mMapId = maxId + 1;
78     mLayout->itemsModel()->updateItemDisplayName( this );
79   }
80   updateToolTip();
81 }
82 
displayName() const83 QString QgsLayoutItem3DMap::displayName() const
84 {
85   if ( !QgsLayoutItem::id().isEmpty() )
86   {
87     return QgsLayoutItem::id();
88   }
89 
90   return tr( "3D Map %1" ).arg( mMapId );
91 }
92 
updateToolTip()93 void QgsLayoutItem3DMap::updateToolTip()
94 {
95   setToolTip( displayName() );
96 }
97 
draw(QgsLayoutItemRenderContext & context)98 void QgsLayoutItem3DMap::draw( QgsLayoutItemRenderContext &context )
99 {
100   QgsRenderContext &ctx = context.renderContext();
101   QPainter *painter = ctx.painter();
102 
103   int w = static_cast<int>( std::ceil( rect().width() * ctx.scaleFactor() ) );
104   int h = static_cast<int>( std::ceil( rect().height() * ctx.scaleFactor() ) );
105   QRect r( 0, 0, w, h );
106 
107   painter->save();
108 
109   if ( !mSettings )
110   {
111     painter->drawText( r, Qt::AlignCenter, tr( "Scene not set" ) );
112     painter->restore();
113     return;
114   }
115 
116   if ( mSettings->backgroundColor() != backgroundColor() )
117   {
118     mSettings->setBackgroundColor( backgroundColor() );
119     mCapturedImage = QImage();
120   }
121 
122   if ( !mCapturedImage.isNull() )
123   {
124     painter->drawImage( r, mCapturedImage );
125     painter->restore();
126     return;
127   }
128 
129   // we do not have a cached image of the rendered scene - let's request one from the engine
130 
131   if ( mLayout->renderContext().isPreviewRender() )
132   {
133     painter->drawText( r, Qt::AlignCenter, tr( "Loading" ) );
134     painter->restore();
135   }
136 
137   QSizeF sizePixels = mLayout->renderContext().measurementConverter().convert( sizeWithUnits(), QgsUnitTypes::LayoutPixels ).toQSizeF();
138   QSize sizePixelsInt = QSize( static_cast<int>( std::ceil( sizePixels.width() ) ),
139                                static_cast<int>( std::ceil( sizePixels.height() ) ) );
140 
141   if ( isTemporal() )
142     mSettings->setTemporalRange( temporalRange() );
143 
144   if ( !mEngine )
145   {
146     mEngine.reset( new QgsOffscreen3DEngine );
147     connect( mEngine.get(), &QgsAbstract3DEngine::imageCaptured, this, &QgsLayoutItem3DMap::onImageCaptured );
148 
149     mEngine->setSize( sizePixelsInt );
150     mScene = new Qgs3DMapScene( *mSettings, mEngine.get() );
151     connect( mScene, &Qgs3DMapScene::sceneStateChanged, this, &QgsLayoutItem3DMap::onSceneStateChanged );
152 
153     mEngine->setRootEntity( mScene );
154 
155   }
156 
157   if ( mEngine->size() != sizePixelsInt )
158     mEngine->setSize( sizePixelsInt );
159 
160   mScene->cameraController()->setCameraPose( mCameraPose );
161 
162   if ( mLayout->renderContext().isPreviewRender() )
163   {
164     onSceneStateChanged();
165   }
166   else
167   {
168     // we can't just request a capture and hope it will arrive at some point later.
169     // this is not a preview, we need the rendered scene now!
170     if ( mDrawing )
171       return;
172     mDrawing = true;
173     Qgs3DUtils::captureSceneImage( *mEngine.get(), mScene );
174     QImage img = Qgs3DUtils::captureSceneImage( *mEngine.get(), mScene );
175     painter->drawImage( r, img );
176     painter->restore();
177     mDrawing = false;
178   }
179 }
180 
onImageCaptured(const QImage & img)181 void QgsLayoutItem3DMap::onImageCaptured( const QImage &img )
182 {
183   mCapturedImage = img;
184   update();
185 }
186 
onSceneStateChanged()187 void QgsLayoutItem3DMap::onSceneStateChanged()
188 {
189   if ( mCapturedImage.isNull() && mScene->sceneState() == Qgs3DMapScene::Ready )
190   {
191     mEngine->requestCaptureImage();
192   }
193 }
194 
onSizePositionChanged()195 void QgsLayoutItem3DMap::onSizePositionChanged()
196 {
197   mCapturedImage = QImage();
198   update();
199 }
200 
201 
writePropertiesToElement(QDomElement & element,QDomDocument & document,const QgsReadWriteContext & context) const202 bool QgsLayoutItem3DMap::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
203 {
204   if ( mSettings )
205   {
206     QDomElement elemSettings = mSettings->writeXml( document, context );
207     element.appendChild( elemSettings );
208   }
209 
210   QDomElement elemCameraPose = mCameraPose.writeXml( document );
211   element.appendChild( elemCameraPose );
212 
213   //temporal settings
214   QDomElement elemTemporal = document.createElement( QStringLiteral( "temporal-settings" ) );
215   elemTemporal.setAttribute( QStringLiteral( "isTemporal" ), isTemporal() ? 1 : 0 );
216   if ( isTemporal() )
217   {
218     elemTemporal.setAttribute( QStringLiteral( "temporalRangeBegin" ), temporalRange().begin().toString( Qt::ISODate ) );
219     elemTemporal.setAttribute( QStringLiteral( "temporalRangeEnd" ), temporalRange().end().toString( Qt::ISODate ) );
220   }
221   element.appendChild( elemTemporal );
222 
223   return true;
224 }
225 
readPropertiesFromElement(const QDomElement & element,const QDomDocument & document,const QgsReadWriteContext & context)226 bool QgsLayoutItem3DMap::readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context )
227 {
228   Q_UNUSED( document )
229   QDomElement elemSettings = element.firstChildElement( QStringLiteral( "qgis3d" ) );
230   if ( !elemSettings.isNull() )
231   {
232     mSettings.reset( new Qgs3DMapSettings );
233     mSettings->readXml( elemSettings, context );
234     if ( mLayout->project() )
235     {
236       mSettings->resolveReferences( *mLayout->project() );
237 
238       mSettings->setTransformContext( mLayout->project()->transformContext() );
239       mSettings->setPathResolver( mLayout->project()->pathResolver() );
240       mSettings->setMapThemeCollection( mLayout->project()->mapThemeCollection() );
241     }
242   }
243 
244   QDomElement elemCameraPose = element.firstChildElement( QStringLiteral( "camera-pose" ) );
245   if ( !elemCameraPose.isNull() )
246     mCameraPose.readXml( elemCameraPose );
247 
248   //temporal settings
249   QDomElement elemTemporal = element.firstChildElement( QStringLiteral( "temporal-settings" ) );
250   setIsTemporal( elemTemporal.attribute( QStringLiteral( "isTemporal" ) ).toInt() );
251   if ( isTemporal() )
252   {
253     QDateTime begin = QDateTime::fromString( elemTemporal.attribute( QStringLiteral( "temporalRangeBegin" ) ), Qt::ISODate );
254     QDateTime end = QDateTime::fromString( elemTemporal.attribute( QStringLiteral( "temporalRangeBegin" ) ), Qt::ISODate );
255     setTemporalRange( QgsDateTimeRange( begin, end ) );
256   }
257 
258   return true;
259 }
260 
finalizeRestoreFromXml()261 void QgsLayoutItem3DMap::finalizeRestoreFromXml()
262 {
263   assignFreeId();
264 }
265 
setMapSettings(Qgs3DMapSettings * settings)266 void QgsLayoutItem3DMap::setMapSettings( Qgs3DMapSettings *settings )
267 {
268   mSettings.reset( settings );
269 
270   mEngine.reset();
271   mScene = nullptr;
272 
273   mCapturedImage = QImage();
274   update();
275 }
276 
refresh()277 void QgsLayoutItem3DMap::refresh()
278 {
279   QgsLayoutItem::refresh();
280 
281   mCapturedImage = QImage();
282 }
283 
setCameraPose(const QgsCameraPose & pose)284 void QgsLayoutItem3DMap::setCameraPose( const QgsCameraPose &pose )
285 {
286   if ( mCameraPose == pose )
287     return;
288 
289   mCameraPose = pose;
290   mCapturedImage = QImage();
291   update();
292 }
293