MAJOR: resample all textures into geographical coordinates and use 2x3 transformation...
[tecorrec.git] / tcViewportWidget.cpp
blobb1cebf5f95da2f5db564e767bac01d791971b96c
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()
191 m_observer->setView(m_globe->imagery()->sunDirection());
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)
262 m_globe->setColourMapping(output, input);
263 updateGL();
267 * Elevation modification slots
270 /// Go to flat elevation mode.
271 void tcViewportWidget::setElevationFlat()
273 if (0 != m_globe)
275 m_globe->setElevationMode(tcGlobe::NoElevation);
276 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
277 updateGL();
281 /// Go to raw SRTM elevation mode.
282 void tcViewportWidget::setElevationRaw()
284 if (0 != m_globe)
286 m_globe->setElevationMode(tcGlobe::RawElevation);
287 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
288 updateGL();
292 /// Go to corrected elevation mode.
293 void tcViewportWidget::setElevationCorrected()
295 if (0 != m_globe)
297 m_globe->setElevationMode(tcGlobe::CorrectedElevation);
298 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
299 updateGL();
303 /// Set the level of correction applied to the elevation data.
304 void tcViewportWidget::setElevationCorrection(int correction)
306 if (0 != m_globe)
308 m_globe->setElevationCorrection((float)correction/100);
309 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
310 updateGL();
314 /// Set the elevation data set name.
315 void tcViewportWidget::setElevationDataSet(const QString& name)
317 if (0 != m_globe)
319 m_globe->setElevationDataSet(name);
320 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
321 updateGL();
326 * Rendering
329 void tcViewportWidget::initializeGL()
331 glClearColor(0.0, 0.3, 0.7, 0.0);
334 void tcViewportWidget::resizeGL(int w, int h)
336 glViewport(0, 0, (GLint)w, (GLint)h);
338 double aspect = (double)w / h;
339 m_observer->setupProjection(aspect);
342 void tcViewportWidget::paintGL()
344 static int maxTexUnits = 0;
345 if (maxTexUnits == 0)
347 glGetIntegerv(GL_MAX_TEXTURE_UNITS, &maxTexUnits);
348 if (maxTexUnits < 3)
350 qDebug() << tr("Maximum texture units is %1, but Tecorrec needs at least 3").arg(maxTexUnits);
355 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
356 glEnable(GL_DEPTH_TEST);
357 glEnable(GL_CULL_FACE);
359 glEnable(GL_BLEND);
360 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
362 glPolygonMode(GL_FRONT_AND_BACK, m_polygonMode);
364 m_observer->setupModelView();
366 if (m_sliced)
368 m_globe->render(m_observer, m_adaptiveQuality, m_slice);
370 else
372 m_globe->render(m_observer);
375 float meanRadius = m_globe->meanRadius();
377 glLineWidth(3);
378 double spotlightExtent = m_observer->range()*0.4;
379 // Draw a spotlight for the mouse
380 if (m_mouseIntersecting)
382 maths::Vector<3,double> vec = m_mouseCoord;
383 float altitude = m_globe->altitudeAt(m_mouseCoord);
384 glBegin(GL_LINES);
386 glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
387 glVertex3(vec*meanRadius);
388 glVertex3(vec*(meanRadius + altitude));
391 glColor4f(1.0f, 0.0f, 1.0f, 1.0f);
392 glVertex3(vec*(meanRadius + altitude));
393 glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
394 glVertex3(vec*(meanRadius + altitude + spotlightExtent));
396 glEnd();
398 // Draw a spotlight for the focus
400 maths::Vector<3,double> vec = m_observer->focus();
401 float altitude = m_globe->altitudeAt(m_observer->focus());
402 glBegin(GL_LINES);
404 glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
405 glVertex3(vec*meanRadius);
406 glVertex3(vec*(meanRadius + altitude));
409 glColor4f(0.0f, 1.0f, 1.0f, 1.0f);
410 glVertex3(vec*(meanRadius + altitude));
411 glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
412 glVertex3(vec*(meanRadius + altitude + spotlightExtent));
414 glEnd();
416 glLineWidth(1);
418 // Draw a fence around the selected region
419 for (int i = 0; i < 1; ++i)
421 tcGeo coord;
422 tcGeo ending;
423 float colour[4] = {1.0f, 0.5f, 1.0f, 1.0f};
424 if (0 == i && m_mouseSlicing && m_mouseIntersecting)
426 coord = m_mouseCoord;
427 ending = m_mouseStartCoord;
429 else if (1 == i && m_sliced)
431 coord = m_slice[0];
432 ending = m_slice[1];
433 colour[0] = 0.0f;
434 colour[3] = 0.5f;
436 else
438 continue;
441 tcGeo delta = ending - coord;
442 int nlon = 2 + fabs(delta.lon())*10*meanRadius / m_observer->range();
443 int nlat = 2 + fabs(delta.lat())*10*meanRadius / m_observer->range();
444 double dlon = delta.lon() / nlon;
445 double dlat = delta.lat() / nlat;
447 glDisable(GL_CULL_FACE);
448 glBegin(GL_TRIANGLE_STRIP);
449 for (int i = 0; i <= nlon*2 + nlat*2; ++i)
451 float radius = m_globe->radiusAt(coord);
452 maths::Vector<3,double> coordVec = coord;
453 glColor4fv(colour);
454 glVertex3(coordVec*radius);
455 glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
456 glVertex3(coordVec*(radius + m_observer->range()*0.1));
458 if (i < nlon)
460 coord.setLon(coord.lon() + dlon);
462 else if (i < nlon + nlat)
464 coord.setLat(coord.lat() + dlat);
466 else if (i < nlon + nlat + nlon)
468 coord.setLon(coord.lon() - dlon);
470 else
472 coord.setLat(coord.lat() - dlat);
475 glEnd();
476 glEnable(GL_CULL_FACE);
482 * Mouse events
485 void tcViewportWidget::mouseMoveEvent(QMouseEvent* event)
487 if (m_mouseInteracting)
489 QPointF dpos = event->posF() - m_mousePos;
490 m_mousePos = event->posF();
492 // Pan the scene, adjust the observer focus
493 m_observer->moveFocusRelative(dpos.x() / height(), dpos.y() / width());
494 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
497 QString mouseDescription;
498 bool ok;
499 m_mouseCoord = geoAt(event->posF().x(), event->posF().y(), &ok);
500 m_mouseIntersecting = ok;
501 if (ok)
503 mouseDescription = m_mouseCoord.describe() + " " + tr("%1 m","metres")
504 .arg(m_globe->altitudeAt(m_mouseCoord));
505 emit mouseGeoChanged(m_mouseCoord);
507 else
509 mouseDescription = tr("--");
511 emit mouseGeoTextChanged(tr("Focus: %1 %2, Mouse: %3")
512 .arg(m_observer->focus().describe())
513 .arg(tr("%1 m","metres")
514 .arg(m_globe->altitudeAt(m_observer->focus())))
515 .arg(mouseDescription));
517 updateGL();
520 void tcViewportWidget::mousePressEvent(QMouseEvent* event)
522 if (m_interactionMode == Navigation)
524 if (event->button() == Qt::LeftButton)
526 m_mouseInteracting = true;
527 m_mousePos = event->posF();
529 else if (event->button() == Qt::RightButton)
531 bool ok;
532 m_mouseStartCoord = geoAt(event->posF().x(), event->posF().y(), &ok);
533 m_mouseIntersecting = false;
534 if (ok)
536 m_mouseSlicing = true;
540 else if (m_interactionMode == TexturePoint)
542 bool ok;
543 tcGeo geoCoord = geoAt(event->posF().x(), event->posF().y(), &ok);
544 if (ok)
546 maths::Vector<2,float> texturePoint(m_globe->textureCoordOfGeo(geoCoord));
547 // need to revert interaction mode first in case slot at other end requests another
548 m_interactionMode = Navigation;
549 QObject* currentObj = m_texturePointObject;
550 const char* currentMember = m_texturePointMember;
551 connect(this, SIGNAL(texturePointSelected(const maths::Vector<2,float>&)),
552 currentObj, currentMember);
553 emit texturePointSelected(texturePoint);
554 disconnect(this, SIGNAL(texturePointSelected(const maths::Vector<2,float>&)),
555 currentObj, currentMember);
560 void tcViewportWidget::mouseReleaseEvent(QMouseEvent* event)
562 if (m_mouseInteracting)
564 m_mouseInteracting = false;
566 else if (m_mouseSlicing)
568 m_mouseSlicing = false;
569 m_sliced = m_mouseIntersecting;
570 if (m_mouseIntersecting)
572 m_slice[0] = m_mouseStartCoord;
573 m_slice[1] = m_mouseCoord;
575 updateGL();
579 void tcViewportWidget::wheelEvent(QWheelEvent* event)
581 float delta = M_PI/180.0 * event->delta()/8;
582 delta *= 0.5f;
583 if (event->orientation() == Qt::Vertical)
585 if (event->modifiers().testFlag(Qt::ControlModifier))
587 m_observer->adjustElevation(delta);
589 else
591 m_observer->adjustRange(-delta);
594 else
596 m_observer->adjustAzimuth(-delta);
598 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
600 updateGL();