source: geovis/trunk/Renderer.cpp @ 4628

Last change on this file since 4628 was 4628, checked in by ldelgass, 7 years ago

Add some Makefile flags for sleep throttling settings

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