Interface for mapping between input bands and output channels
[tecorrec.git] / tcViewportWidget.cpp
blob8beb624197d6e819b4e7ac995fbc57d99b1166a7
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 <Vector.h>
29 #include <glVector.h>
31 #include <QMouseEvent>
32 #include <QtDebug>
34 #include <cmath>
37 * Constructors + destructor
40 /// Primary constructor.
41 tcViewportWidget::tcViewportWidget(QWidget* parent)
42 : QGLWidget(parent)
43 , m_adaptiveQuality(true)
44 , m_polygonMode(GL_FILL)
45 , m_observer(new tcObserver())
46 , m_globe(0)
47 , m_mouseInteracting(false)
48 , m_mousePos()
49 , m_mouseIntersecting(false)
50 , m_mouseCoord()
51 , m_mouseSlicing(false)
52 , m_sliced(false)
54 setMouseTracking(true);
55 setMinimumSize(400,300);
58 /// Destructor.
59 tcViewportWidget::~tcViewportWidget()
61 delete m_observer;
65 * Modifiers
68 /// Set the globe object.
69 void tcViewportWidget::setGlobe(tcGlobe* globe)
71 m_globe = globe;
72 tcGeo pos( 7.03227 * M_PI/180, 45.92093 * M_PI/180);
73 m_observer->setFocus(pos, globe->radiusAt(pos));
77 * Accessors
80 /// Find the coordinates under the mouse.
81 tcGeo tcViewportWidget::geoAt(float x, float y, bool* ok)
83 maths::Vector<2,double> screenXy(x / width(), 1.0 - y / height());
84 maths::Vector<3,double> obs = m_observer->position();
85 maths::Vector<3,double> ray = m_observer->ray(screenXy, (double)width() / height());
86 double r = m_observer->focusAltitude();
87 double ro = obs*ray;
88 double roro = ro*ro;
89 bool intersecting = false;
90 // find intersection P of ray and globe
91 // given globe radius r, observer O, ray R, where |R|=1
92 // globe: P*P = r*r
93 // line: P = O + t*R
94 // (O+t*R)*(O+t*R) = r*r
95 // O*O + 2*O*t*R + t*t*R*R = r*r
96 // O*O + 2*O*t*R + t*t - r*r = 0
97 // quadratic in parameter t
98 // a = 1, b = 2*RO c = O*O-r*r RO = O*R
99 // t = (-b +- sqrt(b*b - 4ac)) / 2a
100 // = (-2RO +- sqrt(4*RO*RO - 4*O*O + 4*r*r)) / 2
101 // = (-2*RO +- 2*sqrt(RO*RO - O*O + r*r)) / 2
102 // = RO += sqrt(RO*RO - O*O + r*r)
103 double discriminant = roro - obs.sqr() + r*r;
104 if (discriminant >= 0.0)
106 discriminant = sqrt(discriminant);
107 double t = -ro - discriminant;
108 if (t > 0.0)
110 obs += ray*t;
111 obs /= r; // should now be normalized
112 intersecting = true;
115 else
117 // Hmm, doesn't work
118 #if 0
119 // mouse is outside of globe
120 // use closest point on outline circle of globe
121 // sphere P*P=r*r
122 // tangential circle: P*(P-O) = 0
123 // P*P - P*O = 0
124 // r*r - P*O = 0
125 // line: P/s = O + tR
126 // P = s(O + tR) (s>0)
127 // r*r - s(O + tR)O = 0
128 // r*r - s(OO + tRO) = 0
129 // s(OO + tRO) = r*r
130 // s = r*r/(OO+tRO)
131 // t = (r*r/s - OO)/RO
133 // (sO + tsR)(sO + tsR - O) = 0
134 // (O + tR)(sO + tsR - O) = 0
135 // sOO + tsRO + tsOR + ttsRR - OO - tRO = 0
136 // sOO + 2tsRO + tts - OO - tRO = 0
137 // s(OO + 2tRO + tt) = OO - tRO
138 // substitute s=rr/(OO+tRO)
139 // rr/(OO+tRO)(OO + 2tRO + tt) = OO - tRO
140 // rr(OO + 2tRO + tt) = (OO - tRO)(OO+tRO)
141 // rrOO + 2rrtRO + rrtt = OO*OO - ttRO*RO
142 // tt(rr + RO*RO) + t(2rrRO) + (rrOO - OO*OO)
143 // a = rr + RO*RO, b = 2rrRO, c = rr00 - OO*OO
144 // t = (-2rrRO +- sqrt(4rrrrRO*RO - 4(rr + RO*RO)(rr00 - OO*OO))) / 2(rr + RO*RO)
145 // t = (-rrRO +- sqrt(rrrrRO*RO - (rr + RO*RO)(rr00 - OO*OO))) / (rr + RO*RO)
146 double rr = r*r;
147 double oo = obs*obs;
148 double oooo = oo*oo;
149 double discriminant = rr*rr*roro - (rr + roro)*(rr*oo - oo*oo);
150 if (discriminant >= 0)
152 discriminant = sqrt(discriminant);
153 double t = (-rr*ro + discriminant) / (rr + roro);
154 double s = rr/(oo + t*ro);
155 if (t >= 0.0)
157 obs += ray*t;
158 obs /= s;
159 obs /= r; // should now be normalized
160 obs.normalize();
161 intersecting = true;
164 #endif
166 if (0 != ok)
168 *ok = intersecting;
170 if (intersecting)
172 return tcGeo(obs, true);
174 else
176 return tcGeo();
181 * General rendering slots
184 /// Change the quality to adaptive.
185 void tcViewportWidget::setQualityAdaptive()
187 m_adaptiveQuality = true;
188 updateGL();
191 /// Change the quality to full resolution.
192 void tcViewportWidget::setQualityFull()
194 m_adaptiveQuality = false;
195 updateGL();
198 /// Change the polygon mode to normal.
199 void tcViewportWidget::setPolygonModeNormal()
201 m_polygonMode = GL_FILL;
202 updateGL();
205 /// Change the polygon mode to wireframe.
206 void tcViewportWidget::setPolygonModeWireframe()
208 m_polygonMode = GL_LINE;
209 updateGL();
212 /// Remove colour coding.
213 void tcViewportWidget::setColourCodingNone()
215 m_globe->setColourCoding(tcGlobe::NoColourCoding);
216 updateGL();
219 /// Set colour coding to elevation sample alignment.
220 void tcViewportWidget::setColourCodingElevationSampleAlignment()
222 m_globe->setColourCoding(tcGlobe::ElevationSampleAlignment);
223 updateGL();
226 /// Set colour mapping for an output channel to an input band.
227 void tcViewportWidget::setColourMapping(int output, int input)
229 m_globe->setColourMapping(output, input);
230 updateGL();
234 * Elevation modification slots
237 /// Go to flat elevation mode.
238 void tcViewportWidget::setElevationFlat()
240 if (0 != m_globe)
242 m_globe->setElevationMode(tcGlobe::NoElevation);
243 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
244 updateGL();
248 /// Go to raw SRTM elevation mode.
249 void tcViewportWidget::setElevationRaw()
251 if (0 != m_globe)
253 m_globe->setElevationMode(tcGlobe::RawElevation);
254 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
255 updateGL();
259 /// Go to corrected elevation mode.
260 void tcViewportWidget::setElevationCorrected()
262 if (0 != m_globe)
264 m_globe->setElevationMode(tcGlobe::CorrectedElevation);
265 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
266 updateGL();
270 /// Set the level of correction applied to the elevation data.
271 void tcViewportWidget::setElevationCorrection(int correction)
273 if (0 != m_globe)
275 m_globe->setElevationCorrection((float)correction/100);
276 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
277 updateGL();
281 /// Set the elevation data set name.
282 void tcViewportWidget::setElevationDataSet(const QString& name)
284 if (0 != m_globe)
286 m_globe->setElevationDataSet(name);
287 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
288 updateGL();
293 * Rendering
296 void tcViewportWidget::initializeGL()
298 glClearColor(0.0, 0.3, 0.7, 0.0);
301 void tcViewportWidget::resizeGL(int w, int h)
303 glViewport(0, 0, (GLint)w, (GLint)h);
305 double aspect = (double)w / h;
306 m_observer->setupProjection(aspect);
309 void tcViewportWidget::paintGL()
311 static int maxTexUnits = 0;
312 if (maxTexUnits == 0)
314 glGetIntegerv(GL_MAX_TEXTURE_UNITS, &maxTexUnits);
315 if (maxTexUnits < 3)
317 qDebug() << tr("Maximum texture units is %1, but Tecorrec needs at least 3").arg(maxTexUnits);
322 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
323 glEnable(GL_DEPTH_TEST);
324 glEnable(GL_CULL_FACE);
326 glEnable(GL_BLEND);
327 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
329 glPolygonMode(GL_FRONT_AND_BACK, m_polygonMode);
331 m_observer->setupModelView();
333 if (m_sliced)
335 m_globe->render(m_observer, m_adaptiveQuality, m_slice);
337 else
339 m_globe->render(m_observer);
342 float meanRadius = m_globe->meanRadius();
344 glLineWidth(3);
345 double spotlightExtent = m_observer->range()*0.4;
346 // Draw a spotlight for the mouse
347 if (m_mouseIntersecting)
349 maths::Vector<3,double> vec = m_mouseCoord;
350 float altitude = m_globe->altitudeAt(m_mouseCoord);
351 glBegin(GL_LINES);
353 glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
354 glVertex3(vec*meanRadius);
355 glVertex3(vec*(meanRadius + altitude));
358 glColor4f(1.0f, 0.0f, 1.0f, 1.0f);
359 glVertex3(vec*(meanRadius + altitude));
360 glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
361 glVertex3(vec*(meanRadius + altitude + spotlightExtent));
363 glEnd();
365 // Draw a spotlight for the focus
367 maths::Vector<3,double> vec = m_observer->focus();
368 float altitude = m_globe->altitudeAt(m_observer->focus());
369 glBegin(GL_LINES);
371 glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
372 glVertex3(vec*meanRadius);
373 glVertex3(vec*(meanRadius + altitude));
376 glColor4f(0.0f, 1.0f, 1.0f, 1.0f);
377 glVertex3(vec*(meanRadius + altitude));
378 glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
379 glVertex3(vec*(meanRadius + altitude + spotlightExtent));
381 glEnd();
383 glLineWidth(1);
385 // Draw a fence around the selected region
386 for (int i = 0; i < 2; ++i)
388 tcGeo coord;
389 tcGeo ending;
390 float colour[4] = {1.0f, 0.5f, 1.0f, 1.0f};
391 if (0 == i && m_mouseSlicing && m_mouseIntersecting)
393 coord = m_mouseCoord;
394 ending = m_mouseStartCoord;
396 else if (1 == i && m_sliced)
398 coord = m_slice[0];
399 ending = m_slice[1];
400 colour[0] = 0.0f;
401 colour[3] = 0.5f;
403 else
405 continue;
408 tcGeo delta = ending - coord;
409 int nlon = 2 + fabs(delta.lon())*10*meanRadius / m_observer->range();
410 int nlat = 2 + fabs(delta.lat())*10*meanRadius / m_observer->range();
411 double dlon = delta.lon() / nlon;
412 double dlat = delta.lat() / nlat;
414 glDisable(GL_CULL_FACE);
415 glBegin(GL_TRIANGLE_STRIP);
416 for (int i = 0; i <= nlon*2 + nlat*2; ++i)
418 float radius = m_globe->radiusAt(coord);
419 maths::Vector<3,double> coordVec = coord;
420 glColor4fv(colour);
421 glVertex3(coordVec*radius);
422 glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
423 glVertex3(coordVec*(radius + m_observer->range()*0.1));
425 if (i < nlon)
427 coord.setLon(coord.lon() + dlon);
429 else if (i < nlon + nlat)
431 coord.setLat(coord.lat() + dlat);
433 else if (i < nlon + nlat + nlon)
435 coord.setLon(coord.lon() - dlon);
437 else
439 coord.setLat(coord.lat() - dlat);
442 glEnd();
443 glEnable(GL_CULL_FACE);
449 * Mouse events
452 void tcViewportWidget::mouseMoveEvent(QMouseEvent* event)
454 if (m_mouseInteracting)
456 QPointF dpos = event->posF() - m_mousePos;
457 m_mousePos = event->posF();
459 // Pan the scene, adjust the observer focus
460 m_observer->moveFocusRelative(dpos.x() / height(), dpos.y() / width());
461 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
464 QString mouseDescription;
465 bool ok;
466 m_mouseCoord = geoAt(event->posF().x(), event->posF().y(), &ok);
467 m_mouseIntersecting = ok;
468 if (ok)
470 mouseDescription = m_mouseCoord.describe() + " " + tr("%1 m","metres")
471 .arg(m_globe->altitudeAt(m_mouseCoord));
472 emit mouseGeoChanged(m_mouseCoord);
474 else
476 mouseDescription = tr("--");
478 emit mouseGeoTextChanged(tr("Focus: %1 %2, Mouse: %3")
479 .arg(m_observer->focus().describe())
480 .arg(tr("%1 m","metres")
481 .arg(m_globe->altitudeAt(m_observer->focus())))
482 .arg(mouseDescription));
484 if (m_mouseInteracting || m_mouseSlicing || m_sliced)
486 updateGL();
490 void tcViewportWidget::mousePressEvent(QMouseEvent* event)
492 if (event->button() == Qt::LeftButton)
494 m_mouseInteracting = true;
495 m_mousePos = event->posF();
497 else if (event->button() == Qt::RightButton)
499 bool ok;
500 m_mouseStartCoord = geoAt(event->posF().x(), event->posF().y(), &ok);
501 m_mouseIntersecting = false;
502 if (ok)
504 m_mouseSlicing = true;
509 void tcViewportWidget::mouseReleaseEvent(QMouseEvent* event)
511 if (m_mouseInteracting)
513 m_mouseInteracting = false;
515 else if (m_mouseSlicing)
517 m_mouseSlicing = false;
518 m_sliced = m_mouseIntersecting;
519 if (m_mouseIntersecting)
521 m_slice[0] = m_mouseStartCoord;
522 m_slice[1] = m_mouseCoord;
524 updateGL();
528 void tcViewportWidget::wheelEvent(QWheelEvent* event)
530 float delta = M_PI/180.0 * event->delta()/8;
531 delta *= 0.5f;
532 if (event->orientation() == Qt::Vertical)
534 if (event->modifiers().testFlag(Qt::ControlModifier))
536 m_observer->adjustElevation(delta);
538 else
540 m_observer->adjustRange(-delta);
543 else
545 m_observer->adjustAzimuth(-delta);
547 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
549 updateGL();