1 /***************************************************************************
2 qgslightswidget.cpp
3 --------------------------------------
4 Date : November 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 "qgslightswidget.h"
17
18 #include "qgs3dmapsettings.h"
19 #include "qgsapplication.h"
20 #include "qgssettings.h"
21
22 #include <QMessageBox>
23 #include <QMenu>
24
QgsLightsWidget(QWidget * parent)25 QgsLightsWidget::QgsLightsWidget( QWidget *parent )
26 : QWidget( parent )
27 {
28 setupUi( this );
29
30 spinPositionX->setClearValue( 0.0 );
31 spinPositionY->setClearValue( 1000.0 );
32 spinPositionZ->setClearValue( 0.0 );
33 spinIntensity->setClearValue( 1.0 );
34 spinA0->setClearValue( 0.0 );
35 spinA1->setClearValue( 0.0 );
36 spinA2->setClearValue( 0.0 );
37 spinDirectionalIntensity->setClearValue( 1.0 );
38
39 mLightsModel = new QgsLightsModel( this );
40 mLightsListView->setModel( mLightsModel );
41
42 connect( mLightsListView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsLightsWidget::selectedLightChanged );
43
44 btnAddLight->setIcon( QIcon( QgsApplication::iconPath( "symbologyAdd.svg" ) ) );
45 btnRemoveLight->setIcon( QIcon( QgsApplication::iconPath( "symbologyRemove.svg" ) ) );
46
47 dialAzimuth->setMaximum( 359 );
48
49 QMenu *addLightMenu = new QMenu( this );
50 QAction *addPointLight = new QAction( tr( "Point Light" ), addLightMenu );
51 connect( addPointLight, &QAction::triggered, this, &QgsLightsWidget::onAddLight );
52 addLightMenu->addAction( addPointLight );
53
54 QAction *addDirectionalLight = new QAction( tr( "Directional Light" ), addLightMenu );
55 connect( addDirectionalLight, &QAction::triggered, this, &QgsLightsWidget::onAddDirectionalLight );
56 addLightMenu->addAction( addDirectionalLight );
57
58 btnAddLight->setMenu( addLightMenu );
59
60 connect( btnRemoveLight, &QToolButton::clicked, this, &QgsLightsWidget::onRemoveLight );
61
62 connect( spinPositionX, qgis::overload<double>::of( &QDoubleSpinBox::valueChanged ), this, &QgsLightsWidget::updateCurrentLightParameters );
63 connect( spinPositionY, qgis::overload<double>::of( &QDoubleSpinBox::valueChanged ), this, &QgsLightsWidget::updateCurrentLightParameters );
64 connect( spinPositionZ, qgis::overload<double>::of( &QDoubleSpinBox::valueChanged ), this, &QgsLightsWidget::updateCurrentLightParameters );
65 connect( spinIntensity, qgis::overload<double>::of( &QDoubleSpinBox::valueChanged ), this, &QgsLightsWidget::updateCurrentLightParameters );
66 connect( btnColor, &QgsColorButton::colorChanged, this, &QgsLightsWidget::updateCurrentLightParameters );
67 connect( spinA0, qgis::overload<double>::of( &QDoubleSpinBox::valueChanged ), this, &QgsLightsWidget::updateCurrentLightParameters );
68 connect( spinA1, qgis::overload<double>::of( &QDoubleSpinBox::valueChanged ), this, &QgsLightsWidget::updateCurrentLightParameters );
69 connect( spinA2, qgis::overload<double>::of( &QDoubleSpinBox::valueChanged ), this, &QgsLightsWidget::updateCurrentLightParameters );
70
71 connect( spinDirectionalIntensity, qgis::overload<double>::of( &QDoubleSpinBox::valueChanged ), this, &QgsLightsWidget::updateCurrentDirectionalLightParameters );
72 connect( btnDirectionalColor, &QgsColorButton::colorChanged, this, &QgsLightsWidget::updateCurrentDirectionalLightParameters );
73
74 connect( dialAzimuth, &QSlider::valueChanged, [this]( int value ) {spinBoxAzimuth->setValue( ( value + 180 ) % 360 );} );
75 connect( sliderAltitude, &QSlider::valueChanged, spinBoxAltitude, &QgsDoubleSpinBox::setValue );
76 connect( spinBoxAzimuth, qgis::overload<double>::of( &QDoubleSpinBox::valueChanged ), this, &QgsLightsWidget::onDirectionChange );
77 connect( spinBoxAltitude, qgis::overload<double>::of( &QDoubleSpinBox::valueChanged ), this, &QgsLightsWidget::onDirectionChange );
78
79 mLightsListView->selectionModel()->select( mLightsModel->index( 0, 0 ), QItemSelectionModel::ClearAndSelect );
80 selectedLightChanged( mLightsListView->selectionModel()->selection(), QItemSelection() );
81 }
82
setLights(const QList<QgsPointLightSettings> & pointLights,const QList<QgsDirectionalLightSettings> & directionalLights)83 void QgsLightsWidget::setLights( const QList<QgsPointLightSettings> &pointLights, const QList<QgsDirectionalLightSettings> &directionalLights )
84 {
85 mLightsModel->setPointLights( pointLights );
86 mLightsModel->setDirectionalLights( directionalLights );
87 mLightsListView->selectionModel()->select( mLightsModel->index( 0, 0 ), QItemSelectionModel::ClearAndSelect );
88 selectedLightChanged( mLightsListView->selectionModel()->selection(), QItemSelection() );
89 }
90
pointLights()91 QList<QgsPointLightSettings> QgsLightsWidget::pointLights()
92 {
93 return mLightsModel->pointLights();
94 }
95
directionalLights()96 QList<QgsDirectionalLightSettings> QgsLightsWidget::directionalLights()
97 {
98 return mLightsModel->directionalLights();
99 }
100
selectedLightChanged(const QItemSelection & selected,const QItemSelection &)101 void QgsLightsWidget::selectedLightChanged( const QItemSelection &selected, const QItemSelection & )
102 {
103 if ( selected.empty() )
104 {
105 mStackedWidget->setCurrentIndex( 0 );
106 return;
107 }
108
109 const QgsLightsModel::LightType lightType = static_cast< QgsLightsModel::LightType >( mLightsModel->data( selected.indexes().at( 0 ), QgsLightsModel::LightTypeRole ).toInt() );
110 const int listIndex = mLightsModel->data( selected.indexes().at( 0 ), QgsLightsModel::LightListIndex ).toInt();
111
112 switch ( lightType )
113 {
114 case QgsLightsModel::Point:
115 mStackedWidget->setCurrentIndex( 1 );
116 showSettingsForPointLight( mLightsModel->pointLights().at( listIndex ) );
117 break;
118
119 case QgsLightsModel::Directional:
120 mStackedWidget->setCurrentIndex( 2 );
121 showSettingsForDirectionalLight( mLightsModel->directionalLights().at( listIndex ) );
122 break;
123 }
124 }
125
showSettingsForPointLight(const QgsPointLightSettings & light)126 void QgsLightsWidget::showSettingsForPointLight( const QgsPointLightSettings &light )
127 {
128 whileBlocking( spinPositionX )->setValue( light.position().x() );
129 whileBlocking( spinPositionY )->setValue( light.position().y() );
130 whileBlocking( spinPositionZ )->setValue( light.position().z() );
131 whileBlocking( btnColor )->setColor( light.color() );
132 whileBlocking( spinIntensity )->setValue( light.intensity() );
133 whileBlocking( spinA0 )->setValue( light.constantAttenuation() );
134 whileBlocking( spinA1 )->setValue( light.linearAttenuation() );
135 whileBlocking( spinA2 )->setValue( light.quadraticAttenuation() );
136 }
137
showSettingsForDirectionalLight(const QgsDirectionalLightSettings & light)138 void QgsLightsWidget::showSettingsForDirectionalLight( const QgsDirectionalLightSettings &light )
139 {
140 mDirectionX = light.direction().x();
141 mDirectionY = light.direction().y();
142 mDirectionZ = light.direction().z();
143 whileBlocking( btnDirectionalColor )->setColor( light.color() );
144 whileBlocking( spinDirectionalIntensity )->setValue( light.intensity() );
145 setAzimuthAltitude();
146 }
147
148
updateCurrentLightParameters()149 void QgsLightsWidget::updateCurrentLightParameters()
150 {
151 const int listIndex = mLightsModel->data( mLightsListView->selectionModel()->selection().indexes().at( 0 ), QgsLightsModel::LightListIndex ).toInt();
152
153 QgsPointLightSettings light;
154 light.setPosition( QgsVector3D( spinPositionX->value(), spinPositionY->value(), spinPositionZ->value() ) );
155 light.setColor( btnColor->color() );
156 light.setIntensity( spinIntensity->value() );
157 light.setConstantAttenuation( spinA0->value() );
158 light.setLinearAttenuation( spinA1->value() );
159 light.setQuadraticAttenuation( spinA2->value() );
160
161 mLightsModel->setPointLightSettings( listIndex, light );
162 }
163
updateCurrentDirectionalLightParameters()164 void QgsLightsWidget::updateCurrentDirectionalLightParameters()
165 {
166 labelX->setText( QString::number( mDirectionX, 'f', 2 ) );
167 labelY->setText( QString::number( mDirectionY, 'f', 2 ) );
168 labelZ->setText( QString::number( mDirectionZ, 'f', 2 ) );
169
170 const int listIndex = mLightsModel->data( mLightsListView->selectionModel()->selection().indexes().at( 0 ), QgsLightsModel::LightListIndex ).toInt();
171
172 QgsDirectionalLightSettings light;
173 light.setDirection( QgsVector3D( mDirectionX, mDirectionY, mDirectionZ ) );
174 light.setColor( btnDirectionalColor->color() );
175 light.setIntensity( spinDirectionalIntensity->value() );
176
177 mLightsModel->setDirectionalLightSettings( listIndex, light );
178 }
179
onAddLight()180 void QgsLightsWidget::onAddLight()
181 {
182 if ( mLightsModel->pointLights().size() >= 8 )
183 {
184 QMessageBox::warning( this, tr( "Add Light" ), tr( "It is not possible to add more than 8 lights to the scene." ) );
185 return;
186 }
187
188 const QModelIndex newIndex = mLightsModel->addPointLight( QgsPointLightSettings() );
189 mLightsListView->selectionModel()->select( newIndex, QItemSelectionModel::ClearAndSelect );
190 emit lightsAdded();
191 }
192
onAddDirectionalLight()193 void QgsLightsWidget::onAddDirectionalLight()
194 {
195 if ( mLightsModel->directionalLights().size() >= 4 )
196 {
197 QMessageBox::warning( this, tr( "Add Directional Light" ), tr( "It is not possible to add more than 4 directional lights to the scene." ) );
198 return;
199 }
200
201 const QModelIndex newIndex = mLightsModel->addDirectionalLight( QgsDirectionalLightSettings() );
202 mLightsListView->selectionModel()->select( newIndex, QItemSelectionModel::ClearAndSelect );
203 emit lightsAdded();
204 }
205
onRemoveLight()206 void QgsLightsWidget::onRemoveLight()
207 {
208 const QItemSelection selected = mLightsListView->selectionModel()->selection();
209 if ( selected.empty() )
210 {
211 return;
212 }
213
214 const int directionalCount = mLightsModel->directionalLights().size();
215 const int pointCount = mLightsModel->pointLights().size();
216
217 mLightsModel->removeRows( selected.indexes().at( 0 ).row(), 1 );
218
219 if ( mLightsModel->directionalLights().size() != directionalCount )
220 emit directionalLightsCountChanged( mLightsModel->directionalLights().size() );
221
222 if ( mLightsModel->rowCount( QModelIndex() ) != directionalCount + pointCount )
223 emit lightsRemoved();
224 }
225
setAzimuthAltitude()226 void QgsLightsWidget::setAzimuthAltitude()
227 {
228 double azimuthAngle;
229 double altitudeAngle;
230
231 double horizontalVectorMagnitude = sqrt( mDirectionX * mDirectionX + mDirectionZ * mDirectionZ );
232
233 if ( horizontalVectorMagnitude == 0 )
234 azimuthAngle = 0;
235 else
236 {
237 azimuthAngle = ( asin( -mDirectionX / horizontalVectorMagnitude ) ) / M_PI * 180;
238 if ( mDirectionZ < 0 )
239 azimuthAngle = 180 - azimuthAngle;
240 azimuthAngle = std::fmod( azimuthAngle + 360.0, 360.0 );
241 }
242
243 whileBlocking( dialAzimuth )->setValue( int( azimuthAngle + 180 ) % 360 );
244 whileBlocking( spinBoxAzimuth )->setValue( azimuthAngle );
245
246 if ( horizontalVectorMagnitude == 0 )
247 altitudeAngle = 90;
248 else
249 altitudeAngle = -atan( mDirectionY / horizontalVectorMagnitude ) / M_PI * 180;
250
251 whileBlocking( spinBoxAltitude )->setValue( altitudeAngle );
252 whileBlocking( sliderAltitude )->setValue( altitudeAngle );
253
254 updateCurrentDirectionalLightParameters();
255 }
256
onDirectionChange()257 void QgsLightsWidget::onDirectionChange()
258 {
259 double altitudeValue = spinBoxAltitude->value();
260 double azimuthValue = spinBoxAzimuth->value();
261
262 double horizontalVectorMagnitude = cos( altitudeValue / 180 * M_PI );
263 mDirectionX = -horizontalVectorMagnitude * sin( azimuthValue / 180 * M_PI );
264 mDirectionZ = horizontalVectorMagnitude * cos( azimuthValue / 180 * M_PI );
265 mDirectionY = -sin( altitudeValue / 180 * M_PI );
266
267 whileBlocking( sliderAltitude )->setValue( altitudeValue );
268 updateCurrentDirectionalLightParameters();
269 }
270
271
272
273 //
274 // QgsLightsModel
275 //
QgsLightsModel(QObject * parent)276 QgsLightsModel::QgsLightsModel( QObject *parent )
277 : QAbstractListModel( parent )
278 {
279
280 }
281
rowCount(const QModelIndex & parent) const282 int QgsLightsModel::rowCount( const QModelIndex &parent ) const
283 {
284 Q_UNUSED( parent )
285 return mPointLights.size() + mDirectionalLights.size();
286 }
287
data(const QModelIndex & index,int role) const288 QVariant QgsLightsModel::data( const QModelIndex &index, int role ) const
289 {
290 if ( index.row() < 0 || index.row() >= rowCount( QModelIndex() ) )
291 return QVariant();
292
293 const LightType lightType = index.row() < mPointLights.size() ? Point : Directional;
294 const int lightListRow = lightType == Point ? index.row() : index.row() - mPointLights.size();
295
296 switch ( role )
297 {
298 case Qt::DisplayRole:
299 case Qt::ToolTipRole:
300 case Qt::EditRole:
301 switch ( lightType )
302 {
303 case Point:
304 return tr( "Point light %1" ).arg( lightListRow + 1 );
305
306 case Directional:
307 return tr( "Directional light %1" ).arg( lightListRow + 1 );
308 }
309 break;
310
311 case LightTypeRole:
312 return lightType;
313
314 case LightListIndex:
315 return lightListRow;
316
317 case Qt::DecorationRole:
318 return QgsApplication::getThemeIcon( QStringLiteral( "/mActionHighlightFeature.svg" ) );
319
320 default:
321 break;
322 }
323 return QVariant();
324 }
325
removeRows(int row,int count,const QModelIndex & parent)326 bool QgsLightsModel::removeRows( int row, int count, const QModelIndex &parent )
327 {
328 beginRemoveRows( parent, row, row + count - 1 );
329 for ( int i = row + count - 1; i >= row; --i )
330 {
331 const LightType lightType = i < mPointLights.size() ? Point : Directional;
332 const int lightListRow = lightType == Point ? i : i - mPointLights.size();
333
334 switch ( lightType )
335 {
336 case Point:
337 mPointLights.removeAt( lightListRow );
338 break;
339
340 case Directional:
341 mDirectionalLights.removeAt( lightListRow );
342 break;
343 }
344 }
345 endRemoveRows();
346 return true;
347 }
348
setPointLights(const QList<QgsPointLightSettings> & lights)349 void QgsLightsModel::setPointLights( const QList<QgsPointLightSettings> &lights )
350 {
351 beginRemoveRows( QModelIndex(), 0, mPointLights.size() - 1 );
352 mPointLights.clear();
353 endRemoveRows();
354
355 beginInsertRows( QModelIndex(), 0, lights.size() - 1 );
356 mPointLights = lights;
357 endInsertRows();
358 }
359
setDirectionalLights(const QList<QgsDirectionalLightSettings> & lights)360 void QgsLightsModel::setDirectionalLights( const QList<QgsDirectionalLightSettings> &lights )
361 {
362 beginRemoveRows( QModelIndex(), mPointLights.size(), mPointLights.size() + mDirectionalLights.size() - 1 );
363 mDirectionalLights.clear();
364 endRemoveRows();
365
366 beginInsertRows( QModelIndex(), mPointLights.size(), mPointLights.size() + lights.size() - 1 );
367 mDirectionalLights = lights;
368 endInsertRows();
369 }
370
pointLights() const371 QList<QgsPointLightSettings> QgsLightsModel::pointLights() const
372 {
373 return mPointLights;
374 }
375
directionalLights() const376 QList<QgsDirectionalLightSettings> QgsLightsModel::directionalLights() const
377 {
378 return mDirectionalLights;
379 }
380
setPointLightSettings(int index,const QgsPointLightSettings & light)381 void QgsLightsModel::setPointLightSettings( int index, const QgsPointLightSettings &light )
382 {
383 mPointLights[ index ] = light;
384 }
385
setDirectionalLightSettings(int index,const QgsDirectionalLightSettings & light)386 void QgsLightsModel::setDirectionalLightSettings( int index, const QgsDirectionalLightSettings &light )
387 {
388 mDirectionalLights[ index ] = light;
389 }
390
addPointLight(const QgsPointLightSettings & light)391 QModelIndex QgsLightsModel::addPointLight( const QgsPointLightSettings &light )
392 {
393 beginInsertRows( QModelIndex(), mPointLights.size(), mPointLights.size() );
394 mPointLights.append( light );
395 endInsertRows();
396
397 return index( mPointLights.size() - 1 );
398 }
399
addDirectionalLight(const QgsDirectionalLightSettings & light)400 QModelIndex QgsLightsModel::addDirectionalLight( const QgsDirectionalLightSettings &light )
401 {
402 beginInsertRows( QModelIndex(), mPointLights.size() + mDirectionalLights.size(), mPointLights.size() + mDirectionalLights.size() );
403 mDirectionalLights.append( light );
404 endInsertRows();
405
406 return index( mPointLights.size() + mDirectionalLights.size() - 1 );
407 }
408