Basic attempt to see normalized difference mentioned in paper scene shadow effects...
[tecorrec.git] / tcViewportWidget.cpp
blob986646cd288908dbd7fbf599c9fff8efc1d2eaaf
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;
213 /// Set slice.
214 void tcViewportWidget::setSlice(const tcGeo& sw, const tcGeo& ne)
216 m_sliced = true;
217 m_slice[0] = sw;
218 m_slice[1] = ne;
219 updateGL();
223 * General rendering slots
226 /// Change the quality to adaptive.
227 void tcViewportWidget::setQualityAdaptive()
229 m_adaptiveQuality = true;
230 updateGL();
233 /// Change the quality to full resolution.
234 void tcViewportWidget::setQualityFull()
236 m_adaptiveQuality = false;
237 updateGL();
240 /// Change the polygon mode to normal.
241 void tcViewportWidget::setPolygonModeNormal()
243 m_polygonMode = GL_FILL;
244 updateGL();
247 /// Change the polygon mode to wireframe.
248 void tcViewportWidget::setPolygonModeWireframe()
250 m_polygonMode = GL_LINE;
251 updateGL();
254 /// Remove colour coding.
255 void tcViewportWidget::setColourCodingNone()
257 m_globe->setColourCoding(tcGlobe::NoColourCoding);
258 updateGL();
261 /// Set colour coding to elevation sample alignment.
262 void tcViewportWidget::setColourCodingElevationSampleAlignment()
264 m_globe->setColourCoding(tcGlobe::ElevationSampleAlignment);
265 updateGL();
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);
272 updateGL();
276 * Elevation modification slots
279 /// Set the primary elevation data set name.
280 void tcViewportWidget::setPrimaryElevationDataSet(const QString& name)
282 if (0 != m_globe)
284 m_globe->setElevationDataSet(0, name);
285 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
286 updateGL();
290 /// Set the secondary elevation data set name.
291 void tcViewportWidget::setSecondaryElevationDataSet(const QString& name)
293 if (0 != m_globe)
295 m_globe->setElevationDataSet(1, name);
296 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
297 updateGL();
301 /// Go to flat primary elevation mode.
302 void tcViewportWidget::setPrimaryElevationFlat()
304 if (0 != m_globe)
306 m_globe->setElevationMode(0, tcGlobe::NoElevation);
307 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
308 updateGL();
312 /// Go to flat secondary elevation mode.
313 void tcViewportWidget::setSecondaryElevationFlat()
315 if (0 != m_globe)
317 m_globe->setElevationMode(1, tcGlobe::NoElevation);
318 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
319 updateGL();
323 /// Go to raw SRTM primary elevation mode.
324 void tcViewportWidget::setPrimaryElevationRaw()
326 if (0 != m_globe)
328 m_globe->setElevationMode(0, tcGlobe::RawElevation);
329 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
330 updateGL();
334 /// Go to raw SRTM secondary elevation mode.
335 void tcViewportWidget::setSecondaryElevationRaw()
337 if (0 != m_globe)
339 m_globe->setElevationMode(1, tcGlobe::RawElevation);
340 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
341 updateGL();
345 /// Go to corrected primary elevation mode.
346 void tcViewportWidget::setPrimaryElevationCorrected()
348 if (0 != m_globe)
350 m_globe->setElevationMode(0, tcGlobe::CorrectedElevation);
351 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
352 updateGL();
356 /// Go to corrected secondary elevation mode.
357 void tcViewportWidget::setSecondaryElevationCorrected()
359 if (0 != m_globe)
361 m_globe->setElevationMode(1, tcGlobe::CorrectedElevation);
362 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
363 updateGL();
367 /// Set the interpolation value.
368 void tcViewportWidget::setElevationInterpolation(int interpolation)
370 if (0 != m_globe)
372 m_globe->setElevationInterpolation((float)interpolation/100);
373 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
374 updateGL();
379 * Rendering
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);
401 if (maxTexUnits < 3)
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);
412 glEnable(GL_BLEND);
413 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
415 glPolygonMode(GL_FRONT_AND_BACK, m_polygonMode);
417 m_observer->setupModelView();
419 if (m_sliced)
421 m_globe->render(m_observer, m_adaptiveQuality, m_slice);
423 else
425 m_globe->render(m_observer);
428 float meanRadius = m_globe->meanRadius();
430 glLineWidth(3);
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);
437 glBegin(GL_LINES);
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));
449 glEnd();
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());
455 glBegin(GL_LINES);
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));
467 glEnd();
469 glLineWidth(1);
471 // Draw a fence around the selected region
472 for (int i = 0; i < 1; ++i)
474 tcGeo coord;
475 tcGeo ending;
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)
484 coord = m_slice[0];
485 ending = m_slice[1];
486 colour[0] = 0.0f;
487 colour[3] = 0.5f;
489 else
491 continue;
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;
506 glColor4fv(colour);
507 glVertex3(coordVec*radius);
508 glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
509 glVertex3(coordVec*(radius + m_observer->range()*0.1));
511 if (i < nlon)
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);
523 else
525 coord.setLat(coord.lat() - dlat);
528 glEnd();
529 glEnable(GL_CULL_FACE);
535 * Mouse events
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;
551 bool ok;
552 m_mouseCoord = geoAt(event->posF().x(), event->posF().y(), &ok);
553 m_mouseIntersecting = ok;
554 if (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);
561 else
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));
571 updateGL();
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)
585 bool ok;
586 m_mouseStartCoord = geoAt(event->posF().x(), event->posF().y(), &ok);
587 m_mouseIntersecting = false;
588 if (ok)
590 m_mouseSlicing = true;
594 else if (m_interactionMode == TexturePoint)
596 bool ok;
597 tcGeo geoCoord = geoAt(event->posF().x(), event->posF().y(), &ok);
598 if (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;
629 updateGL();
633 void tcViewportWidget::wheelEvent(QWheelEvent* event)
635 float delta = M_PI/180.0 * event->delta()/8;
636 delta *= 0.5f;
637 if (event->orientation() == Qt::Vertical)
639 if (event->modifiers().testFlag(Qt::ControlModifier))
641 m_observer->adjustElevation(delta);
643 else
645 m_observer->adjustRange(-delta);
648 else
650 m_observer->adjustAzimuth(-delta);
652 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
654 updateGL();