1 /***************************************************************************
2 * This file is part of Tecorrec. *
3 * Copyright 2008 James Hogan <james@albanarts.com> *
5 * Tecorrec is free software: you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation, either version 2 of the License, or *
8 * (at your option) any later version. *
10 * Tecorrec is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with Tecorrec. If not, write to the Free Software Foundation, *
17 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
18 ***************************************************************************/
21 * @file tcViewportWidget.cpp
22 * @brief OpenGL viewport widget.
25 #include "tcViewportWidget.h"
26 #include <tcObserver.h>
28 #include <tcGeoImageData.h>
29 #include <tcChannelManager.h>
30 #include <tcChannel.h>
34 #include <QMouseEvent>
40 * Constructors + destructor
43 /// Primary constructor.
44 tcViewportWidget::tcViewportWidget(QWidget
* parent
)
46 , m_adaptiveQuality(true)
47 , m_polygonMode(GL_FILL
)
48 , m_observer(new tcObserver())
50 , m_interactionMode(Navigation
)
51 , m_mouseInteracting(false)
53 , m_mouseIntersecting(false)
55 , m_mouseSlicing(false)
56 , m_mouseCrossing(false)
58 , m_crossSectioned(false)
59 , m_texturePointObject(0)
60 , m_texturePointMember(0)
62 setMouseTracking(true);
63 setMinimumSize(400,300);
67 tcViewportWidget::~tcViewportWidget()
76 /// Set the globe object.
77 void tcViewportWidget::setGlobe(tcGlobe
* globe
)
80 tcGeo
pos( 7.03227 * M_PI
/180, 45.92093 * M_PI
/180);
81 m_observer
->setFocus(pos
, globe
->radiusAt(pos
));
88 /// Find the coordinates under the mouse.
89 tcGeo
tcViewportWidget::geoAt(float x
, float y
, bool* ok
)
91 maths::Vector
<2,double> screenXy(x
/ width(), 1.0 - y
/ height());
92 maths::Vector
<3,double> obs
= m_observer
->position();
93 maths::Vector
<3,double> ray
= m_observer
->ray(screenXy
, (double)width() / height());
94 double r
= m_observer
->focusAltitude();
97 bool intersecting
= false;
98 // find intersection P of ray and globe
99 // given globe radius r, observer O, ray R, where |R|=1
102 // (O+t*R)*(O+t*R) = r*r
103 // O*O + 2*O*t*R + t*t*R*R = r*r
104 // O*O + 2*O*t*R + t*t - r*r = 0
105 // quadratic in parameter t
106 // a = 1, b = 2*RO c = O*O-r*r RO = O*R
107 // t = (-b +- sqrt(b*b - 4ac)) / 2a
108 // = (-2RO +- sqrt(4*RO*RO - 4*O*O + 4*r*r)) / 2
109 // = (-2*RO +- 2*sqrt(RO*RO - O*O + r*r)) / 2
110 // = RO += sqrt(RO*RO - O*O + r*r)
111 double discriminant
= roro
- obs
.sqr() + r
*r
;
112 if (discriminant
>= 0.0)
114 discriminant
= sqrt(discriminant
);
115 double t
= -ro
- discriminant
;
119 obs
/= r
; // should now be normalized
127 // mouse is outside of globe
128 // use closest point on outline circle of globe
130 // tangential circle: P*(P-O) = 0
133 // line: P/s = O + tR
134 // P = s(O + tR) (s>0)
135 // r*r - s(O + tR)O = 0
136 // r*r - s(OO + tRO) = 0
139 // t = (r*r/s - OO)/RO
141 // (sO + tsR)(sO + tsR - O) = 0
142 // (O + tR)(sO + tsR - O) = 0
143 // sOO + tsRO + tsOR + ttsRR - OO - tRO = 0
144 // sOO + 2tsRO + tts - OO - tRO = 0
145 // s(OO + 2tRO + tt) = OO - tRO
146 // substitute s=rr/(OO+tRO)
147 // rr/(OO+tRO)(OO + 2tRO + tt) = OO - tRO
148 // rr(OO + 2tRO + tt) = (OO - tRO)(OO+tRO)
149 // rrOO + 2rrtRO + rrtt = OO*OO - ttRO*RO
150 // tt(rr + RO*RO) + t(2rrRO) + (rrOO - OO*OO)
151 // a = rr + RO*RO, b = 2rrRO, c = rr00 - OO*OO
152 // t = (-2rrRO +- sqrt(4rrrrRO*RO - 4(rr + RO*RO)(rr00 - OO*OO))) / 2(rr + RO*RO)
153 // t = (-rrRO +- sqrt(rrrrRO*RO - (rr + RO*RO)(rr00 - OO*OO))) / (rr + RO*RO)
157 double discriminant
= rr
*rr
*roro
- (rr
+ roro
)*(rr
*oo
- oo
*oo
);
158 if (discriminant
>= 0)
160 discriminant
= sqrt(discriminant
);
161 double t
= (-rr
*ro
+ discriminant
) / (rr
+ roro
);
162 double s
= rr
/(oo
+ t
*ro
);
167 obs
/= r
; // should now be normalized
180 return tcGeo(obs
, true);
192 /// Change to sun view.
193 void tcViewportWidget::sunView(int imagery
)
195 m_observer
->setView(tcGeo(m_globe
->imagery()[imagery
]->sunDirection(m_observer
->focus()), true));
200 * Interaction control
203 /// Enable navigation mode.
204 void tcViewportWidget::navigationMode()
206 m_interactionMode
= Navigation
;
209 /// Enable texture point selection mode.
210 void tcViewportWidget::texturePointMode(QObject
* receiver
, const char* member
)
212 m_interactionMode
= TexturePoint
;
213 m_texturePointObject
= receiver
;
214 m_texturePointMember
= member
;
218 void tcViewportWidget::setSlice(const tcGeo
& sw
, const tcGeo
& ne
)
226 /// Set cross section.
227 void tcViewportWidget::setCrossSection(const tcGeo
& p1
, const tcGeo
& p2
)
229 m_crossSectioned
= true;
230 m_crossSection
[0] = p1
;
231 m_crossSection
[1] = p2
;
232 // Let all channels know of changes
233 QList
<tcGeoImageData
*> imagery
= m_globe
->imagery();
234 foreach (tcGeoImageData
* image
, imagery
)
236 for (int i
= 0; i
< image
->channelManager()->numChannels(); ++i
)
238 image
->channelManager()->channel(i
)->newCrossSection(p1
, p2
);
246 * General rendering slots
249 /// Change the quality to adaptive.
250 void tcViewportWidget::setQualityAdaptive()
252 m_adaptiveQuality
= true;
256 /// Change the quality to full resolution.
257 void tcViewportWidget::setQualityFull()
259 m_adaptiveQuality
= false;
263 /// Change the polygon mode to normal.
264 void tcViewportWidget::setPolygonModeNormal()
266 m_polygonMode
= GL_FILL
;
270 /// Change the polygon mode to wireframe.
271 void tcViewportWidget::setPolygonModeWireframe()
273 m_polygonMode
= GL_LINE
;
277 /// Remove colour coding.
278 void tcViewportWidget::setColourCodingNone()
280 m_globe
->setColourCoding(tcGlobe::NoColourCoding
);
284 /// Set colour coding to elevation sample alignment.
285 void tcViewportWidget::setColourCodingElevationSampleAlignment()
287 m_globe
->setColourCoding(tcGlobe::ElevationSampleAlignment
);
291 /// Set colour mapping for an output channel to an input band.
292 void tcViewportWidget::setColourMapping(int output
, int input
, int inputGroup
)
294 m_globe
->setColourMapping(output
, input
, inputGroup
);
299 * Elevation modification slots
302 /// Set the primary elevation data set name.
303 void tcViewportWidget::setPrimaryElevationDataSet(const QString
& name
)
307 m_globe
->setElevationDataSet(0, name
);
308 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
313 /// Set the secondary elevation data set name.
314 void tcViewportWidget::setSecondaryElevationDataSet(const QString
& name
)
318 m_globe
->setElevationDataSet(1, name
);
319 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
324 /// Go to flat primary elevation mode.
325 void tcViewportWidget::setPrimaryElevationFlat()
329 m_globe
->setElevationMode(0, tcGlobe::NoElevation
);
330 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
335 /// Go to flat secondary elevation mode.
336 void tcViewportWidget::setSecondaryElevationFlat()
340 m_globe
->setElevationMode(1, tcGlobe::NoElevation
);
341 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
346 /// Go to raw SRTM primary elevation mode.
347 void tcViewportWidget::setPrimaryElevationRaw()
351 m_globe
->setElevationMode(0, tcGlobe::RawElevation
);
352 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
357 /// Go to raw SRTM secondary elevation mode.
358 void tcViewportWidget::setSecondaryElevationRaw()
362 m_globe
->setElevationMode(1, tcGlobe::RawElevation
);
363 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
368 /// Go to corrected primary elevation mode.
369 void tcViewportWidget::setPrimaryElevationCorrected()
373 m_globe
->setElevationMode(0, tcGlobe::CorrectedElevation
);
374 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
379 /// Go to corrected secondary elevation mode.
380 void tcViewportWidget::setSecondaryElevationCorrected()
384 m_globe
->setElevationMode(1, tcGlobe::CorrectedElevation
);
385 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
390 /// Set the interpolation value.
391 void tcViewportWidget::setElevationInterpolation(int interpolation
)
395 m_globe
->setElevationInterpolation((float)interpolation
/100);
396 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
405 void tcViewportWidget::initializeGL()
407 glClearColor(0.0, 0.3, 0.7, 0.0);
410 void tcViewportWidget::resizeGL(int w
, int h
)
412 glViewport(0, 0, (GLint
)w
, (GLint
)h
);
414 double aspect
= (double)w
/ h
;
415 m_observer
->setupProjection(aspect
);
418 void tcViewportWidget::paintGL()
420 static int maxTexUnits
= 0;
421 if (maxTexUnits
== 0)
423 glGetIntegerv(GL_MAX_TEXTURE_UNITS
, &maxTexUnits
);
426 qDebug() << tr("Maximum texture units is %1, but Tecorrec needs at least 3").arg(maxTexUnits
);
431 glClear(GL_COLOR_BUFFER_BIT
| GL_DEPTH_BUFFER_BIT
);
432 glEnable(GL_DEPTH_TEST
);
433 glEnable(GL_CULL_FACE
);
436 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
438 glPolygonMode(GL_FRONT_AND_BACK
, m_polygonMode
);
440 m_observer
->setupModelView();
442 m_globe
->render(m_observer
,
443 !m_sliced
|| m_adaptiveQuality
,
444 (m_sliced
? m_slice
: 0)
447 float meanRadius
= m_globe
->meanRadius();
450 double spotlightExtent
= m_observer
->range()*0.4;
451 // Draw a spotlight for the mouse
452 if (m_mouseIntersecting
)
454 maths::Vector
<3,double> vec
= m_mouseCoord
;
455 float altitude
= m_globe
->altitudeAt(m_mouseCoord
);
458 glColor4f(1.0f
, 1.0f
, 0.0f
, 1.0f
);
459 glVertex3(vec
*meanRadius
);
460 glVertex3(vec
*(meanRadius
+ altitude
));
463 glColor4f(1.0f
, 0.0f
, 1.0f
, 1.0f
);
464 glVertex3(vec
*(meanRadius
+ altitude
));
465 glColor4f(1.0f
, 1.0f
, 1.0f
, 0.0f
);
466 glVertex3(vec
*(meanRadius
+ altitude
+ spotlightExtent
));
470 // Draw a spotlight for the focus
472 maths::Vector
<3,double> vec
= m_observer
->focus();
473 float altitude
= m_globe
->altitudeAt(m_observer
->focus());
476 glColor4f(1.0f
, 1.0f
, 0.0f
, 1.0f
);
477 glVertex3(vec
*meanRadius
);
478 glVertex3(vec
*(meanRadius
+ altitude
));
481 glColor4f(0.0f
, 1.0f
, 1.0f
, 1.0f
);
482 glVertex3(vec
*(meanRadius
+ altitude
));
483 glColor4f(1.0f
, 1.0f
, 1.0f
, 0.0f
);
484 glVertex3(vec
*(meanRadius
+ altitude
+ spotlightExtent
));
490 // Draw a fence around the selected region
491 for (int i
= 0; i
< 1; ++i
)
495 float colour
[4] = {1.0f
, 0.5f
, 1.0f
, 1.0f
};
496 if (0 == i
&& m_mouseSlicing
&& m_mouseIntersecting
)
498 coord
= m_mouseCoord
;
499 ending
= m_mouseStartCoord
;
501 else if (1 == i
&& m_sliced
)
513 tcGeo delta
= ending
- coord
;
514 int nlon
= 2 + fabs(delta
.lon())*10*meanRadius
/ m_observer
->range();
515 int nlat
= 2 + fabs(delta
.lat())*10*meanRadius
/ m_observer
->range();
516 double dlon
= delta
.lon() / nlon
;
517 double dlat
= delta
.lat() / nlat
;
519 glDisable(GL_CULL_FACE
);
520 glBegin(GL_TRIANGLE_STRIP
);
521 for (int i
= 0; i
<= nlon
*2 + nlat
*2; ++i
)
523 float radius
= m_globe
->radiusAt(coord
);
524 maths::Vector
<3,double> coordVec
= coord
;
526 glVertex3(coordVec
*radius
);
527 glColor4f(1.0f
, 1.0f
, 1.0f
, 0.0f
);
528 glVertex3(coordVec
*(radius
+ m_observer
->range()*0.1));
532 coord
.setLon(coord
.lon() + dlon
);
534 else if (i
< nlon
+ nlat
)
536 coord
.setLat(coord
.lat() + dlat
);
538 else if (i
< nlon
+ nlat
+ nlon
)
540 coord
.setLon(coord
.lon() - dlon
);
544 coord
.setLat(coord
.lat() - dlat
);
548 glEnable(GL_CULL_FACE
);
550 // Draw the cross section area
551 for (int i
= 0; i
< 2; ++i
)
555 float colour
[4] = {1.0f
, 0.5f
, 1.0f
, 1.0f
};
556 if (0 == i
&& m_mouseCrossing
&& m_mouseIntersecting
)
558 coord
= m_mouseCoord
;
559 ending
= m_mouseStartCoord
;
561 else if (1 == i
&& m_crossSectioned
)
563 coord
= m_crossSection
[0];
564 ending
= m_crossSection
[1];
573 tcGeo delta
= ending
- coord
;
574 int nseg
= 2 + tcGeo::angleBetween(ending
, coord
)*100*meanRadius
/ m_observer
->range();
575 double dlon
= delta
.lon() / (nseg
-1);
576 double dlat
= delta
.lat() / (nseg
-1);
578 glDisable(GL_CULL_FACE
);
581 glBegin(GL_TRIANGLE_STRIP
);
582 for (int i
= 0; i
< nseg
; ++i
)
584 float radius
= m_globe
->radiusAt(tmp
);
585 maths::Vector
<3,double> coordVec
= tmp
;
587 glVertex3(coordVec
*radius
);
588 glColor4f(1.0f
, 1.0f
, 1.0f
, 0.5f
);
589 glVertex3(coordVec
*(radius
+ m_observer
->range()*0.1));
591 tmp
.setLon(tmp
.lon() + dlon
);
592 tmp
.setLat(tmp
.lat() + dlat
);
597 glBegin(GL_TRIANGLE_STRIP
);
598 glColor4f(1.0f
, 1.0f
, 1.0f
, 0.5f
);
599 for (int i
= 0; i
< nseg
; ++i
)
601 float radius
= m_globe
->radiusAt(tmp
);
602 maths::Vector
<3,double> coordVec
= tmp
;
603 glVertex3(coordVec
*(radius
+ m_observer
->range()*0.1));
604 glVertex3(coordVec
*(radius
+ m_observer
->range()*0.6));
606 tmp
.setLon(tmp
.lon() + dlon
);
607 tmp
.setLat(tmp
.lat() + dlat
);
610 glDisable(GL_DEPTH_TEST
);
611 glEnable(GL_LINE_SMOOTH
);
613 tcSrtmModel
* model
= m_globe
->dem();
614 for (int dem
= 0; dem
< 2; ++dem
)
617 glBegin(GL_LINE_STRIP
);
618 for (int i
= 0; i
< nseg
; ++i
)
621 float radius
= m_globe
->meanRadius() + model
[dem
].altitudeAt(tmp
, true, &accurate
);
622 maths::Vector
<3,double> coordVec
= tmp
;
625 glColor4f(0.0f
, 0.5f
*dem
, 0.5f
*(1.0f
-dem
), 1.0f
);
629 glColor4f(0.5f
, 0.5f
*dem
, 0.5f
*(1.0f
-dem
), 1.0f
);
631 glVertex3(coordVec
*(radius
+ m_observer
->range()*0.3));
633 tmp
.setLon(tmp
.lon() + dlon
);
634 tmp
.setLat(tmp
.lat() + dlat
);
639 glEnable(GL_DEPTH_TEST
);
640 glEnable(GL_CULL_FACE
);
649 void tcViewportWidget::mouseMoveEvent(QMouseEvent
* event
)
651 if (m_mouseInteracting
)
653 QPointF dpos
= event
->posF() - m_mousePos
;
654 m_mousePos
= event
->posF();
656 // Pan the scene, adjust the observer focus
657 m_observer
->moveFocusRelative(dpos
.x() / height(), dpos
.y() / width());
658 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
661 QString mouseDescription
;
663 m_mouseCoord
= geoAt(event
->posF().x(), event
->posF().y(), &ok
);
664 m_mouseIntersecting
= ok
;
667 mouseDescription
= m_mouseCoord
.describe() + " " + tr("%1 m, range: %2 m")
668 .arg(m_globe
->altitudeAt(m_mouseCoord
))
669 .arg(tcGeo::angleBetween(m_mouseCoord
,m_observer
->focus()) * m_globe
->meanRadius());
670 emit
mouseGeoChanged(m_mouseCoord
);
674 mouseDescription
= tr("--");
676 emit
mouseGeoTextChanged(tr("Focus: %1 %2, Mouse: %3")
677 .arg(m_observer
->focus().describe())
678 .arg(tr("%1 m","metres")
679 .arg(m_globe
->altitudeAt(m_observer
->focus())))
680 .arg(mouseDescription
));
685 void tcViewportWidget::mousePressEvent(QMouseEvent
* event
)
687 if (m_interactionMode
== Navigation
)
689 if (event
->button() == Qt::LeftButton
)
691 m_mouseInteracting
= true;
692 m_mousePos
= event
->posF();
694 else if (event
->button() == Qt::RightButton
)
697 m_mouseStartCoord
= geoAt(event
->posF().x(), event
->posF().y(), &ok
);
698 m_mouseIntersecting
= false;
701 m_mouseSlicing
= true;
704 else if (event
->button() == Qt::MidButton
)
707 m_mouseStartCoord
= geoAt(event
->posF().x(), event
->posF().y(), &ok
);
708 m_mouseIntersecting
= false;
711 m_mouseCrossing
= true;
715 else if (m_interactionMode
== TexturePoint
)
718 tcGeo geoCoord
= geoAt(event
->posF().x(), event
->posF().y(), &ok
);
721 maths::Vector
<2,float> texturePoint(m_globe
->textureCoordOfGeo(geoCoord
));
722 // need to revert interaction mode first in case slot at other end requests another
723 m_interactionMode
= Navigation
;
724 QObject
* currentObj
= m_texturePointObject
;
725 const char* currentMember
= m_texturePointMember
;
726 connect(this, SIGNAL(texturePointSelected(const maths::Vector
<2,float>&)),
727 currentObj
, currentMember
);
728 emit
texturePointSelected(texturePoint
);
729 disconnect(this, SIGNAL(texturePointSelected(const maths::Vector
<2,float>&)),
730 currentObj
, currentMember
);
736 void tcViewportWidget::mouseReleaseEvent(QMouseEvent
* event
)
738 if (m_mouseInteracting
)
740 m_mouseInteracting
= false;
742 else if (m_mouseSlicing
)
744 m_mouseSlicing
= false;
745 m_sliced
= m_mouseIntersecting
;
746 if (m_mouseIntersecting
)
748 m_slice
[0] = m_mouseStartCoord
;
749 m_slice
[1] = m_mouseCoord
;
753 else if (m_mouseCrossing
)
755 m_mouseCrossing
= false;
756 if (m_mouseIntersecting
)
758 setCrossSection(m_mouseStartCoord
, m_mouseCoord
);
762 m_crossSectioned
= false;
768 void tcViewportWidget::wheelEvent(QWheelEvent
* event
)
770 float delta
= M_PI
/180.0 * event
->delta()/8;
772 if (event
->orientation() == Qt::Vertical
)
774 if (event
->modifiers().testFlag(Qt::ControlModifier
))
776 m_observer
->adjustElevation(delta
);
780 m_observer
->adjustRange(-delta
);
785 m_observer
->adjustAzimuth(-delta
);
787 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));