Added gui options for wireframe, and colour coding
[tecorrec.git] / tcViewportWidget.cpp
blobbc0c7cb5553362f4d86665c4fd77f9c39e629c88
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>
33 #include <cmath>
36 * Constructors + destructor
39 /// Primary constructor.
40 tcViewportWidget::tcViewportWidget(QWidget* parent)
41 : QGLWidget(parent)
42 , m_polygonMode(GL_FILL)
43 , m_observer(new tcObserver())
44 , m_globe(0)
45 , m_mouseInteracting(false)
46 , m_mousePos()
47 , m_mouseIntersecting(false)
48 , m_mouseCoord()
49 , m_mouseSlicing(false)
50 , m_sliced(false)
52 setMouseTracking(true);
53 setMinimumSize(400,300);
56 /// Destructor.
57 tcViewportWidget::~tcViewportWidget()
59 delete m_observer;
63 * Modifiers
66 /// Set the globe object.
67 void tcViewportWidget::setGlobe(tcGlobe* globe)
69 m_globe = globe;
70 tcGeo pos( 7.03227 * M_PI/180, 45.92093 * M_PI/180);
71 m_observer->setFocus(pos, globe->radiusAt(pos));
75 * Accessors
78 /// Find the coordinates under the mouse.
79 tcGeo tcViewportWidget::geoAt(float x, float y, bool* ok)
81 maths::Vector<2,double> screenXy(x / width(), 1.0 - y / height());
82 maths::Vector<3,double> obs = m_observer->position();
83 maths::Vector<3,double> ray = m_observer->ray(screenXy, (double)width() / height());
84 double r = m_globe->meanRadius();
85 double ro = obs*ray;
86 double roro = ro*ro;
87 bool intersecting = false;
88 // find intersection P of ray and globe
89 // given globe radius r, observer O, ray R, where |R|=1
90 // globe: P*P = r*r
91 // line: P = O + t*R
92 // (O+t*R)*(O+t*R) = r*r
93 // O*O + 2*O*t*R + t*t*R*R = r*r
94 // O*O + 2*O*t*R + t*t - r*r = 0
95 // quadratic in parameter t
96 // a = 1, b = 2*RO c = O*O-r*r RO = O*R
97 // t = (-b +- sqrt(b*b - 4ac)) / 2a
98 // = (-2RO +- sqrt(4*RO*RO - 4*O*O + 4*r*r)) / 2
99 // = (-2*RO +- 2*sqrt(RO*RO - O*O + r*r)) / 2
100 // = RO += sqrt(RO*RO - O*O + r*r)
101 double discriminant = roro - obs.sqr() + r*r;
102 if (discriminant >= 0.0)
104 discriminant = sqrt(discriminant);
105 double t = -ro - discriminant;
106 if (t > 0.0)
108 obs += ray*t;
109 obs /= r; // should now be normalized
110 intersecting = true;
113 else
115 // Hmm, doesn't work
116 #if 0
117 // mouse is outside of globe
118 // use closest point on outline circle of globe
119 // sphere P*P=r*r
120 // tangential circle: P*(P-O) = 0
121 // P*P - P*O = 0
122 // r*r - P*O = 0
123 // line: P/s = O + tR
124 // P = s(O + tR) (s>0)
125 // r*r - s(O + tR)O = 0
126 // r*r - s(OO + tRO) = 0
127 // s(OO + tRO) = r*r
128 // s = r*r/(OO+tRO)
129 // t = (r*r/s - OO)/RO
131 // (sO + tsR)(sO + tsR - O) = 0
132 // (O + tR)(sO + tsR - O) = 0
133 // sOO + tsRO + tsOR + ttsRR - OO - tRO = 0
134 // sOO + 2tsRO + tts - OO - tRO = 0
135 // s(OO + 2tRO + tt) = OO - tRO
136 // substitute s=rr/(OO+tRO)
137 // rr/(OO+tRO)(OO + 2tRO + tt) = OO - tRO
138 // rr(OO + 2tRO + tt) = (OO - tRO)(OO+tRO)
139 // rrOO + 2rrtRO + rrtt = OO*OO - ttRO*RO
140 // tt(rr + RO*RO) + t(2rrRO) + (rrOO - OO*OO)
141 // a = rr + RO*RO, b = 2rrRO, c = rr00 - OO*OO
142 // t = (-2rrRO +- sqrt(4rrrrRO*RO - 4(rr + RO*RO)(rr00 - OO*OO))) / 2(rr + RO*RO)
143 // t = (-rrRO +- sqrt(rrrrRO*RO - (rr + RO*RO)(rr00 - OO*OO))) / (rr + RO*RO)
144 double rr = r*r;
145 double oo = obs*obs;
146 double oooo = oo*oo;
147 double discriminant = rr*rr*roro - (rr + roro)*(rr*oo - oo*oo);
148 if (discriminant >= 0)
150 discriminant = sqrt(discriminant);
151 double t = (-rr*ro + discriminant) / (rr + roro);
152 double s = rr/(oo + t*ro);
153 if (t >= 0.0)
155 obs += ray*t;
156 obs /= s;
157 obs /= r; // should now be normalized
158 obs.normalize();
159 intersecting = true;
162 #endif
164 if (0 != ok)
166 *ok = intersecting;
168 if (intersecting)
170 return tcGeo(obs, true);
172 else
174 return tcGeo();
179 * General rendering slots
182 /// Change the polygon mode to normal.
183 void tcViewportWidget::setPolygonModeNormal()
185 m_polygonMode = GL_FILL;
186 updateGL();
189 /// Change the polygon mode to wireframe.
190 void tcViewportWidget::setPolygonModeWireframe()
192 m_polygonMode = GL_LINE;
193 updateGL();
196 /// Remove colour coding.
197 void tcViewportWidget::setColourCodingNone()
199 m_globe->setColourCoding(tcGlobe::NoColourCoding);
200 updateGL();
203 /// Set colour coding to elevation sample alignment.
204 void tcViewportWidget::setColourCodingElevationSampleAlignment()
206 m_globe->setColourCoding(tcGlobe::ElevationSampleAlignment);
207 updateGL();
211 * Elevation modification slots
214 /// Go to flat elevation mode.
215 void tcViewportWidget::setElevationFlat()
217 if (0 != m_globe)
219 m_globe->setElevationMode(tcGlobe::NoElevation);
220 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
221 updateGL();
225 /// Go to raw SRTM elevation mode.
226 void tcViewportWidget::setElevationRaw()
228 if (0 != m_globe)
230 m_globe->setElevationMode(tcGlobe::RawElevation);
231 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
232 updateGL();
236 /// Go to corrected elevation mode.
237 void tcViewportWidget::setElevationCorrected()
239 if (0 != m_globe)
241 m_globe->setElevationMode(tcGlobe::CorrectedElevation);
242 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
243 updateGL();
247 /// Set the level of correction applied to the elevation data.
248 void tcViewportWidget::setElevationCorrection(int correction)
250 if (0 != m_globe)
252 m_globe->setElevationCorrection((float)correction/100);
253 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
254 updateGL();
258 /// Set the elevation data set name.
259 void tcViewportWidget::setElevationDataSet(const QString& name)
261 if (0 != m_globe)
263 m_globe->setElevationDataSet(name);
264 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
265 updateGL();
270 * Rendering
273 void tcViewportWidget::initializeGL()
275 glClearColor(0.0, 0.3, 0.7, 0.0);
278 void tcViewportWidget::resizeGL(int w, int h)
280 glViewport(0, 0, (GLint)w, (GLint)h);
282 double aspect = (double)w / h;
283 m_observer->setupProjection(aspect);
286 void tcViewportWidget::paintGL()
288 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
289 glEnable(GL_DEPTH_TEST);
290 glEnable(GL_CULL_FACE);
292 glEnable(GL_BLEND);
293 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
295 glPolygonMode(GL_FRONT_AND_BACK, m_polygonMode);
297 m_observer->setupModelView();
299 if (m_sliced)
301 m_globe->render(m_observer, m_slice);
303 else
305 m_globe->render(m_observer);
308 float meanRadius = m_globe->meanRadius();
310 glLineWidth(3);
311 double spotlightExtent = m_observer->range()*0.4;
312 // Draw a spotlight for the mouse
313 if (m_mouseIntersecting)
315 maths::Vector<3,double> vec = m_mouseCoord;
316 float altitude = m_globe->altitudeAt(m_mouseCoord);
317 glBegin(GL_LINES);
319 glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
320 glVertex3(vec*meanRadius);
321 glVertex3(vec*(meanRadius + altitude));
324 glColor4f(1.0f, 0.0f, 1.0f, 1.0f);
325 glVertex3(vec*(meanRadius + altitude));
326 glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
327 glVertex3(vec*(meanRadius + altitude + spotlightExtent));
329 glEnd();
331 // Draw a spotlight for the focus
333 maths::Vector<3,double> vec = m_observer->focus();
334 float altitude = m_globe->altitudeAt(m_observer->focus());
335 glBegin(GL_LINES);
337 glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
338 glVertex3(vec*meanRadius);
339 glVertex3(vec*(meanRadius + altitude));
342 glColor4f(0.0f, 1.0f, 1.0f, 1.0f);
343 glVertex3(vec*(meanRadius + altitude));
344 glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
345 glVertex3(vec*(meanRadius + altitude + spotlightExtent));
347 glEnd();
349 glLineWidth(1);
351 // Draw a fence around the selected region
352 for (int i = 0; i < 2; ++i)
354 tcGeo coord;
355 tcGeo ending;
356 float colour[4] = {1.0f, 0.5f, 1.0f, 1.0f};
357 if (0 == i && m_mouseSlicing && m_mouseIntersecting)
359 coord = m_mouseCoord;
360 ending = m_mouseStartCoord;
362 else if (1 == i && m_sliced)
364 coord = m_slice[0];
365 ending = m_slice[1];
366 colour[0] = 0.0f;
367 colour[3] = 0.5f;
369 else
371 continue;
374 tcGeo delta = ending - coord;
375 int nlon = 2 + fabs(delta.lon())*10*meanRadius / m_observer->range();
376 int nlat = 2 + fabs(delta.lat())*10*meanRadius / m_observer->range();
377 double dlon = delta.lon() / nlon;
378 double dlat = delta.lat() / nlat;
380 glDisable(GL_CULL_FACE);
381 glBegin(GL_TRIANGLE_STRIP);
382 for (int i = 0; i <= nlon*2 + nlat*2; ++i)
384 float radius = m_globe->radiusAt(coord);
385 maths::Vector<3,double> coordVec = coord;
386 glColor4fv(colour);
387 glVertex3(coordVec*radius);
388 glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
389 glVertex3(coordVec*(radius + m_observer->range()*0.1));
391 if (i < nlon)
393 coord.setLon(coord.lon() + dlon);
395 else if (i < nlon + nlat)
397 coord.setLat(coord.lat() + dlat);
399 else if (i < nlon + nlat + nlon)
401 coord.setLon(coord.lon() - dlon);
403 else
405 coord.setLat(coord.lat() - dlat);
408 glEnd();
409 glEnable(GL_CULL_FACE);
415 * Mouse events
418 void tcViewportWidget::mouseMoveEvent(QMouseEvent* event)
420 if (m_mouseInteracting)
422 QPointF dpos = event->posF() - m_mousePos;
423 m_mousePos = event->posF();
425 // Pan the scene, adjust the observer focus
426 m_observer->moveFocusRelative(dpos.x() / height(), dpos.y() / width());
427 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
430 QString mouseDescription;
431 bool ok;
432 m_mouseCoord = geoAt(event->posF().x(), event->posF().y(), &ok);
433 m_mouseIntersecting = ok;
434 if (ok)
436 mouseDescription = m_mouseCoord.describe() + " " + tr("%1 m","metres")
437 .arg(m_globe->altitudeAt(m_mouseCoord));
438 emit mouseGeoChanged(m_mouseCoord);
440 else
442 mouseDescription = tr("--");
444 emit mouseGeoTextChanged(tr("Focus: %1 %2, Mouse: %3")
445 .arg(m_observer->focus().describe())
446 .arg(tr("%1 m","metres")
447 .arg(m_globe->altitudeAt(m_observer->focus())))
448 .arg(mouseDescription));
450 if (m_mouseInteracting || m_mouseSlicing || m_sliced)
452 updateGL();
456 void tcViewportWidget::mousePressEvent(QMouseEvent* event)
458 if (event->button() == Qt::LeftButton)
460 m_mouseInteracting = true;
461 m_mousePos = event->posF();
463 else if (event->button() == Qt::RightButton)
465 bool ok;
466 m_mouseStartCoord = geoAt(event->posF().x(), event->posF().y(), &ok);
467 m_mouseIntersecting = false;
468 if (ok)
470 m_mouseSlicing = true;
475 void tcViewportWidget::mouseReleaseEvent(QMouseEvent* event)
477 if (m_mouseInteracting)
479 m_mouseInteracting = false;
481 else if (m_mouseSlicing)
483 m_mouseSlicing = false;
484 m_sliced = m_mouseIntersecting;
485 if (m_mouseIntersecting)
487 m_slice[0] = m_mouseStartCoord;
488 m_slice[1] = m_mouseCoord;
490 updateGL();
494 void tcViewportWidget::wheelEvent(QWheelEvent* event)
496 float delta = M_PI/180.0 * event->delta()/8;
497 delta *= 0.5f;
498 if (event->orientation() == Qt::Vertical)
500 if (event->modifiers().testFlag(Qt::ControlModifier))
502 m_observer->adjustElevation(delta);
504 else
506 m_observer->adjustRange(-delta);
509 else
511 m_observer->adjustAzimuth(-delta);
513 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
515 updateGL();