Final changes before project hand-in
[tecorrec.git] / tcViewportWidget.cpp
blob3c3bc5d8fc0453a08223b056b6b5c185f77bc067
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 <tcChannelManager.h>
30 #include <tcChannel.h>
31 #include <Vector.h>
32 #include <glVector.h>
34 #include <QMouseEvent>
35 #include <QtDebug>
37 #include <cmath>
40 * Constructors + destructor
43 /// Primary constructor.
44 tcViewportWidget::tcViewportWidget(QWidget* parent)
45 : QGLWidget(parent)
46 , m_adaptiveQuality(true)
47 , m_polygonMode(GL_FILL)
48 , m_observer(new tcObserver())
49 , m_globe(0)
50 , m_interactionMode(Navigation)
51 , m_mouseInteracting(false)
52 , m_mousePos()
53 , m_mouseIntersecting(false)
54 , m_mouseCoord()
55 , m_mouseSlicing(false)
56 , m_mouseCrossing(false)
57 , m_sliced(false)
58 , m_crossSectioned(false)
59 , m_texturePointObject(0)
60 , m_texturePointMember(0)
62 setMouseTracking(true);
63 setMinimumSize(400,300);
66 /// Destructor.
67 tcViewportWidget::~tcViewportWidget()
69 delete m_observer;
73 * Modifiers
76 /// Set the globe object.
77 void tcViewportWidget::setGlobe(tcGlobe* globe)
79 m_globe = globe;
80 tcGeo pos( 7.03227 * M_PI/180, 45.92093 * M_PI/180);
81 m_observer->setFocus(pos, globe->radiusAt(pos));
85 * Accessors
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();
95 double ro = obs*ray;
96 double roro = ro*ro;
97 bool intersecting = false;
98 // find intersection P of ray and globe
99 // given globe radius r, observer O, ray R, where |R|=1
100 // globe: P*P = r*r
101 // line: P = O + t*R
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;
116 if (t > 0.0)
118 obs += ray*t;
119 obs /= r; // should now be normalized
120 intersecting = true;
123 else
125 // Hmm, doesn't work
126 #if 0
127 // mouse is outside of globe
128 // use closest point on outline circle of globe
129 // sphere P*P=r*r
130 // tangential circle: P*(P-O) = 0
131 // P*P - P*O = 0
132 // r*r - 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
137 // s(OO + tRO) = r*r
138 // s = r*r/(OO+tRO)
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)
154 double rr = r*r;
155 double oo = obs*obs;
156 double oooo = oo*oo;
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);
163 if (t >= 0.0)
165 obs += ray*t;
166 obs /= s;
167 obs /= r; // should now be normalized
168 obs.normalize();
169 intersecting = true;
172 #endif
174 if (0 != ok)
176 *ok = intersecting;
178 if (intersecting)
180 return tcGeo(obs, true);
182 else
184 return tcGeo();
189 * Observer control
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));
196 updateGL();
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;
217 /// Set slice.
218 void tcViewportWidget::setSlice(const tcGeo& sw, const tcGeo& ne)
220 m_sliced = true;
221 m_slice[0] = sw;
222 m_slice[1] = ne;
223 updateGL();
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);
241 updateGL();
246 * General rendering slots
249 /// Change the quality to adaptive.
250 void tcViewportWidget::setQualityAdaptive()
252 m_adaptiveQuality = true;
253 updateGL();
256 /// Change the quality to full resolution.
257 void tcViewportWidget::setQualityFull()
259 m_adaptiveQuality = false;
260 updateGL();
263 /// Change the polygon mode to normal.
264 void tcViewportWidget::setPolygonModeNormal()
266 m_polygonMode = GL_FILL;
267 updateGL();
270 /// Change the polygon mode to wireframe.
271 void tcViewportWidget::setPolygonModeWireframe()
273 m_polygonMode = GL_LINE;
274 updateGL();
277 /// Remove colour coding.
278 void tcViewportWidget::setColourCodingNone()
280 m_globe->setColourCoding(tcGlobe::NoColourCoding);
281 updateGL();
284 /// Set colour coding to elevation sample alignment.
285 void tcViewportWidget::setColourCodingElevationSampleAlignment()
287 m_globe->setColourCoding(tcGlobe::ElevationSampleAlignment);
288 updateGL();
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);
295 updateGL();
299 * Elevation modification slots
302 /// Set the primary elevation data set name.
303 void tcViewportWidget::setPrimaryElevationDataSet(const QString& name)
305 if (0 != m_globe)
307 m_globe->setElevationDataSet(0, name);
308 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
309 updateGL();
313 /// Set the secondary elevation data set name.
314 void tcViewportWidget::setSecondaryElevationDataSet(const QString& name)
316 if (0 != m_globe)
318 m_globe->setElevationDataSet(1, name);
319 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
320 updateGL();
324 /// Go to flat primary elevation mode.
325 void tcViewportWidget::setPrimaryElevationFlat()
327 if (0 != m_globe)
329 m_globe->setElevationMode(0, tcGlobe::NoElevation);
330 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
331 updateGL();
335 /// Go to flat secondary elevation mode.
336 void tcViewportWidget::setSecondaryElevationFlat()
338 if (0 != m_globe)
340 m_globe->setElevationMode(1, tcGlobe::NoElevation);
341 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
342 updateGL();
346 /// Go to raw SRTM primary elevation mode.
347 void tcViewportWidget::setPrimaryElevationRaw()
349 if (0 != m_globe)
351 m_globe->setElevationMode(0, tcGlobe::RawElevation);
352 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
353 updateGL();
357 /// Go to raw SRTM secondary elevation mode.
358 void tcViewportWidget::setSecondaryElevationRaw()
360 if (0 != m_globe)
362 m_globe->setElevationMode(1, tcGlobe::RawElevation);
363 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
364 updateGL();
368 /// Go to corrected primary elevation mode.
369 void tcViewportWidget::setPrimaryElevationCorrected()
371 if (0 != m_globe)
373 m_globe->setElevationMode(0, tcGlobe::CorrectedElevation);
374 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
375 updateGL();
379 /// Go to corrected secondary elevation mode.
380 void tcViewportWidget::setSecondaryElevationCorrected()
382 if (0 != m_globe)
384 m_globe->setElevationMode(1, tcGlobe::CorrectedElevation);
385 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
386 updateGL();
390 /// Set the interpolation value.
391 void tcViewportWidget::setElevationInterpolation(int interpolation)
393 if (0 != m_globe)
395 m_globe->setElevationInterpolation((float)interpolation/100);
396 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
397 updateGL();
402 * Rendering
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);
424 if (maxTexUnits < 3)
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);
435 glEnable(GL_BLEND);
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();
449 glLineWidth(3);
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);
456 glBegin(GL_LINES);
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));
468 glEnd();
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());
474 glBegin(GL_LINES);
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));
486 glEnd();
488 glLineWidth(1);
490 // Draw a fence around the selected region
491 for (int i = 0; i < 1; ++i)
493 tcGeo coord;
494 tcGeo ending;
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)
503 coord = m_slice[0];
504 ending = m_slice[1];
505 colour[0] = 0.0f;
506 colour[3] = 0.5f;
508 else
510 continue;
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;
525 glColor4fv(colour);
526 glVertex3(coordVec*radius);
527 glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
528 glVertex3(coordVec*(radius + m_observer->range()*0.1));
530 if (i < nlon)
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);
542 else
544 coord.setLat(coord.lat() - dlat);
547 glEnd();
548 glEnable(GL_CULL_FACE);
550 // Draw the cross section area
551 for (int i = 0; i < 2; ++i)
553 tcGeo coord;
554 tcGeo ending;
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];
565 colour[0] = 0.0f;
566 colour[3] = 0.5f;
568 else
570 continue;
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);
579 // Lower bit
580 tcGeo tmp = coord;
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;
586 glColor4fv(colour);
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);
594 glEnd();
595 // Upper bit
596 tmp = coord;
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);
609 glEnd();
610 glDisable(GL_DEPTH_TEST);
611 glEnable(GL_LINE_SMOOTH);
612 glLineWidth(2);
613 tcSrtmModel* model = m_globe->dem();
614 for (int dem = 0; dem < 2; ++dem)
616 tmp = coord;
617 glBegin(GL_LINE_STRIP);
618 for (int i = 0; i < nseg; ++i)
620 bool accurate;
621 float radius = m_globe->meanRadius() + model[dem].altitudeAt(tmp, true, &accurate);
622 maths::Vector<3,double> coordVec = tmp;
623 if (accurate)
625 glColor4f(0.0f, 0.5f*dem, 0.5f*(1.0f-dem), 1.0f);
627 else
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);
636 glEnd();
638 glLineWidth(1);
639 glEnable(GL_DEPTH_TEST);
640 glEnable(GL_CULL_FACE);
646 * Mouse events
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;
662 bool ok;
663 m_mouseCoord = geoAt(event->posF().x(), event->posF().y(), &ok);
664 m_mouseIntersecting = ok;
665 if (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);
672 else
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));
682 updateGL();
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)
696 bool ok;
697 m_mouseStartCoord = geoAt(event->posF().x(), event->posF().y(), &ok);
698 m_mouseIntersecting = false;
699 if (ok)
701 m_mouseSlicing = true;
704 else if (event->button() == Qt::MidButton)
706 bool ok;
707 m_mouseStartCoord = geoAt(event->posF().x(), event->posF().y(), &ok);
708 m_mouseIntersecting = false;
709 if (ok)
711 m_mouseCrossing = true;
715 else if (m_interactionMode == TexturePoint)
717 bool ok;
718 tcGeo geoCoord = geoAt(event->posF().x(), event->posF().y(), &ok);
719 if (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);
735 #include <QtDebug>
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;
751 updateGL();
753 else if (m_mouseCrossing)
755 m_mouseCrossing = false;
756 if (m_mouseIntersecting)
758 setCrossSection(m_mouseStartCoord, m_mouseCoord);
760 else
762 m_crossSectioned = false;
764 updateGL();
768 void tcViewportWidget::wheelEvent(QWheelEvent* event)
770 float delta = M_PI/180.0 * event->delta()/8;
771 delta *= 0.5f;
772 if (event->orientation() == Qt::Vertical)
774 if (event->modifiers().testFlag(Qt::ControlModifier))
776 m_observer->adjustElevation(delta);
778 else
780 m_observer->adjustRange(-delta);
783 else
785 m_observer->adjustAzimuth(-delta);
787 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
789 updateGL();