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