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