Inverted mouse control and allowed range selecting without losing previous selection...
[tecorrec.git] / tcViewportWidget.cpp
blob25ad83233fa9b72fe3e9aab3abe303a9681f47c0
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_observer(new tcObserver())
43 , m_globe(0)
44 , m_mouseInteracting(false)
45 , m_mousePos()
46 , m_mouseIntersecting(false)
47 , m_mouseCoord()
48 , m_mouseSlicing(false)
49 , m_sliced(false)
51 setMouseTracking(true);
54 /// Destructor.
55 tcViewportWidget::~tcViewportWidget()
57 delete m_observer;
61 * Modifiers
64 /// Set the globe object.
65 void tcViewportWidget::setGlobe(tcGlobe* globe)
67 m_globe = globe;
68 tcGeo pos( 7.03227 * M_PI/180, 45.92093 * M_PI/180);
69 m_observer->setFocus(pos, globe->radiusAt(pos));
73 * Accessors
76 /// Find the coordinates under the mouse.
77 tcGeo tcViewportWidget::geoAt(float x, float y, bool* ok)
79 maths::Vector<2,double> screenXy(x / width(), 1.0 - y / height());
80 maths::Vector<3,double> obs = m_observer->position();
81 maths::Vector<3,double> ray = m_observer->ray(screenXy, (double)width() / height());
82 double r = m_globe->meanRadius();
83 double ro = obs*ray;
84 double roro = ro*ro;
85 bool intersecting = false;
86 // find intersection P of ray and globe
87 // given globe radius r, observer O, ray R, where |R|=1
88 // globe: P*P = r*r
89 // line: P = O + t*R
90 // (O+t*R)*(O+t*R) = r*r
91 // O*O + 2*O*t*R + t*t*R*R = r*r
92 // O*O + 2*O*t*R + t*t - r*r = 0
93 // quadratic in parameter t
94 // a = 1, b = 2*RO c = O*O-r*r RO = O*R
95 // t = (-b +- sqrt(b*b - 4ac)) / 2a
96 // = (-2RO +- sqrt(4*RO*RO - 4*O*O + 4*r*r)) / 2
97 // = (-2*RO +- 2*sqrt(RO*RO - O*O + r*r)) / 2
98 // = RO += sqrt(RO*RO - O*O + r*r)
99 double discriminant = roro - obs.sqr() + r*r;
100 if (discriminant >= 0.0)
102 discriminant = sqrt(discriminant);
103 double t = -ro - discriminant;
104 if (t > 0.0)
106 obs += ray*t;
107 obs /= r; // should now be normalized
108 intersecting = true;
111 else
113 // Hmm, doesn't work
114 #if 0
115 // mouse is outside of globe
116 // use closest point on outline circle of globe
117 // sphere P*P=r*r
118 // tangential circle: P*(P-O) = 0
119 // P*P - P*O = 0
120 // r*r - P*O = 0
121 // line: P/s = O + tR
122 // P = s(O + tR) (s>0)
123 // r*r - s(O + tR)O = 0
124 // r*r - s(OO + tRO) = 0
125 // s(OO + tRO) = r*r
126 // s = r*r/(OO+tRO)
127 // t = (r*r/s - OO)/RO
129 // (sO + tsR)(sO + tsR - O) = 0
130 // (O + tR)(sO + tsR - O) = 0
131 // sOO + tsRO + tsOR + ttsRR - OO - tRO = 0
132 // sOO + 2tsRO + tts - OO - tRO = 0
133 // s(OO + 2tRO + tt) = OO - tRO
134 // substitute s=rr/(OO+tRO)
135 // rr/(OO+tRO)(OO + 2tRO + tt) = OO - tRO
136 // rr(OO + 2tRO + tt) = (OO - tRO)(OO+tRO)
137 // rrOO + 2rrtRO + rrtt = OO*OO - ttRO*RO
138 // tt(rr + RO*RO) + t(2rrRO) + (rrOO - OO*OO)
139 // a = rr + RO*RO, b = 2rrRO, c = rr00 - OO*OO
140 // t = (-2rrRO +- sqrt(4rrrrRO*RO - 4(rr + RO*RO)(rr00 - OO*OO))) / 2(rr + RO*RO)
141 // t = (-rrRO +- sqrt(rrrrRO*RO - (rr + RO*RO)(rr00 - OO*OO))) / (rr + RO*RO)
142 double rr = r*r;
143 double oo = obs*obs;
144 double oooo = oo*oo;
145 double discriminant = rr*rr*roro - (rr + roro)*(rr*oo - oo*oo);
146 if (discriminant >= 0)
148 discriminant = sqrt(discriminant);
149 double t = (-rr*ro + discriminant) / (rr + roro);
150 double s = rr/(oo + t*ro);
151 if (t >= 0.0)
153 obs += ray*t;
154 obs /= s;
155 obs /= r; // should now be normalized
156 obs.normalize();
157 intersecting = true;
160 #endif
162 if (0 != ok)
164 *ok = intersecting;
166 if (intersecting)
168 return tcGeo(obs, true);
170 else
172 return tcGeo();
177 * Elevation modification slots
180 /// Go to flat elevation mode.
181 void tcViewportWidget::setElevationFlat()
183 if (0 != m_globe)
185 m_globe->setElevationMode(tcGlobe::NoElevation);
186 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
187 updateGL();
191 /// Go to raw SRTM elevation mode.
192 void tcViewportWidget::setElevationRaw()
194 if (0 != m_globe)
196 m_globe->setElevationMode(tcGlobe::RawElevation);
197 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
198 updateGL();
202 /// Go to corrected elevation mode.
203 void tcViewportWidget::setElevationCorrected()
205 if (0 != m_globe)
207 m_globe->setElevationMode(tcGlobe::CorrectedElevation);
208 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
209 updateGL();
213 /// Set the level of correction applied to the elevation data.
214 void tcViewportWidget::setElevationCorrection(int correction)
216 if (0 != m_globe)
218 m_globe->setElevationCorrection((float)correction/100);
219 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
220 updateGL();
224 /// Set the elevation data set name.
225 void tcViewportWidget::setElevationDataSet(const QString& name)
227 if (0 != m_globe)
229 m_globe->setElevationDataSet(name);
230 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
231 updateGL();
236 * Rendering
239 void tcViewportWidget::initializeGL()
241 glClearColor(0.0, 0.3, 0.7, 0.0);
244 void tcViewportWidget::resizeGL(int w, int h)
246 glViewport(0, 0, (GLint)w, (GLint)h);
248 double aspect = (double)w / h;
249 m_observer->setupProjection(aspect);
252 void tcViewportWidget::paintGL()
254 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
255 glEnable(GL_DEPTH_TEST);
256 glEnable(GL_CULL_FACE);
258 glEnable(GL_BLEND);
259 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
262 m_observer->setupModelView();
264 if (m_sliced)
266 m_globe->render(m_observer, m_slice);
268 else
270 m_globe->render(m_observer);
273 float meanRadius = m_globe->meanRadius();
275 glLineWidth(3);
276 double spotlightExtent = m_observer->range()*0.4;
277 // Draw a spotlight for the mouse
278 if (m_mouseIntersecting)
280 maths::Vector<3,double> vec = m_mouseCoord;
281 float altitude = m_globe->altitudeAt(m_mouseCoord);
282 glBegin(GL_LINES);
284 glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
285 glVertex3(vec*meanRadius);
286 glVertex3(vec*(meanRadius + altitude));
289 glColor4f(1.0f, 0.0f, 1.0f, 1.0f);
290 glVertex3(vec*(meanRadius + altitude));
291 glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
292 glVertex3(vec*(meanRadius + altitude + spotlightExtent));
294 glEnd();
296 // Draw a spotlight for the focus
298 maths::Vector<3,double> vec = m_observer->focus();
299 float altitude = m_globe->altitudeAt(m_observer->focus());
300 glBegin(GL_LINES);
302 glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
303 glVertex3(vec*meanRadius);
304 glVertex3(vec*(meanRadius + altitude));
307 glColor4f(0.0f, 1.0f, 1.0f, 1.0f);
308 glVertex3(vec*(meanRadius + altitude));
309 glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
310 glVertex3(vec*(meanRadius + altitude + spotlightExtent));
312 glEnd();
314 glLineWidth(1);
316 // Draw a fence around the selected region
317 for (int i = 0; i < 2; ++i)
319 tcGeo coord;
320 tcGeo ending;
321 float colour[4] = {1.0f, 0.5f, 1.0f, 1.0f};
322 if (0 == i && m_mouseSlicing && m_mouseIntersecting)
324 coord = m_mouseCoord;
325 ending = m_mouseStartCoord;
327 else if (1 == i && m_sliced)
329 coord = m_slice[0];
330 ending = m_slice[1];
331 colour[0] = 0.0f;
332 colour[3] = 0.5f;
334 else
336 continue;
339 tcGeo delta = ending - coord;
340 int nlon = 2 + fabs(delta.lon())*10*meanRadius / m_observer->range();
341 int nlat = 2 + fabs(delta.lat())*10*meanRadius / m_observer->range();
342 double dlon = delta.lon() / nlon;
343 double dlat = delta.lat() / nlat;
345 glDisable(GL_CULL_FACE);
346 glBegin(GL_TRIANGLE_STRIP);
347 for (int i = 0; i <= nlon*2 + nlat*2; ++i)
349 float radius = m_globe->radiusAt(coord);
350 maths::Vector<3,double> coordVec = coord;
351 glColor4fv(colour);
352 glVertex3(coordVec*radius);
353 glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
354 glVertex3(coordVec*(radius + m_observer->range()*0.1));
356 if (i < nlon)
358 coord.setLon(coord.lon() + dlon);
360 else if (i < nlon + nlat)
362 coord.setLat(coord.lat() + dlat);
364 else if (i < nlon + nlat + nlon)
366 coord.setLon(coord.lon() - dlon);
368 else
370 coord.setLat(coord.lat() - dlat);
373 glEnd();
374 glEnable(GL_CULL_FACE);
380 * Mouse events
383 void tcViewportWidget::mouseMoveEvent(QMouseEvent* event)
385 if (m_mouseInteracting)
387 QPointF dpos = event->posF() - m_mousePos;
388 m_mousePos = event->posF();
390 // Pan the scene, adjust the observer focus
391 m_observer->moveFocusRelative(dpos.x() / height(), dpos.y() / width());
392 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
395 QString mouseDescription;
396 bool ok;
397 m_mouseCoord = geoAt(event->posF().x(), event->posF().y(), &ok);
398 m_mouseIntersecting = ok;
399 if (ok)
401 mouseDescription = m_mouseCoord.describe() + " " + tr("%1 m","metres")
402 .arg(m_globe->altitudeAt(m_mouseCoord));
403 emit mouseGeoChanged(m_mouseCoord);
405 else
407 mouseDescription = tr("--");
409 emit mouseGeoTextChanged(tr("Focus: %1 %2, Mouse: %3")
410 .arg(m_observer->focus().describe())
411 .arg(tr("%1 m","metres")
412 .arg(m_globe->altitudeAt(m_observer->focus())))
413 .arg(mouseDescription));
415 if (m_mouseInteracting || m_mouseSlicing || m_sliced)
417 updateGL();
421 void tcViewportWidget::mousePressEvent(QMouseEvent* event)
423 if (event->button() == Qt::LeftButton)
425 m_mouseInteracting = true;
426 m_mousePos = event->posF();
428 else if (event->button() == Qt::RightButton)
430 bool ok;
431 m_mouseStartCoord = geoAt(event->posF().x(), event->posF().y(), &ok);
432 m_mouseIntersecting = false;
433 if (ok)
435 m_mouseSlicing = true;
440 void tcViewportWidget::mouseReleaseEvent(QMouseEvent* event)
442 if (m_mouseInteracting)
444 m_mouseInteracting = false;
446 else if (m_mouseSlicing)
448 m_mouseSlicing = false;
449 m_sliced = m_mouseIntersecting;
450 if (m_mouseIntersecting)
452 m_slice[0] = m_mouseStartCoord;
453 m_slice[1] = m_mouseCoord;
455 updateGL();
459 void tcViewportWidget::wheelEvent(QWheelEvent* event)
461 float delta = M_PI/180.0 * event->delta()/8;
462 delta *= 0.5f;
463 if (event->orientation() == Qt::Vertical)
465 if (event->modifiers().testFlag(Qt::ControlModifier))
467 m_observer->adjustElevation(delta);
469 else
471 m_observer->adjustRange(-delta);
474 else
476 m_observer->adjustAzimuth(-delta);
478 m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus()));
480 updateGL();