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>
32 #include <QMouseEvent>
38 * Constructors + destructor
41 /// Primary constructor.
42 tcViewportWidget::tcViewportWidget(QWidget
* parent
)
44 , m_adaptiveQuality(true)
45 , m_polygonMode(GL_FILL
)
46 , m_observer(new tcObserver())
48 , m_interactionMode(Navigation
)
49 , m_mouseInteracting(false)
51 , m_mouseIntersecting(false)
53 , m_mouseSlicing(false)
55 , m_texturePointObject(0)
56 , m_texturePointMember(0)
58 setMouseTracking(true);
59 setMinimumSize(400,300);
63 tcViewportWidget::~tcViewportWidget()
72 /// Set the globe object.
73 void tcViewportWidget::setGlobe(tcGlobe
* globe
)
76 tcGeo
pos( 7.03227 * M_PI
/180, 45.92093 * M_PI
/180);
77 m_observer
->setFocus(pos
, globe
->radiusAt(pos
));
84 /// Find the coordinates under the mouse.
85 tcGeo
tcViewportWidget::geoAt(float x
, float y
, bool* ok
)
87 maths::Vector
<2,double> screenXy(x
/ width(), 1.0 - y
/ height());
88 maths::Vector
<3,double> obs
= m_observer
->position();
89 maths::Vector
<3,double> ray
= m_observer
->ray(screenXy
, (double)width() / height());
90 double r
= m_observer
->focusAltitude();
93 bool intersecting
= false;
94 // find intersection P of ray and globe
95 // given globe radius r, observer O, ray R, where |R|=1
98 // (O+t*R)*(O+t*R) = r*r
99 // O*O + 2*O*t*R + t*t*R*R = r*r
100 // O*O + 2*O*t*R + t*t - r*r = 0
101 // quadratic in parameter t
102 // a = 1, b = 2*RO c = O*O-r*r RO = O*R
103 // t = (-b +- sqrt(b*b - 4ac)) / 2a
104 // = (-2RO +- sqrt(4*RO*RO - 4*O*O + 4*r*r)) / 2
105 // = (-2*RO +- 2*sqrt(RO*RO - O*O + r*r)) / 2
106 // = RO += sqrt(RO*RO - O*O + r*r)
107 double discriminant
= roro
- obs
.sqr() + r
*r
;
108 if (discriminant
>= 0.0)
110 discriminant
= sqrt(discriminant
);
111 double t
= -ro
- discriminant
;
115 obs
/= r
; // should now be normalized
123 // mouse is outside of globe
124 // use closest point on outline circle of globe
126 // tangential circle: P*(P-O) = 0
129 // line: P/s = O + tR
130 // P = s(O + tR) (s>0)
131 // r*r - s(O + tR)O = 0
132 // r*r - s(OO + tRO) = 0
135 // t = (r*r/s - OO)/RO
137 // (sO + tsR)(sO + tsR - O) = 0
138 // (O + tR)(sO + tsR - O) = 0
139 // sOO + tsRO + tsOR + ttsRR - OO - tRO = 0
140 // sOO + 2tsRO + tts - OO - tRO = 0
141 // s(OO + 2tRO + tt) = OO - tRO
142 // substitute s=rr/(OO+tRO)
143 // rr/(OO+tRO)(OO + 2tRO + tt) = OO - tRO
144 // rr(OO + 2tRO + tt) = (OO - tRO)(OO+tRO)
145 // rrOO + 2rrtRO + rrtt = OO*OO - ttRO*RO
146 // tt(rr + RO*RO) + t(2rrRO) + (rrOO - OO*OO)
147 // a = rr + RO*RO, b = 2rrRO, c = rr00 - OO*OO
148 // t = (-2rrRO +- sqrt(4rrrrRO*RO - 4(rr + RO*RO)(rr00 - OO*OO))) / 2(rr + RO*RO)
149 // t = (-rrRO +- sqrt(rrrrRO*RO - (rr + RO*RO)(rr00 - OO*OO))) / (rr + RO*RO)
153 double discriminant
= rr
*rr
*roro
- (rr
+ roro
)*(rr
*oo
- oo
*oo
);
154 if (discriminant
>= 0)
156 discriminant
= sqrt(discriminant
);
157 double t
= (-rr
*ro
+ discriminant
) / (rr
+ roro
);
158 double s
= rr
/(oo
+ t
*ro
);
163 obs
/= r
; // should now be normalized
176 return tcGeo(obs
, true);
188 /// Change to sun view.
189 void tcViewportWidget::sunView(int imagery
)
191 m_observer
->setView(tcGeo(m_globe
->imagery()[imagery
]->sunDirection(m_observer
->focus()), true));
196 * Interaction control
199 /// Enable navigation mode.
200 void tcViewportWidget::navigationMode()
202 m_interactionMode
= Navigation
;
205 /// Enable texture point selection mode.
206 void tcViewportWidget::texturePointMode(QObject
* receiver
, const char* member
)
208 m_interactionMode
= TexturePoint
;
209 m_texturePointObject
= receiver
;
210 m_texturePointMember
= member
;
214 void tcViewportWidget::setSlice(const tcGeo
& sw
, const tcGeo
& ne
)
223 * General rendering slots
226 /// Change the quality to adaptive.
227 void tcViewportWidget::setQualityAdaptive()
229 m_adaptiveQuality
= true;
233 /// Change the quality to full resolution.
234 void tcViewportWidget::setQualityFull()
236 m_adaptiveQuality
= false;
240 /// Change the polygon mode to normal.
241 void tcViewportWidget::setPolygonModeNormal()
243 m_polygonMode
= GL_FILL
;
247 /// Change the polygon mode to wireframe.
248 void tcViewportWidget::setPolygonModeWireframe()
250 m_polygonMode
= GL_LINE
;
254 /// Remove colour coding.
255 void tcViewportWidget::setColourCodingNone()
257 m_globe
->setColourCoding(tcGlobe::NoColourCoding
);
261 /// Set colour coding to elevation sample alignment.
262 void tcViewportWidget::setColourCodingElevationSampleAlignment()
264 m_globe
->setColourCoding(tcGlobe::ElevationSampleAlignment
);
268 /// Set colour mapping for an output channel to an input band.
269 void tcViewportWidget::setColourMapping(int output
, int input
, int inputGroup
)
271 m_globe
->setColourMapping(output
, input
, inputGroup
);
276 * Elevation modification slots
279 /// Set the primary elevation data set name.
280 void tcViewportWidget::setPrimaryElevationDataSet(const QString
& name
)
284 m_globe
->setElevationDataSet(0, name
);
285 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
290 /// Set the secondary elevation data set name.
291 void tcViewportWidget::setSecondaryElevationDataSet(const QString
& name
)
295 m_globe
->setElevationDataSet(1, name
);
296 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
301 /// Go to flat primary elevation mode.
302 void tcViewportWidget::setPrimaryElevationFlat()
306 m_globe
->setElevationMode(0, tcGlobe::NoElevation
);
307 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
312 /// Go to flat secondary elevation mode.
313 void tcViewportWidget::setSecondaryElevationFlat()
317 m_globe
->setElevationMode(1, tcGlobe::NoElevation
);
318 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
323 /// Go to raw SRTM primary elevation mode.
324 void tcViewportWidget::setPrimaryElevationRaw()
328 m_globe
->setElevationMode(0, tcGlobe::RawElevation
);
329 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
334 /// Go to raw SRTM secondary elevation mode.
335 void tcViewportWidget::setSecondaryElevationRaw()
339 m_globe
->setElevationMode(1, tcGlobe::RawElevation
);
340 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
345 /// Go to corrected primary elevation mode.
346 void tcViewportWidget::setPrimaryElevationCorrected()
350 m_globe
->setElevationMode(0, tcGlobe::CorrectedElevation
);
351 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
356 /// Go to corrected secondary elevation mode.
357 void tcViewportWidget::setSecondaryElevationCorrected()
361 m_globe
->setElevationMode(1, tcGlobe::CorrectedElevation
);
362 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
367 /// Set the interpolation value.
368 void tcViewportWidget::setElevationInterpolation(int interpolation
)
372 m_globe
->setElevationInterpolation((float)interpolation
/100);
373 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
382 void tcViewportWidget::initializeGL()
384 glClearColor(0.0, 0.3, 0.7, 0.0);
387 void tcViewportWidget::resizeGL(int w
, int h
)
389 glViewport(0, 0, (GLint
)w
, (GLint
)h
);
391 double aspect
= (double)w
/ h
;
392 m_observer
->setupProjection(aspect
);
395 void tcViewportWidget::paintGL()
397 static int maxTexUnits
= 0;
398 if (maxTexUnits
== 0)
400 glGetIntegerv(GL_MAX_TEXTURE_UNITS
, &maxTexUnits
);
403 qDebug() << tr("Maximum texture units is %1, but Tecorrec needs at least 3").arg(maxTexUnits
);
408 glClear(GL_COLOR_BUFFER_BIT
| GL_DEPTH_BUFFER_BIT
);
409 glEnable(GL_DEPTH_TEST
);
410 glEnable(GL_CULL_FACE
);
413 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
415 glPolygonMode(GL_FRONT_AND_BACK
, m_polygonMode
);
417 m_observer
->setupModelView();
421 m_globe
->render(m_observer
, m_adaptiveQuality
, m_slice
);
425 m_globe
->render(m_observer
);
428 float meanRadius
= m_globe
->meanRadius();
431 double spotlightExtent
= m_observer
->range()*0.4;
432 // Draw a spotlight for the mouse
433 if (m_mouseIntersecting
)
435 maths::Vector
<3,double> vec
= m_mouseCoord
;
436 float altitude
= m_globe
->altitudeAt(m_mouseCoord
);
439 glColor4f(1.0f
, 1.0f
, 0.0f
, 1.0f
);
440 glVertex3(vec
*meanRadius
);
441 glVertex3(vec
*(meanRadius
+ altitude
));
444 glColor4f(1.0f
, 0.0f
, 1.0f
, 1.0f
);
445 glVertex3(vec
*(meanRadius
+ altitude
));
446 glColor4f(1.0f
, 1.0f
, 1.0f
, 0.0f
);
447 glVertex3(vec
*(meanRadius
+ altitude
+ spotlightExtent
));
451 // Draw a spotlight for the focus
453 maths::Vector
<3,double> vec
= m_observer
->focus();
454 float altitude
= m_globe
->altitudeAt(m_observer
->focus());
457 glColor4f(1.0f
, 1.0f
, 0.0f
, 1.0f
);
458 glVertex3(vec
*meanRadius
);
459 glVertex3(vec
*(meanRadius
+ altitude
));
462 glColor4f(0.0f
, 1.0f
, 1.0f
, 1.0f
);
463 glVertex3(vec
*(meanRadius
+ altitude
));
464 glColor4f(1.0f
, 1.0f
, 1.0f
, 0.0f
);
465 glVertex3(vec
*(meanRadius
+ altitude
+ spotlightExtent
));
471 // Draw a fence around the selected region
472 for (int i
= 0; i
< 1; ++i
)
476 float colour
[4] = {1.0f
, 0.5f
, 1.0f
, 1.0f
};
477 if (0 == i
&& m_mouseSlicing
&& m_mouseIntersecting
)
479 coord
= m_mouseCoord
;
480 ending
= m_mouseStartCoord
;
482 else if (1 == i
&& m_sliced
)
494 tcGeo delta
= ending
- coord
;
495 int nlon
= 2 + fabs(delta
.lon())*10*meanRadius
/ m_observer
->range();
496 int nlat
= 2 + fabs(delta
.lat())*10*meanRadius
/ m_observer
->range();
497 double dlon
= delta
.lon() / nlon
;
498 double dlat
= delta
.lat() / nlat
;
500 glDisable(GL_CULL_FACE
);
501 glBegin(GL_TRIANGLE_STRIP
);
502 for (int i
= 0; i
<= nlon
*2 + nlat
*2; ++i
)
504 float radius
= m_globe
->radiusAt(coord
);
505 maths::Vector
<3,double> coordVec
= coord
;
507 glVertex3(coordVec
*radius
);
508 glColor4f(1.0f
, 1.0f
, 1.0f
, 0.0f
);
509 glVertex3(coordVec
*(radius
+ m_observer
->range()*0.1));
513 coord
.setLon(coord
.lon() + dlon
);
515 else if (i
< nlon
+ nlat
)
517 coord
.setLat(coord
.lat() + dlat
);
519 else if (i
< nlon
+ nlat
+ nlon
)
521 coord
.setLon(coord
.lon() - dlon
);
525 coord
.setLat(coord
.lat() - dlat
);
529 glEnable(GL_CULL_FACE
);
538 void tcViewportWidget::mouseMoveEvent(QMouseEvent
* event
)
540 if (m_mouseInteracting
)
542 QPointF dpos
= event
->posF() - m_mousePos
;
543 m_mousePos
= event
->posF();
545 // Pan the scene, adjust the observer focus
546 m_observer
->moveFocusRelative(dpos
.x() / height(), dpos
.y() / width());
547 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));
550 QString mouseDescription
;
552 m_mouseCoord
= geoAt(event
->posF().x(), event
->posF().y(), &ok
);
553 m_mouseIntersecting
= ok
;
556 mouseDescription
= m_mouseCoord
.describe() + " " + tr("%1 m, range: %2 m")
557 .arg(m_globe
->altitudeAt(m_mouseCoord
))
558 .arg((m_mouseCoord
-m_observer
->focus()).angle() * m_globe
->meanRadius());
559 emit
mouseGeoChanged(m_mouseCoord
);
563 mouseDescription
= tr("--");
565 emit
mouseGeoTextChanged(tr("Focus: %1 %2, Mouse: %3")
566 .arg(m_observer
->focus().describe())
567 .arg(tr("%1 m","metres")
568 .arg(m_globe
->altitudeAt(m_observer
->focus())))
569 .arg(mouseDescription
));
574 void tcViewportWidget::mousePressEvent(QMouseEvent
* event
)
576 if (m_interactionMode
== Navigation
)
578 if (event
->button() == Qt::LeftButton
)
580 m_mouseInteracting
= true;
581 m_mousePos
= event
->posF();
583 else if (event
->button() == Qt::RightButton
)
586 m_mouseStartCoord
= geoAt(event
->posF().x(), event
->posF().y(), &ok
);
587 m_mouseIntersecting
= false;
590 m_mouseSlicing
= true;
594 else if (m_interactionMode
== TexturePoint
)
597 tcGeo geoCoord
= geoAt(event
->posF().x(), event
->posF().y(), &ok
);
600 maths::Vector
<2,float> texturePoint(m_globe
->textureCoordOfGeo(geoCoord
));
601 // need to revert interaction mode first in case slot at other end requests another
602 m_interactionMode
= Navigation
;
603 QObject
* currentObj
= m_texturePointObject
;
604 const char* currentMember
= m_texturePointMember
;
605 connect(this, SIGNAL(texturePointSelected(const maths::Vector
<2,float>&)),
606 currentObj
, currentMember
);
607 emit
texturePointSelected(texturePoint
);
608 disconnect(this, SIGNAL(texturePointSelected(const maths::Vector
<2,float>&)),
609 currentObj
, currentMember
);
614 void tcViewportWidget::mouseReleaseEvent(QMouseEvent
* event
)
616 if (m_mouseInteracting
)
618 m_mouseInteracting
= false;
620 else if (m_mouseSlicing
)
622 m_mouseSlicing
= false;
623 m_sliced
= m_mouseIntersecting
;
624 if (m_mouseIntersecting
)
626 m_slice
[0] = m_mouseStartCoord
;
627 m_slice
[1] = m_mouseCoord
;
633 void tcViewportWidget::wheelEvent(QWheelEvent
* event
)
635 float delta
= M_PI
/180.0 * event
->delta()/8;
637 if (event
->orientation() == Qt::Vertical
)
639 if (event
->modifiers().testFlag(Qt::ControlModifier
))
641 m_observer
->adjustElevation(delta
);
645 m_observer
->adjustRange(-delta
);
650 m_observer
->adjustAzimuth(-delta
);
652 m_observer
->setFocusAltitude(m_globe
->radiusAt(m_observer
->focus()));