Fix arc to hgt converter overwriting and make more verbose
[tecorrec.git] / tcViewportWidget.cpp
blob45cc1052414e4aaedb9aeb83140d3a867c5ab0fa
1 /***************************************************************************
2 * This file is part of Tecorrec. *
3 * Copyright 2008 James Hogan <james@albanarts.com> *
4 * *
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. *
9 * *
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. *
14 * *
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 ***************************************************************************/
20 /**
21 * @file tcViewportWidget.cpp
22 * @brief OpenGL viewport widget.
25 #include "tcViewportWidget.h"
26 #include <tcObserver.h>
27 #include <tcGlobe.h>
28 #include <tcGeoImageData.h>
29 #include <Vector.h>
30 #include <glVector.h>
32 #include <QMouseEvent>
33 #include <QtDebug>
35 #include <cmath>
38 * Constructors + destructor
41 /// Primary constructor.
42 tcViewportWidget::tcViewportWidget(QWidget* parent)
43 : QGLWidget(parent)
44 , m_adaptiveQuality(true)
45 , m_polygonMode(GL_FILL)
46 , m_observer(new tcObserver())
47 , m_globe(0)
48 , m_interactionMode(Navigation)
49 , m_mouseInteracting(false)
50 , m_mousePos()
51 , m_mouseIntersecting(false)
52 , m_mouseCoord()
53 , m_mouseSlicing(false)
54 , m_sliced(false)
55 , m_texturePointObject(0)
56 , m_texturePointMember(0)
58 setMouseTracking(true);
59 setMinimumSize(400,300);
62 /// Destructor.
63 tcViewportWidget::~tcViewportWidget()
65 delete m_observer;
69 * Modifiers
72 /// Set the globe object.
73 void tcViewportWidget::setGlobe(tcGlobe* globe)
75 m_globe = globe;
76 tcGeo pos( 7.03227 * M_PI/180, 45.92093 * M_PI/180);
77 m_observer->setFocus(pos, globe->radiusAt(pos));
81 * Accessors
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();
91 double ro = obs*ray;
92 double roro = ro*ro;
93 bool intersecting = false;
94 // find intersection P of ray and globe
95 // given globe radius r, observer O, ray R, where |R|=1
96 // globe: P*P = r*r
97 // line: P = O + t*R
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;
112 if (t > 0.0)
114 obs += ray*t;
115 obs /= r; // should now be normalized
116 intersecting = true;
119 else
121 // Hmm, doesn't work
122 #if 0
123 // mouse is outside of globe
124 // use closest point on outline circle of globe
125 // sphere P*P=r*r
126 // tangential circle: P*(P-O) = 0
127 // P*P - P*O = 0
128 // r*r - 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
133 // s(OO + tRO) = r*r
134 // s = r*r/(OO+tRO)
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)
150 double rr = r*r;
151 double oo = obs*obs;
152 double oooo = oo*oo;
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);
159 if (t >= 0.0)
161 obs += ray*t;
162 obs /= s;
163 obs /= r; // should now be normalized
164 obs.normalize();
165 intersecting = true;
168 #endif
170 if (0 != ok)
172 *ok = intersecting;
174 if (intersecting)
176 return tcGeo(obs, true);
178 else
180 return tcGeo();
185 * Observer control
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));
192 updateGL();
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 * General rendering slots
217 /// Change the quality to adaptive.
218 void tcViewportWidget::setQualityAdaptive()
220 m_adaptiveQuality = true;
221 updateGL();
224 /// Change the quality to full resolution.
225 void tcViewportWidget::setQualityFull()
227 m_adaptiveQuality = false;
228 updateGL();
231 /// Change the polygon mode to normal.
232 void tcViewportWidget::setPolygonModeNormal()
234 m_polygonMode = GL_FILL;
235 updateGL();
238 /// Change the polygon mode to wireframe.
239 void tcViewportWidget::setPolygonModeWireframe()
241 m_polygonMode = GL_LINE;
242 updateGL();
245 /// Remove colour coding.
246 void tcViewportWidget::setColourCodingNone()
248 m_globe->setColourCoding(tcGlobe::NoColourCoding);
249 updateGL();
252 /// Set colour coding to elevation sample alignment.
253 void tcViewportWidget::setColourCodingElevationSampleAlignment()
255 m_globe->setColourCoding(tcGlobe::ElevationSampleAlignment);
256 updateGL();
259 /// Set colour mapping for an output channel to an input band.
260 void tcViewportWidget::setColourMapping(int output, int input, int inputGroup)
262 m_globe->setColourMapping(output, input, inputGroup);
263 updateGL();
267 * Elevation modification slots
270 /// Set the primary elevation data set name.
271 void tcViewportWidget::setPrimaryElevationDataSet(const QString& name)
273 if (0 != m_globe)
275 m_globe->setElevationDataSet(0, name);
276 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
277 updateGL();
281 /// Set the secondary elevation data set name.
282 void tcViewportWidget::setSecondaryElevationDataSet(const QString& name)
284 if (0 != m_globe)
286 m_globe->setElevationDataSet(1, name);
287 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
288 updateGL();
292 /// Go to flat primary elevation mode.
293 void tcViewportWidget::setPrimaryElevationFlat()
295 if (0 != m_globe)
297 m_globe->setElevationMode(0, tcGlobe::NoElevation);
298 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
299 updateGL();
303 /// Go to flat secondary elevation mode.
304 void tcViewportWidget::setSecondaryElevationFlat()
306 if (0 != m_globe)
308 m_globe->setElevationMode(1, tcGlobe::NoElevation);
309 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
310 updateGL();
314 /// Go to raw SRTM primary elevation mode.
315 void tcViewportWidget::setPrimaryElevationRaw()
317 if (0 != m_globe)
319 m_globe->setElevationMode(0, tcGlobe::RawElevation);
320 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
321 updateGL();
325 /// Go to raw SRTM secondary elevation mode.
326 void tcViewportWidget::setSecondaryElevationRaw()
328 if (0 != m_globe)
330 m_globe->setElevationMode(1, tcGlobe::RawElevation);
331 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
332 updateGL();
336 /// Go to corrected primary elevation mode.
337 void tcViewportWidget::setPrimaryElevationCorrected()
339 if (0 != m_globe)
341 m_globe->setElevationMode(0, tcGlobe::CorrectedElevation);
342 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
343 updateGL();
347 /// Go to corrected secondary elevation mode.
348 void tcViewportWidget::setSecondaryElevationCorrected()
350 if (0 != m_globe)
352 m_globe->setElevationMode(1, tcGlobe::CorrectedElevation);
353 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
354 updateGL();
358 /// Set the interpolation value.
359 void tcViewportWidget::setElevationInterpolation(int interpolation)
361 if (0 != m_globe)
363 m_globe->setElevationInterpolation((float)interpolation/100);
364 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
365 updateGL();
370 * Rendering
373 void tcViewportWidget::initializeGL()
375 glClearColor(0.0, 0.3, 0.7, 0.0);
378 void tcViewportWidget::resizeGL(int w, int h)
380 glViewport(0, 0, (GLint)w, (GLint)h);
382 double aspect = (double)w / h;
383 m_observer->setupProjection(aspect);
386 void tcViewportWidget::paintGL()
388 static int maxTexUnits = 0;
389 if (maxTexUnits == 0)
391 glGetIntegerv(GL_MAX_TEXTURE_UNITS, &maxTexUnits);
392 if (maxTexUnits < 3)
394 qDebug() << tr("Maximum texture units is %1, but Tecorrec needs at least 3").arg(maxTexUnits);
399 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
400 glEnable(GL_DEPTH_TEST);
401 glEnable(GL_CULL_FACE);
403 glEnable(GL_BLEND);
404 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
406 glPolygonMode(GL_FRONT_AND_BACK, m_polygonMode);
408 m_observer->setupModelView();
410 if (m_sliced)
412 m_globe->render(m_observer, m_adaptiveQuality, m_slice);
414 else
416 m_globe->render(m_observer);
419 float meanRadius = m_globe->meanRadius();
421 glLineWidth(3);
422 double spotlightExtent = m_observer->range()*0.4;
423 // Draw a spotlight for the mouse
424 if (m_mouseIntersecting)
426 maths::Vector<3,double> vec = m_mouseCoord;
427 float altitude = m_globe->altitudeAt(m_mouseCoord);
428 glBegin(GL_LINES);
430 glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
431 glVertex3(vec*meanRadius);
432 glVertex3(vec*(meanRadius + altitude));
435 glColor4f(1.0f, 0.0f, 1.0f, 1.0f);
436 glVertex3(vec*(meanRadius + altitude));
437 glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
438 glVertex3(vec*(meanRadius + altitude + spotlightExtent));
440 glEnd();
442 // Draw a spotlight for the focus
444 maths::Vector<3,double> vec = m_observer->focus();
445 float altitude = m_globe->altitudeAt(m_observer->focus());
446 glBegin(GL_LINES);
448 glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
449 glVertex3(vec*meanRadius);
450 glVertex3(vec*(meanRadius + altitude));
453 glColor4f(0.0f, 1.0f, 1.0f, 1.0f);
454 glVertex3(vec*(meanRadius + altitude));
455 glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
456 glVertex3(vec*(meanRadius + altitude + spotlightExtent));
458 glEnd();
460 glLineWidth(1);
462 // Draw a fence around the selected region
463 for (int i = 0; i < 1; ++i)
465 tcGeo coord;
466 tcGeo ending;
467 float colour[4] = {1.0f, 0.5f, 1.0f, 1.0f};
468 if (0 == i && m_mouseSlicing && m_mouseIntersecting)
470 coord = m_mouseCoord;
471 ending = m_mouseStartCoord;
473 else if (1 == i && m_sliced)
475 coord = m_slice[0];
476 ending = m_slice[1];
477 colour[0] = 0.0f;
478 colour[3] = 0.5f;
480 else
482 continue;
485 tcGeo delta = ending - coord;
486 int nlon = 2 + fabs(delta.lon())*10*meanRadius / m_observer->range();
487 int nlat = 2 + fabs(delta.lat())*10*meanRadius / m_observer->range();
488 double dlon = delta.lon() / nlon;
489 double dlat = delta.lat() / nlat;
491 glDisable(GL_CULL_FACE);
492 glBegin(GL_TRIANGLE_STRIP);
493 for (int i = 0; i <= nlon*2 + nlat*2; ++i)
495 float radius = m_globe->radiusAt(coord);
496 maths::Vector<3,double> coordVec = coord;
497 glColor4fv(colour);
498 glVertex3(coordVec*radius);
499 glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
500 glVertex3(coordVec*(radius + m_observer->range()*0.1));
502 if (i < nlon)
504 coord.setLon(coord.lon() + dlon);
506 else if (i < nlon + nlat)
508 coord.setLat(coord.lat() + dlat);
510 else if (i < nlon + nlat + nlon)
512 coord.setLon(coord.lon() - dlon);
514 else
516 coord.setLat(coord.lat() - dlat);
519 glEnd();
520 glEnable(GL_CULL_FACE);
526 * Mouse events
529 void tcViewportWidget::mouseMoveEvent(QMouseEvent* event)
531 if (m_mouseInteracting)
533 QPointF dpos = event->posF() - m_mousePos;
534 m_mousePos = event->posF();
536 // Pan the scene, adjust the observer focus
537 m_observer->moveFocusRelative(dpos.x() / height(), dpos.y() / width());
538 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
541 QString mouseDescription;
542 bool ok;
543 m_mouseCoord = geoAt(event->posF().x(), event->posF().y(), &ok);
544 m_mouseIntersecting = ok;
545 if (ok)
547 mouseDescription = m_mouseCoord.describe() + " " + tr("%1 m, range: %2 m")
548 .arg(m_globe->altitudeAt(m_mouseCoord))
549 .arg((m_mouseCoord-m_observer->focus()).angle() * m_globe->meanRadius());
550 emit mouseGeoChanged(m_mouseCoord);
552 else
554 mouseDescription = tr("--");
556 emit mouseGeoTextChanged(tr("Focus: %1 %2, Mouse: %3")
557 .arg(m_observer->focus().describe())
558 .arg(tr("%1 m","metres")
559 .arg(m_globe->altitudeAt(m_observer->focus())))
560 .arg(mouseDescription));
562 updateGL();
565 void tcViewportWidget::mousePressEvent(QMouseEvent* event)
567 if (m_interactionMode == Navigation)
569 if (event->button() == Qt::LeftButton)
571 m_mouseInteracting = true;
572 m_mousePos = event->posF();
574 else if (event->button() == Qt::RightButton)
576 bool ok;
577 m_mouseStartCoord = geoAt(event->posF().x(), event->posF().y(), &ok);
578 m_mouseIntersecting = false;
579 if (ok)
581 m_mouseSlicing = true;
585 else if (m_interactionMode == TexturePoint)
587 bool ok;
588 tcGeo geoCoord = geoAt(event->posF().x(), event->posF().y(), &ok);
589 if (ok)
591 maths::Vector<2,float> texturePoint(m_globe->textureCoordOfGeo(geoCoord));
592 // need to revert interaction mode first in case slot at other end requests another
593 m_interactionMode = Navigation;
594 QObject* currentObj = m_texturePointObject;
595 const char* currentMember = m_texturePointMember;
596 connect(this, SIGNAL(texturePointSelected(const maths::Vector<2,float>&)),
597 currentObj, currentMember);
598 emit texturePointSelected(texturePoint);
599 disconnect(this, SIGNAL(texturePointSelected(const maths::Vector<2,float>&)),
600 currentObj, currentMember);
605 void tcViewportWidget::mouseReleaseEvent(QMouseEvent* event)
607 if (m_mouseInteracting)
609 m_mouseInteracting = false;
611 else if (m_mouseSlicing)
613 m_mouseSlicing = false;
614 m_sliced = m_mouseIntersecting;
615 if (m_mouseIntersecting)
617 m_slice[0] = m_mouseStartCoord;
618 m_slice[1] = m_mouseCoord;
620 updateGL();
624 void tcViewportWidget::wheelEvent(QWheelEvent* event)
626 float delta = M_PI/180.0 * event->delta()/8;
627 delta *= 0.5f;
628 if (event->orientation() == Qt::Vertical)
630 if (event->modifiers().testFlag(Qt::ControlModifier))
632 m_observer->adjustElevation(delta);
634 else
636 m_observer->adjustRange(-delta);
639 else
641 m_observer->adjustAzimuth(-delta);
643 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
645 updateGL();