From f4ef25de74c1c094083b8f875cfb48275eeed073 Mon Sep 17 00:00:00 2001 From: James Hogan Date: Sat, 22 Nov 2008 05:37:01 +0000 Subject: [PATCH] Can now select a region of the map and it only draws that bit and at higher sample rate it can now subdivide horizontally only as well as vertically only added edges to blocks of terrain - makes it look a bit nicer --- NOTES | 4 +- geo/tcGlobe.cpp | 241 ++++++++++++++++++++++++++++------------- geo/tcGlobe.h | 10 +- geo/tcObserver.cpp | 4 +- tcMainWindow.cpp | 98 ++++++++++------- tcViewportWidget.cpp | 296 +++++++++++++++++++++++++++++++++++---------------- tcViewportWidget.h | 16 +++ 7 files changed, 457 insertions(+), 212 deletions(-) diff --git a/NOTES b/NOTES index b9029c4..cfbef00 100644 --- a/NOTES +++ b/NOTES @@ -24,9 +24,9 @@ todo [X] gui to toggle {flat, unprocessed, corrected -> refined} [ ] interaction [X] focus extended altitude - [ ] mouse click -> lon lat transformation + [X] mouse click -> lon lat transformation [ ] terrain grab while dragging - [ ] selection of region + [X] selection of region [ ] region image preview (full resolution) [ ] shadow detection [ ] in region, detect shadow areas diff --git a/geo/tcGlobe.cpp b/geo/tcGlobe.cpp index 4d5eb18..c60830d 100644 --- a/geo/tcGlobe.cpp +++ b/geo/tcGlobe.cpp @@ -84,7 +84,8 @@ void tcGlobe::drawLineOfLatitude(double latitude) const } /// Render a cell. -void tcGlobe::renderCell(tcObserver* const observer, const tcGeo& swCorner, const tcGeo& neCorner) const +void tcGlobe::renderCell(tcObserver* const observer, const tcGeo& swCorner, const tcGeo& neCorner, int samples, + bool northEdge, bool eastEdge, bool southEdge, bool westEdge) const { // Find the square distances to each corner GLmat4d modelview; @@ -112,7 +113,11 @@ void tcGlobe::renderCell(tcObserver* const observer, const tcGeo& swCorner, cons float diagonal = (cartCorners[0]-cartCorners[2]).sqr()*4; bool subdivide = true; // If it is disproportionately tall, only subdivide horizontally - bool tall = (cartCorners[1] - cartCorners[0]) > (cartCorners[3] - cartCorners[0]).sqr()*4.0; + bool tall = (cartCorners[1] - cartCorners[0]).sqr() > (cartCorners[3] - cartCorners[0]).sqr()*4.0 + || (cartCorners[2] - cartCorners[3]).sqr() > (cartCorners[2] - cartCorners[1]).sqr()*4.0; + // If it is disproportionately wide, only subdivide vertically + bool wide = (cartCorners[3] - cartCorners[0]).sqr() > (cartCorners[1] - cartCorners[0]).sqr()*4.0 + || (cartCorners[2] - cartCorners[1]).sqr() > (cartCorners[2] - cartCorners[3]).sqr()*4.0; for (int i = 0; i < 4; ++i) { if (toCorners[i] > diagonal) @@ -145,80 +150,106 @@ void tcGlobe::renderCell(tcObserver* const observer, const tcGeo& swCorner, cons if (tall) { // bottom - renderCell(observer, geoCorners[0], (geoCorners[3] + geoCorners[2]) * 0.5); + renderCell(observer, geoCorners[0], (geoCorners[3] + geoCorners[2]) * 0.5, samples, + false, eastEdge, southEdge, westEdge); // top - renderCell(observer, (geoCorners[0] + geoCorners[1]) * 0.5, geoCorners[2]); + renderCell(observer, (geoCorners[0] + geoCorners[1]) * 0.5, geoCorners[2], samples, + northEdge, eastEdge, false, westEdge); + } + else if (wide) + { + // left + renderCell(observer, geoCorners[0], (geoCorners[1] + geoCorners[2]) * 0.5, samples, + northEdge, false, southEdge, westEdge); + // right + renderCell(observer, (geoCorners[0] + geoCorners[3]) * 0.5, geoCorners[2], samples, + northEdge, eastEdge, southEdge, false); } else { // bottom left - renderCell(observer, geoCorners[0], (geoCorners[0] + geoCorners[2]) * 0.5); + renderCell(observer, geoCorners[0], (geoCorners[0] + geoCorners[2]) * 0.5, samples, + false, false, southEdge, westEdge); // bottom right - renderCell(observer, (geoCorners[0] + geoCorners[3]) * 0.5, (geoCorners[3] + geoCorners[2]) * 0.5); - // top right - renderCell(observer, (geoCorners[0] + geoCorners[1]) * 0.5, (geoCorners[1] + geoCorners[2]) * 0.5); + renderCell(observer, (geoCorners[0] + geoCorners[3]) * 0.5, (geoCorners[3] + geoCorners[2]) * 0.5, samples, + false, eastEdge, southEdge, false); + // top left + renderCell(observer, (geoCorners[0] + geoCorners[1]) * 0.5, (geoCorners[1] + geoCorners[2]) * 0.5, samples, + northEdge, false, false, westEdge); // top right - renderCell(observer, (geoCorners[0] + geoCorners[2]) * 0.5, geoCorners[2]); + renderCell(observer, (geoCorners[0] + geoCorners[2]) * 0.5, geoCorners[2], samples, + northEdge, eastEdge, false, false); } return; } + double dlon = (neCorner.lon()-swCorner.lon()) / samples; + double dlat = (neCorner.lat()-swCorner.lat()) / samples; + + #define EDGE_KERNEL_1 \ + bool accurate = true; \ + double alt = altitudeAt(coord, &accurate); \ + glColor4f(0.5f, 0.3f, 0.2f, 1.0f); \ + glVertex3(dir * (m_meanRadius+alt)); \ + glColor4f(0.5f, 0.5f, 0.5f, 1.0f); \ + glVertex3(dir * m_meanRadius); + #define EDGE_KERNEL_2 \ + bool accurate = true; \ + double alt = altitudeAt(coord, &accurate); \ + glColor4f(0.5f, 0.5f, 1.0f, 1.0f); \ + glVertex3(dir * m_meanRadius); \ + glColor4f(0.5f, 0.5f, 1.0f, 1.0f); \ + glVertex3(dir * (m_meanRadius-(accurate ? 100.0 : 1000.0))); + #define EDGE_KERNEL_3 \ + bool accurate = true; \ + double alt = altitudeAt(coord, &accurate); \ + glColor4f(0.5f, 0.5f, 0.5f, 1.0f); \ + glVertex3(dir * (m_meanRadius-(accurate ? 100.0 : 1000.0))); \ + glColor4f(0.5f, 0.5f, 0.5f, 1.0f); \ + glVertex3(dir * (m_meanRadius-5000.0)); + #define EDGE_KERNEL_4 \ + glColor4f(0.5f, 0.5f, 0.5f, 1.0f); \ + glVertex3(dir * (m_meanRadius-5000.0)); \ + glColor4f(1.0f, 0.0f, 0.0f, 0.5f); \ + glVertex3(dir * (m_meanRadius-8000.0)); + #define EDGE_KERNEL(STAGE, EDGE, LON, LAT) \ + if (EDGE##Edge) \ + { \ + glBegin(GL_TRIANGLE_STRIP); \ + for (int i = 0; i <= samples; ++i) \ + { \ + tcGeo coord = swCorner + tcGeo(LON, LAT); \ + GLvec3d dir = coord; \ + EDGE_KERNEL_##STAGE \ + } \ + glEnd(); \ + } + #define EDGE(STAGE) \ + EDGE_KERNEL(STAGE, north, i * dlon, samples * dlat); \ + EDGE_KERNEL(STAGE, east, samples * dlon, i * dlat); \ + EDGE_KERNEL(STAGE, south, i * dlon, 0.0); \ + EDGE_KERNEL(STAGE, west, 0.0, i * dlat); + + // Render the solid rock walls + glDisable(GL_CULL_FACE); + EDGE(1) + EDGE(2) + EDGE(3) + EDGE(4) + glEnable(GL_CULL_FACE); + // Render this cell - const int resolution = 8; - for (int i = 0; i < resolution; ++i) + for (int i = 0; i < samples; ++i) { glBegin(GL_TRIANGLE_STRIP); { - for (int j = 0; j <= resolution; ++j) + for (int j = 0; j <= samples; ++j) { for (int k = 0; k < 2; ++k) { - double dlon = (neCorner.lon()-swCorner.lon()) / resolution; - double dlat = (neCorner.lat()-swCorner.lat()) / resolution; tcGeo coord = swCorner + tcGeo((i+k) * dlon, j * dlat); bool accurate = true; - double alt = 0.0; - switch (m_elevationMode) - { - case NoElevation: - { - // get accurate set - m_elevation->altitudeAt(coord, true, &accurate); - } - break; - case RawElevation: - { - alt = m_elevation->altitudeAt(coord, false, &accurate); - if (!accurate) - { - alt = 0.0; - } - } - break; - case CorrectedElevation: - { - // get accurate set - m_elevation->altitudeAt(coord, true, &accurate); - if (m_elevationCorrection <= 0.0f) - { - alt = m_elevation->altitudeAt(coord, false, 0); - } - else if (m_elevationCorrection >= 1.0f) - { - alt = m_elevation->altitudeAt(coord, true, 0); - } - else - { - double alt1 = m_elevation->altitudeAt(coord, false, 0); - double alt2 = m_elevation->altitudeAt(coord, true, 0); - alt = alt1*(1.0-m_elevationCorrection) + alt2*m_elevationCorrection; - } - } - break; - default: - Q_ASSERT(false); - break; - } + double alt = altitudeAt(coord, &accurate); // Get colour float intensity = 0.0; @@ -236,7 +267,7 @@ void tcGlobe::renderCell(tcObserver* const observer, const tcGeo& swCorner, cons if (!accurate) { - glColor4f(intensity, intensity, intensity, 0.5f); + glColor4f(0.5f, 0.5f, 1.0f, 1.0f); } else { @@ -254,7 +285,7 @@ void tcGlobe::renderCell(tcObserver* const observer, const tcGeo& swCorner, cons } /// Render from the POV of an observer. -void tcGlobe::render(tcObserver* const observer) +void tcGlobe::render(tcObserver* const observer, const tcGeo* extent) { /// @todo use a really simple fragment shader to cull backfacing lines #if 1 @@ -323,18 +354,38 @@ void tcGlobe::render(tcObserver* const observer) data->renderSchematic(m_meanRadius, observer); } - // Go through cells - const int dlon = 30; - const int dlat = 30; - for (int lon = -180; lon < 180; lon += dlon) + if (0 == extent) { - for (int lat = -90; lat < 90; lat += dlat) + // Go through cells + const int dlon = 30; + const int dlat = 30; + for (int lon = -180; lon < 180; lon += dlon) { - tcGeo sw(M_PI/180 * (lon), M_PI/180 * (lat)); - tcGeo ne(M_PI/180 * (lon+dlon), M_PI/180 * (lat+dlat)); - renderCell(observer, sw, ne); + for (int lat = -90; lat < 90; lat += dlat) + { + tcGeo sw(M_PI/180 * (lon), M_PI/180 * (lat)); + tcGeo ne(M_PI/180 * (lon+dlon), M_PI/180 * (lat+dlat)); + renderCell(observer, sw, ne, 8); + } } } + else + { + tcGeo sw = extent[0]; + tcGeo ne = extent[1]; + if (sw.lon() > ne.lon()) + { + sw.setLon(ne.lon()); + ne.setLon(extent[0].lon()); + } + if (sw.lat() > ne.lat()) + { + sw.setLat(ne.lat()); + ne.setLat(extent[0].lat()); + } + /// @todo If it is really big, split it + renderCell(observer, sw, ne, 20, true, true, true, true); + } } /// Set the elevation mode to render in. @@ -365,15 +416,61 @@ double tcGlobe::meanRadius() const return m_meanRadius; } -/// Get the radius at a coordinate. -double tcGlobe::radiusAt(const tcGeo& coord) +/// Get the altitude above sea level at a coordinate. +double tcGlobe::altitudeAt(const tcGeo& coord, bool* isAccurate) const { - if (m_elevationMode == NoElevation) + bool accurate = true; + double alt = 0.0; + switch (m_elevationMode) { - return m_meanRadius; + case NoElevation: + { + // get accurate set + m_elevation->altitudeAt(coord, true, &accurate); + } + break; + case RawElevation: + { + alt = m_elevation->altitudeAt(coord, false, &accurate); + if (!accurate) + { + alt = 0.0; + } + } + break; + case CorrectedElevation: + { + // get accurate set + m_elevation->altitudeAt(coord, true, &accurate); + if (m_elevationCorrection <= 0.0f) + { + alt = m_elevation->altitudeAt(coord, false, 0); + } + else if (m_elevationCorrection >= 1.0f) + { + alt = m_elevation->altitudeAt(coord, true, 0); + } + else + { + double alt1 = m_elevation->altitudeAt(coord, false, 0); + double alt2 = m_elevation->altitudeAt(coord, true, 0); + alt = alt1*(1.0-m_elevationCorrection) + alt2*m_elevationCorrection; + } + } + break; + default: + Q_ASSERT(false); + break; } - else + if (0 != isAccurate) { - return m_meanRadius + (double)m_elevation->altitudeAt(coord, true); + *isAccurate = accurate; } + return alt; +} + +/// Get the radius at a coordinate. +double tcGlobe::radiusAt(const tcGeo& coord) const +{ + return m_meanRadius + altitudeAt(coord); } diff --git a/geo/tcGlobe.h b/geo/tcGlobe.h index 0c04641..02b754b 100644 --- a/geo/tcGlobe.h +++ b/geo/tcGlobe.h @@ -73,7 +73,7 @@ class tcGlobe */ /// Render from the POV of an observer. - void render(tcObserver* const observer); + void render(tcObserver* const observer, const tcGeo* extent = 0); /// Set the elevation mode to render in. void setElevationMode(ElevationMode mode); @@ -91,8 +91,11 @@ class tcGlobe /// Get the mean radius. double meanRadius() const; + /// Get the altitude above sea level at a coordinate. + double altitudeAt(const tcGeo& coord, bool* isAccurate = 0) const; + /// Get the radius at a coordinate. - double radiusAt(const tcGeo& coord); + double radiusAt(const tcGeo& coord) const; private: @@ -104,7 +107,8 @@ class tcGlobe void drawLineOfLatitude(double latitude) const; /// Render a cell. - void renderCell(tcObserver* const observer, const tcGeo& swCorner, const tcGeo& neCorner) const; + void renderCell(tcObserver* const observer, const tcGeo& swCorner, const tcGeo& neCorner, int samples, + bool northEdge = false, bool eastEdge = false, bool southEdge = false, bool westEdge = false) const; private: diff --git a/geo/tcObserver.cpp b/geo/tcObserver.cpp index 9835331..cabe46e 100644 --- a/geo/tcObserver.cpp +++ b/geo/tcObserver.cpp @@ -56,8 +56,8 @@ void tcObserver::setupProjection(double aspect) const { glMatrixMode(GL_PROJECTION); glLoadIdentity(); - #define SIZE 120.0 - #define NEAR_DISTANCE 100.0 + #define SIZE 5.0 + #define NEAR_DISTANCE 10.0 glFrustum(-SIZE*aspect, SIZE*aspect, -SIZE, SIZE, NEAR_DISTANCE, 20000e3); glMatrixMode(GL_MODELVIEW); } diff --git a/tcMainWindow.cpp b/tcMainWindow.cpp index 087bfeb..32e3e33 100644 --- a/tcMainWindow.cpp +++ b/tcMainWindow.cpp @@ -48,51 +48,67 @@ tcMainWindow::tcMainWindow() m_viewport->setGlobe(m_globe); setCentralWidget(m_viewport); - // Status bar - QStatusBar* statusBar = new QStatusBar(this); - connect(m_viewport, SIGNAL(mouseGeoTextChanged(const QString&)), - statusBar, SLOT(showMessage(const QString&))); - setStatusBar(statusBar); + { + // Status bar + QStatusBar* statusBar = new QStatusBar(this); + connect(m_viewport, SIGNAL(mouseGeoTextChanged(const QString&)), + statusBar, SLOT(showMessage(const QString&))); + setStatusBar(statusBar); + } - // Docker for elevation data settings - QDockWidget* dockElevation = new QDockWidget(tr("Elevation Data"), this); - QWidget* dockElevationWidget = new QWidget(dockElevation); - QVBoxLayout* layout = new QVBoxLayout(dockElevationWidget); - dockElevation->setWidget(dockElevationWidget); - addDockWidget(Qt::LeftDockWidgetArea, dockElevation); + { + // Docker for elevation data settings + QDockWidget* docker = new QDockWidget(tr("Elevation Data"), this); + QWidget* dockerWidget = new QWidget(docker); + QVBoxLayout* layout = new QVBoxLayout(dockerWidget); + docker->setWidget(dockerWidget); + addDockWidget(Qt::LeftDockWidgetArea, docker); - // Combo box to select data set - QComboBox* elevDataSet = new QComboBox(dockElevationWidget); - layout->addWidget(elevDataSet); - QStringList elevDataSets = tcSrtmCache::dataSets(); - foreach (QString dataSet, elevDataSets) - { - elevDataSet->addItem(dataSet); - } - connect(elevDataSet, SIGNAL(currentIndexChanged(const QString&)), m_viewport, SLOT(setElevationDataSet(const QString&))); - if (-1 != elevDataSet->currentIndex()) - { - m_viewport->setElevationDataSet(elevDataSet->currentText()); - } + // Combo box to select data set + QComboBox* elevDataSet = new QComboBox(dockerWidget); + layout->addWidget(elevDataSet); + QStringList elevDataSets = tcSrtmCache::dataSets(); + foreach (QString dataSet, elevDataSets) + { + elevDataSet->addItem(dataSet); + } + connect(elevDataSet, SIGNAL(currentIndexChanged(const QString&)), m_viewport, SLOT(setElevationDataSet(const QString&))); + if (-1 != elevDataSet->currentIndex()) + { + m_viewport->setElevationDataSet(elevDataSet->currentText()); + } - // Radio buttons - QRadioButton* elevFlat = new QRadioButton(tr("Don't Show Elevation (Flat)"), dockElevationWidget); - layout->addWidget(elevFlat); - connect(elevFlat, SIGNAL(pressed()), m_viewport, SLOT(setElevationFlat())); - QRadioButton* elevRaw = new QRadioButton(tr("Raw SRTM Elevation Data"), dockElevationWidget); - layout->addWidget(elevRaw); - elevRaw->setChecked(true); - connect(elevRaw, SIGNAL(pressed()), m_viewport, SLOT(setElevationRaw())); - QRadioButton* elevCorrected = new QRadioButton(tr("Corrected SRTM Elevation Data"), dockElevationWidget); - layout->addWidget(elevCorrected); - connect(elevCorrected, SIGNAL(pressed()), m_viewport, SLOT(setElevationCorrected())); - QSlider* elevCorrectionLevel = new QSlider(Qt::Horizontal, dockElevationWidget); - layout->addWidget(elevCorrectionLevel); - elevCorrectionLevel->setRange(0,100); - connect(elevCorrectionLevel, SIGNAL(valueChanged(int)), m_viewport, SLOT(setElevationCorrection(int))); + // Radio buttons + QRadioButton* elevFlat = new QRadioButton(tr("Don't Show Elevation (Flat)"), dockerWidget); + layout->addWidget(elevFlat); + connect(elevFlat, SIGNAL(pressed()), m_viewport, SLOT(setElevationFlat())); + QRadioButton* elevRaw = new QRadioButton(tr("Raw SRTM Elevation Data"), dockerWidget); + layout->addWidget(elevRaw); + elevRaw->setChecked(true); + connect(elevRaw, SIGNAL(pressed()), m_viewport, SLOT(setElevationRaw())); + QRadioButton* elevCorrected = new QRadioButton(tr("Corrected SRTM Elevation Data"), dockerWidget); + layout->addWidget(elevCorrected); + connect(elevCorrected, SIGNAL(pressed()), m_viewport, SLOT(setElevationCorrected())); + QSlider* elevCorrectionLevel = new QSlider(Qt::Horizontal, dockerWidget); + layout->addWidget(elevCorrectionLevel); + elevCorrectionLevel->setRange(0,100); + connect(elevCorrectionLevel, SIGNAL(valueChanged(int)), m_viewport, SLOT(setElevationCorrection(int))); - // Spacer - layout->addStretch(); + // Spacer + layout->addStretch(); + } + + { + // Docker for processing + QDockWidget* docker = new QDockWidget(tr("Processing"), this); + QWidget* dockerWidget = new QWidget(docker); + QVBoxLayout* layout = new QVBoxLayout(dockerWidget); + docker->setWidget(dockerWidget); + addDockWidget(Qt::LeftDockWidgetArea, docker); + + // Spacer + layout->addStretch(); + } } /// Destructor. diff --git a/tcViewportWidget.cpp b/tcViewportWidget.cpp index 0de26f5..f9d0fee 100644 --- a/tcViewportWidget.cpp +++ b/tcViewportWidget.cpp @@ -45,6 +45,8 @@ tcViewportWidget::tcViewportWidget(QWidget* parent) , m_mousePos() , m_mouseIntersecting(false) , m_mouseCoord() +, m_mouseSlicing(false) +, m_sliced(false) { setMouseTracking(true); } @@ -68,6 +70,110 @@ void tcViewportWidget::setGlobe(tcGlobe* globe) } /* + * Accessors + */ + +/// Find the coordinates under the mouse. +tcGeo tcViewportWidget::geoAt(float x, float y, bool* ok) +{ + maths::Vector<2,double> screenXy(x / width(), 1.0 - y / height()); + maths::Vector<3,double> obs = m_observer->position(); + maths::Vector<3,double> ray = m_observer->ray(screenXy, (double)width() / height()); + double r = m_globe->meanRadius(); + double ro = obs*ray; + double roro = ro*ro; + bool intersecting = false; + // find intersection P of ray and globe + // given globe radius r, observer O, ray R, where |R|=1 + // globe: P*P = r*r + // line: P = O + t*R + // (O+t*R)*(O+t*R) = r*r + // O*O + 2*O*t*R + t*t*R*R = r*r + // O*O + 2*O*t*R + t*t - r*r = 0 + // quadratic in parameter t + // a = 1, b = 2*RO c = O*O-r*r RO = O*R + // t = (-b +- sqrt(b*b - 4ac)) / 2a + // = (-2RO +- sqrt(4*RO*RO - 4*O*O + 4*r*r)) / 2 + // = (-2*RO +- 2*sqrt(RO*RO - O*O + r*r)) / 2 + // = RO += sqrt(RO*RO - O*O + r*r) + double discriminant = roro - obs.sqr() + r*r; + if (discriminant >= 0.0) + { + discriminant = sqrt(discriminant); + double t = -ro - discriminant; + if (t > 0.0) + { + obs += ray*t; + obs /= r; // should now be normalized + intersecting = true; + } + } + else + { + // Hmm, doesn't work +#if 0 + // mouse is outside of globe + // use closest point on outline circle of globe + // sphere P*P=r*r + // tangential circle: P*(P-O) = 0 + // P*P - P*O = 0 + // r*r - P*O = 0 + // line: P/s = O + tR + // P = s(O + tR) (s>0) + // r*r - s(O + tR)O = 0 + // r*r - s(OO + tRO) = 0 + // s(OO + tRO) = r*r + // s = r*r/(OO+tRO) + // t = (r*r/s - OO)/RO + // + // (sO + tsR)(sO + tsR - O) = 0 + // (O + tR)(sO + tsR - O) = 0 + // sOO + tsRO + tsOR + ttsRR - OO - tRO = 0 + // sOO + 2tsRO + tts - OO - tRO = 0 + // s(OO + 2tRO + tt) = OO - tRO + // substitute s=rr/(OO+tRO) + // rr/(OO+tRO)(OO + 2tRO + tt) = OO - tRO + // rr(OO + 2tRO + tt) = (OO - tRO)(OO+tRO) + // rrOO + 2rrtRO + rrtt = OO*OO - ttRO*RO + // tt(rr + RO*RO) + t(2rrRO) + (rrOO - OO*OO) + // a = rr + RO*RO, b = 2rrRO, c = rr00 - OO*OO + // t = (-2rrRO +- sqrt(4rrrrRO*RO - 4(rr + RO*RO)(rr00 - OO*OO))) / 2(rr + RO*RO) + // t = (-rrRO +- sqrt(rrrrRO*RO - (rr + RO*RO)(rr00 - OO*OO))) / (rr + RO*RO) + double rr = r*r; + double oo = obs*obs; + double oooo = oo*oo; + double discriminant = rr*rr*roro - (rr + roro)*(rr*oo - oo*oo); + if (discriminant >= 0) + { + discriminant = sqrt(discriminant); + double t = (-rr*ro + discriminant) / (rr + roro); + double s = rr/(oo + t*ro); + if (t >= 0.0) + { + obs += ray*t; + obs /= s; + obs /= r; // should now be normalized + obs.normalize(); + intersecting = true; + } + } +#endif + } + if (0 != ok) + { + *ok = intersecting; + } + if (intersecting) + { + return tcGeo(obs, true); + } + else + { + return tcGeo(); + } +} + +/* * Elevation modification slots */ @@ -155,7 +261,14 @@ void tcViewportWidget::paintGL() m_observer->setupModelView(); - m_globe->render(m_observer); + if (m_sliced) + { + m_globe->render(m_observer, m_slice); + } + else + { + m_globe->render(m_observer); + } // Draw a line for the mouse if (m_mouseIntersecting) @@ -173,6 +286,56 @@ void tcViewportWidget::paintGL() glEnd(); glLineWidth(1); } + + // Draw a fence around the selected region + if (m_mouseSlicing || m_sliced) + { + tcGeo ending = (m_mouseSlicing ? m_mouseCoord : m_slice[1]); + float colour[4] = {1.0f, 0.5f, 1.0f, 1.0f}; + if (!m_mouseSlicing) + { + colour[0] = 0.0f; + colour[3] = 0.5f; + } + tcGeo delta = ending - m_slice[0]; + int nlon = 2 + fabs(delta.lon())*10*m_globe->meanRadius() / m_observer->range(); + int nlat = 2 + fabs(delta.lat())*10*m_globe->meanRadius() / m_observer->range(); + double dlon = delta.lon() / nlon; + double dlat = delta.lat() / nlat; + tcGeo coord = m_slice[0]; + + glDisable(GL_CULL_FACE); + glBegin(GL_TRIANGLE_STRIP); + for (int i = 0; i <= nlon*2 + nlat*2; ++i) + { + float radius = m_globe->radiusAt(coord); + maths::Vector<3,double> coordVec = coord; + glColor4fv(colour); + glVertex3(coordVec*radius); + glColor4f(1.0f, 1.0f, 1.0f, 0.0f); + glVertex3(coordVec*(radius + m_observer->range()*0.1)); + + if (i < nlon) + { + coord.setLon(coord.lon() + dlon); + } + else if (i < nlon + nlat) + { + coord.setLat(coord.lat() + dlat); + } + else if (i < nlon + nlat + nlon) + { + coord.setLon(coord.lon() - dlon); + } + else + { + coord.setLat(coord.lat() - dlat); + } + } + glEnd(); + glEnable(GL_CULL_FACE); + } + } /* @@ -191,95 +354,14 @@ void tcViewportWidget::mouseMoveEvent(QMouseEvent* event) m_observer->setFocusAltitude(m_globe->radiusAt(m_observer->focus())); } - maths::Vector<2,double> screenXy(event->posF().x() / width(), 1.0 - event->posF().y() / height()); - maths::Vector<3,double> obs = m_observer->position(); - maths::Vector<3,double> ray = m_observer->ray(screenXy, (double)width() / height()); - double r = m_globe->meanRadius(); - // find intersection P of ray and globe - // given globe radius r, observer O, ray R, where |R|=1 - // globe: P*P = r*r - // line: P = O + t*R - // (O+t*R)*(O+t*R) = r*r - // O*O + 2*O*t*R + t*t*R*R = r*r - // O*O + 2*O*t*R + t*t - r*r = 0 - // quadratic in parameter t - // a = 1, b = 2*z c = O*O-r*r z = O*R - // t = (-b +- sqrt(b*b - 4ac)) / 2a - // = (-2z +- sqrt(4*z*z - 4*O*O + 4*r*r)) / 2 - // = (-2*z +- 2*sqrt(z*z - O*O + r*r)) / 2 - // = z += sqrt(z*z - O*O + r*r) - double z = obs*ray; - double discriminant = z*z - obs.sqr() + r*r; - m_mouseIntersecting = false; - if (discriminant >= 0.0) - { - discriminant = sqrt(discriminant); - double t = -z - discriminant; - if (t > 0.0) - { - obs += ray*t; - obs /= r; // should now be normalized - m_mouseIntersecting = true; - } - } - else - { - // Hmm, doesn't work -#if 0 - // mouse is outside of globe - // use closest point on outline circle of globe - // sphere P*P=r*r - // tangential circle: P*(P-O) = 0 - // P*P - P*O = 0 - // r*r - P*O = 0 - // line: P/s = O + tR - // P = s(O + tR) (s>0) - // r*r - s(O + tR)O = 0 - // r*r - s(OO + tRO) = 0 - // s(OO + tRO) = r*r - // s = r*r/(OO+tRO) - // t = (r*r/s - OO)/RO - // - // (sO + tsR)(sO + tsR - O) = 0 - // (O + tR)(sO + tsR - O) = 0 - // sOO + tsRO + tsOR + ttsRR - OO - tRO = 0 - // sOO + 2tsRO + tts - OO - tRO = 0 - // s(OO + 2tRO + tt) = OO - tRO - // substitute s=rr/(OO+tRO) - // rr/(OO+tRO)(OO + 2tRO + tt) = OO - tRO - // rr(OO + 2tRO + tt) = (OO - tRO)(OO+tRO) - // rrOO + 2rrtRO + rrtt = OO*OO - ttRO*RO - // tt(rr + RO*RO) + t(2rrRO) + (rrOO - OO*OO) - // a = rr + RO*RO, b = 2rrRO, c = rr00 - OO*OO - // t = (-2rrRO +- sqrt(4rrrrRO*RO - 4(rr + RO*RO)(rr00 - OO*OO))) / 2(rr + RO*RO) - // t = (-rrRO +- sqrt(rrrrRO*RO - (rr + RO*RO)(rr00 - OO*OO))) / (rr + RO*RO) - double rr = r*r; - double zz = z*z; - double oo = obs*obs; - double oooo = oo*oo; - double discriminant = rr*rr*zz - (rr + zz)*(rr*oo - oo*oo); - if (discriminant >= 0) - { - discriminant = sqrt(discriminant); - double t = (-rr*z + discriminant) / (rr + zz); - double s = rr/(oo + t*z); - if (t >= 0.0) - { - obs += ray*t; - obs /= s; - obs /= r; // should now be normalized - obs.normalize(); - m_mouseIntersecting = true; - } - } -#endif - } QString mouseDescription; - if (m_mouseIntersecting) + bool ok; + m_mouseCoord = geoAt(event->posF().x(), event->posF().y(), &ok); + m_mouseIntersecting = ok; + if (ok) { - m_mouseCoord = tcGeo(obs, true); mouseDescription = m_mouseCoord.describe() + " " + tr("%1 m","metres") - .arg(m_globe->radiusAt(m_mouseCoord) - r); + .arg(m_globe->altitudeAt(m_mouseCoord)); emit mouseGeoChanged(m_mouseCoord); } else @@ -289,21 +371,51 @@ void tcViewportWidget::mouseMoveEvent(QMouseEvent* event) emit mouseGeoTextChanged(tr("Focus: %1 %2, Mouse: %3") .arg(m_observer->focus().describe()) .arg(tr("%1 m","metres") - .arg(m_globe->radiusAt(m_observer->focus()) - r)) + .arg(m_globe->altitudeAt(m_observer->focus()))) .arg(mouseDescription)); - updateGL(); + if (m_mouseInteracting || m_mouseSlicing || m_sliced) + { + updateGL(); + } } void tcViewportWidget::mousePressEvent(QMouseEvent* event) { - m_mouseInteracting = true; - m_mousePos = event->posF(); + if (event->button() == Qt::LeftButton) + { + m_mouseInteracting = true; + m_mousePos = event->posF(); + } + else if (event->button() == Qt::RightButton) + { + bool ok; + m_slice[0] = geoAt(event->posF().x(), event->posF().y(), &ok); + m_mouseIntersecting = false; + if (ok) + { + m_mouseSlicing = true; + m_sliced = false; + } + } } void tcViewportWidget::mouseReleaseEvent(QMouseEvent* event) { - m_mouseInteracting = false; + if (m_mouseInteracting) + { + m_mouseInteracting = false; + } + else if (m_mouseSlicing) + { + m_mouseSlicing = false; + if (m_mouseIntersecting) + { + m_sliced = true; + m_slice[1] = m_mouseCoord; + } + updateGL(); + } } void tcViewportWidget::wheelEvent(QWheelEvent* event) diff --git a/tcViewportWidget.h b/tcViewportWidget.h index 2656dd2..223877b 100644 --- a/tcViewportWidget.h +++ b/tcViewportWidget.h @@ -60,6 +60,13 @@ class tcViewportWidget : public QGLWidget /// Set the globe object. void setGlobe(tcGlobe* globe); + /* + * Accessors + */ + + /// Find the coordinates under the mouse. + tcGeo geoAt(float x, float y, bool* ok = 0); + public slots: /* @@ -153,6 +160,15 @@ class tcViewportWidget : public QGLWidget /// Current mouse coordinate. tcGeo m_mouseCoord; + + /// Slicing with the mouse? + bool m_mouseSlicing; + + /// Sliced? + bool m_sliced; + + /// Coordinates of slice. + tcGeo m_slice[2]; }; #endif -- 2.11.4.GIT