source: geovis/trunk/Renderer.cpp @ 4643

Last change on this file since 4643 was 4643, checked in by ldelgass, 10 years ago

Fixes for building with osgearth 2.6

File size: 65.2 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * Copyright (C) 2004-2013  HUBzero Foundation, LLC
4 *
5 * Author: Leif Delgass <ldelgass@purdue.edu>
6 */
7
8#include <cfloat>
9#include <cstring>
10#include <cassert>
11#include <cmath>
12#include <cstdlib>
13
14#include <limits>
15#include <set>
16
17#include <sys/types.h>
18#include <unistd.h> // For getpid()
19
20#include <GL/gl.h>
21
22#ifdef WANT_TRACE
23#include <sys/time.h>
24#endif
25
26#include <osgDB/FileUtils>
27#include <osgDB/FileNameUtils>
28#include <osgGA/StateSetManipulator>
29#include <osgGA/GUIEventAdapter>
30#include <osgViewer/ViewerEventHandlers>
31
32#include <osgEarth/Version>
33#include <osgEarth/FileUtils>
34#include <osgEarth/Cache>
35#include <osgEarthDrivers/cache_filesystem/FileSystemCache>
36#include <osgEarth/MapNode>
37#include <osgEarth/Bounds>
38#include <osgEarth/Profile>
39#include <osgEarth/Viewpoint>
40#include <osgEarth/GeoMath>
41#include <osgEarth/TerrainLayer>
42#include <osgEarth/ImageLayer>
43#include <osgEarth/ElevationLayer>
44#include <osgEarth/ModelLayer>
45#include <osgEarth/DateTime>
46#include <osgEarth/Pickers>
47#include <osgEarthAnnotation/AnnotationNode>
48#include <osgEarthAnnotation/PlaceNode>
49#include <osgEarthAnnotation/HighlightDecoration>
50#include <osgEarthAnnotation/ScaleDecoration>
51#include <osgEarthUtil/EarthManipulator>
52#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1)
53#include <osgEarthUtil/Sky>
54#include <osgEarthDrivers/sky_simple/SimpleSkyOptions>
55#else
56#include <osgEarthUtil/SkyNode>
57#endif
58#include <osgEarthUtil/AutoClipPlaneHandler>
59#include <osgEarthUtil/MouseCoordsTool>
60#include <osgEarthUtil/UTMGraticule>
61#include <osgEarthUtil/MGRSGraticule>
62#include <osgEarthUtil/GeodeticGraticule>
63#include <osgEarthUtil/LatLongFormatter>
64#include <osgEarthUtil/MGRSFormatter>
65#include <osgEarthUtil/GLSLColorFilter>
66#include <osgEarthUtil/VerticalScale>
67#include <osgEarthDrivers/gdal/GDALOptions>
68#include <osgEarthDrivers/engine_mp/MPTerrainEngineOptions>
69
70#include "Renderer.h"
71#if 0
72#include "SingleWindow.h"
73#endif
74#include "ScaleBar.h"
75#include "FileUtil.h"
76#include "Trace.h"
77
78#define MSECS_ELAPSED(t1, t2) \
79    ((t1).tv_sec == (t2).tv_sec ? (((t2).tv_usec - (t1).tv_usec)/1.0e+3) : \
80     (((t2).tv_sec - (t1).tv_sec))*1.0e+3 + (double)((t2).tv_usec - (t1).tv_usec)/1.0e+3)
81
82#define BASE_IMAGE "world.tif"
83#define PIN_ICON "placemark32.png"
84
85using namespace GeoVis;
86
87Renderer::Renderer() :
88    _needsRedraw(false),
89    _windowWidth(500),
90    _windowHeight(500),
91    _scaleBarUnits(UNITS_METERS)
92{
93    TRACE("Enter");
94
95    _bgColor[0] = 0;
96    _bgColor[1] = 0;
97    _bgColor[2] = 0;
98    //setMaximumFrameRateInHertz(15.0);
99    // 100 Mbps
100    setMaximumBitrate(1.0e8);
101    _lastFrameTime = _minFrameTime;
102    TRACE("Bandwidth target: %.2f Mbps", (float)(getMaximumBitrate()/1.0e6));
103    TRACE("Frame rate target: %.2f Hz", (float)getMaximumFrameRateInHertz());
104    TRACE("Frame time target: %.2f msec", _minFrameTime * 1000.0f);
105
106    char *base = getenv("MAP_BASE_URI");
107    if (base != NULL) {
108        _baseURI = base;
109        TRACE("Setting base URI: %s", _baseURI.c_str());
110    }
111}
112
113osgGA::EventQueue *Renderer::getEventQueue()
114{
115    if (_viewer.valid()) {
116        osgViewer::ViewerBase::Windows windows;
117        _viewer->getWindows(windows);
118        if (windows.size() > 0) {
119            return windows[0]->getEventQueue();
120        }
121    }
122    return NULL;
123}
124
125Renderer::~Renderer()
126{
127    TRACE("Enter");
128
129    removeDirectory(_cacheDir.c_str());
130
131    TRACE("Leave");
132}
133
134std::string Renderer::getBaseImage()
135{
136    std::ostringstream oss;
137    oss << _resourcePath << "/" << BASE_IMAGE;
138    return oss.str();
139}
140
141std::string Renderer::getPinIcon()
142{
143    std::ostringstream oss;
144    oss << _resourcePath << "/" << PIN_ICON;
145    return oss.str();
146}
147
148void Renderer::setupCache()
149{
150    std::ostringstream dir;
151    dir << "/tmp/geovis_cache" << getpid();
152    _cacheDir = dir.str();
153    const char *path = _cacheDir.c_str();
154    TRACE("Cache dir: %s", path);
155    removeDirectory(path);
156    if (!osgDB::makeDirectory(_cacheDir)) {
157        ERROR("Failed to create directory '%s'", path);
158    }
159}
160
161void Renderer::initColorMaps()
162{
163    if (!_colorMaps.empty())
164        return;
165
166    osg::TransferFunction1D *defaultColorMap = new osg::TransferFunction1D;
167    defaultColorMap->allocate(256);
168    defaultColorMap->setColor(0.00, osg::Vec4f(0,0,1,1), false);
169    defaultColorMap->setColor(0.25, osg::Vec4f(0,1,1,1), false);
170    defaultColorMap->setColor(0.50, osg::Vec4f(0,1,0,1), false);
171    defaultColorMap->setColor(0.75, osg::Vec4f(1,1,0,1), false);
172    defaultColorMap->setColor(1.00, osg::Vec4f(1,0,0,1), false);
173    defaultColorMap->updateImage();
174    addColorMap("default", defaultColorMap);
175    osg::TransferFunction1D *defaultGrayColorMap = new osg::TransferFunction1D;
176    defaultGrayColorMap->allocate(256);
177    defaultGrayColorMap->setColor(0, osg::Vec4f(0,0,0,1), false);
178    defaultGrayColorMap->setColor(1, osg::Vec4f(1,1,1,1), false);
179    defaultGrayColorMap->updateImage();
180    addColorMap("grayDefault", defaultGrayColorMap);
181}
182
183void Renderer::addColorMap(const ColorMapId& id, osg::TransferFunction1D *xfer)
184{
185    _colorMaps[id] = xfer;
186}
187
188void Renderer::deleteColorMap(const ColorMapId& id)
189{
190    ColorMapHashmap::iterator itr;
191    bool doAll = false;
192
193    if (id.compare("all") == 0) {
194        itr = _colorMaps.begin();
195        doAll = true;
196    } else {
197        itr = _colorMaps.find(id);
198    }
199
200    if (itr == _colorMaps.end()) {
201        ERROR("Unknown ColorMap %s", id.c_str());
202        return;
203    }
204
205    do {
206        if (itr->first.compare("default") == 0 ||
207            itr->first.compare("grayDefault") == 0) {
208            if (id.compare("all") != 0) {
209                WARN("Cannot delete a default color map");
210            }
211            continue;
212        }
213
214        TRACE("Deleting ColorMap %s", itr->first.c_str());
215        itr = _colorMaps.erase(itr);
216    } while (doAll && itr != _colorMaps.end());
217}
218
219void Renderer::setColorMapNumberOfTableEntries(const ColorMapId& id,
220                                               int numEntries)
221{
222    ColorMapHashmap::iterator itr;
223    bool doAll = false;
224
225    if (id.compare("all") == 0) {
226        itr = _colorMaps.begin();
227        doAll = true;
228    } else {
229        itr = _colorMaps.find(id);
230    }
231
232    if (itr == _colorMaps.end()) {
233        ERROR("Unknown ColorMap %s", id.c_str());
234        return;
235    }
236
237    do {
238        itr->second->allocate(numEntries);
239        itr->second->updateImage();
240    } while (doAll && ++itr != _colorMaps.end());
241}
242
243void Renderer::initViewer() {
244    if (_viewer.valid())
245        return;
246    _viewer = new osgViewer::Viewer();
247#if 1
248    osg::DisplaySettings *ds = _viewer->getDisplaySettings();
249    if (ds == NULL) {
250        ds = osg::DisplaySettings::instance().get();
251    }
252    ds->setDoubleBuffer(false);
253    ds->setMinimumNumAlphaBits(8);
254    ds->setMinimumNumStencilBits(8);
255    ds->setNumMultiSamples(0);
256#endif
257    _viewer->setThreadingModel(osgViewer::ViewerBase::SingleThreaded);
258    _viewer->getDatabasePager()->setUnrefImageDataAfterApplyPolicy(false, false);
259    _viewer->setReleaseContextAtEndOfFrameHint(false);
260    //_viewer->setLightingMode(osg::View::SKY_LIGHT);
261    _viewer->getCamera()->setClearColor(osg::Vec4(_bgColor[0], _bgColor[1], _bgColor[2], 1));
262    _viewer->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
263    _viewer->getCamera()->setNearFarRatio(0.00002);
264    _viewer->getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
265    _stateManip = new osgGA::StateSetManipulator(_viewer->getCamera()->getOrCreateStateSet());
266    _viewer->addEventHandler(_stateManip);
267    //_viewer->addEventHandler(new osgViewer::StatsHandler());
268
269#ifdef DEBUG
270    if (_viewer->getViewerStats() != NULL) {
271        TRACE("Enabling stats");
272        _viewer->getViewerStats()->collectStats("scene", true);
273    }
274#endif
275#if 0
276    osgViewer::ViewerBase::Windows windows;
277    _viewer->getWindows(windows);
278    if (windows.size() == 1) {
279        windows[0]->setSyncToVBlank(false);
280    } else {
281        ERROR("Num windows: %lu", windows.size());
282    }
283#endif
284}
285
286void Renderer::finalizeViewer() {
287    initViewer();
288    TRACE("Before _viewer->isRealized()");
289    if (!_viewer->isRealized()) {
290        int screen = 0;
291        const char *displayEnv = getenv("DISPLAY");
292        if (displayEnv != NULL) {
293            TRACE("DISPLAY: %s", displayEnv);
294            // 3 parts: host, display, screen
295            int part = 0;
296            for (size_t c = 0; c < strlen(displayEnv); c++) {
297                if (displayEnv[c] == ':') {
298                    part = 1;
299                } else if (part == 1 && displayEnv[c] == '.') {
300                    part = 2;
301                } else if (part == 2) {
302                    screen = atoi(&displayEnv[c]);
303                    break;
304                }
305            }
306        }
307        TRACE("Using screen: %d", screen);
308#ifdef USE_OFFSCREEN_RENDERING
309#ifdef USE_PBUFFER
310        osg::ref_ptr<osg::GraphicsContext> pbuffer;
311        osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
312        traits->x = 0;
313        traits->y = 0;
314        traits->width = _windowWidth;
315        traits->height = _windowHeight;
316        traits->red = 8;
317        traits->green = 8;
318        traits->blue = 8;
319        traits->alpha = 8;
320        traits->windowDecoration = false;
321        traits->pbuffer = true;
322        traits->doubleBuffer = true;
323        traits->sharedContext = 0;
324
325        pbuffer = osg::GraphicsContext::createGraphicsContext(traits.get());
326        if (pbuffer.valid()) {
327            TRACE("Pixel buffer has been created successfully.");
328        } else {
329            ERROR("Pixel buffer has not been created successfully.");
330        }
331        osg::Camera *camera = new osg::Camera;
332        camera->setGraphicsContext(pbuffer.get());
333        camera->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
334        GLenum buffer = pbuffer->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
335        camera->setDrawBuffer(buffer);
336        camera->setReadBuffer(buffer);
337        _captureCallback = new ScreenCaptureCallback();
338        camera->setFinalDrawCallback(_captureCallback.get());
339        _viewer->addSlave(camera, osg::Matrixd(), osg::Matrixd());
340#else
341        _viewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
342        osg::Texture2D* texture2D = new osg::Texture2D;
343        texture2D->setTextureSize(_windowWidth, _windowHeight);
344        texture2D->setInternalFormat(GL_RGBA);
345        texture2D->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST);
346        texture2D->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST);
347
348        _viewer->getCamera()->setImplicitBufferAttachmentMask(0, 0);
349        _viewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, texture2D);
350        //_viewer->getCamera()->attach(osg::Camera::DEPTH_BUFFER, GL_DEPTH_COMPONENT24);
351        _viewer->getCamera()->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, GL_DEPTH24_STENCIL8_EXT);
352        _captureCallback = new ScreenCaptureCallback(texture2D);
353        _viewer->getCamera()->setFinalDrawCallback(_captureCallback.get());
354        _viewer->setUpViewInWindow(0, 0, _windowWidth, _windowHeight, screen);
355#endif
356#else
357        _captureCallback = new ScreenCaptureCallback();
358        _viewer->getCamera()->setFinalDrawCallback(_captureCallback.get());
359#if 1
360        _viewer->setUpViewInWindow(0, 0, _windowWidth, _windowHeight, screen);
361#else
362        SingleWindow *windowConfig = new SingleWindow(0, 0, _windowWidth, _windowHeight, screen);
363        osg::DisplaySettings *ds = windowConfig->getActiveDisplaySetting(*_viewer.get());
364        ds->setDoubleBuffer(false);
365        ds->setMinimumNumAlphaBits(8);
366        ds->setMinimumNumStencilBits(8);
367        ds->setNumMultiSamples(0);
368        windowConfig->setWindowDecoration(false);
369        windowConfig->setOverrideRedirect(false);
370        _viewer->apply(windowConfig);
371#endif
372#endif
373        _viewer->realize();
374        initColorMaps();
375        // HACK: This seems to initialize something required for properly
376        // mapping mouse coords
377        assert(getEventQueue() != NULL);
378        getEventQueue()->mouseMotion(_windowWidth/2, _windowHeight/2);
379    }
380}
381
382void Renderer::initControls()
383{
384    if (_hbox.valid())
385        return;
386    _hbox =
387        new osgEarth::Util::Controls::HBox(osgEarth::Util::Controls::Control::ALIGN_RIGHT,
388                                           osgEarth::Util::Controls::Control::ALIGN_BOTTOM,
389                                           osgEarth::Util::Controls::Gutter(0, 2, 2, 0), 2.0f);
390    _copyrightLabel =
391        new osgEarth::Util::Controls::LabelControl("Map data © 2014 ACME Corp.", 12.0f);
392    _copyrightLabel->setForeColor(osg::Vec4f(1, 1, 1, 1));
393    _copyrightLabel->setHaloColor(osg::Vec4f(0, 0, 0, 1));
394    _copyrightLabel->setEncoding(osgText::String::ENCODING_UTF8);
395    _scaleLabel =
396        new osgEarth::Util::Controls::LabelControl("- km", 12.0f);
397    _scaleLabel->setForeColor(osg::Vec4f(1, 1, 1, 1));
398    _scaleLabel->setHaloColor(osg::Vec4f(0, 0, 0, 1));
399    _scaleBar =
400        new osgEarth::Util::Controls::Frame();
401    _scaleBar->setVertFill(true);
402    _scaleBar->setForeColor(osg::Vec4f(0, 0, 0, 1));
403    _scaleBar->setBackColor(osg::Vec4f(1, 1, 1, 0.6));
404    _scaleBar->setBorderColor(osg::Vec4f(0, 0, 0 ,1));
405    _scaleBar->setBorderWidth(1.0);
406    _hbox->addControl(_copyrightLabel.get());
407    _hbox->addControl(_scaleLabel.get());
408    _hbox->addControl(_scaleBar.get());
409#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1)
410    osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->addControl(_hbox.get());
411#else
412    osgEarth::Util::Controls::ControlCanvas::get(_viewer.get(), true)->addControl(_hbox.get());
413#endif
414    // Install an event callback to handle scale bar updates
415    // Can't use an update callback since that will trigger
416    // constant rendering
417    _mapNode->setEventCallback(new MapNodeCallback(this));
418}
419
420void Renderer::setGraticule(bool enable, GraticuleType type)
421{
422    if (!_mapNode.valid() || !_sceneRoot.valid())
423        return;
424    if (enable) {
425        if (_graticule.valid()) {
426            _sceneRoot->removeChild(_graticule.get());
427            _graticule = NULL;
428        }
429        switch (type) {
430        case GRATICULE_UTM: {
431            osgEarth::Util::UTMGraticule *gr = new osgEarth::Util::UTMGraticule(_mapNode.get());
432            _sceneRoot->addChild(gr);
433            _graticule = gr;
434        }
435            break;
436        case GRATICULE_MGRS: {
437            osgEarth::Util::MGRSGraticule *gr = new osgEarth::Util::MGRSGraticule(_mapNode.get());
438            _sceneRoot->addChild(gr);
439            _graticule = gr;
440        }
441            break;
442        case GRATICULE_GEODETIC:
443        default:
444            osgEarth::Util::GeodeticGraticule *gr = new osgEarth::Util::GeodeticGraticule(_mapNode.get());
445            osgEarth::Util::GeodeticGraticuleOptions opt = gr->getOptions();
446            opt.lineStyle()->getOrCreate<osgEarth::Symbology::LineSymbol>()->stroke()->color().set(1,0,0,1);
447            gr->setOptions(opt);
448            _sceneRoot->addChild(gr);
449            _graticule = gr;
450        }
451    } else if (_graticule.valid()) {
452        _sceneRoot->removeChild(_graticule.get());
453        _graticule = NULL;
454    }
455    _needsRedraw = true;
456}
457
458void Renderer::setReadout(int x, int y)
459{
460    if (!_coordsCallback.valid() || !_mapNode.valid() || !_viewer.valid())
461        return;
462
463    osgEarth::GeoPoint mapCoord;
464    if (mapMouseCoords(x, y, mapCoord)) {
465        _coordsCallback->set(mapCoord, _viewer->asView(), _mapNode);
466    } else {
467        _coordsCallback->reset(_viewer->asView(), _mapNode);
468    }
469    _needsRedraw = true;
470}
471
472void Renderer::clearReadout()
473{
474    if (_coordsCallback.valid()) {
475        _coordsCallback->reset(_viewer->asView(), _mapNode);
476    }
477    _needsRedraw = true;
478}
479
480void Renderer::setCoordinateReadout(bool state, CoordinateDisplayType type,
481                                    int precision)
482{
483    if (!state) {
484        if (_mouseCoordsTool.valid() && _viewer.valid()) {
485            _viewer->removeEventHandler(_mouseCoordsTool.get());
486            _mouseCoordsTool = NULL;
487        }
488        if (_coordsCallback.valid() && _viewer.valid()) {
489            osgEarth::Util::Controls::LabelControl *readout =
490                _coordsCallback->getLabel();
491#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1)
492            osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->removeControl(readout);
493#else
494            osgEarth::Util::Controls::ControlCanvas::get(_viewer.get(), true)->removeControl(readout);
495#endif
496            _coordsCallback = NULL;
497        }
498    } else {
499        initMouseCoordsTool(type, precision);
500    }
501    _needsRedraw = true;
502}
503
504osgEarth::Util::MGRSFormatter::Precision
505Renderer::getMGRSPrecision(int precisionInMeters)
506{
507    switch (precisionInMeters) {
508    case 1:
509        return osgEarth::Util::MGRSFormatter::PRECISION_1M;
510    case 10:
511        return osgEarth::Util::MGRSFormatter::PRECISION_10M;
512    case 100:
513        return osgEarth::Util::MGRSFormatter::PRECISION_100M;
514    case 1000:
515        return osgEarth::Util::MGRSFormatter::PRECISION_1000M;
516    case 10000:
517        return osgEarth::Util::MGRSFormatter::PRECISION_10000M;
518    case 100000:
519        return osgEarth::Util::MGRSFormatter::PRECISION_100000M;
520    default:
521        ERROR("Invalid precision: %d", precisionInMeters);
522        return osgEarth::Util::MGRSFormatter::PRECISION_1M;
523    }
524}
525
526void Renderer::initMouseCoordsTool(CoordinateDisplayType type, int precision)
527{
528    if (!_viewer.valid())
529        return;
530    if (_mouseCoordsTool.valid()) {
531        _viewer->removeEventHandler(_mouseCoordsTool.get());
532    }
533    _mouseCoordsTool = new MouseCoordsTool(_mapNode.get());
534    osgEarth::Util::Controls::LabelControl *readout;
535    if (_coordsCallback.valid()) {
536        readout = _coordsCallback->getLabel();
537        _coordsCallback = NULL;
538    } else {
539        readout = new osgEarth::Util::Controls::LabelControl("", 12.0f);
540#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1)
541        osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->addControl(readout);
542#else
543        osgEarth::Util::Controls::ControlCanvas::get(_viewer.get(), true)->addControl(readout);
544#endif
545        readout->setForeColor(osg::Vec4f(1, 1, 1, 1));
546        readout->setHaloColor(osg::Vec4f(0, 0, 0, 1));
547    }
548
549    osgEarth::Util::Formatter *formatter = NULL;
550    if (type == COORDS_MGRS) {
551        osgEarth::Util::MGRSFormatter::Precision prec =
552            osgEarth::Util::MGRSFormatter::PRECISION_1M;
553        if (precision > 0) {
554            prec = getMGRSPrecision(precision);
555        }
556        unsigned int opts = 0u;
557        formatter = new osgEarth::Util::MGRSFormatter(prec, NULL, opts);
558    } else {
559        osgEarth::Util::LatLongFormatter::AngularFormat af;
560        unsigned int opts =  osgEarth::Util::LatLongFormatter::USE_SYMBOLS;
561        switch (type) {
562        case COORDS_LATLONG_DEGREES_DECIMAL_MINUTES:
563            af = osgEarth::Util::LatLongFormatter::FORMAT_DEGREES_DECIMAL_MINUTES;
564            break;
565        case COORDS_LATLONG_DEGREES_MINUTES_SECONDS:
566            af = osgEarth::Util::LatLongFormatter::FORMAT_DEGREES_MINUTES_SECONDS;
567            break;
568        default:
569            af = osgEarth::Util::LatLongFormatter::FORMAT_DECIMAL_DEGREES;
570        }
571        osgEarth::Util::LatLongFormatter *latlong = new osgEarth::Util::LatLongFormatter(af, opts);
572        if (precision > 0) {
573            latlong->setPrecision(precision);
574        }
575        formatter = latlong;
576    }
577    _coordsCallback = new MouseCoordsCallback(readout, formatter);
578
579    _mouseCoordsTool->addCallback(_coordsCallback.get());
580    _viewer->addEventHandler(_mouseCoordsTool.get());
581}
582
583void Renderer::initEarthManipulator()
584{
585    _manipulator = new osgEarth::Util::EarthManipulator;
586#if 1
587    osgEarth::Util::EarthManipulator::Settings *settings = _manipulator->getSettings();
588    settings->bindMouse(osgEarth::Util::EarthManipulator::ACTION_ROTATE,
589                        osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON,
590                        osgGA::GUIEventAdapter::MODKEY_ALT);
591    osgEarth::Util::EarthManipulator::ActionOptions options;
592    options.clear();
593    options.add(osgEarth::Util::EarthManipulator::OPTION_CONTINUOUS, true);
594    settings->bindMouse(osgEarth::Util::EarthManipulator::ACTION_ZOOM,
595                        osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON,
596                        osgGA::GUIEventAdapter::MODKEY_ALT, options);
597    _manipulator->applySettings(settings);
598#endif
599    _viewer->setCameraManipulator(_manipulator.get());
600    _manipulator->setNode(NULL);
601    _manipulator->setNode(_sceneRoot.get());
602    //_manipulator->computeHomePosition();
603}
604
605void Renderer::loadEarthFile(const char *path)
606{
607    TRACE("Loading %s", path);
608    osg::Node *node = osgDB::readNodeFile(path);
609    if (node == NULL) {
610        ERROR("Couldn't load %s", path);
611        return;
612    }
613    osgEarth::MapNode *mapNode = osgEarth::MapNode::findMapNode(node);
614    if (mapNode == NULL) {
615        ERROR("Couldn't find MapNode");
616        return;
617    } else {
618        initViewer();
619        _sceneRoot = new osg::Group;
620        _sceneRoot->addChild(node);
621        _map = mapNode->getMap();
622    }
623    _mapNode = mapNode;
624
625    if (_clipPlaneCullCallback.valid()) {
626        _viewer->getCamera()->removeCullCallback(_clipPlaneCullCallback.get());
627        _clipPlaneCullCallback = NULL;
628    }
629    if (_map->isGeocentric()) {
630        _clipPlaneCullCallback = new osgEarth::Util::AutoClipPlaneCullCallback(mapNode);
631        _viewer->getCamera()->addCullCallback(_clipPlaneCullCallback.get());
632    }
633    _viewer->setSceneData(_sceneRoot.get());
634
635    if (_mouseCoordsTool.valid()) {
636        initMouseCoordsTool();
637    }
638    initControls();
639    initEarthManipulator();
640   
641    _viewer->home();
642    finalizeViewer();
643    _needsRedraw = true;
644}
645
646void Renderer::resetMap(osgEarth::MapOptions::CoordinateSystemType type,
647                        const char *profile,
648                        double bounds[4])
649{
650    TRACE("Restting map with type %d, profile %s", type, profile);
651
652    osgEarth::MapOptions mapOpts;
653    mapOpts.coordSysType() = type;
654    if (profile != NULL) {
655        if (bounds != NULL) {
656            mapOpts.profile() = osgEarth::ProfileOptions();
657            if (strcmp(profile, "geodetic") == 0) {
658                mapOpts.profile()->srsString() = "epsg:4326";
659            } else if (strcmp(profile, "spherical-mercator") == 0) {
660                // Projection used by Google/Bing/OSM
661                // aka epsg:900913 meters in x/y
662                // aka WGS84 Web Mercator (Auxiliary Sphere)
663                // X/Y: -20037508.34m to 20037508.34m
664                mapOpts.profile()->srsString() = "epsg:3857";
665            } else {
666                mapOpts.profile()->srsString() = profile;
667            }
668            TRACE("Setting profile bounds: %g %g %g %g",
669                  bounds[0], bounds[1], bounds[2], bounds[3]);
670            mapOpts.profile()->bounds() =
671                osgEarth::Bounds(bounds[0], bounds[1], bounds[2], bounds[3]);
672        } else {
673            mapOpts.profile() = osgEarth::ProfileOptions(profile);
674        }
675    } else if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) {
676        mapOpts.profile() = osgEarth::ProfileOptions("global-mercator");
677    }
678
679#ifdef USE_CACHE
680    setupCache();
681    osgEarth::Drivers::FileSystemCacheOptions cacheOpts;
682    cacheOpts.rootPath() = _cacheDir;
683    mapOpts.cache() = cacheOpts;
684#endif
685
686    initViewer();
687
688    //mapOpts.referenceURI() = _baseURI;
689    osgEarth::Map *map = new osgEarth::Map(mapOpts);
690    _map = map;
691    osgEarth::Drivers::GDALOptions bopts;
692    bopts.url() = getBaseImage();
693    addImageLayer("base", bopts);
694#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1)
695    osgEarth::Drivers::MPTerrainEngine::MPTerrainEngineOptions mpOpt;
696#else
697    osgEarth::Drivers::MPTerrainEngineOptions mpOpt;
698#endif
699    // Set background layer color
700    mpOpt.color() = osg::Vec4(1, 1, 1, 1);
701    //mpOpt.minLOD() = 1;
702    // Sets shader uniform for terrain renderer (config var defaults to false)
703    mpOpt.enableLighting() = false;
704    osgEarth::MapNodeOptions mapNodeOpts(mpOpt);
705    // Sets GL_LIGHTING state in MapNode's StateSet (config var defaults to true)
706    mapNodeOpts.enableLighting() = true;
707    //mapNodeOpts.getTerrainOptions().loadingPolicy().mapLoadingThreadsPerCore() = 1;
708    //mapNodeOpts.getTerrainOptions().loadingPolicy().numLoadingThreads() = 1;
709    //mapNodeOpts.getTerrainOptions().loadingPolicy().numCompileThreadsPerCore() = 1;
710    //mapNodeOpts.getTerrainOptions().loadingPolicy().numCompileThreads() = 1;
711    osgEarth::MapNode *mapNode = new osgEarth::MapNode(map, mapNodeOpts);
712    _mapNode = mapNode;
713    if (_map->isGeocentric()) {
714        osgEarth::DateTime now;
715#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1)
716        TRACE("Creating SkyNode");
717        osgEarth::Drivers::SimpleSky::SimpleSkyOptions skyOpts;
718        skyOpts.hours() = now.hours();
719        skyOpts.ambient() = 0.2f;
720        skyOpts.atmosphericLighting() = true;
721        skyOpts.exposure() = 3.0;
722        osgEarth::Util::SkyNode *sky = osgEarth::Util::SkyNode::create(skyOpts, mapNode);
723        sky->addChild(mapNode);
724        sky->attach(_viewer.get(), 0);
725        _sceneRoot = sky;
726#else
727        _sceneRoot = new osg::Group();
728        _sceneRoot->addChild(_mapNode.get());
729
730        TRACE("Creating SkyNode");
731        osgEarth::Util::SkyNode *sky = new osgEarth::Util::SkyNode(map);
732        _sceneRoot->addChild(sky);
733        sky->setAmbientBrightness(0.2f);
734        sky->setDateTime(now);
735        sky->attach(_viewer.get(), 0);
736#endif
737    } else {
738        _sceneRoot = new osg::Group();
739        _sceneRoot->addChild(_mapNode.get());
740    }
741
742    if (_clipPlaneCullCallback.valid()) {
743        _viewer->getCamera()->removeCullCallback(_clipPlaneCullCallback.get());
744        _clipPlaneCullCallback = NULL;
745    }
746    if (_map->isGeocentric()) {
747        _clipPlaneCullCallback = new osgEarth::Util::AutoClipPlaneCullCallback(mapNode);
748        _viewer->getCamera()->addCullCallback(_clipPlaneCullCallback.get());
749    }
750    _viewer->setSceneData(_sceneRoot.get());
751    if (_mouseCoordsTool.valid()) {
752        initMouseCoordsTool();
753    }
754    initControls();
755    //_viewer->setSceneData(_sceneRoot.get());
756    initEarthManipulator();
757    _viewer->home();
758
759    finalizeViewer();
760    _needsRedraw = true;
761}
762
763void Renderer::clearMap()
764{
765    if (_map.valid()) {
766        _map->clear();
767        _needsRedraw = true;
768    }
769}
770
771void Renderer::setLighting(bool state)
772{
773    if (_mapNode.valid()) {
774        TRACE("Setting lighting: %d", state ? 1 : 0);
775        _mapNode->getOrCreateStateSet()
776            ->setMode(GL_LIGHTING, state ? 1 : 0);
777        _needsRedraw = true;
778    }
779}
780
781void Renderer::setViewerLightType(osg::View::LightingMode mode)
782{
783    if (_viewer.valid()) {
784        _viewer->setLightingMode(mode);
785        _needsRedraw = true;
786    }
787}
788
789void Renderer::setTerrainVerticalScale(double scale)
790{
791    if (_mapNode.valid()) {
792        if (!_verticalScale.valid()) {
793            _verticalScale = new osgEarth::Util::VerticalScale;
794            _mapNode->getTerrainEngine()->addEffect(_verticalScale);
795        }
796        _verticalScale->setScale(scale);
797        _needsRedraw = true;
798    }
799}
800
801void Renderer::setTerrainLighting(bool state)
802{
803#if 1
804    if (!_mapNode.valid()) {
805        ERROR("No map node");
806        return;
807    }
808    // XXX: HACK alert
809    // Find the terrain engine container (might be above one or more decorators)
810    osg::Group *group = _mapNode->getTerrainEngine();
811    while (group->getParent(0) != NULL && group->getParent(0) != _mapNode.get()) {
812        group = group->getParent(0);
813    }
814    if (group != NULL && group->getParent(0) == _mapNode.get()) {
815        TRACE("Setting terrain lighting: %d", state ? 1 : 0);
816        if (group->getOrCreateStateSet()->getUniform("oe_mode_GL_LIGHTING") != NULL) {
817            group->getStateSet()->getUniform("oe_mode_GL_LIGHTING")->set(state);
818        } else {
819            ERROR("Can't get terrain lighting uniform");
820        }
821    } else {
822        ERROR("Can't find terrain engine container");
823    }
824#else
825    if (_stateManip.valid()) {
826        _stateManip->setLightingEnabled(state);
827    }
828#endif
829    _needsRedraw = true;
830}
831
832void Renderer::setTerrainWireframe(bool state)
833{
834    if (!_map.valid()) {
835        ERROR("No map");
836        return;
837    }
838#if 0
839    if (!_mapNode.valid()) {
840        ERROR("No map node");
841        return;
842    }
843    TRACE("Setting terrain wireframe: %d", state ? 1 : 0);
844    osg::StateSet *state = _mapNode->getOrCreateStateSet();
845    osg::PolygonMode *pmode = dynamic_cast< osg::PolygonMode* >(state->getAttribute(osg::StateAttribute::POLYGONMODE));
846    if (pmode == NULL) {
847        pmode = new osg::PolygonMode;
848        state->setAttribute(pmode);
849    }
850    if (state) {
851        pmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE);
852    } else {
853        pmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL);
854    }
855    _needsRedraw = true;
856#else
857    if (_stateManip.valid()) {
858        _stateManip->setPolygonMode(state ? osg::PolygonMode::LINE : osg::PolygonMode::FILL);
859        _needsRedraw = true;
860    }
861#endif
862}
863
864void Renderer::saveNamedViewpoint(const char *name)
865{
866    _viewpoints[name] = getViewpoint();
867}
868
869bool Renderer::removeNamedViewpoint(const char *name)
870{
871    ViewpointHashmap::iterator itr = _viewpoints.find(name);
872    if (itr != _viewpoints.end()) {
873        _viewpoints.erase(name);
874        return true;
875    } else {
876        ERROR("Unknown viewpoint: '%s'", name);
877        return false;
878    }
879}
880
881bool Renderer::restoreNamedViewpoint(const char *name, double durationSecs)
882{
883    ViewpointHashmap::iterator itr = _viewpoints.find(name);
884    if (itr != _viewpoints.end()) {
885        setViewpoint(itr->second, durationSecs);
886        return true;
887    } else {
888        ERROR("Unknown viewpoint: '%s'", name);
889        return false;
890    }
891}
892
893void Renderer::setViewpoint(const osgEarth::Viewpoint& v, double durationSecs)
894{
895    if (_manipulator.valid()) {
896        TRACE("Setting viewpoint: %g %g %g %g %g %g",
897              v.x(), v.y(), v.z(), v.getHeading(), v.getPitch(), v.getRange());
898        _manipulator->setViewpoint(v, durationSecs);
899        _needsRedraw = true;
900    } else {
901        ERROR("No manipulator");
902    }
903}
904
905osgEarth::Viewpoint Renderer::getViewpoint()
906{
907    if (_manipulator.valid()) {
908        return _manipulator->getViewpoint();
909    } else {
910        // Uninitialized, invalid viewpoint
911        return osgEarth::Viewpoint();
912    }
913}
914
915static void srsInfo(const osgEarth::SpatialReference *srs)
916{
917    TRACE("SRS: %s", srs->getName().c_str());
918    TRACE("horiz: \"%s\" vert: \"%s\"", srs->getHorizInitString().c_str(), srs->getVertInitString().c_str());
919    TRACE("geographic: %d geodetic: %d projected: %d ecef: %d mercator: %d spherical mercator: %d northpolar: %d southpolar: %d userdefined: %d contiguous: %d cube: %d ltp: %d plate_carre: %d",
920          srs->isGeographic() ? 1 : 0,
921          srs->isGeodetic() ? 1 : 0,
922          srs->isProjected() ? 1 : 0,
923          srs->isECEF() ? 1 : 0,
924          srs->isMercator() ? 1 : 0,
925          srs->isSphericalMercator() ? 1 : 0,
926          srs->isNorthPolar() ? 1 : 0,
927          srs->isSouthPolar() ? 1 : 0,
928          srs->isUserDefined() ? 1 : 0,
929          srs->isContiguous() ? 1 : 0,
930          srs->isCube() ? 1 : 0,
931          srs->isLTP() ? 1 : 0,
932          srs->isPlateCarre() ? 1 : 0);
933}
934
935bool Renderer::getWorldCoords(const osgEarth::GeoPoint& mapPt, osg::Vec3d *world)
936{
937    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
938        ERROR("No map");
939        return false;
940    }
941    TRACE("Input SRS:");
942    srsInfo(mapPt.getSRS());
943    TRACE("Map SRS:");
944    srsInfo(_mapNode->getMapSRS());
945    bool ret = mapPt.toWorld(*world, _mapNode->getTerrain());
946    TRACE("In: %g,%g,%g Out: %g,%g,%g",
947          mapPt.x(), mapPt.y(), mapPt.z(),
948          world->x(), world->y(), world->z());
949    return ret;
950}
951
952bool Renderer::worldToScreen(const osg::Vec3d& world, osg::Vec3d *screen, bool invertY)
953{
954    if (!_viewer.valid()) {
955        ERROR("No viewer");
956        return false;
957    }
958    osg::Camera *cam = _viewer->getCamera();
959    osg::Matrixd MVP = cam->getViewMatrix() * cam->getProjectionMatrix();
960    // Get clip coords
961    osg::Vec4d pt;
962    pt = osg::Vec4d(world, 1.0) * MVP;
963    // Clip
964    if (pt.x() < -pt.w() ||
965        pt.x() > pt.w() ||
966        pt.y() < -pt.w() ||
967        pt.y() > pt.w() ||
968        pt.z() < -pt.w() ||
969        pt.z() > pt.w()) {
970        // Outside frustum
971        TRACE("invalid pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
972        return false;
973    }
974    TRACE("clip pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
975    // Perspective divide: now NDC
976    pt /= pt.w();
977    const osg::Viewport *viewport = cam->getViewport();
978#if 1
979    screen->x() = viewport->x() + viewport->width() * 0.5 + pt.x() * viewport->width() * 0.5;
980    screen->y() = viewport->y() + viewport->height() * 0.5 + pt.y() * viewport->height() * 0.5;
981    //double near = 0;
982    //double far = 1;
983    //screen->z() = (far + near) * 0.5 + (far - near) * 0.5 * pt.z();
984    screen->z() = 0.5 + 0.5 * pt.z();
985#else
986    *screen = osg::Vec3d(pt.x(), pt.y(), pt.z()) * cam->getViewport()->computeWindowMatrix();
987#endif
988    if (invertY) {
989        screen->y() = viewport->height() - screen->y();
990    }
991    TRACE("screen: %g,%g,%g", screen->x(), screen->y(), screen->z());
992    return true;
993}
994
995bool Renderer::worldToScreen(std::vector<osg::Vec3d>& coords, bool invertY)
996{
997    if (!_viewer.valid()) {
998        ERROR("No viewer");
999        return false;
1000    }
1001    osg::Camera *cam = _viewer->getCamera();
1002    osg::Matrixd MVP = cam->getViewMatrix() * cam->getProjectionMatrix();
1003    const osg::Viewport *viewport = cam->getViewport();
1004    for (std::vector<osg::Vec3d>::iterator itr = coords.begin();
1005         itr != coords.end(); ++itr) {
1006        // Get clip coords
1007        osg::Vec4d pt;
1008        pt = osg::Vec4d(*itr, 1.0) * MVP;
1009        // Clip
1010        if (pt.x() < -pt.w() ||
1011            pt.x() > pt.w() ||
1012            pt.y() < -pt.w() ||
1013            pt.y() > pt.w() ||
1014            pt.z() < -pt.w() ||
1015            pt.z() > pt.w()) {
1016            // Outside frustum
1017            TRACE("invalid pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1018            itr->set(std::numeric_limits<double>::quiet_NaN(),
1019                     std::numeric_limits<double>::quiet_NaN(),
1020                     std::numeric_limits<double>::quiet_NaN());
1021            continue;
1022        }
1023        TRACE("clip pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1024        // Perspective divide: now NDC
1025        pt /= pt.w();
1026#if 1
1027        itr->x() = viewport->x() + viewport->width() * 0.5 + pt.x() * viewport->width() * 0.5;
1028        itr->y() = viewport->y() + viewport->height() * 0.5 + pt.y() * viewport->height() * 0.5;
1029        //double near = 0;
1030        //double far = 1;
1031        //itr->z() = (far + near) * 0.5 + (far - near) * 0.5 * pt.z();
1032        itr->z() = 0.5 + 0.5 * pt.z();
1033#else
1034        *itr = osg::Vec3d(pt.x(), pt.y(), pt.z()) * viewport->computeWindowMatrix();
1035#endif
1036        if (invertY) {
1037            itr->y() = viewport->height() - itr->y();
1038        }
1039        TRACE("screen: %g,%g,%g", itr->x(), itr->y(), itr->z());
1040    }
1041    return true;
1042}
1043
1044/**
1045 * \brief Map screen mouse coordinates to map coordinates
1046 *
1047 * This method assumes that mouse Y coordinates are 0 at the top
1048 * of the screen and increase going down if invertY is set, and
1049 * 0 at the bottom and increase going up otherwise.
1050 */
1051bool Renderer::mapMouseCoords(float mouseX, float mouseY,
1052                              osgEarth::GeoPoint& map, bool invertY)
1053{
1054    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
1055        ERROR("No map");
1056        return false;
1057    }
1058    if (!_viewer.valid()) {
1059        ERROR("No viewer");
1060        return false;
1061    }
1062    if (invertY) {
1063        mouseY = ((float)_windowHeight - mouseY);
1064    }
1065    osg::Vec3d world;
1066    if (_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), mouseX, mouseY, world)) {
1067        map.fromWorld(getMapSRS(), world);
1068        return true;
1069    }
1070    return false;
1071}
1072
1073bool Renderer::addImageLayer(const char *name,
1074                             osgEarth::TileSourceOptions& opts,
1075                             bool makeShared,
1076                             bool visible)
1077{
1078    if (!_map.valid()) {
1079        ERROR("No map");
1080        return false;
1081    }
1082    TRACE("layer: %s", name);
1083    if (!opts.tileSize().isSet()) {
1084        opts.tileSize() = 256;
1085    }
1086    osgEarth::ImageLayerOptions layerOpts(name, opts);
1087#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1)
1088    layerOpts.textureCompression() = osg::Texture::USE_IMAGE_DATA_FORMAT;
1089#endif
1090    if (makeShared) {
1091        layerOpts.shared() = true;
1092    }
1093    if (!visible) {
1094        layerOpts.visible() = false;
1095    }
1096    osg::ref_ptr<osgEarth::ImageLayer> layer = new osgEarth::ImageLayer(layerOpts);
1097    _map->addImageLayer(layer.get());
1098    if (layer->getTileSource() == NULL || !layer->getTileSource()->isOK()) {
1099        ERROR("Failed to add image layer: %s", name);
1100        _map->removeImageLayer(layer.get());
1101        return false;
1102    }
1103    _needsRedraw = true;
1104    return true;
1105}
1106
1107void Renderer::addColorFilter(const char *name,
1108                              const char *shader)
1109{
1110    if (!_map.valid()) {
1111        ERROR("No map");
1112        return;
1113    }
1114    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1115    if (layer == NULL) {
1116        TRACE("Image layer not found: %s", name);
1117        return;
1118    }
1119    osgEarth::Util::GLSLColorFilter *filter = new osgEarth::Util::GLSLColorFilter;
1120    filter->setCode(shader);
1121    //filter->setCode("color.rgb = color.r > 0.5 ? vec3(1.0) : vec3(0.0);");
1122    layer->addColorFilter(filter);
1123    _needsRedraw = true;
1124}
1125
1126void Renderer::removeColorFilter(const char *name, int idx)
1127{
1128    if (!_map.valid()) {
1129        ERROR("No map");
1130        return;
1131    }
1132    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1133    if (layer == NULL) {
1134        TRACE("Image layer not found: %s", name);
1135        return;
1136    }
1137    if (idx < 0) {
1138        while (!layer->getColorFilters().empty()) {
1139            layer->removeColorFilter(layer->getColorFilters()[0]);
1140        }
1141    } else {
1142        layer->removeColorFilter(layer->getColorFilters().at(idx));
1143    }
1144    _needsRedraw = true;
1145}
1146
1147void Renderer::removeImageLayer(const char *name)
1148{
1149    if (!_map.valid()) {
1150        ERROR("No map");
1151        return;
1152    }
1153    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1154    if (layer != NULL) {
1155        _map->removeImageLayer(layer);
1156        _needsRedraw = true;
1157    } else {
1158        TRACE("Image layer not found: %s", name);
1159    }
1160}
1161
1162void Renderer::moveImageLayer(const char *name, unsigned int pos)
1163{
1164    if (!_map.valid()) {
1165        ERROR("No map");
1166        return;
1167    }
1168    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1169    if (layer != NULL) {
1170        _map->moveImageLayer(layer, pos);
1171        _needsRedraw = true;
1172    } else {
1173        TRACE("Image layer not found: %s", name);
1174    }
1175}
1176
1177void Renderer::setImageLayerOpacity(const char *name, double opacity)
1178{
1179    if (!_map.valid()) {
1180        ERROR("No map");
1181        return;
1182    }
1183    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1184    if (layer != NULL) {
1185        layer->setOpacity(opacity);
1186        _needsRedraw = true;
1187    } else {
1188        TRACE("Image layer not found: %s", name);
1189    }
1190}
1191
1192void Renderer::setImageLayerVisibility(const char *name, bool state)
1193{
1194#if OSGEARTH_MIN_VERSION_REQUIRED(2, 4, 0)
1195    if (!_map.valid()) {
1196        ERROR("No map");
1197        return;
1198    }
1199    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1200    if (layer != NULL) {
1201        layer->setVisible(state);
1202        _needsRedraw = true;
1203    } else {
1204        TRACE("Image layer not found: %s", name);
1205    }
1206#endif
1207}
1208
1209void Renderer::addElevationLayer(const char *name,
1210                                 osgEarth::TileSourceOptions& opts)
1211{
1212    if (!_map.valid()) {
1213        ERROR("No map");
1214        return;
1215    }
1216    TRACE("layer: %s", name);
1217    if (!opts.tileSize().isSet()) {
1218        opts.tileSize() = 15;
1219    }
1220    osgEarth::ElevationLayerOptions layerOpts(name, opts);
1221    // XXX: GDAL does not report vertical datum, it should be specified here
1222    // Common options: geodetic (default), egm96, egm84, egm2008
1223    //layerOpts.verticalDatum() = "egm96";
1224    _map->addElevationLayer(new osgEarth::ElevationLayer(layerOpts));
1225    _needsRedraw = true;
1226}
1227
1228void Renderer::removeElevationLayer(const char *name)
1229{
1230    if (!_map.valid()) {
1231        ERROR("No map");
1232        return;
1233    }
1234    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1235    if (layer != NULL) {
1236        _map->removeElevationLayer(layer);
1237        _needsRedraw = true;
1238    } else {
1239        TRACE("Elevation layer not found: %s", name);
1240    }
1241}
1242
1243void Renderer::moveElevationLayer(const char *name, unsigned int pos)
1244{
1245    if (!_map.valid()) {
1246        ERROR("No map");
1247        return;
1248    }
1249    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1250    if (layer != NULL) {
1251        _map->moveElevationLayer(layer, pos);
1252        _needsRedraw = true;
1253    } else {
1254        TRACE("Elevation layer not found: %s", name);
1255    }
1256}
1257
1258void Renderer::setElevationLayerVisibility(const char *name, bool state)
1259{
1260#if OSGEARTH_MIN_VERSION_REQUIRED(2, 4, 0)
1261    if (!_map.valid()) {
1262        ERROR("No map");
1263        return;
1264    }
1265    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1266    if (layer != NULL) {
1267        layer->setVisible(state);
1268        _needsRedraw = true;
1269    } else {
1270        TRACE("Elevation layer not found: %s", name);
1271    }
1272#endif
1273}
1274
1275void Renderer::addPlaceNode(double latitude, double longitude, char *labelText)
1276{
1277    if (!_mapNode.valid()) {
1278        ERROR("No map node");
1279        return;
1280    }
1281    if (!_annotations.valid()) {
1282        _annotations = new osg::Group();
1283        _sceneRoot->addChild(_annotations.get());
1284        _placeNodes = new osg::Group();
1285        osgEarth::Decluttering::setEnabled(_placeNodes->getOrCreateStateSet(), true);
1286        _annotations->addChild(_placeNodes.get());
1287    }
1288
1289    const osgEarth::SpatialReference* geoSRS = _mapNode->getMapSRS()->getGeographicSRS();
1290
1291    osgEarth::Symbology::Style pin;
1292    pin.getOrCreate<osgEarth::Symbology::IconSymbol>()->url()->setLiteral(getPinIcon());
1293    osgEarth::Annotation::AnnotationNode *anno = new osgEarth::Annotation::PlaceNode(_mapNode, osgEarth::GeoPoint(geoSRS, longitude, latitude), labelText, pin);
1294    _placeNodes->addChild(anno);
1295    osgEarth::Annotation::DecorationInstaller
1296        highlightInstaller("hover", new osgEarth::Annotation::HighlightDecoration());
1297    _annotations->accept(highlightInstaller);
1298    // scale labels when hovering:
1299    osgEarth::Annotation::DecorationInstaller
1300        scaleInstaller("hover", new osgEarth::Annotation::ScaleDecoration(1.1f));
1301    _placeNodes->accept(scaleInstaller);
1302    _needsRedraw = true;
1303}
1304
1305void Renderer::hoverPlaceNode(int x, int y, bool invertY)
1306{
1307    osgEarth::Picker picker(_viewer.get(), _placeNodes.get());
1308    osgEarth::Picker::Hits hits;
1309    float mouseX = (float)x;
1310    float mouseY = (float)y;
1311    if (invertY) {
1312        mouseY = ((float)_windowHeight - mouseY);
1313    }
1314    std::set<osgEarth::Annotation::AnnotationNode*> toUnHover;
1315    for (std::set<osgEarth::Annotation::AnnotationNode*>::iterator itr = _hovered.begin();
1316         itr != _hovered.end(); ++itr) {
1317        toUnHover.insert(*itr);
1318    }
1319    if (picker.pick(mouseX, mouseY, hits)) {
1320        TRACE("Picker hit!");
1321        for (osgEarth::Picker::Hits::const_iterator hitr = hits.begin();
1322             hitr != hits.end(); ++hitr) {
1323            osgEarth::Annotation::AnnotationNode *anno =
1324                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
1325            if (anno != NULL) {
1326                if (_hovered.find(anno) == _hovered.end()) {
1327                    _hovered.insert(anno);
1328                    anno->setDecoration("hover");
1329                    _needsRedraw = true;
1330                }
1331                toUnHover.erase(anno);
1332            }
1333        }
1334    }
1335    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = toUnHover.begin();
1336         itr != toUnHover.end(); ++itr) {
1337        _hovered.erase(*itr);
1338        (*itr)->clearDecoration();
1339        _needsRedraw = true;
1340    }
1341}
1342
1343void Renderer::deletePlaceNode(int x, int y, bool invertY)
1344{
1345    osgEarth::Picker picker(_viewer.get(), _placeNodes.get());
1346    osgEarth::Picker::Hits hits;
1347    float mouseX = (float)x;
1348    float mouseY = (float)y;
1349    if (invertY) {
1350        mouseY = ((float)_windowHeight - mouseY);
1351    }
1352    if (picker.pick(mouseX, mouseY, hits)) {
1353        TRACE("Picker hit!");
1354        // prevent multiple hits on the same instance
1355        std::set<osgEarth::Annotation::AnnotationNode *> fired;
1356        for (osgEarth::Picker::Hits::const_iterator hitr = hits.begin();
1357             hitr != hits.end(); ++hitr) {
1358            osgEarth::Annotation::AnnotationNode *anno =
1359                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
1360            if (anno != NULL && fired.find(anno) == fired.end()) {
1361                fired.insert(anno);
1362                _needsRedraw = true;
1363            }
1364        }
1365        for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = fired.begin();
1366             itr != fired.end(); ++itr) {
1367            (*itr)->clearDecoration();
1368            _placeNodes->removeChild(*itr);
1369            if (_hovered.find(*itr) != _hovered.end()) {
1370                _hovered.erase(*itr);
1371            }
1372        }
1373    } else {
1374        TRACE("NO Picker hits");
1375    }
1376}
1377
1378void Renderer::addModelLayer(const char *name, osgEarth::ModelSourceOptions& opts)
1379{
1380    if (!_map.valid()) {
1381        ERROR("No map");
1382        return;
1383    }
1384    TRACE("layer: %s", name);
1385    osgEarth::ModelLayerOptions layerOpts(name, opts);
1386    _map->addModelLayer(new osgEarth::ModelLayer(layerOpts));
1387    _needsRedraw = true;
1388}
1389
1390void Renderer::removeModelLayer(const char *name)
1391{
1392    if (!_map.valid()) {
1393        ERROR("No map");
1394        return;
1395    }
1396    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
1397    if (layer != NULL) {
1398        _map->removeModelLayer(layer);
1399        _needsRedraw = true;
1400    } else {
1401        TRACE("Model layer not found: %s", name);
1402    }
1403}
1404
1405void Renderer::moveModelLayer(const char *name, unsigned int pos)
1406{
1407    if (!_map.valid()) {
1408        ERROR("No map");
1409        return;
1410    }
1411    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
1412    if (layer != NULL) {
1413        _map->moveModelLayer(layer, pos);
1414        _needsRedraw = true;
1415    } else {
1416        TRACE("Model layer not found: %s", name);
1417    }
1418}
1419
1420void Renderer::setModelLayerOpacity(const char *name, double opacity)
1421{
1422#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 0)
1423    if (!_map.valid()) {
1424        ERROR("No map");
1425        return;
1426    }
1427    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
1428    if (layer != NULL) {
1429        layer->setOpacity(opacity);
1430        _needsRedraw = true;
1431    } else {
1432        TRACE("Model layer not found: %s", name);
1433    }
1434#endif
1435}
1436
1437void Renderer::setModelLayerVisibility(const char *name, bool state)
1438{
1439#if OSGEARTH_MIN_VERSION_REQUIRED(2, 4, 0)
1440    if (!_map.valid()) {
1441        ERROR("No map");
1442        return;
1443    }
1444    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
1445    if (layer != NULL) {
1446        layer->setVisible(state);
1447        _needsRedraw = true;
1448    } else {
1449        TRACE("Model layer not found: %s", name);
1450    }
1451#endif
1452}
1453
1454/**
1455 * \brief Resize the render window (image size for renderings)
1456 */
1457void Renderer::setWindowSize(int width, int height)
1458{
1459    if (_windowWidth == width &&
1460        _windowHeight == height) {
1461        TRACE("No change");
1462        return;
1463    }
1464
1465    TRACE("Setting window size to %dx%d", width, height);
1466
1467    double origBitrate = getMaximumBitrate();
1468
1469    _windowWidth = width;
1470    _windowHeight = height;
1471
1472    setMaximumBitrate(origBitrate);
1473    TRACE("Bandwidth target: %.2f Mbps", (float)(getMaximumBitrate()/1.0e6));
1474    TRACE("Frame rate target: %.2f Hz", (float)getMaximumFrameRateInHertz());
1475
1476    if (_viewer.valid()) {
1477#ifdef USE_OFFSCREEN_RENDERING
1478#ifdef USE_PBUFFER
1479        osg::ref_ptr<osg::GraphicsContext> pbuffer;
1480        osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
1481        traits->x = 0;
1482        traits->y = 0;
1483        traits->width = _windowWidth;
1484        traits->height = _windowHeight;
1485        traits->red = 8;
1486        traits->green = 8;
1487        traits->blue = 8;
1488        traits->alpha = 8;
1489        traits->windowDecoration = false;
1490        traits->pbuffer = true;
1491        traits->doubleBuffer = true;
1492        traits->sharedContext = 0;
1493
1494        pbuffer = osg::GraphicsContext::createGraphicsContext(traits.get());
1495        if (pbuffer.valid()) {
1496            TRACE("Pixel buffer has been created successfully.");
1497        } else {
1498            ERROR("Pixel buffer has not been created successfully.");
1499        }
1500        osg::Camera *camera = new osg::Camera;
1501        camera->setGraphicsContext(pbuffer.get());
1502        camera->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
1503        GLenum buffer = pbuffer->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
1504        camera->setDrawBuffer(buffer);
1505        camera->setReadBuffer(buffer);
1506        camera->setFinalDrawCallback(_captureCallback.get());
1507        _viewer->addSlave(camera, osg::Matrixd(), osg::Matrixd());
1508        _viewer->realize();
1509#else
1510        if (_captureCallback.valid()) {
1511            _captureCallback->getTexture()->setTextureSize(_windowWidth, _windowHeight);
1512        }
1513        osgViewer::ViewerBase::Windows windows;
1514        _viewer->getWindows(windows);
1515        if (windows.size() == 1) {
1516            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
1517        } else {
1518            ERROR("Num windows: %lu", windows.size());
1519        }
1520#endif
1521#else
1522        osgViewer::ViewerBase::Windows windows;
1523        _viewer->getWindows(windows);
1524        if (windows.size() == 1) {
1525            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
1526        } else {
1527            ERROR("Num windows: %lu", windows.size());
1528        }
1529#endif
1530        // HACK: Without this, the mouse coordinate mapping uses the old size
1531        // for 1 frame.
1532        assert(_viewer->getEventQueue() != NULL);
1533        //TRACE("Window EventQueue: %p", getEventQueue());
1534        //TRACE("Viewer EventQueue: %p", _viewer->getEventQueue());
1535        _viewer->getEventQueue()->windowResize(0, 0, _windowWidth, _windowHeight);
1536        _needsRedraw = true;
1537    }
1538}
1539
1540/**
1541 * \brief Set the orientation of the camera from a quaternion
1542 * rotation
1543 *
1544 * \param[in] quat A quaternion with scalar part first: w,x,y,z
1545 * \param[in] absolute Is rotation absolute or relative?
1546 */
1547void Renderer::setCameraOrientation(const double quat[4], bool absolute)
1548{
1549    if (_manipulator.valid()) {
1550        _manipulator->setRotation(osg::Quat(quat[1], quat[2], quat[3], quat[0]));
1551        _needsRedraw = true;
1552    }
1553}
1554
1555/**
1556 * \brief Reset pan, zoom, clipping planes and optionally rotation
1557 *
1558 * \param[in] resetOrientation Reset the camera rotation/orientation also
1559 */
1560void Renderer::resetCamera(bool resetOrientation)
1561{
1562    TRACE("Enter: resetOrientation=%d", resetOrientation ? 1 : 0);
1563    if (_viewer.valid()) {
1564        _viewer->home();
1565        _needsRedraw = true;
1566    }
1567}
1568
1569/**
1570 * \brief Perform a 2D translation of the camera
1571 *
1572 * x,y pan amount are specified as signed absolute pan amount in viewport
1573 * units -- i.e. 0 is no pan, .5 is half the viewport, 2 is twice the viewport,
1574 * etc.
1575 *
1576 * \param[in] x Viewport coordinate horizontal panning (positive number pans
1577 * camera left, object right)
1578 * \param[in] y Viewport coordinate vertical panning (positive number pans
1579 * camera up, object down)
1580 */
1581void Renderer::panCamera(double x, double y)
1582{
1583    TRACE("Enter: %g %g", x, y);
1584
1585    if (_manipulator.valid()) {
1586        // Wants mouse delta x,y in normalized screen coords
1587        _manipulator->pan(x, y);
1588        _needsRedraw = true;
1589    }
1590}
1591
1592void Renderer::rotateCamera(double x, double y)
1593{
1594    TRACE("Enter: %g %g", x, y);
1595
1596    if (_manipulator.valid()) {
1597        _manipulator->rotate(x, y);
1598        _needsRedraw = true;
1599    }
1600}
1601
1602/**
1603 * \brief Dolly camera or set orthographic scaling based on camera type
1604 *
1605 * \param[in] y Mouse y coordinate in normalized screen coords
1606 */
1607void Renderer::zoomCamera(double y)
1608{
1609    TRACE("Enter: y: %g", y);
1610
1611    if (_manipulator.valid()) {
1612        TRACE("camDist: %g", _manipulator->getDistance());
1613        // FIXME: zoom here wants y mouse coords in normalized viewport coords
1614#if 1
1615       _manipulator->zoom(0, y);
1616#else
1617        double dist = _manipulator->getDistance();
1618        dist *= (1.0 + y);
1619        _manipulator->setDistance(dist);
1620#endif
1621        _needsRedraw = true;
1622    }
1623}
1624
1625/**
1626 * \brief Dolly camera to set distance from focal point
1627 *
1628 * \param[in] dist distance in map? coordinates
1629 */
1630void Renderer::setCameraDistance(double dist)
1631{
1632    TRACE("Enter: dist: %g", dist);
1633
1634    if (_manipulator.valid()) {
1635        TRACE("camDist: %g", _manipulator->getDistance());
1636
1637        _manipulator->setDistance(dist);
1638
1639        _needsRedraw = true;
1640    }
1641}
1642
1643void Renderer::keyPress(int key)
1644{
1645    osgGA::EventQueue *queue = getEventQueue();
1646    if (queue != NULL) {
1647        queue->keyPress(key);
1648        _needsRedraw = true;
1649    }
1650}
1651
1652void Renderer::keyRelease(int key)
1653{
1654    osgGA::EventQueue *queue = getEventQueue();
1655    if (queue != NULL) {
1656        queue->keyRelease(key);
1657        _needsRedraw = true;
1658    }
1659}
1660
1661void Renderer::setThrowingEnabled(bool state)
1662{
1663    if (_manipulator.valid()) {
1664        _manipulator->getSettings()->setThrowingEnabled(state);
1665    }
1666}
1667
1668void Renderer::mouseDoubleClick(int button, double x, double y)
1669{
1670    osgGA::EventQueue *queue = getEventQueue();
1671    if (queue != NULL) {
1672        queue->mouseDoubleButtonPress((float)x, (float)y, button);
1673        _needsRedraw = true;
1674    }
1675}
1676
1677void Renderer::mouseClick(int button, double x, double y)
1678{
1679    osgGA::EventQueue *queue = getEventQueue();
1680    if (queue != NULL) {
1681        queue->mouseButtonPress((float)x, (float)y, button);
1682        _needsRedraw = true;
1683    }
1684}
1685
1686void Renderer::mouseDrag(int button, double x, double y)
1687{
1688    osgGA::EventQueue *queue = getEventQueue();
1689    if (queue != NULL) {
1690        queue->mouseMotion((float)x, (float)y);
1691        _needsRedraw = true;
1692    }
1693}
1694
1695void Renderer::mouseRelease(int button, double x, double y)
1696{
1697    osgGA::EventQueue *queue = getEventQueue();
1698    if (queue != NULL) {
1699        queue->mouseButtonRelease((float)x, (float)y, button);
1700        _needsRedraw = true;
1701    }
1702}
1703
1704void Renderer::mouseMotion(double x, double y)
1705{
1706    if (_mouseCoordsTool.valid()) {
1707#if 1
1708        osgGA::EventQueue *queue = getEventQueue();
1709        if (queue != NULL) {
1710            queue->mouseMotion((float)x, (float)y);
1711            _needsRedraw = true;
1712        }
1713#else
1714        if (_viewer.valid() && _coordsCallback.valid()) {
1715            osgEarth::GeoPoint mapPt;
1716            if (mapMouseCoords((float)x, (float)y, mapPt)) {
1717                _coordsCallback->set(mapPt, _viewer->asView(), _mapNode);
1718            } else {
1719                _coordsCallback->reset(_viewer->asView(), _mapNode);
1720            }
1721            _needsRedraw = true;
1722        }
1723#endif
1724    }
1725}
1726
1727void Renderer::mouseScroll(int direction)
1728{
1729    osgGA::EventQueue *queue = getEventQueue();
1730    if (queue != NULL) {
1731        queue->mouseScroll((direction > 0 ? osgGA::GUIEventAdapter::SCROLL_UP : osgGA::GUIEventAdapter::SCROLL_DOWN));
1732        _needsRedraw = true;
1733    }
1734}
1735
1736/**
1737 * \brief Set the RGB background color to render into the image
1738 */
1739void Renderer::setBackgroundColor(float color[3])
1740{
1741    _bgColor[0] = color[0];
1742    _bgColor[1] = color[1];
1743    _bgColor[2] = color[2];
1744
1745    if (_viewer.valid()) {
1746        _viewer->getCamera()->setClearColor(osg::Vec4(color[0], color[1], color[2], 1));
1747
1748        _needsRedraw = true;
1749    }
1750}
1751
1752/**
1753 * \brief Sets flag to trigger rendering next time render() is called
1754 */
1755void Renderer::eventuallyRender()
1756{
1757    _needsRedraw = true;
1758}
1759
1760/**
1761 * \brief Get a timeout in usecs for select()
1762 *
1763 * If the paging thread is idle, returns <0 indicating that the
1764 * select call can block until data is available.  Otherwise,
1765 * if the frame render time was faster than the target frame
1766 * rate, return the remaining frame time.
1767 */
1768long Renderer::getTimeout()
1769{
1770    if (!checkNeedToDoFrame())
1771        // <0 means no timeout, block until socket has data
1772        return -1L;
1773    if (_lastFrameTime < _minFrameTime) {
1774        return (long)1.0e6*(_minFrameTime - _lastFrameTime);
1775    } else {
1776        // No timeout (poll)
1777        return 0L;
1778    }
1779}
1780
1781/**
1782 * \brief Check if paging thread is quiescent
1783 */
1784bool Renderer::isPagerIdle()
1785{
1786    if (!_viewer.valid())
1787        return true;
1788    else
1789        return (!_viewer->getDatabasePager()->requiresUpdateSceneGraph() &&
1790                !_viewer->getDatabasePager()->getRequestsInProgress());
1791}
1792
1793/**
1794 * \brief Check is frame call is necessary to render and/or update
1795 * in response to events or timed actions
1796 */
1797bool Renderer::checkNeedToDoFrame()
1798{
1799    return (_needsRedraw ||
1800            (_viewer.valid() && _viewer->checkNeedToDoFrame()));
1801}
1802
1803/**
1804 * \brief MapNode event phase
1805 *
1806 * This is called by the MapNode's event callback during the event
1807 * traversal of the viewer
1808 */
1809void Renderer::mapNodeUpdate()
1810{
1811    computeMapScale();
1812}
1813
1814void Renderer::markFrameStart()
1815{
1816    _startFrameTime = osg::Timer::instance()->tick();
1817}
1818
1819void Renderer::markFrameEnd()
1820{
1821    osg::Timer_t endFrameTick = osg::Timer::instance()->tick();
1822    _lastFrameTime = osg::Timer::instance()->delta_s(_startFrameTime, endFrameTick);
1823    if (_lastFrameTime > _minFrameTime) {
1824        ERROR("BROKE FRAME by %.2f msec", (_lastFrameTime - _minFrameTime)*1000.0f);
1825    } else {
1826        TRACE("Frame time: %.2f msec", _lastFrameTime*1000.0f);
1827    }
1828#ifdef USE_THROTTLING_SLEEP
1829    if (_lastFrameTime < _minFrameTime) {
1830        TRACE("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
1831        OpenThreads::Thread::microSleep(static_cast<unsigned int>(1000000.0*(_minFrameTime - _lastFrameTime)));
1832    }
1833#endif
1834}
1835
1836/**
1837 * \brief Cause the rendering to render a new image if needed
1838 *
1839 * The _needsRedraw flag indicates if a state change has occured since
1840 * the last rendered frame
1841 */
1842bool Renderer::render()
1843{
1844    if (_viewer.valid() && checkNeedToDoFrame()) {
1845        TRACE("Enter needsRedraw=%d",  _needsRedraw ? 1 : 0);
1846        _renderStartTime = osg::Timer::instance()->tick();
1847        TRACE("Before frame()");
1848        _viewer->frame();
1849        TRACE("After frame()");
1850        _renderStopTime = osg::Timer::instance()->tick();
1851        _renderTime = osg::Timer::instance()->delta_s(_renderStartTime, _renderStopTime);
1852        TRACE("Render time: %g msec", _renderTime * 1000.0);
1853#ifndef SLEEP_AFTER_QUEUE_FRAME
1854        _lastFrameTime = _renderTime;
1855#ifdef USE_THROTTLING_SLEEP
1856        if (_lastFrameTime < _minFrameTime) {
1857            TRACE("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
1858            OpenThreads::Thread::microSleep(static_cast<unsigned int>(1.0e6*(_minFrameTime - _lastFrameTime)));
1859        }
1860#endif
1861#endif
1862#ifdef WANT_TRACE
1863        if (_viewer->getViewerStats() != NULL) {
1864            _viewer->getViewerStats()->report(std::cerr, _viewer->getViewerStats()->getLatestFrameNumber());
1865        }
1866#endif
1867        _needsRedraw = false;
1868        return true;
1869    } else {
1870        _renderStartTime = _renderStopTime = osg::Timer::instance()->tick();
1871        _renderTime = 0;
1872        return false;
1873    }
1874}
1875
1876/**
1877 * \brief Read back the rendered framebuffer image
1878 */
1879osg::Image *Renderer::getRenderedFrame()
1880{
1881    if (_captureCallback.valid())
1882        return _captureCallback->getImage();
1883    else
1884        return NULL;
1885}
1886
1887void Renderer::setScaleBar(bool state)
1888{
1889    if (_scaleLabel.valid()) {
1890        _scaleLabel->setVisible(state);
1891    }
1892    if (_scaleBar.valid()) {
1893        _scaleBar->setVisible(state);
1894    }
1895    _needsRedraw = true;
1896}
1897
1898void Renderer::setScaleBarUnits(ScaleBarUnits units)
1899{
1900    _scaleBarUnits = units;
1901    _needsRedraw = true;
1902}
1903
1904/**
1905 * \brief Compute the scale ratio of the map based on a horizontal center line
1906 *
1907 * The idea here is to take 2 screen points on a horizontal line in the center
1908 * of the screen and convert to lat/long.  The lat/long coordinates are then
1909 * used to compute the great circle distance (assuming spherical earth) between
1910 * the points.
1911 *
1912 * We could use local projected map coordinates for the distance computation,
1913 * which would be faster; however, this would not show e.g. the change in
1914 * scale at different latitudes
1915 */
1916double Renderer::computeMapScale()
1917{
1918    if (!_scaleLabel.valid() || !_scaleLabel->visible()) {
1919        return -1.0;
1920    }
1921    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
1922        ERROR("No map");
1923        return -1.0;
1924    }
1925    if (!_viewer.valid()) {
1926        ERROR("No viewer");
1927        return -1.0;
1928    }
1929
1930    double x, y;
1931    double pixelWidth = _windowWidth * 0.1 * 2.0;
1932    if (pixelWidth < 10)
1933        pixelWidth = 10;
1934    if (pixelWidth > 150)
1935        pixelWidth = 150;
1936    x = (double)(_windowWidth -1)/2.0 - pixelWidth / 2.0;
1937    y = (double)(_windowHeight-1)/2.0;
1938
1939    osg::Vec3d world1, world2;
1940    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world1)) {
1941        // off map
1942        TRACE("Off map coords: %g %g", x, y);
1943        _scaleLabel->setText("");
1944        _scaleBar->setWidth(0);
1945        return -1.0;
1946    }
1947    x += pixelWidth;
1948    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world2)) {
1949        // off map
1950        TRACE("Off map coords: %g %g", x, y);
1951        _scaleLabel->setText("");
1952        _scaleBar->setWidth(0);
1953        return -1.0;
1954    }
1955
1956    TRACE("w1: %g %g %g w2: %g %g %g",
1957          world1.x(), world1.y(), world1.z(),
1958          world2.x(), world2.y(), world2.z());
1959
1960    double meters;
1961    double radius = 6378137.0;
1962    if (_mapNode->getMapSRS() &&
1963        _mapNode->getMapSRS()->getEllipsoid()) {
1964        radius = _mapNode->getMapSRS()->getEllipsoid()->getRadiusEquator();
1965    }
1966    if (!_map->isGeocentric() &&
1967        _mapNode->getMapSRS() &&
1968        _mapNode->getMapSRS()->isGeographic()) {
1969        TRACE("Map is geographic");
1970        // World cords are already lat/long
1971        // Compute great circle distance
1972        meters =
1973            osgEarth::GeoMath::distance(world1, world2, _mapNode->getMapSRS());
1974    } else if (_mapNode->getMapSRS()) {
1975        // Get map coords in lat/long
1976        osgEarth::GeoPoint mapPoint1, mapPoint2;
1977        mapPoint1.fromWorld(_mapNode->getMapSRS(), world1);
1978        mapPoint1.makeGeographic();
1979        mapPoint2.fromWorld(_mapNode->getMapSRS(), world2);
1980        mapPoint2.makeGeographic();
1981        // Compute great circle distance
1982        meters =
1983            osgEarth::GeoMath::distance(osg::DegreesToRadians(mapPoint1.y()),
1984                                        osg::DegreesToRadians(mapPoint1.x()),
1985                                        osg::DegreesToRadians(mapPoint2.y()),
1986                                        osg::DegreesToRadians(mapPoint2.x()),
1987                                        radius);
1988    } else {
1989        // Assume geocentric?
1990        ERROR("No map SRS");
1991        _scaleLabel->setText("");
1992        _scaleBar->setWidth(0);
1993        return -1.0;
1994    }
1995
1996    double scale = meters / pixelWidth;
1997    // 1mi = 5280 feet
1998    //double scaleMiles = scale / 1609.344; // International mile = 1609.344m
1999    //double scaleNauticalMiles = scale / 1852.0; // nautical mile = 1852m
2000    //double scaleUSSurveyMiles = scale / 1609.347218694; // US survey mile = 5280 US survey feet
2001    //double scaleUSSurveyFeet = scale * 3937.0/1200.0; // US survey foot = 1200/3937 m
2002    TRACE("m: %g px: %g m/px: %g", meters, pixelWidth, scale);
2003    switch (_scaleBarUnits) {
2004    case UNITS_NAUTICAL_MILES: {
2005        double nmi = meters / 1852.0;
2006        scale = nmi / pixelWidth;
2007        nmi = normalizeScaleNauticalMiles(nmi);
2008        pixelWidth = nmi / scale;
2009        if (_scaleLabel.valid()) {
2010            _scaleLabel->setText(osgEarth::Stringify()
2011                                 << nmi
2012                                 << " nmi");
2013        }
2014    }
2015        break;
2016    case UNITS_US_SURVEY_FEET: {
2017        double feet = meters * 3937.0/1200.0;
2018        scale = feet / pixelWidth;
2019        feet = normalizeScaleFeet(feet);
2020        pixelWidth = feet / scale;
2021        if (_scaleLabel.valid()) {
2022            if (feet >= 5280) {
2023                _scaleLabel->setText(osgEarth::Stringify()
2024                                     << feet / 5280.0
2025                                     << " miUS");
2026             } else {
2027                _scaleLabel->setText(osgEarth::Stringify()
2028                                     << feet
2029                                     << " ftUS");
2030            }
2031        }
2032    }
2033        break;
2034    case UNITS_INTL_FEET: {
2035        double feet = 5280.0 * meters / 1609.344;
2036        scale = feet / pixelWidth;
2037        feet = normalizeScaleFeet(feet);
2038        pixelWidth = feet / scale;
2039        if (_scaleLabel.valid()) {
2040            if (feet >= 5280) {
2041                _scaleLabel->setText(osgEarth::Stringify()
2042                                     << feet / 5280.0
2043                                     << " mi");
2044            } else {
2045                _scaleLabel->setText(osgEarth::Stringify()
2046                                     << feet
2047                                     << " ft");
2048            }
2049        }
2050    }
2051        break;
2052    case UNITS_METERS:
2053    default: {
2054        meters = normalizeScaleMeters(meters);
2055        pixelWidth = meters / scale;
2056        if (_scaleLabel.valid()) {
2057            if (meters >= 1000) {
2058                _scaleLabel->setText(osgEarth::Stringify()
2059                                     << meters / 1000.0
2060                                     << " km");
2061            } else {
2062                _scaleLabel->setText(osgEarth::Stringify()
2063                                     << meters
2064                                     << " m");
2065            }
2066        }
2067    }
2068        break;
2069    }
2070    if (_scaleBar.valid()) {
2071        _scaleBar->setWidth(pixelWidth);
2072    }
2073    return scale;
2074}
2075
2076std::string Renderer::getCanonicalPath(const std::string& url) const
2077{
2078    std::string retStr;
2079    std::string proto = osgDB::getServerProtocol(url);
2080    if (proto.empty()) {
2081        retStr = osgDB::getRealPath(url);
2082        if (!osgDB::fileExists(retStr)) {
2083            retStr = "";
2084        }
2085    } else {
2086        retStr = url;
2087    }
2088    return retStr;
2089}
Note: See TracBrowser for help on using the repository browser.