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, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsLightsWidget::updateCurrentLightParameters );
63 connect( spinPositionY, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsLightsWidget::updateCurrentLightParameters );
64 connect( spinPositionZ, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsLightsWidget::updateCurrentLightParameters );
65 connect( spinIntensity, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsLightsWidget::updateCurrentLightParameters );
66 connect( btnColor, &QgsColorButton::colorChanged, this, &QgsLightsWidget::updateCurrentLightParameters );
67 connect( spinA0, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsLightsWidget::updateCurrentLightParameters );
68 connect( spinA1, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsLightsWidget::updateCurrentLightParameters );
69 connect( spinA2, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsLightsWidget::updateCurrentLightParameters );
70
71 connect( spinDirectionalIntensity, qOverload<double>( &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, qOverload<double>( &QDoubleSpinBox::valueChanged ), this, &QgsLightsWidget::onDirectionChange );
77 connect( spinBoxAltitude, qOverload<double>( &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 emit directionalLightsCountChanged( mLightsModel->directionalLights().size() );
205 }
206
onRemoveLight()207 void QgsLightsWidget::onRemoveLight()
208 {
209 const QItemSelection selected = mLightsListView->selectionModel()->selection();
210 if ( selected.empty() )
211 {
212 return;
213 }
214
215 const int directionalCount = mLightsModel->directionalLights().size();
216 const int pointCount = mLightsModel->pointLights().size();
217
218 mLightsModel->removeRows( selected.indexes().at( 0 ).row(), 1 );
219
220 if ( mLightsModel->directionalLights().size() != directionalCount )
221 emit directionalLightsCountChanged( mLightsModel->directionalLights().size() );
222
223 if ( mLightsModel->rowCount( QModelIndex() ) != directionalCount + pointCount )
224 emit lightsRemoved();
225 }
226
setAzimuthAltitude()227 void QgsLightsWidget::setAzimuthAltitude()
228 {
229 double azimuthAngle;
230 double altitudeAngle;
231
232 const double horizontalVectorMagnitude = sqrt( mDirectionX * mDirectionX + mDirectionZ * mDirectionZ );
233
234 if ( horizontalVectorMagnitude == 0 )
235 azimuthAngle = 0;
236 else
237 {
238 azimuthAngle = ( asin( -mDirectionX / horizontalVectorMagnitude ) ) / M_PI * 180;
239 if ( mDirectionZ < 0 )
240 azimuthAngle = 180 - azimuthAngle;
241 azimuthAngle = std::fmod( azimuthAngle + 360.0, 360.0 );
242 }
243
244 whileBlocking( dialAzimuth )->setValue( int( azimuthAngle + 180 ) % 360 );
245 whileBlocking( spinBoxAzimuth )->setValue( azimuthAngle );
246
247 if ( horizontalVectorMagnitude == 0 )
248 altitudeAngle = 90;
249 else
250 altitudeAngle = -atan( mDirectionY / horizontalVectorMagnitude ) / M_PI * 180;
251
252 whileBlocking( spinBoxAltitude )->setValue( altitudeAngle );
253 whileBlocking( sliderAltitude )->setValue( altitudeAngle );
254
255 updateCurrentDirectionalLightParameters();
256 }
257
onDirectionChange()258 void QgsLightsWidget::onDirectionChange()
259 {
260 const double altitudeValue = spinBoxAltitude->value();
261 const double azimuthValue = spinBoxAzimuth->value();
262
263 const double horizontalVectorMagnitude = cos( altitudeValue / 180 * M_PI );
264 mDirectionX = -horizontalVectorMagnitude * sin( azimuthValue / 180 * M_PI );
265 mDirectionZ = horizontalVectorMagnitude * cos( azimuthValue / 180 * M_PI );
266 mDirectionY = -sin( altitudeValue / 180 * M_PI );
267
268 whileBlocking( sliderAltitude )->setValue( altitudeValue );
269 updateCurrentDirectionalLightParameters();
270 }
271
272
273
274 //
275 // QgsLightsModel
276 //
QgsLightsModel(QObject * parent)277 QgsLightsModel::QgsLightsModel( QObject *parent )
278 : QAbstractListModel( parent )
279 {
280
281 }
282
rowCount(const QModelIndex & parent) const283 int QgsLightsModel::rowCount( const QModelIndex &parent ) const
284 {
285 Q_UNUSED( parent )
286 return mPointLights.size() + mDirectionalLights.size();
287 }
288
data(const QModelIndex & index,int role) const289 QVariant QgsLightsModel::data( const QModelIndex &index, int role ) const
290 {
291 if ( index.row() < 0 || index.row() >= rowCount( QModelIndex() ) )
292 return QVariant();
293
294 const LightType lightType = index.row() < mPointLights.size() ? Point : Directional;
295 const int lightListRow = lightType == Point ? index.row() : index.row() - mPointLights.size();
296
297 switch ( role )
298 {
299 case Qt::DisplayRole:
300 case Qt::ToolTipRole:
301 case Qt::EditRole:
302 switch ( lightType )
303 {
304 case Point:
305 return tr( "Point light %1" ).arg( lightListRow + 1 );
306
307 case Directional:
308 return tr( "Directional light %1" ).arg( lightListRow + 1 );
309 }
310 break;
311
312 case LightTypeRole:
313 return lightType;
314
315 case LightListIndex:
316 return lightListRow;
317
318 case Qt::DecorationRole:
319 return QgsApplication::getThemeIcon( QStringLiteral( "/mActionHighlightFeature.svg" ) );
320
321 default:
322 break;
323 }
324 return QVariant();
325 }
326
removeRows(int row,int count,const QModelIndex & parent)327 bool QgsLightsModel::removeRows( int row, int count, const QModelIndex &parent )
328 {
329 beginRemoveRows( parent, row, row + count - 1 );
330 for ( int i = row + count - 1; i >= row; --i )
331 {
332 const LightType lightType = i < mPointLights.size() ? Point : Directional;
333 const int lightListRow = lightType == Point ? i : i - mPointLights.size();
334
335 switch ( lightType )
336 {
337 case Point:
338 mPointLights.removeAt( lightListRow );
339 break;
340
341 case Directional:
342 mDirectionalLights.removeAt( lightListRow );
343 break;
344 }
345 }
346 endRemoveRows();
347 return true;
348 }
349
setPointLights(const QList<QgsPointLightSettings> & lights)350 void QgsLightsModel::setPointLights( const QList<QgsPointLightSettings> &lights )
351 {
352 beginRemoveRows( QModelIndex(), 0, mPointLights.size() - 1 );
353 mPointLights.clear();
354 endRemoveRows();
355
356 beginInsertRows( QModelIndex(), 0, lights.size() - 1 );
357 mPointLights = lights;
358 endInsertRows();
359 }
360
setDirectionalLights(const QList<QgsDirectionalLightSettings> & lights)361 void QgsLightsModel::setDirectionalLights( const QList<QgsDirectionalLightSettings> &lights )
362 {
363 beginRemoveRows( QModelIndex(), mPointLights.size(), mPointLights.size() + mDirectionalLights.size() - 1 );
364 mDirectionalLights.clear();
365 endRemoveRows();
366
367 beginInsertRows( QModelIndex(), mPointLights.size(), mPointLights.size() + lights.size() - 1 );
368 mDirectionalLights = lights;
369 endInsertRows();
370 }
371
pointLights() const372 QList<QgsPointLightSettings> QgsLightsModel::pointLights() const
373 {
374 return mPointLights;
375 }
376
directionalLights() const377 QList<QgsDirectionalLightSettings> QgsLightsModel::directionalLights() const
378 {
379 return mDirectionalLights;
380 }
381
setPointLightSettings(int index,const QgsPointLightSettings & light)382 void QgsLightsModel::setPointLightSettings( int index, const QgsPointLightSettings &light )
383 {
384 mPointLights[ index ] = light;
385 }
386
setDirectionalLightSettings(int index,const QgsDirectionalLightSettings & light)387 void QgsLightsModel::setDirectionalLightSettings( int index, const QgsDirectionalLightSettings &light )
388 {
389 mDirectionalLights[ index ] = light;
390 }
391
addPointLight(const QgsPointLightSettings & light)392 QModelIndex QgsLightsModel::addPointLight( const QgsPointLightSettings &light )
393 {
394 beginInsertRows( QModelIndex(), mPointLights.size(), mPointLights.size() );
395 mPointLights.append( light );
396 endInsertRows();
397
398 return index( mPointLights.size() - 1 );
399 }
400
addDirectionalLight(const QgsDirectionalLightSettings & light)401 QModelIndex QgsLightsModel::addDirectionalLight( const QgsDirectionalLightSettings &light )
402 {
403 beginInsertRows( QModelIndex(), mPointLights.size() + mDirectionalLights.size(), mPointLights.size() + mDirectionalLights.size() );
404 mDirectionalLights.append( light );
405 endInsertRows();
406
407 return index( mPointLights.size() + mDirectionalLights.size() - 1 );
408 }
409