source: geovis/trunk/Renderer.cpp @ 6084

Last change on this file since 6084 was 6084, checked in by ldelgass, 9 years ago

Quiet log output from info to trace, now that frame tracing has a separate
define.

File size: 99.8 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * Copyright (C) 2004-2015  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#include <cerrno>
14
15#include <limits>
16#include <set>
17#include <fstream>
18#include <sstream>
19
20#include <sys/types.h>
21#include <unistd.h> // For getpid()
22
23#include <GL/gl.h>
24
25#ifdef WANT_TRACE
26#include <sys/time.h>
27#endif
28
29#include <osgDB/WriteFile>
30#include <osgDB/FileUtils>
31#include <osgDB/FileNameUtils>
32#include <osgGA/StateSetManipulator>
33#include <osgGA/GUIEventAdapter>
34#include <osgViewer/ViewerEventHandlers>
35
36#include <osgEarth/Version>
37#include <osgEarth/Registry>
38#include <osgEarth/FileUtils>
39#include <osgEarth/Cache>
40#include <osgEarth/CachePolicy>
41#include <osgEarthDrivers/cache_filesystem/FileSystemCache>
42#include <osgEarth/MapNode>
43#include <osgEarth/TerrainEngineNode>
44#include <osgEarth/Bounds>
45#include <osgEarth/Profile>
46#include <osgEarth/Viewpoint>
47#include <osgEarth/GeoMath>
48#include <osgEarth/TerrainLayer>
49#include <osgEarth/ImageLayer>
50#include <osgEarth/ElevationLayer>
51#include <osgEarth/ModelLayer>
52#include <osgEarth/DateTime>
53#ifdef USE_RTT_PICKER
54#include <osgEarthUtil/RTTPicker>
55#include <osgEarthFeatures/FeatureIndex>
56#else
57#include <osgEarth/IntersectionPicker>
58#endif
59#include <osgEarthUtil/GraticuleNode>
60#include <osgEarthFeatures/FeatureModelSource>
61#include <osgEarthFeatures/Feature>
62#include <osgEarthSymbology/Color>
63#include <osgEarthSymbology/Geometry>
64#include <osgEarthSymbology/Style>
65#include <osgEarthSymbology/StyleSheet>
66#include <osgEarthSymbology/IconSymbol>
67#include <osgEarthSymbology/LineSymbol>
68
69#include <osgEarthAnnotation/AnnotationNode>
70#include <osgEarthAnnotation/FeatureNode>
71#include <osgEarthAnnotation/PlaceNode>
72#include <osgEarthAnnotation/HighlightDecoration>
73#include <osgEarthAnnotation/ScaleDecoration>
74#include <osgEarthUtil/EarthManipulator>
75#include <osgEarthUtil/Sky>
76#include <osgEarthDrivers/sky_simple/SimpleSkyOptions>
77#include <osgEarthUtil/AutoClipPlaneHandler>
78#include <osgEarthUtil/MouseCoordsTool>
79#include <osgEarthUtil/UTMGraticule>
80#include <osgEarthUtil/MGRSGraticule>
81#include <osgEarthUtil/GeodeticGraticule>
82#include <osgEarthUtil/LatLongFormatter>
83#include <osgEarthUtil/MGRSFormatter>
84#include <osgEarthUtil/GLSLColorFilter>
85#include <osgEarthUtil/VerticalScale>
86#include <osgEarthDrivers/gdal/GDALOptions>
87#include <osgEarthDrivers/engine_mp/MPTerrainEngineOptions>
88#ifdef USE_REX
89#include <osgEarthDrivers/engine_rex/RexTerrainEngineOptions>
90#endif
91
92#include "Renderer.h"
93#ifdef USE_RTT_PICKER
94#include "Picker.h"
95#endif
96#include "IData.h"
97#include "ColorMap.h"
98#if 0
99#include "SingleWindow.h"
100#endif
101#include "ScaleBar.h"
102#include "FileUtil.h"
103#include "Util.h"
104#include "Icons.h"
105#include "Trace.h"
106
107#define MSECS_ELAPSED(t1, t2) \
108    ((t1).tv_sec == (t2).tv_sec ? (((t2).tv_usec - (t1).tv_usec)/1.0e+3) : \
109     (((t2).tv_sec - (t1).tv_sec))*1.0e+3 + (double)((t2).tv_usec - (t1).tv_usec)/1.0e+3)
110
111#ifndef CACHE_DIR
112#define CACHE_DIR "/var/cache/geovis"
113#endif
114#define BASE_IMAGE "world.tif"
115#define PIN_ICON "placemark32.png"
116
117using namespace GeoVis;
118using namespace IData;
119
120Renderer::Renderer() :
121    _needsRedraw(false),
122    _windowWidth(500),
123    _windowHeight(500),
124    _pickPending(false),
125    _scaleBarUnits(UNITS_METERS)
126{
127    TRACE("Enter");
128
129    initIconMap();
130    _bgColor[0] = 0;
131    _bgColor[1] = 0;
132    _bgColor[2] = 0;
133    //setMaximumFrameRateInHertz(15.0);
134    // 100 Mbps
135    setMaximumBitrate(1.0e8);
136    _lastFrameTime = _minFrameTime;
137    TRACE("Bandwidth target: %.2f Mbps", (float)(getMaximumBitrate()/1.0e6));
138    TRACE("Frame rate target: %.2f Hz", (float)getMaximumFrameRateInHertz());
139    TRACE("Frame time target: %.2f msec", _minFrameTime * 1000.0f);
140
141    iDataInit(getenv("USER"));
142    setCacheBaseDirectory(CACHE_DIR);
143    char *base = getenv("MAP_BASE_URI");
144    if (base != NULL) {
145        _baseURI = base;
146        TRACE("Setting base URI: %s", _baseURI.c_str());
147    }
148}
149
150osgGA::EventQueue *Renderer::getEventQueue()
151{
152    if (_viewer.valid()) {
153        osgViewer::ViewerBase::Windows windows;
154        _viewer->getWindows(windows);
155        if (windows.size() > 0) {
156            return windows[0]->getEventQueue();
157        }
158    }
159    return NULL;
160}
161
162Renderer::~Renderer()
163{
164    TRACE("Enter");
165
166    // Removes temp files
167    deleteColorMap("all");
168
169    iDataCleanup();
170
171    // Cache is deleted in main() to allow ref-counted objects to
172    // be deleted and threads to exit first
173
174    TRACE("Leave");
175}
176
177std::string Renderer::getIconFile(const char *name) const
178{
179    std::ostringstream oss;
180    oss << _resourcePath << "/" << GeoVis::getIconFile(name);
181    return oss.str();
182}
183
184std::string Renderer::getBaseImage() const
185{
186    std::ostringstream oss;
187    oss << _resourcePath << "/" << BASE_IMAGE;
188    return oss.str();
189}
190
191std::string Renderer::getPinIcon() const
192{
193    std::ostringstream oss;
194    oss << _resourcePath << "/" << PIN_ICON;
195    return oss.str();
196}
197
198void Renderer::setupCache()
199{
200    std::ostringstream dir;
201    dir << _cacheBaseDir << "/geovis_cache" << getpid();
202    _cacheDir = dir.str();
203    const char *path = _cacheDir.c_str();
204    TRACE("Cache dir: %s", path);
205    removeDirectory(path);
206    if (!osgDB::makeDirectory(_cacheDir)) {
207        ERROR("Failed to create directory '%s'", path);
208    }
209}
210
211void Renderer::initColorMaps()
212{
213    if (!_colorMaps.empty())
214        return;
215#if 0
216    osg::TransferFunction1D *defaultColorMap = new osg::TransferFunction1D;
217    defaultColorMap->allocate(256);
218    defaultColorMap->setColor(0.00, osg::Vec4f(0,0,1,1), false);
219    defaultColorMap->setColor(0.25, osg::Vec4f(0,1,1,1), false);
220    defaultColorMap->setColor(0.50, osg::Vec4f(0,1,0,1), false);
221    defaultColorMap->setColor(0.75, osg::Vec4f(1,1,0,1), false);
222    defaultColorMap->setColor(1.00, osg::Vec4f(1,0,0,1), false);
223    defaultColorMap->updateImage();
224    addColorMap("default", defaultColorMap);
225    osg::TransferFunction1D *defaultGrayColorMap = new osg::TransferFunction1D;
226    defaultGrayColorMap->allocate(256);
227    defaultGrayColorMap->setColor(0, osg::Vec4f(0,0,0,1), false);
228    defaultGrayColorMap->setColor(1, osg::Vec4f(1,1,1,1), false);
229    defaultGrayColorMap->updateImage();
230    addColorMap("grayDefault", defaultGrayColorMap);
231#endif
232}
233
234void Renderer::saveCLRFile(const std::string& path, osg::TransferFunction1D *xfer)
235{
236    std::ofstream out(path.c_str());
237    float value;
238    unsigned int r, g, b, a;
239    for (osg::TransferFunction1D::ColorMap::const_iterator itr = xfer->getColorMap().begin();
240         itr != xfer->getColorMap().end(); ++itr) {
241        value = itr->first;
242        r = (unsigned int)(255.0 * itr->second.r());
243        g = (unsigned int)(255.0 * itr->second.g());
244        b = (unsigned int)(255.0 * itr->second.b());
245        a = (unsigned int)(255.0 * itr->second.a());
246        out << value << " " << r << " " << g << " " << b << " " << a << std::endl;
247    }
248}
249
250void Renderer::getColorMapRange(const ColorMapId& id, float *min, float *max) const
251{
252    ColorMapHashmap::const_iterator itr = _colorMaps.find(id);
253    if (itr == _colorMaps.end()) {
254        ERROR("Unknown ColorMap %s", id.c_str());
255        return;
256    }
257
258    *min = itr->second->getMinimum();
259    *max = itr->second->getMaximum();
260}
261
262std::string Renderer::getColorMapFilePath(const ColorMapId& id) const
263{
264    std::ostringstream path;
265    path << "/tmp/" << id << "_" << getpid() << ".clr";
266    return path.str();
267}
268
269void Renderer::addColorMap(const ColorMapId& id, osg::TransferFunction1D *xfer)
270{
271    _colorMaps[id] = xfer;
272    saveCLRFile(getColorMapFilePath(id), xfer);
273}
274
275void Renderer::deleteColorMap(const ColorMapId& id)
276{
277    ColorMapHashmap::iterator itr;
278    bool doAll = false;
279
280    if (id.compare("all") == 0) {
281        itr = _colorMaps.begin();
282        doAll = true;
283    } else {
284        itr = _colorMaps.find(id);
285    }
286
287    if (itr == _colorMaps.end()) {
288        ERROR("Unknown ColorMap %s", id.c_str());
289        return;
290    }
291
292    do {
293        TRACE("Deleting ColorMap %s", itr->first.c_str());
294        std::string path = getColorMapFilePath(itr->first);
295        if (remove(path.c_str()) < 0) {
296            ERROR("Failed to delete colormap file '%s': %s",
297                  path.c_str(), strerror(errno));
298        }
299        itr = _colorMaps.erase(itr);
300    } while (doAll && itr != _colorMaps.end());
301}
302
303void Renderer::setColorMapNumberOfTableEntries(const ColorMapId& id,
304                                               int numEntries)
305{
306    ColorMapHashmap::iterator itr;
307    bool doAll = false;
308
309    if (id.compare("all") == 0) {
310        itr = _colorMaps.begin();
311        doAll = true;
312    } else {
313        itr = _colorMaps.find(id);
314    }
315
316    if (itr == _colorMaps.end()) {
317        ERROR("Unknown ColorMap %s", id.c_str());
318        return;
319    }
320
321    do {
322        itr->second->allocate(numEntries);
323    } while (doAll && ++itr != _colorMaps.end());
324}
325
326bool
327Renderer::renderColorMap(const ColorMapId& id, int width, int height,
328                         osg::Image *imgData,
329                         bool opaque, float bgColor[3],
330                         bool bgr, int bytesPerPixel) const
331{
332    ColorMapHashmap::const_iterator itr = _colorMaps.find(id);
333    if (itr == _colorMaps.end()) {
334        ERROR("Unknown ColorMap %s", id.c_str());
335        return false;
336    }
337
338    ColorMap::renderColorMap(itr->second, width, height, imgData, opaque, bgColor, bgr, bytesPerPixel);
339    return true;
340}
341
342void Renderer::initViewer() {
343    if (_viewer.valid())
344        return;
345    _viewer = new osgViewer::Viewer();
346#if 1
347    osg::DisplaySettings *ds = _viewer->getDisplaySettings();
348    if (ds == NULL) {
349        ds = osg::DisplaySettings::instance().get();
350    }
351    ds->setDoubleBuffer(false);
352    ds->setMinimumNumAlphaBits(8);
353    ds->setMinimumNumStencilBits(8);
354    ds->setNumMultiSamples(0);
355#endif
356    _viewer->setThreadingModel(osgViewer::ViewerBase::SingleThreaded);
357    _viewer->getDatabasePager()->setUnrefImageDataAfterApplyPolicy(false, false);
358    _viewer->setReleaseContextAtEndOfFrameHint(false);
359    //_viewer->setLightingMode(osg::View::SKY_LIGHT);
360    _viewer->getCamera()->setClearColor(osg::Vec4(_bgColor[0], _bgColor[1], _bgColor[2], 1));
361    _viewer->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
362    _viewer->getCamera()->setNearFarRatio(0.00002);
363    _viewer->getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
364    _stateManip = new osgGA::StateSetManipulator(_viewer->getCamera()->getOrCreateStateSet());
365    _viewer->addEventHandler(_stateManip);
366    //_viewer->addEventHandler(new osgViewer::StatsHandler());
367
368#ifdef DEBUG
369    if (_viewer->getViewerStats() != NULL) {
370        TRACE("Enabling stats");
371        _viewer->getViewerStats()->collectStats("scene", true);
372    }
373#endif
374#if 0
375    osgViewer::ViewerBase::Windows windows;
376    _viewer->getWindows(windows);
377    if (windows.size() == 1) {
378        windows[0]->setSyncToVBlank(false);
379    } else {
380        ERROR("Num windows: %lu", windows.size());
381    }
382#endif
383}
384
385void Renderer::finalizeViewer() {
386    initViewer();
387    TRACE("Before _viewer->isRealized()");
388    if (!_viewer->isRealized()) {
389        int screen = 0;
390        const char *displayEnv = getenv("DISPLAY");
391        if (displayEnv != NULL) {
392            TRACE("DISPLAY: %s", displayEnv);
393            // 3 parts: host, display, screen
394            int part = 0;
395            for (size_t c = 0; c < strlen(displayEnv); c++) {
396                if (displayEnv[c] == ':') {
397                    part = 1;
398                } else if (part == 1 && displayEnv[c] == '.') {
399                    part = 2;
400                } else if (part == 2) {
401                    screen = atoi(&displayEnv[c]);
402                    break;
403                }
404            }
405        }
406        TRACE("Using screen: %d", screen);
407#ifdef USE_OFFSCREEN_RENDERING
408#ifdef USE_PBUFFER
409        osg::ref_ptr<osg::GraphicsContext> pbuffer;
410        osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
411        traits->x = 0;
412        traits->y = 0;
413        traits->width = _windowWidth;
414        traits->height = _windowHeight;
415        traits->red = 8;
416        traits->green = 8;
417        traits->blue = 8;
418        traits->alpha = 8;
419        traits->windowDecoration = false;
420        traits->pbuffer = true;
421        traits->doubleBuffer = true;
422        traits->sharedContext = 0;
423
424        pbuffer = osg::GraphicsContext::createGraphicsContext(traits.get());
425        if (pbuffer.valid()) {
426            TRACE("Pixel buffer has been created successfully.");
427        } else {
428            ERROR("Pixel buffer has not been created successfully.");
429        }
430        osg::Camera *camera = new osg::Camera;
431        camera->setGraphicsContext(pbuffer.get());
432        //camera->getOrCreateStateSet()->setGlobalDefaults();
433        camera->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
434        GLenum buffer = pbuffer->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
435        camera->setDrawBuffer(buffer);
436        camera->setReadBuffer(buffer);
437        _captureCallback = new ScreenCaptureCallback();
438        camera->setFinalDrawCallback(_captureCallback.get());
439        _viewer->addSlave(camera, osg::Matrixd(), osg::Matrixd());
440#else
441        _viewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
442        osg::Texture2D* texture2D = new osg::Texture2D;
443        texture2D->setTextureSize(_windowWidth, _windowHeight);
444        texture2D->setInternalFormat(GL_RGBA);
445        texture2D->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST);
446        texture2D->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST);
447
448        _viewer->getCamera()->setImplicitBufferAttachmentMask(0, 0);
449        _viewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, texture2D);
450        //_viewer->getCamera()->attach(osg::Camera::DEPTH_BUFFER, GL_DEPTH_COMPONENT24);
451        _viewer->getCamera()->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, GL_DEPTH24_STENCIL8_EXT);
452        _captureCallback = new ScreenCaptureCallback(texture2D);
453        _viewer->getCamera()->setFinalDrawCallback(_captureCallback.get());
454        _viewer->setUpViewInWindow(0, 0, _windowWidth, _windowHeight, screen);
455#endif
456#else
457        _captureCallback = new ScreenCaptureCallback();
458        _viewer->getCamera()->setFinalDrawCallback(_captureCallback.get());
459#if 1
460        _viewer->setUpViewInWindow(0, 0, _windowWidth, _windowHeight, screen);
461#else
462        SingleWindow *windowConfig = new SingleWindow(0, 0, _windowWidth, _windowHeight, screen);
463        osg::DisplaySettings *ds = windowConfig->getActiveDisplaySetting(*_viewer.get());
464        ds->setDoubleBuffer(false);
465        ds->setMinimumNumAlphaBits(8);
466        ds->setMinimumNumStencilBits(8);
467        ds->setNumMultiSamples(0);
468        windowConfig->setWindowDecoration(false);
469        windowConfig->setOverrideRedirect(false);
470        _viewer->apply(windowConfig);
471#endif
472#endif
473        _viewer->realize();
474        initColorMaps();
475        // HACK: This seems to initialize something required for properly
476        // mapping mouse coords
477        assert(getEventQueue() != NULL);
478        getEventQueue()->mouseMotion(_windowWidth/2, _windowHeight/2);
479    }
480}
481
482void Renderer::setAttribution(const std::string& attrib)
483{
484    TRACE("Setting map attribution: '%s'", attrib.c_str());
485    _attribution = attrib;
486    if (_copyrightLabel.valid()) {
487        _copyrightLabel->setText(_attribution);
488        _needsRedraw = true;
489    }
490}
491
492void Renderer::initControls()
493{
494    if (_hbox.valid())
495        return;
496    _hbox =
497        new osgEarth::Util::Controls::HBox(osgEarth::Util::Controls::Control::ALIGN_RIGHT,
498                                           osgEarth::Util::Controls::Control::ALIGN_BOTTOM,
499                                           osgEarth::Util::Controls::Gutter(0, 2, 2, 0), 2.0f);
500    _attribution = "Map data © OpenStreetMap";
501    _copyrightLabel =
502        new osgEarth::Util::Controls::LabelControl(_attribution, 12.0f);
503    _copyrightLabel->setForeColor(osg::Vec4f(1, 1, 1, 1));
504    _copyrightLabel->setHaloColor(osg::Vec4f(0, 0, 0, 1));
505    _copyrightLabel->setEncoding(osgText::String::ENCODING_UTF8);
506    _scaleLabel =
507        new osgEarth::Util::Controls::LabelControl("- km", 12.0f);
508    _scaleLabel->setForeColor(osg::Vec4f(1, 1, 1, 1));
509    _scaleLabel->setHaloColor(osg::Vec4f(0, 0, 0, 1));
510    _scaleBar =
511        new osgEarth::Util::Controls::Frame();
512    _scaleBar->setVertFill(true);
513    _scaleBar->setForeColor(osg::Vec4f(0, 0, 0, 1));
514    _scaleBar->setBackColor(osg::Vec4f(1, 1, 1, 0.6));
515    _scaleBar->setBorderColor(osg::Vec4f(0, 0, 0 ,1));
516    _scaleBar->setBorderWidth(1.0);
517    _hbox->addControl(_copyrightLabel.get());
518    _hbox->addControl(_scaleLabel.get());
519    _hbox->addControl(_scaleBar.get());
520    osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->addControl(_hbox.get());
521    // Install an event callback to handle scale bar updates
522    // Can't use an update callback since that will trigger
523    // constant rendering
524    _mapNode->setEventCallback(new MapNodeCallback(this));
525}
526
527void Renderer::setGraticule(bool enable, GraticuleType type)
528{
529    if (!_mapNode.valid() || !_sceneRoot.valid())
530        return;
531    if (enable) {
532        if (_graticule.valid()) {
533            _sceneRoot->removeChild(_graticule.get());
534            _graticule = NULL;
535        }
536        switch (type) {
537        case GRATICULE_UTM: {
538            osgEarth::Util::UTMGraticule *gr = new osgEarth::Util::UTMGraticule(_mapNode.get());
539            _sceneRoot->addChild(gr);
540            _graticule = gr;
541        }
542            break;
543        case GRATICULE_MGRS: {
544            osgEarth::Util::MGRSGraticule *gr = new osgEarth::Util::MGRSGraticule(_mapNode.get());
545            _sceneRoot->addChild(gr);
546            _graticule = gr;
547        }
548            break;
549        case GRATICULE_SHADER: {
550            osgEarth::Util::GraticuleOptions opt;
551            opt.color() = osgEarth::Symbology::Color(1, 1, 0);
552            opt.labelColor() = osgEarth::Symbology::Color(1, 1, 0);
553            opt.lineWidth() = 1.0;
554            osgEarth::Util::GraticuleNode *gr = new osgEarth::Util::GraticuleNode(_mapNode.get(), opt);
555            _sceneRoot->addChild(gr);
556            _graticule = gr;
557        }
558            break;
559        case GRATICULE_GEODETIC:
560        default:
561            osgEarth::Util::GeodeticGraticule *gr = new osgEarth::Util::GeodeticGraticule(_mapNode.get());
562            osgEarth::Util::GeodeticGraticuleOptions opt = gr->getOptions();
563            opt.lineStyle()->getOrCreate<osgEarth::Symbology::LineSymbol>()->stroke()->color().set(1,0,0,1);
564            gr->setOptions(opt);
565            _sceneRoot->addChild(gr);
566            _graticule = gr;
567        }
568    } else if (_graticule.valid()) {
569        _sceneRoot->removeChild(_graticule.get());
570        _graticule = NULL;
571    }
572    _needsRedraw = true;
573}
574
575void Renderer::setReadout(int x, int y)
576{
577    if (!_coordsCallback.valid() || !_mapNode.valid() || !_viewer.valid())
578        return;
579
580    osgEarth::GeoPoint mapCoord;
581    if (mapMouseCoords(x, y, mapCoord)) {
582        _coordsCallback->set(mapCoord, _viewer->asView(), _mapNode);
583    } else {
584        _coordsCallback->reset(_viewer->asView(), _mapNode);
585    }
586    _needsRedraw = true;
587}
588
589void Renderer::clearReadout()
590{
591    if (_coordsCallback.valid()) {
592        _coordsCallback->reset(_viewer->asView(), _mapNode);
593    }
594    _needsRedraw = true;
595}
596
597void Renderer::setCoordinateReadout(bool state, CoordinateDisplayType type,
598                                    int precision)
599{
600    if (!state) {
601        if (_mouseCoordsTool.valid() && _viewer.valid()) {
602            _viewer->removeEventHandler(_mouseCoordsTool.get());
603            _mouseCoordsTool = NULL;
604        }
605        if (_coordsCallback.valid() && _viewer.valid()) {
606            osgEarth::Util::Controls::LabelControl *readout =
607                _coordsCallback->getLabel();
608            osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->removeControl(readout);
609            _coordsCallback = NULL;
610        }
611    } else {
612        initMouseCoordsTool(type, precision);
613    }
614    _needsRedraw = true;
615}
616
617osgEarth::Util::MGRSFormatter::Precision
618Renderer::getMGRSPrecision(int precisionInMeters)
619{
620    switch (precisionInMeters) {
621    case 1:
622        return osgEarth::Util::MGRSFormatter::PRECISION_1M;
623    case 10:
624        return osgEarth::Util::MGRSFormatter::PRECISION_10M;
625    case 100:
626        return osgEarth::Util::MGRSFormatter::PRECISION_100M;
627    case 1000:
628        return osgEarth::Util::MGRSFormatter::PRECISION_1000M;
629    case 10000:
630        return osgEarth::Util::MGRSFormatter::PRECISION_10000M;
631    case 100000:
632        return osgEarth::Util::MGRSFormatter::PRECISION_100000M;
633    default:
634        ERROR("Invalid precision: %d", precisionInMeters);
635        return osgEarth::Util::MGRSFormatter::PRECISION_1M;
636    }
637}
638
639void Renderer::initMouseCoordsTool(CoordinateDisplayType type, int precision)
640{
641    if (!_viewer.valid())
642        return;
643    if (_mouseCoordsTool.valid()) {
644        _viewer->removeEventHandler(_mouseCoordsTool.get());
645    }
646    _mouseCoordsTool = new MouseCoordsTool(_mapNode.get());
647    osgEarth::Util::Controls::LabelControl *readout;
648    if (_coordsCallback.valid()) {
649        readout = _coordsCallback->getLabel();
650        _coordsCallback = NULL;
651    } else {
652        readout = new osgEarth::Util::Controls::LabelControl("", 12.0f);
653        osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->addControl(readout);
654        readout->setForeColor(osg::Vec4f(1, 1, 1, 1));
655        readout->setHaloColor(osg::Vec4f(0, 0, 0, 1));
656    }
657
658    osgEarth::Util::Formatter *formatter = NULL;
659    if (type == COORDS_MGRS) {
660        osgEarth::Util::MGRSFormatter::Precision prec =
661            osgEarth::Util::MGRSFormatter::PRECISION_1M;
662        if (precision > 0) {
663            prec = getMGRSPrecision(precision);
664        }
665        unsigned int opts = 0u;
666        formatter = new osgEarth::Util::MGRSFormatter(prec, NULL, opts);
667    } else {
668        osgEarth::Util::LatLongFormatter::AngularFormat af;
669        unsigned int opts =  osgEarth::Util::LatLongFormatter::USE_SYMBOLS;
670        switch (type) {
671        case COORDS_LATLONG_DEGREES_DECIMAL_MINUTES:
672            af = osgEarth::Util::LatLongFormatter::FORMAT_DEGREES_DECIMAL_MINUTES;
673            break;
674        case COORDS_LATLONG_DEGREES_MINUTES_SECONDS:
675            af = osgEarth::Util::LatLongFormatter::FORMAT_DEGREES_MINUTES_SECONDS;
676            break;
677        default:
678            af = osgEarth::Util::LatLongFormatter::FORMAT_DECIMAL_DEGREES;
679        }
680        osgEarth::Util::LatLongFormatter *latlong = new osgEarth::Util::LatLongFormatter(af, opts);
681        if (precision > 0) {
682            latlong->setPrecision(precision);
683        }
684        formatter = latlong;
685    }
686    _coordsCallback = new MouseCoordsCallback(readout, formatter);
687
688    _mouseCoordsTool->addCallback(_coordsCallback.get());
689    _viewer->addEventHandler(_mouseCoordsTool.get());
690}
691
692void Renderer::initEarthManipulator()
693{
694    _manipulator = new osgEarth::Util::EarthManipulator;
695#if 1
696    osgEarth::Util::EarthManipulator::Settings *settings = _manipulator->getSettings();
697
698    settings->bindMouse(osgEarth::Util::EarthManipulator::ACTION_ROTATE,
699                        osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON,
700                        osgGA::GUIEventAdapter::MODKEY_ALT);
701    osgEarth::Util::EarthManipulator::ActionOptions options;
702    options.clear();
703    options.add(osgEarth::Util::EarthManipulator::OPTION_CONTINUOUS, true);
704    settings->bindMouse(osgEarth::Util::EarthManipulator::ACTION_ZOOM,
705                        osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON,
706                        osgGA::GUIEventAdapter::MODKEY_ALT, options);
707
708    if (_map->isGeocentric()) {
709        settings->setMinMaxDistance(1.0, 2.0e7);
710    } else {
711        osgEarth::GeoExtent extent = _map->getProfile()->getExtent();
712        settings->setMinMaxDistance(1.0, getMaxDistanceFromExtent(extent));
713    }
714
715    _manipulator->applySettings(settings);
716#endif
717    _viewer->setCameraManipulator(_manipulator.get());
718    _manipulator->setNode(NULL);
719    _manipulator->setNode(_sceneRoot.get());
720    //_manipulator->computeHomePosition();
721}
722
723void Renderer::loadEarthFile(const char *path)
724{
725    TRACE("Loading %s", path);
726    osg::Node *node = osgDB::readNodeFile(path);
727    if (node == NULL) {
728        ERROR("Couldn't load %s", path);
729        return;
730    }
731    osgEarth::MapNode *mapNode = osgEarth::MapNode::findMapNode(node);
732    if (mapNode == NULL) {
733        ERROR("Couldn't find MapNode");
734        return;
735    } else {
736        initViewer();
737        _sceneRoot = new osg::Group;
738        _sceneRoot->addChild(node);
739        _map = mapNode->getMap();
740    }
741    _mapNode = mapNode;
742
743    if (_clipPlaneCullCallback.valid()) {
744        _viewer->getCamera()->removeCullCallback(_clipPlaneCullCallback.get());
745        _clipPlaneCullCallback = NULL;
746    }
747    if (_map->isGeocentric()) {
748        _clipPlaneCullCallback = new osgEarth::Util::AutoClipPlaneCullCallback(mapNode);
749        _viewer->getCamera()->addCullCallback(_clipPlaneCullCallback.get());
750    }
751    _viewer->setSceneData(_sceneRoot.get());
752
753    if (_mouseCoordsTool.valid()) {
754        initMouseCoordsTool();
755    }
756    initControls();
757    initEarthManipulator();
758   
759    _viewer->home();
760    finalizeViewer();
761    _needsRedraw = true;
762}
763
764void Renderer::resetMap(osgEarth::MapOptions::CoordinateSystemType type,
765                        const osg::Vec4f& bgColor,
766                        const char *profile,
767                        double bounds[4])
768{
769    TRACE("Restting map with type %d, profile %s", type, profile);
770
771    osgEarth::MapOptions mapOpts;
772    mapOpts.coordSysType() = type;
773    if (profile != NULL) {
774        if (bounds != NULL) {
775            mapOpts.profile() = osgEarth::ProfileOptions();
776            if (strcmp(profile, "geodetic") == 0) {
777                mapOpts.profile()->srsString() = "epsg:4326"; // WGS84
778            } else if (strcmp(profile, "spherical-mercator") == 0) {
779                // Projection used by Google/Bing/OSM
780                // aka epsg:900913 meters in x/y
781                // aka WGS84 Web Mercator (Auxiliary Sphere)
782                // X/Y: -20037508.34m to 20037508.34m
783                mapOpts.profile()->srsString() = "epsg:3857";
784            } else {
785                mapOpts.profile()->srsString() = profile;
786            }
787            TRACE("Setting profile bounds: %g %g %g %g",
788                  bounds[0], bounds[1], bounds[2], bounds[3]);
789            if (1) {
790                osg::ref_ptr<osgEarth::SpatialReference> fromSRS = osgEarth::SpatialReference::create("wgs84");
791                osg::ref_ptr<osgEarth::SpatialReference> toSRS = osgEarth::SpatialReference::create(mapOpts.profile()->srsString().get());
792                osgEarth::Bounds extents(bounds[0], bounds[1], bounds[2], bounds[3]);
793                extents.transform(fromSRS, toSRS);
794                mapOpts.profile()->bounds() = extents;
795                //mapOpts.profile()->numTilesWideAtLod0() = 1;
796                //mapOpts.profile()->numTilesHighAtLod0() = 2;
797            } else {
798                mapOpts.profile()->bounds() =
799                    osgEarth::Bounds(bounds[0], bounds[1], bounds[2], bounds[3]);
800            }
801        } else {
802            mapOpts.profile() = osgEarth::ProfileOptions(profile);
803        }
804    } else if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) {
805        mapOpts.profile() = osgEarth::ProfileOptions("global-mercator");
806    }
807
808#ifdef USE_CACHE
809    setupCache();
810    osgEarth::Drivers::FileSystemCacheOptions cacheOpts;
811    cacheOpts.rootPath() = _cacheDir;
812    mapOpts.cache() = cacheOpts;
813#endif
814
815    initViewer();
816
817    //mapOpts.referenceURI() = _baseURI;
818    osgEarth::Map *map = new osgEarth::Map(mapOpts);
819    _map = map;
820    osgEarth::Drivers::GDALOptions bopts;
821    bopts.url() = getBaseImage();
822    addImageLayer("base", bopts);
823#ifdef USE_REX
824    osgEarth::Drivers::RexTerrainEngine::RexTerrainEngineOptions engineOpt;
825#else
826    osgEarth::Drivers::MPTerrainEngine::MPTerrainEngineOptions engineOpt;
827#endif
828    // Set background layer color
829    engineOpt.color() = bgColor;
830    //engineOpt.minLOD() = 1;
831    // Sets shader uniform for terrain renderer (config var defaults to false)
832    if (!_map->isGeocentric()) {
833        engineOpt.enableLighting() = false;
834    }
835    osgEarth::MapNodeOptions mapNodeOpts(engineOpt);
836    // Sets GL_LIGHTING state in MapNode StateSet (config var defaults to true)
837    //mapNodeOpts.enableLighting() = true;
838    //mapNodeOpts.getTerrainOptions().loadingPolicy().mapLoadingThreadsPerCore() = 1;
839    //mapNodeOpts.getTerrainOptions().loadingPolicy().numLoadingThreads() = 1;
840    //mapNodeOpts.getTerrainOptions().loadingPolicy().numCompileThreadsPerCore() = 1;
841    //mapNodeOpts.getTerrainOptions().loadingPolicy().numCompileThreads() = 1;
842    osgEarth::MapNode *mapNode = new osgEarth::MapNode(map, mapNodeOpts);
843    _mapNode = mapNode;
844    if (_map->isGeocentric()) {
845        osgEarth::DateTime now;
846        TRACE("Creating SkyNode");
847        osgEarth::Drivers::SimpleSky::SimpleSkyOptions skyOpts;
848        skyOpts.hours() = now.hours();
849        skyOpts.ambient() = 0.2f;
850        skyOpts.atmosphericLighting() = true;
851        skyOpts.exposure() = 3.0;
852        _skyNode = osgEarth::Util::SkyNode::create(skyOpts, mapNode);
853        _skyNode->addChild(mapNode);
854        _skyNode->attach(_viewer.get(), 0);
855        _sceneRoot = new osg::Group();
856        _sceneRoot->addChild(_skyNode.get());
857        //_sceneRoot = _skyNode;
858    } else {
859        _sceneRoot = new osg::Group();
860        _sceneRoot->addChild(_mapNode.get());
861    }
862
863    if (_clipPlaneCullCallback.valid()) {
864        _viewer->getCamera()->removeCullCallback(_clipPlaneCullCallback.get());
865        _clipPlaneCullCallback = NULL;
866    }
867    if (_map->isGeocentric()) {
868        _clipPlaneCullCallback = new osgEarth::Util::AutoClipPlaneCullCallback(mapNode);
869        _viewer->getCamera()->addCullCallback(_clipPlaneCullCallback.get());
870    }
871    _viewer->setSceneData(_sceneRoot.get());
872    if (_mouseCoordsTool.valid()) {
873        initMouseCoordsTool();
874    }
875#ifdef USE_RTT_PICKER
876    if (_picker.valid()) {
877        _viewer->removeEventHandler(_picker.get());
878        _picker = NULL;
879    }
880    if (!_picker.valid()) {
881        _picker = new osgEarth::Util::RTTPicker;
882        _picker->addChild(mapNode);
883        //osg::ref_ptr<HoverCallback> callback = new HoverCallback(this);
884        osg::ref_ptr<SelectCallback> callback = new SelectCallback(this);
885        _picker->setDefaultCallback(callback.get());
886        _viewer->addEventHandler(_picker.get());
887    }
888#endif
889    initControls();
890    //_viewer->setSceneData(_sceneRoot.get());
891    initEarthManipulator();
892    _viewer->home();
893
894    finalizeViewer();
895    _needsRedraw = true;
896}
897
898void Renderer::clearMap()
899{
900    if (_map.valid()) {
901        _map->clear();
902        _needsRedraw = true;
903    }
904}
905
906void Renderer::setSkyAmbient(float value)
907{
908#if OSG_MIN_VERSION_REQUIRED(2, 7, 0)
909    if (_skyNode.valid()) {
910        _skyNode->setMinimumAmbient(osg::Vec4f(value, value, value, 1.0f));
911        _needsRedraw = true;
912    }
913#endif
914}
915
916void Renderer::setLighting(bool state)
917{
918    if (_mapNode.valid()) {
919        TRACE("Setting lighting: %d", state ? 1 : 0);
920        _mapNode->getOrCreateStateSet()
921            ->setMode(GL_LIGHTING, state ? 1 : 0);
922        _needsRedraw = true;
923    }
924}
925
926void Renderer::setViewerLightType(osg::View::LightingMode mode)
927{
928    if (_viewer.valid()) {
929        _viewer->setLightingMode(mode);
930        _needsRedraw = true;
931    }
932}
933
934void Renderer::setTerrainVerticalScale(double scale)
935{
936    if (_mapNode.valid()) {
937        if (!_verticalScale.valid()) {
938            _verticalScale = new osgEarth::Util::VerticalScale;
939            _mapNode->getTerrainEngine()->addEffect(_verticalScale);
940        }
941        _verticalScale->setScale(scale);
942        _needsRedraw = true;
943    }
944}
945
946void Renderer::setEphemerisTime(time_t utcTime)
947{
948    if (_skyNode.valid()) {
949        osgEarth::DateTime time(utcTime);
950        _skyNode->setDateTime(time);
951        _needsRedraw = true;
952    }
953}
954
955void Renderer::setEphemerisTime(int year, int month, int day, double hours)
956{
957    if (_skyNode.valid()) {
958        osgEarth::DateTime time(year, month, day, hours);
959        _skyNode->setDateTime(time);
960        _needsRedraw = true;
961    }
962}
963
964void Renderer::setTerrainColor(const osg::Vec4f& color)
965{
966    // XXX: Don't know how to change this at runtime without a map reset
967    _needsRedraw = true;
968}
969
970void Renderer::setTerrainLighting(bool state)
971{
972    if (_skyNode.valid()) {
973        _skyNode->setLighting((state ? osg::StateAttribute::ON : osg::StateAttribute::OFF));
974    } else {
975#if 1
976        if (!_mapNode.valid()) {
977            ERROR("No map node");
978            return;
979        }
980        // XXX: HACK alert
981        // Find the terrain engine container (might be above one or more decorators)
982        osg::Group *group = _mapNode->getTerrainEngine();
983        while (group->getParent(0) != NULL && group->getParent(0) != _mapNode.get()) {
984            group = group->getParent(0);
985        }
986        if (group != NULL && group->getParent(0) == _mapNode.get()) {
987            TRACE("Setting terrain lighting: %d", state ? 1 : 0);
988            if (group->getOrCreateStateSet()->getUniform("oe_mode_GL_LIGHTING") != NULL) {
989                group->getStateSet()->getUniform("oe_mode_GL_LIGHTING")->set(state);
990            } else {
991                ERROR("Can't get terrain lighting uniform");
992            }
993        } else {
994            ERROR("Can't find terrain engine container");
995        }
996#else
997        if (_stateManip.valid()) {
998            _stateManip->setLightingEnabled(state);
999        }
1000#endif
1001    }
1002    _needsRedraw = true;
1003}
1004
1005void Renderer::setTerrainWireframe(bool state)
1006{
1007    if (!_map.valid()) {
1008        ERROR("No map");
1009        return;
1010    }
1011#if 0
1012    if (!_mapNode.valid()) {
1013        ERROR("No map node");
1014        return;
1015    }
1016    TRACE("Setting terrain wireframe: %d", state ? 1 : 0);
1017    osg::StateSet *state = _mapNode->getOrCreateStateSet();
1018    osg::PolygonMode *pmode = dynamic_cast< osg::PolygonMode* >(state->getAttribute(osg::StateAttribute::POLYGONMODE));
1019    if (pmode == NULL) {
1020        pmode = new osg::PolygonMode;
1021        state->setAttribute(pmode);
1022    }
1023    if (state) {
1024        pmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE);
1025    } else {
1026        pmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL);
1027    }
1028    _needsRedraw = true;
1029#else
1030    if (_stateManip.valid()) {
1031        _stateManip->setPolygonMode(state ? osg::PolygonMode::LINE : osg::PolygonMode::FILL);
1032        _needsRedraw = true;
1033    }
1034#endif
1035}
1036
1037void Renderer::saveNamedViewpoint(const char *name)
1038{
1039    _viewpoints[name] = getViewpoint();
1040}
1041
1042bool Renderer::removeNamedViewpoint(const char *name)
1043{
1044    ViewpointHashmap::iterator itr = _viewpoints.find(name);
1045    if (itr != _viewpoints.end()) {
1046        _viewpoints.erase(name);
1047        return true;
1048    } else {
1049        ERROR("Unknown viewpoint: '%s'", name);
1050        return false;
1051    }
1052}
1053
1054bool Renderer::restoreNamedViewpoint(const char *name, double durationSecs)
1055{
1056    ViewpointHashmap::iterator itr = _viewpoints.find(name);
1057    if (itr != _viewpoints.end()) {
1058        setViewpoint(itr->second, durationSecs);
1059        return true;
1060    } else {
1061        ERROR("Unknown viewpoint: '%s'", name);
1062        return false;
1063    }
1064}
1065
1066void Renderer::setViewpoint(const osgEarth::Viewpoint& v, double durationSecs)
1067{
1068    if (_manipulator.valid()) {
1069        TRACE("Setting viewpoint: %g %g %g %g %g %g",
1070              v.focalPoint()->x(), v.focalPoint()->y(), v.focalPoint()->z(),
1071              v.heading()->as(osgEarth::Units::DEGREES),
1072              v.pitch()->as(osgEarth::Units::DEGREES),
1073              v.range()->as(osgEarth::Units::METERS));
1074        _manipulator->setViewpoint(v, durationSecs);
1075        _needsRedraw = true;
1076    } else {
1077        ERROR("No manipulator");
1078    }
1079}
1080
1081osgEarth::Viewpoint Renderer::getViewpoint()
1082{
1083    if (_manipulator.valid()) {
1084        return _manipulator->getViewpoint();
1085    } else {
1086        // Uninitialized, invalid viewpoint
1087        return osgEarth::Viewpoint();
1088    }
1089}
1090
1091static void srsInfo(const osgEarth::SpatialReference *srs)
1092{
1093    TRACE("SRS: %s", srs->getName().c_str());
1094    TRACE("horiz: \"%s\" vert: \"%s\"", srs->getHorizInitString().c_str(), srs->getVertInitString().c_str());
1095    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",
1096          srs->isGeographic() ? 1 : 0,
1097          srs->isGeodetic() ? 1 : 0,
1098          srs->isProjected() ? 1 : 0,
1099          srs->isECEF() ? 1 : 0,
1100          srs->isMercator() ? 1 : 0,
1101          srs->isSphericalMercator() ? 1 : 0,
1102          srs->isNorthPolar() ? 1 : 0,
1103          srs->isSouthPolar() ? 1 : 0,
1104          srs->isUserDefined() ? 1 : 0,
1105          srs->isContiguous() ? 1 : 0,
1106          srs->isCube() ? 1 : 0,
1107          srs->isLTP() ? 1 : 0,
1108          srs->isPlateCarre() ? 1 : 0);
1109}
1110
1111bool Renderer::getWorldCoords(const osgEarth::GeoPoint& mapPt, osg::Vec3d *world)
1112{
1113    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
1114        ERROR("No map");
1115        return false;
1116    }
1117    TRACE("Input SRS:");
1118    srsInfo(mapPt.getSRS());
1119    TRACE("Map SRS:");
1120    srsInfo(_mapNode->getMapSRS());
1121    bool ret = mapPt.toWorld(*world, _mapNode->getTerrain());
1122    TRACE("In: %g,%g,%g Out: %g,%g,%g",
1123          mapPt.x(), mapPt.y(), mapPt.z(),
1124          world->x(), world->y(), world->z());
1125    return ret;
1126}
1127
1128bool Renderer::worldToScreen(const osg::Vec3d& world, osg::Vec3d *screen, bool invertY)
1129{
1130    if (!_viewer.valid()) {
1131        ERROR("No viewer");
1132        return false;
1133    }
1134    osg::Camera *cam = _viewer->getCamera();
1135    osg::Matrixd MVP = cam->getViewMatrix() * cam->getProjectionMatrix();
1136    // Get clip coords
1137    osg::Vec4d pt;
1138    pt = osg::Vec4d(world, 1.0) * MVP;
1139    // Clip
1140    if (pt.x() < -pt.w() ||
1141        pt.x() > pt.w() ||
1142        pt.y() < -pt.w() ||
1143        pt.y() > pt.w() ||
1144        pt.z() < -pt.w() ||
1145        pt.z() > pt.w()) {
1146        // Outside frustum
1147        TRACE("invalid pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1148        return false;
1149    }
1150    TRACE("clip pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1151    // Perspective divide: now NDC
1152    pt /= pt.w();
1153    const osg::Viewport *viewport = cam->getViewport();
1154#if 1
1155    screen->x() = viewport->x() + viewport->width() * 0.5 + pt.x() * viewport->width() * 0.5;
1156    screen->y() = viewport->y() + viewport->height() * 0.5 + pt.y() * viewport->height() * 0.5;
1157    //double near = 0;
1158    //double far = 1;
1159    //screen->z() = (far + near) * 0.5 + (far - near) * 0.5 * pt.z();
1160    screen->z() = 0.5 + 0.5 * pt.z();
1161#else
1162    *screen = osg::Vec3d(pt.x(), pt.y(), pt.z()) * cam->getViewport()->computeWindowMatrix();
1163#endif
1164    if (invertY) {
1165        screen->y() = viewport->height() - screen->y();
1166    }
1167    TRACE("screen: %g,%g,%g", screen->x(), screen->y(), screen->z());
1168    return true;
1169}
1170
1171bool Renderer::worldToScreen(std::vector<osg::Vec3d>& coords, bool invertY)
1172{
1173    if (!_viewer.valid()) {
1174        ERROR("No viewer");
1175        return false;
1176    }
1177    bool ret = true;
1178    osg::Camera *cam = _viewer->getCamera();
1179    osg::Matrixd MVP = cam->getViewMatrix() * cam->getProjectionMatrix();
1180    const osg::Viewport *viewport = cam->getViewport();
1181    for (std::vector<osg::Vec3d>::iterator itr = coords.begin();
1182         itr != coords.end(); ++itr) {
1183        // Get clip coords
1184        osg::Vec4d pt;
1185        pt = osg::Vec4d(*itr, 1.0) * MVP;
1186        // Clip
1187        if (pt.x() < -pt.w() ||
1188            pt.x() > pt.w() ||
1189            pt.y() < -pt.w() ||
1190            pt.y() > pt.w() ||
1191            pt.z() < -pt.w() ||
1192            pt.z() > pt.w()) {
1193            // Outside frustum
1194            TRACE("invalid pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1195            itr->set(std::numeric_limits<double>::quiet_NaN(),
1196                     std::numeric_limits<double>::quiet_NaN(),
1197                     std::numeric_limits<double>::quiet_NaN());
1198            ret = false;
1199            continue;
1200        }
1201        TRACE("clip pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1202        // Perspective divide: now NDC
1203        pt /= pt.w();
1204#if 1
1205        itr->x() = viewport->x() + viewport->width() * 0.5 + pt.x() * viewport->width() * 0.5;
1206        itr->y() = viewport->y() + viewport->height() * 0.5 + pt.y() * viewport->height() * 0.5;
1207        //double near = 0;
1208        //double far = 1;
1209        //itr->z() = (far + near) * 0.5 + (far - near) * 0.5 * pt.z();
1210        itr->z() = 0.5 + 0.5 * pt.z();
1211#else
1212        *itr = osg::Vec3d(pt.x(), pt.y(), pt.z()) * viewport->computeWindowMatrix();
1213#endif
1214        if (invertY) {
1215            itr->y() = viewport->height() - itr->y();
1216        }
1217        TRACE("screen: %g,%g,%g", itr->x(), itr->y(), itr->z());
1218    }
1219    return ret;
1220}
1221
1222bool Renderer::mouseToLatLong(int mouseX, int mouseY,
1223                              double *latitude, double *longitude)
1224{
1225    if (getMapSRS() == NULL)
1226        return false;
1227    const osgEarth::SpatialReference *outSRS =
1228        getMapSRS()->getGeographicSRS();
1229    if (outSRS == NULL)
1230        return false;
1231    osgEarth::GeoPoint mapPoint;
1232    if (mapMouseCoords(mouseX, mouseY, mapPoint)) {
1233        mapPoint = mapPoint.transform(outSRS);
1234        *longitude = mapPoint.x();
1235        *latitude = mapPoint.y();
1236        return true;
1237    } else {
1238        return false;
1239    }
1240}
1241#if 0
1242bool Renderer::mapToLatLong(osgEarth::GeoPoint& mapPoint)
1243{
1244    if (getMapSRS() == NULL)
1245        return false;
1246    const osgEarth::SpatialReference *outSRS =
1247        getMapSRS()->getGeographicSRS();
1248    if (outSRS == NULL)
1249        return false;
1250    mapPoint = mapPoint.transform(outSRS);
1251    return true;
1252}
1253#endif
1254/**
1255 * \brief Map screen mouse coordinates to map coordinates
1256 *
1257 * This method assumes that mouse Y coordinates are 0 at the top
1258 * of the screen and increase going down if invertY is set, and
1259 * 0 at the bottom and increase going up otherwise.
1260 */
1261bool Renderer::mapMouseCoords(float mouseX, float mouseY,
1262                              osgEarth::GeoPoint& map, bool invertY)
1263{
1264    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
1265        ERROR("No map");
1266        return false;
1267    }
1268    if (!_viewer.valid()) {
1269        ERROR("No viewer");
1270        return false;
1271    }
1272    if (invertY) {
1273        mouseY = ((float)_windowHeight - mouseY);
1274    }
1275    osg::Vec3d world;
1276    if (_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), mouseX, mouseY, world)) {
1277        map.fromWorld(getMapSRS(), world);
1278        return true;
1279    }
1280    return false;
1281}
1282
1283bool Renderer::addImageLayer(const char *name,
1284                             osgEarth::TileSourceOptions& opts,
1285                             bool enableCache,
1286                             bool coverage,
1287                             bool makeShared,
1288                             bool visible,
1289                             unsigned int minLOD, unsigned int maxLOD)
1290{
1291    if (!_map.valid()) {
1292        ERROR("No map");
1293        return false;
1294    }
1295    TRACE("layer: %s", name);
1296    if (!opts.tileSize().isSet()) {
1297        opts.tileSize() = 256;
1298    }
1299    osgEarth::ImageLayerOptions layerOpts(name, opts);
1300    layerOpts.textureCompression() = osg::Texture::USE_IMAGE_DATA_FORMAT;
1301    if (coverage) {
1302        layerOpts.coverage() = true;
1303    }
1304    if (makeShared) {
1305        layerOpts.shared() = true;
1306    }
1307    if (!enableCache) {
1308        TRACE("Disabling cache for layer %s", name);
1309        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1310    }
1311    if (!visible) {
1312        layerOpts.visible() = false;
1313    }
1314    layerOpts.minLevel() = minLOD;
1315    layerOpts.maxLevel() = maxLOD;
1316    osg::ref_ptr<osgEarth::ImageLayer> layer = new osgEarth::ImageLayer(layerOpts);
1317    _map->addImageLayer(layer.get());
1318    if (layer->getTileSource() == NULL || !layer->getTileSource()->isOK()) {
1319        ERROR("Failed to add image layer: %s", name);
1320        _map->removeImageLayer(layer.get());
1321        return false;
1322    }
1323    _needsRedraw = true;
1324    return true;
1325}
1326
1327void Renderer::addColorFilter(const char *name,
1328                              const char *shader)
1329{
1330    if (!_map.valid()) {
1331        ERROR("No map");
1332        return;
1333    }
1334    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1335    if (layer == NULL) {
1336        TRACE("Image layer not found: %s", name);
1337        return;
1338    }
1339    osgEarth::Util::GLSLColorFilter *filter = new osgEarth::Util::GLSLColorFilter;
1340    filter->setCode(shader);
1341    //filter->setCode("color.rgb = color.r > 0.5 ? vec3(1.0) : vec3(0.0);");
1342    layer->addColorFilter(filter);
1343    _needsRedraw = true;
1344}
1345
1346void Renderer::removeColorFilter(const char *name, int idx)
1347{
1348    if (!_map.valid()) {
1349        ERROR("No map");
1350        return;
1351    }
1352    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1353    if (layer == NULL) {
1354        TRACE("Image layer not found: %s", name);
1355        return;
1356    }
1357    if (idx < 0) {
1358        while (!layer->getColorFilters().empty()) {
1359            layer->removeColorFilter(layer->getColorFilters()[0]);
1360        }
1361    } else {
1362        layer->removeColorFilter(layer->getColorFilters().at(idx));
1363    }
1364    _needsRedraw = true;
1365}
1366
1367void Renderer::removeImageLayer(const char *name)
1368{
1369    if (!_map.valid()) {
1370        ERROR("No map");
1371        return;
1372    }
1373    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1374    if (layer != NULL) {
1375        _map->removeImageLayer(layer);
1376        _needsRedraw = true;
1377    } else {
1378        TRACE("Image layer not found: %s", name);
1379    }
1380}
1381
1382void Renderer::moveImageLayer(const char *name, unsigned int pos)
1383{
1384    if (!_map.valid()) {
1385        ERROR("No map");
1386        return;
1387    }
1388    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1389    if (layer != NULL) {
1390        _map->moveImageLayer(layer, pos);
1391        _needsRedraw = true;
1392    } else {
1393        TRACE("Image layer not found: %s", name);
1394    }
1395}
1396
1397void Renderer::setImageLayerOpacity(const char *name, double opacity)
1398{
1399    if (!_map.valid()) {
1400        ERROR("No map");
1401        return;
1402    }
1403    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1404    if (layer != NULL) {
1405        layer->setOpacity(opacity);
1406        _needsRedraw = true;
1407    } else {
1408        TRACE("Image layer not found: %s", name);
1409    }
1410}
1411
1412void Renderer::setImageLayerVisibleRange(const char *name, float min, float max)
1413{
1414    if (!_map.valid()) {
1415        ERROR("No map");
1416        return;
1417    }
1418    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1419    if (layer != NULL) {
1420        layer->setMinVisibleRange(min);
1421        layer->setMaxVisibleRange(max);
1422        _needsRedraw = true;
1423    } else {
1424        TRACE("Image layer not found: %s", name);
1425    }
1426}
1427
1428void Renderer::setImageLayerVisibility(const char *name, bool state)
1429{
1430    if (!_map.valid()) {
1431        ERROR("No map");
1432        return;
1433    }
1434    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1435    if (layer != NULL) {
1436        layer->setVisible(state);
1437        _needsRedraw = true;
1438    } else {
1439        TRACE("Image layer not found: %s", name);
1440    }
1441}
1442
1443void Renderer::addElevationLayer(const char *name,
1444                                 osgEarth::TileSourceOptions& opts,
1445                                 bool enableCache,
1446                                 bool visible,
1447                                 unsigned int minLOD, unsigned int maxLOD)
1448{
1449    if (!_map.valid()) {
1450        ERROR("No map");
1451        return;
1452    }
1453    TRACE("layer: %s", name);
1454    if (!opts.tileSize().isSet()) {
1455        opts.tileSize() = 17;
1456    }
1457    osgEarth::ElevationLayerOptions layerOpts(name, opts);
1458    if (!enableCache) {
1459        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1460    }
1461    if (!visible) {
1462        layerOpts.visible() = false;
1463    }
1464    layerOpts.minLevel() = minLOD;
1465    layerOpts.maxLevel() = maxLOD;
1466    // XXX: GDAL does not report vertical datum, it should be specified here
1467    // Common options: geodetic (default), egm96, egm84, egm2008
1468    //layerOpts.verticalDatum() = "egm96";
1469    _map->addElevationLayer(new osgEarth::ElevationLayer(layerOpts));
1470    _needsRedraw = true;
1471}
1472
1473void Renderer::removeElevationLayer(const char *name)
1474{
1475    if (!_map.valid()) {
1476        ERROR("No map");
1477        return;
1478    }
1479    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1480    if (layer != NULL) {
1481        _map->removeElevationLayer(layer);
1482        _needsRedraw = true;
1483    } else {
1484        TRACE("Elevation layer not found: %s", name);
1485    }
1486}
1487
1488void Renderer::moveElevationLayer(const char *name, unsigned int pos)
1489{
1490    if (!_map.valid()) {
1491        ERROR("No map");
1492        return;
1493    }
1494    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1495    if (layer != NULL) {
1496        _map->moveElevationLayer(layer, pos);
1497        _needsRedraw = true;
1498    } else {
1499        TRACE("Elevation layer not found: %s", name);
1500    }
1501}
1502
1503void Renderer::setElevationLayerVisibility(const char *name, bool state)
1504{
1505    if (!_map.valid()) {
1506        ERROR("No map");
1507        return;
1508    }
1509    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1510    if (layer != NULL) {
1511        layer->setVisible(state);
1512        _needsRedraw = true;
1513    } else {
1514        TRACE("Elevation layer not found: %s", name);
1515    }
1516}
1517
1518void Renderer::initAnnotations()
1519{
1520    if (!_annotations.valid()) {
1521        _annotations = new osg::Group();
1522        _annotations->setName("Annotations");
1523        _sceneRoot->addChild(_annotations.get());
1524        _placeNodes = new osg::Group();
1525        _placeNodes->setName("Place Nodes");
1526        osgEarth::Decluttering::setEnabled(_placeNodes->getOrCreateStateSet(), true);
1527        _annotations->addChild(_placeNodes.get());
1528        if (_picker.valid()) {
1529            _picker->addChild(_placeNodes.get());
1530        }
1531    }
1532}
1533
1534void Renderer::setSelectMode(SelectMode mode)
1535{
1536    _selectMode = mode;
1537    switch (_selectMode) {
1538    case SELECT_OFF: {
1539#ifdef USE_RTT_PICKER
1540        if (_picker.valid()) {
1541            _viewer->removeEventHandler(_picker.get());
1542            _picker = NULL;
1543        }
1544#endif
1545    }
1546        break;
1547    case SELECT_ON: {
1548#ifdef USE_RTT_PICKER
1549        if (!_picker.valid()) {
1550            _picker = new osgEarth::Util::RTTPicker;
1551            _picker->addChild(_mapNode);
1552            osg::ref_ptr<SelectCallback> callback = new SelectCallback(this);
1553            _picker->setDefaultCallback(callback.get());
1554            _viewer->addEventHandler(_picker.get());
1555        }
1556#endif
1557    }
1558        break;
1559    default:
1560        ERROR("Unknown select mode");
1561    }
1562}
1563
1564void Renderer::setPlacardConfig(const Placard& placardConf, const char *layerName)
1565{
1566    osgEarth::ModelLayer *layer = _map->getModelLayerByName(layerName);
1567    if (layer == NULL) {
1568        ERROR("Unknown layer '%s'", layerName);
1569        return;
1570    }
1571    //PlacardNode *node;
1572    //node->setConfig(placardConf);
1573    _placardConfigs[layerName] = placardConf;
1574}
1575
1576void Renderer::selectFeatures(std::vector<unsigned long>& featureIDs, const char *layerName)
1577{
1578    TRACE("Select layer '%s', num features: %u", layerName, featureIDs.size());
1579    clearSelection();
1580    if (featureIDs.size() == 0) {
1581        // clear selection
1582        return;
1583    }
1584    osgEarth::ModelLayer *layer = _map->getModelLayerByName(layerName);
1585    if (layer == NULL) {
1586        ERROR("Unknown layer '%s'", layerName);
1587        return;
1588    }
1589#ifdef WANT_TRACE
1590    for (unsigned int i = 0; i < featureIDs.size(); i++) {
1591        TRACE("feature ID: %u", featureIDs.at(i));
1592    }
1593#endif
1594    unsigned long fid = featureIDs.at(0);
1595
1596    osgEarth::ModelSource *modelSource = layer->getModelSource();
1597    osgEarth::Features::FeatureModelSource *fms = dynamic_cast<osgEarth::Features::FeatureModelSource *>(modelSource);
1598    if (fms != NULL) {
1599        osgEarth::Features::FeatureSource *fs = fms->getFeatureSource();
1600        FindFeatureSourceIndexNodeVisitor visitor;
1601        visitor.source = fs;
1602        _sceneRoot->accept(visitor);
1603        TRACE("Num FeatureSourceIndexNodes found: %lu", visitor.nodes.size());
1604        for (size_t i = 0; i < visitor.nodes.size(); i++) {
1605            osgEarth::Features::FeatureIndex *index = visitor.nodes[i]->getIndex();
1606#if OSGEARTH_MIN_VERSION_REQUIRED(3, 0, 0)
1607            osgEarth::ObjectID id = index->getObjectID(fid);
1608#if 0
1609            osgEarth::Features::Feature *feature = index->getFeature(id);
1610            if (feature) {
1611                // Find feature centroid
1612                osgEarth::GeoPoint location;
1613                if (feature->getGeometry()) {
1614                    osg::BoundingSphered bound;
1615                    feature->getWorldBound(getMapSRS(), bound);
1616                    location.set(getMapSRS(), bound.center(),
1617                                 osgEarth::ALTMODE_ABSOLUTE);
1618                }
1619                addPlacard(location, feature, layerName);
1620            }
1621#endif
1622            setHighlightByObjectID(id);
1623            TRACE("FID %lu = OID %d", fid, id);
1624#endif
1625        }
1626        _needsRedraw = true;
1627    }
1628}
1629
1630void Renderer::addPlacard(const osgEarth::GeoPoint& location,
1631                          osgEarth::Features::Feature *feature,
1632                          const char *layerName)
1633{
1634    if (feature == NULL) return;
1635
1636    const osgEarth::Features::AttributeTable &attrs = feature->getAttrs();
1637    Placard placard = getPlacardConfig(layerName);
1638    if (placard.getNumEntries() == 0) {
1639        placard.addAllAttributes(attrs);
1640    }
1641    PlacardNode *label =
1642        new PlacardNode(_mapNode.get(), location, placard, attrs);
1643    label->getOrCreateStateSet()->setRenderBinDetails(INT_MAX, "RenderBin");
1644    getAnnotations()->addChild(label);
1645}
1646
1647void Renderer::clearSelection()
1648{
1649    clearHighlight();
1650    clearSelectionAnnotationNodes();
1651    clearBoxSelection();
1652    _needsRedraw = true;
1653}
1654
1655/**
1656 * \brief Remove annotation nodes created during a pick/selection
1657 *
1658 * The primary purpose of this method is to take down the feature placard
1659 */
1660void Renderer::clearSelectionAnnotationNodes()
1661{
1662    osg::Group *nodes = getAnnotations();
1663    std::vector<osg::Node *> toRemove;
1664    for (unsigned int i = 0; i < nodes->getNumChildren(); i++) {
1665        osg::Node *node = nodes->getChild(i);
1666        // This can be Placard, PlacardLabel, Label, Place or Track Node
1667        if (dynamic_cast<osgEarth::Annotation::OrthoNode *>(node) != NULL) {
1668            toRemove.push_back(node);
1669        }
1670    }
1671    for (std::vector<osg::Node *>::iterator itr = toRemove.begin();
1672         itr != toRemove.end(); ++itr) {
1673        nodes->removeChild(*itr);
1674    }
1675    _needsRedraw = true;
1676}
1677
1678void Renderer::initBoxSelection(int x, int y)
1679{
1680    double latitude, longitude;
1681    if (!mouseToLatLong(x, y, &latitude, &longitude)) {
1682        return;
1683    }
1684    _anchorLat = latitude;
1685    _anchorLong = longitude;
1686    addRhumbBox(latitude, latitude, longitude, longitude);
1687}
1688
1689void Renderer::updateBoxSelection(int x, int y)
1690{
1691    double nlat, nlong;
1692    if (!mouseToLatLong(x, y, &nlat, &nlong)) {
1693        return;
1694    }
1695    double latMin, latMax, longMin, longMax;
1696    if (nlong >= _anchorLong && nlat >= _anchorLat) {
1697        // +x +y
1698        longMin = _anchorLong;
1699        latMin = _anchorLat;
1700        longMax = nlong;
1701        latMax = nlat;
1702    } else if (nlong < _anchorLong && nlat >= _anchorLat) {
1703        // -x +y
1704        longMin = nlong;
1705        latMin = _anchorLat;
1706        longMax = _anchorLong;
1707        latMax = nlat;
1708    } else if (nlong < _anchorLong && nlat < _anchorLat) {
1709        // -x -y
1710        longMin = nlong;
1711        latMin = nlat;
1712        longMax = _anchorLong;
1713        latMax = _anchorLat;
1714    } else {
1715        // +x -y
1716        longMin = _anchorLong;
1717        latMin = nlat;
1718        longMax = nlong;
1719        latMax = _anchorLat;
1720    }
1721    osgEarth::Annotation::FeatureNode *node = _selectionBox.get();
1722    osgEarth::Symbology::Geometry *geom = node->getFeature()->getGeometry();
1723    (*geom)[0] = osg::Vec3d(longMax, latMin, 0);
1724    (*geom)[1] = osg::Vec3d(longMin, latMin, 0);
1725    (*geom)[2] = osg::Vec3d(longMin, latMax, 0);
1726    (*geom)[3] = osg::Vec3d(longMax, latMax, 0);
1727    node->init();
1728    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = _selected.begin();
1729         itr != _selected.end(); ++itr) {
1730        (*itr)->clearDecoration();
1731    }
1732    _selected.clear();
1733    SelectPlaceNodesVisitor spnv(this, latMin, latMax, longMin, longMax);
1734    _sceneRoot->accept(spnv);
1735    _needsRedraw = true;
1736}
1737
1738void SelectPlaceNodesVisitor::apply(osg::Node& node)
1739{
1740    osgEarth::Annotation::PlaceNode *placeNode =
1741        dynamic_cast<osgEarth::Annotation::PlaceNode*>(&node);
1742    if (placeNode != NULL) {
1743        const osgEarth::SpatialReference *outSRS =
1744            _renderer->getMapSRS()->getGeographicSRS();
1745        osgEarth::GeoPoint pt = placeNode->getPosition();
1746        pt = pt.transform(outSRS);
1747        if (pt.x() >= _longMin && pt.x() <= _longMax &&
1748            pt.y() >= _latMin && pt.y() <= _latMax) {
1749            if (_renderer->select(placeNode)) {
1750                TRACE("Select PlaceNode: %g %g n:'%s' t:'%s'",
1751                     pt.x(), pt.y(), placeNode->getName().c_str(),
1752                     placeNode->getText().c_str());
1753                placeNode->setDecoration("select");
1754            }
1755        }
1756    }
1757    traverse(node);
1758}
1759
1760void Renderer::clearBoxSelection()
1761{
1762    if (_annotations.valid() && _selectionBox.valid()) {
1763        _annotations->removeChild(_selectionBox.get());
1764        _selectionBox = NULL;
1765    }
1766    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = _selected.begin();
1767         itr != _selected.end(); ++itr) {
1768        (*itr)->clearDecoration();
1769    }
1770    _selected.clear();
1771    _needsRedraw = true;
1772}
1773
1774void Renderer::addRhumbBox(double latMin, double latMax,
1775                           double longMin, double longMax)
1776{
1777    if (!_mapNode.valid()) {
1778        ERROR("No map node");
1779        return;
1780    }
1781    initAnnotations();
1782
1783    if (_selectionBox.valid()) {
1784        osgEarth::Symbology::Geometry *geom = _selectionBox->getFeature()->getGeometry();
1785        (*geom)[0] = osg::Vec3d(longMax, latMin, 0);
1786        (*geom)[1] = osg::Vec3d(longMin, latMin, 0);
1787        (*geom)[2] = osg::Vec3d(longMin, latMax, 0);
1788        (*geom)[3] = osg::Vec3d(longMax, latMax, 0);
1789        _selectionBox->init();
1790    } else {
1791        const osgEarth::SpatialReference* geoSRS = _mapNode->getMapSRS()->getGeographicSRS();
1792        osgEarth::Symbology::Geometry *geom = new osgEarth::Symbology::Polygon();
1793        geom->push_back(osg::Vec3d(longMax, latMin, 0));
1794        geom->push_back(osg::Vec3d(longMin, latMin, 0));
1795        geom->push_back(osg::Vec3d(longMin, latMax, 0));
1796        geom->push_back(osg::Vec3d(longMax, latMax, 0));
1797        osgEarth::Symbology::Style boxStyle;
1798#if 1
1799        osgEarth::Symbology::PolygonSymbol *poly = boxStyle.getOrCreate<osgEarth::Symbology::PolygonSymbol>();
1800        poly->fill()->color() = osgEarth::Symbology::Color::Cyan;
1801        poly->fill()->color().a() = 0.5;
1802#else
1803        osgEarth::Symbology::LineSymbol *line = boxStyle.getOrCreate<osgEarth::Symbology::LineSymbol>();
1804        line->stroke()->color() = osgEarth::Symbology::Color::Yellow;
1805        line->stroke()->width() = 2.0f;
1806        //line->creaseAngle() = 45.0f;
1807        line->tessellation() = 10;
1808#endif
1809        osgEarth::Symbology::AltitudeSymbol *alt = boxStyle.getOrCreate<osgEarth::Symbology::AltitudeSymbol>();
1810        alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
1811        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
1812        alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
1813        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_SCENE;
1814#if 0
1815        osgEarth::Symbology::RenderSymbol* rs = boxStyle.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1816        rs->depthOffset()->enabled() = true;
1817        rs->depthOffset()->minBias() = 1000;
1818#endif
1819        osgEarth::Features::Feature *feature = new osgEarth::Features::Feature(geom, geoSRS, boxStyle);
1820        //feature->geoInterp() = osgEarth::GEOINTERP_GREAT_CIRCLE;
1821        feature->geoInterp() = osgEarth::GEOINTERP_RHUMB_LINE;
1822        _selectionBox =
1823            new osgEarth::Annotation::FeatureNode(_mapNode, feature);
1824        _annotations->addChild(_selectionBox.get());
1825    }
1826
1827    _needsRedraw = true;
1828}
1829
1830void Renderer::addPlaceNode(double latitude, double longitude, char *labelText)
1831{
1832    if (!_mapNode.valid()) {
1833        ERROR("No map node");
1834        return;
1835    }
1836    initAnnotations();
1837
1838    const osgEarth::SpatialReference *geoSRS = _mapNode->getMapSRS()->getGeographicSRS();
1839
1840    osgEarth::Symbology::Style pin;
1841    pin.getOrCreate<osgEarth::Symbology::IconSymbol>()->url()->setLiteral(getPinIcon());
1842    osgEarth::Annotation::AnnotationNode *anno =
1843        new osgEarth::Annotation::PlaceNode(_mapNode, osgEarth::GeoPoint(geoSRS, longitude, latitude), labelText, pin);
1844    anno->setName(labelText);
1845    _placeNodes->addChild(anno);
1846#ifdef USE_RTT_PICKER
1847    osgEarth::Registry::objectIndex()->tagNode(anno, anno);
1848#else
1849    osgEarth::Annotation::DecorationInstaller
1850        highlightInstaller("select", new osgEarth::Annotation::HighlightDecoration(osg::Vec4f(1,1,0,0.5)));
1851    _placeNodes->accept(highlightInstaller);
1852
1853    // scale labels when hovering:
1854    osgEarth::Annotation::DecorationInstaller
1855        scaleInstaller("hover", new osgEarth::Annotation::ScaleDecoration(1.1f));
1856    _placeNodes->accept(scaleInstaller);
1857#endif
1858#if 0
1859    writeScene("/tmp/test.osg");
1860#endif
1861    _needsRedraw = true;
1862}
1863#if 0
1864void Renderer::hoverFeatureNodes(int x, int y, bool invertY)
1865{
1866    if (!_mapNode.valid()) {
1867        ERROR("No map node");
1868        return;
1869    }
1870    osgEarth::IntersectionPicker picker(_viewer.get(), _mapNode.get());
1871    osgEarth::IntersectionPicker::Hits hits;
1872    float mouseX = (float)x;
1873    float mouseY = (float)y;
1874    if (invertY) {
1875        mouseY = ((float)_windowHeight - mouseY);
1876    }
1877    std::set<osgEarth::Annotation::FeatureNode*> toUnHover;
1878    for (std::set<osgEarth::Annotation::FeatureNode*>::iterator itr = _hovered.begin();
1879         itr != _hovered.end(); ++itr) {
1880        toUnHover.insert(*itr);
1881    }
1882    if (picker.pick(mouseX, mouseY, hits)) {
1883        TRACE("Picker hits: %d", hits.size());
1884        for (osgEarth::IntersectionPicker::Hits::const_iterator hitr = hits.begin();
1885             hitr != hits.end(); ++hitr) {
1886            osgEarth::Annotation::FeatureNode *anno =
1887                picker.getNode<osgEarth::Annotation::FeatureNode>(*hitr);
1888            if (anno != NULL) {
1889                TRACE("Hit FeatureNode: %p", anno);
1890                if (_hovered.find(anno) == _hovered.end()) {
1891                    _hovered.insert(anno);
1892                    anno->setDecoration("hover");
1893                    _needsRedraw = true;
1894                }
1895                toUnHover.erase(anno);
1896            }
1897        }
1898#if 0
1899        writeScene("/tmp/test.osgt");
1900#endif
1901    }
1902    for (std::set<osgEarth::Annotation::FeatureNode *>::iterator itr = toUnHover.begin();
1903         itr != toUnHover.end(); ++itr) {
1904        _hovered.erase(*itr);
1905        (*itr)->clearDecoration();
1906        _needsRedraw = true;
1907    }
1908}
1909#endif
1910
1911#ifdef USE_RTT_PICKER
1912void Renderer::hoverPlaceNode(int x, int y, bool invertY)
1913{
1914    if (!_placeNodes.valid()) {
1915        TRACE("No place nodes");
1916        return;
1917    }
1918    return;
1919
1920    float mouseX = (float)x;
1921    float mouseY = (float)y;
1922    if (invertY) {
1923        mouseY = ((float)_windowHeight - mouseY);
1924    }
1925    if (_picker->pick(_viewer.get(), mouseX, mouseY)) {
1926        TRACE("Hover pick queued: %g %g", mouseX, mouseY);
1927    } else {
1928        TRACE("Failed to queue pick: %g %g", mouseX, mouseY);
1929    }
1930}
1931
1932void Renderer::deletePlaceNode(int x, int y, bool invertY)
1933{
1934    if (!_placeNodes.valid()) {
1935        TRACE("No place nodes");
1936        return;
1937    }
1938    return;
1939
1940    osg::ref_ptr<osgEarth::Util::RTTPicker> picker = new osgEarth::Util::RTTPicker;
1941    picker->addChild(_placeNodes.get());
1942    osg::ref_ptr<DeleteCallback> callback = new DeleteCallback(this);
1943    float mouseX = (float)x;
1944    float mouseY = (float)y;
1945    if (invertY) {
1946        mouseY = ((float)_windowHeight - mouseY);
1947    }
1948    if (picker->pick(_viewer.get(), mouseX, mouseY, callback)) {
1949        TRACE("Delete pick queued: %g %g", mouseX, mouseY);
1950    }
1951}
1952#else
1953void Renderer::hoverPlaceNode(int x, int y, bool invertY)
1954{
1955    if (!_placeNodes.valid()) {
1956        TRACE("No place nodes");
1957        return;
1958    }
1959
1960    osgEarth::IntersectionPicker picker(_viewer.get(), _placeNodes.get());
1961    osgEarth::IntersectionPicker::Hits hits;
1962    float mouseX = (float)x;
1963    float mouseY = (float)y;
1964    if (invertY) {
1965        mouseY = ((float)_windowHeight - mouseY);
1966    }
1967    std::set<osgEarth::Annotation::AnnotationNode*> toUnHover;
1968    for (std::set<osgEarth::Annotation::AnnotationNode*>::iterator itr = _hovered.begin();
1969         itr != _hovered.end(); ++itr) {
1970        toUnHover.insert(*itr);
1971    }
1972    if (picker.pick(mouseX, mouseY, hits)) {
1973        TRACE("Picker hits: %d", hits.size());
1974        for (osgEarth::IntersectionPicker::Hits::const_iterator hitr = hits.begin();
1975             hitr != hits.end(); ++hitr) {
1976            TRACE("Hit: node %p drawable %p idx %d", picker.getNode<osg::Node>(*hitr), hitr->drawable.get(), hitr->primitiveIndex);
1977            osgEarth::Annotation::AnnotationNode *anno =
1978                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
1979            if (anno != NULL && anno->getDecoration().empty()) {
1980                TRACE("Hit AnnotationNode: %p", anno);
1981                if (_hovered.find(anno) == _hovered.end()) {
1982                    _hovered.insert(anno);
1983                    anno->setDecoration("hover");
1984                    _needsRedraw = true;
1985                }
1986                toUnHover.erase(anno);
1987            }
1988        }
1989#if 0
1990        writeScene("/tmp/test.osgt");
1991#endif
1992    }
1993    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = toUnHover.begin();
1994         itr != toUnHover.end(); ++itr) {
1995        _hovered.erase(*itr);
1996        (*itr)->clearDecoration();
1997        _needsRedraw = true;
1998    }
1999}
2000
2001void Renderer::deletePlaceNode(int x, int y, bool invertY)
2002{
2003    if (!_placeNodes.valid()) {
2004        TRACE("No place nodes");
2005        return;
2006    }
2007    osgEarth::IntersectionPicker picker(_viewer.get(), _placeNodes.get());
2008    osgEarth::IntersectionPicker::Hits hits;
2009    float mouseX = (float)x;
2010    float mouseY = (float)y;
2011    if (invertY) {
2012        mouseY = ((float)_windowHeight - mouseY);
2013    }
2014    if (picker.pick(mouseX, mouseY, hits)) {
2015        TRACE("Picker hit!");
2016        // prevent multiple hits on the same instance
2017        std::set<osgEarth::Annotation::AnnotationNode *> fired;
2018        for (osgEarth::IntersectionPicker::Hits::const_iterator hitr = hits.begin();
2019             hitr != hits.end(); ++hitr) {
2020            osgEarth::Annotation::AnnotationNode *anno =
2021                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
2022            if (anno != NULL && fired.find(anno) == fired.end()) {
2023                fired.insert(anno);
2024                _needsRedraw = true;
2025            }
2026        }
2027        for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = fired.begin();
2028             itr != fired.end(); ++itr) {
2029            (*itr)->clearDecoration();
2030            _placeNodes->removeChild(*itr);
2031            if (_hovered.find(*itr) != _hovered.end()) {
2032                _hovered.erase(*itr);
2033            }
2034        }
2035    } else {
2036        TRACE("NO Picker hits");
2037    }
2038#if 0
2039    writeScene("/tmp/test.osg");
2040#endif
2041}
2042#endif
2043
2044void Renderer::addModelLayer(const char *name,
2045                             osgEarth::ModelSourceOptions& opts,
2046                             bool enableCache,
2047                             bool lighting,
2048                             bool visible)
2049{
2050    if (!_map.valid()) {
2051        ERROR("No map");
2052        return;
2053    }
2054    TRACE("layer: %s", name);
2055    osgEarth::ModelLayerOptions layerOpts(name, opts);
2056    if (!enableCache) {
2057        TRACE("Disabling cache for layer %s", name);
2058        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
2059    }
2060    if (!visible) {
2061        layerOpts.visible() = false;
2062    }
2063    layerOpts.lightingEnabled() = lighting;
2064    _map->addModelLayer(new osgEarth::ModelLayer(layerOpts));
2065    _needsRedraw = true;
2066}
2067
2068void Renderer::removeModelLayer(const char *name)
2069{
2070    if (!_map.valid()) {
2071        ERROR("No map");
2072        return;
2073    }
2074    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2075    if (layer != NULL) {
2076        _map->removeModelLayer(layer);
2077        _needsRedraw = true;
2078    } else {
2079        TRACE("Model layer not found: %s", name);
2080    }
2081}
2082
2083void Renderer::moveModelLayer(const char *name, unsigned int pos)
2084{
2085    if (!_map.valid()) {
2086        ERROR("No map");
2087        return;
2088    }
2089    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2090    if (layer != NULL) {
2091        _map->moveModelLayer(layer, pos);
2092        _needsRedraw = true;
2093    } else {
2094        TRACE("Model layer not found: %s", name);
2095    }
2096}
2097
2098void Renderer::setModelLayerOpacity(const char *name, double opacity)
2099{
2100    if (!_map.valid()) {
2101        ERROR("No map");
2102        return;
2103    }
2104    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2105    if (layer != NULL) {
2106        layer->setOpacity(opacity);
2107        _needsRedraw = true;
2108    } else {
2109        TRACE("Model layer not found: %s", name);
2110    }
2111}
2112#if 0
2113void Renderer::setModelLayerVisibleRange(const char *name, float min, float max)
2114{
2115    if (!_map.valid()) {
2116        ERROR("No map");
2117        return;
2118    }
2119    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2120    if (layer != NULL) {
2121        layer->minVisibleRange(min);
2122        layer->maxVisibleRange(max);
2123        _needsRedraw = true;
2124    } else {
2125        TRACE("Model layer not found: %s", name);
2126    }
2127}
2128#endif
2129void Renderer::setModelLayerVisibility(const char *name, bool state)
2130{
2131    if (!_map.valid()) {
2132        ERROR("No map");
2133        return;
2134    }
2135    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2136    if (layer != NULL) {
2137        layer->setVisible(state);
2138        _needsRedraw = true;
2139    } else {
2140        TRACE("Model layer not found: %s", name);
2141    }
2142}
2143
2144/**
2145 * \brief Resize the render window (image size for renderings)
2146 */
2147void Renderer::setWindowSize(int width, int height)
2148{
2149    if (_windowWidth == width &&
2150        _windowHeight == height) {
2151        TRACE("No change");
2152        return;
2153    }
2154
2155    TRACE("Setting window size to %dx%d", width, height);
2156
2157    double origBitrate = getMaximumBitrate();
2158
2159    _windowWidth = width;
2160    _windowHeight = height;
2161
2162    setMaximumBitrate(origBitrate);
2163    TRACE("Bandwidth target: %.2f Mbps", (float)(getMaximumBitrate()/1.0e6));
2164    TRACE("Frame rate target: %.2f Hz", (float)getMaximumFrameRateInHertz());
2165
2166    if (_viewer.valid()) {
2167#ifdef USE_OFFSCREEN_RENDERING
2168#ifdef USE_PBUFFER
2169        osg::ref_ptr<osg::GraphicsContext> pbuffer;
2170        osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
2171        traits->x = 0;
2172        traits->y = 0;
2173        traits->width = _windowWidth;
2174        traits->height = _windowHeight;
2175        traits->red = 8;
2176        traits->green = 8;
2177        traits->blue = 8;
2178        traits->alpha = 8;
2179        traits->windowDecoration = false;
2180        traits->pbuffer = true;
2181        traits->doubleBuffer = true;
2182        traits->sharedContext = 0;
2183
2184        pbuffer = osg::GraphicsContext::createGraphicsContext(traits.get());
2185        if (pbuffer.valid()) {
2186            TRACE("Pixel buffer has been created successfully.");
2187        } else {
2188            ERROR("Pixel buffer has not been created successfully.");
2189        }
2190        osg::Camera *camera = new osg::Camera;
2191        camera->setGraphicsContext(pbuffer.get());
2192        //camera->getOrCreateStateSet()->setGlobalDefaults();
2193        camera->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
2194        GLenum buffer = pbuffer->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
2195        camera->setDrawBuffer(buffer);
2196        camera->setReadBuffer(buffer);
2197        camera->setFinalDrawCallback(_captureCallback.get());
2198        _viewer->addSlave(camera, osg::Matrixd(), osg::Matrixd());
2199        _viewer->realize();
2200#else
2201        if (_captureCallback.valid()) {
2202            _captureCallback->getTexture()->setTextureSize(_windowWidth, _windowHeight);
2203        }
2204        osgViewer::ViewerBase::Windows windows;
2205        _viewer->getWindows(windows);
2206        if (windows.size() == 1) {
2207            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
2208        } else {
2209            ERROR("Num windows: %lu", windows.size());
2210        }
2211#endif
2212#else
2213        osgViewer::ViewerBase::Windows windows;
2214        _viewer->getWindows(windows);
2215#if 1
2216        for (osgViewer::Viewer::Windows::iterator itr = windows.begin();
2217             itr != windows.end(); ++itr) {
2218            osgViewer::GraphicsWindow *window = *itr;
2219            window->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
2220            //window->grabFocusIfPointerInWindow();
2221        }
2222        //_viewer->getCamera()->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
2223#else
2224        if (windows.size() == 1) {
2225            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
2226        } else {
2227            ERROR("Num windows: %lu", windows.size());
2228        }
2229#endif
2230#endif
2231        // HACK: Without this, the mouse coordinate mapping uses the old size
2232        // for 1 frame.
2233        assert(_viewer->getEventQueue() != NULL);
2234        //TRACE("Window EventQueue: %p", getEventQueue());
2235        //TRACE("Viewer EventQueue: %p", _viewer->getEventQueue());
2236        _viewer->getEventQueue()->windowResize(0, 0, _windowWidth, _windowHeight);
2237        _needsRedraw = true;
2238    }
2239}
2240
2241/**
2242 * \brief Set the orientation of the camera from a quaternion
2243 * rotation
2244 *
2245 * \param[in] quat A quaternion with scalar part first: w,x,y,z
2246 * \param[in] absolute Is rotation absolute or relative?
2247 */
2248void Renderer::setCameraOrientation(const double quat[4], bool absolute)
2249{
2250    if (_manipulator.valid()) {
2251        _manipulator->setRotation(osg::Quat(quat[1], quat[2], quat[3], quat[0]));
2252        _needsRedraw = true;
2253    }
2254}
2255
2256/**
2257 * \brief Reset pan, zoom, clipping planes and optionally rotation
2258 *
2259 * \param[in] resetOrientation Reset the camera rotation/orientation also
2260 */
2261void Renderer::resetCamera(bool resetOrientation)
2262{
2263    TRACE("Enter: resetOrientation=%d", resetOrientation ? 1 : 0);
2264    if (_viewer.valid()) {
2265        _viewer->home();
2266        _needsRedraw = true;
2267    }
2268}
2269
2270/**
2271 * \brief Perform a 2D translation of the camera
2272 *
2273 * x,y pan amount are specified as signed absolute pan amount in viewport
2274 * units -- i.e. 0 is no pan, .5 is half the viewport, 2 is twice the viewport,
2275 * etc.
2276 *
2277 * \param[in] x Viewport coordinate horizontal panning (positive number pans
2278 * camera left, object right)
2279 * \param[in] y Viewport coordinate vertical panning (positive number pans
2280 * camera up, object down)
2281 */
2282void Renderer::panCamera(double x, double y)
2283{
2284    TRACE("Enter: %g %g", x, y);
2285
2286    if (_manipulator.valid()) {
2287        // Wants mouse delta x,y in normalized screen coords
2288        _manipulator->pan(x, y);
2289        _needsRedraw = true;
2290    }
2291}
2292
2293void Renderer::rotateCamera(double x, double y)
2294{
2295    TRACE("Enter: %g %g", x, y);
2296
2297    if (_manipulator.valid()) {
2298        _manipulator->rotate(x, y);
2299        _needsRedraw = true;
2300    }
2301}
2302
2303/**
2304 * \brief Dolly camera or set orthographic scaling based on camera type
2305 *
2306 * \param[in] y Mouse y coordinate in normalized screen coords
2307 */
2308void Renderer::zoomCamera(double y)
2309{
2310    TRACE("Enter: y: %g", y);
2311
2312    if (_manipulator.valid()) {
2313        // +y = zoom out, -y = zoom in
2314        TRACE("camDist: %g", _manipulator->getDistance());
2315#ifdef LIMIT_ZOOM_BY_MAP_SCALE
2316        if ((_mapScale < 0.0 && y > 0.0) ||
2317            (_mapScale < 0.1 && y < 0.0) ||
2318            (_mapScale > 40000.0 && y > 0.0))
2319            return;
2320#endif
2321#if 1
2322       _manipulator->zoom(0, y);
2323#else
2324        double dist = _manipulator->getDistance();
2325        dist *= (1.0 + y);
2326        _manipulator->setDistance(dist);
2327#endif
2328        _needsRedraw = true;
2329    }
2330}
2331
2332bool Renderer::getImageLayerExtent(const char *name, osgEarth::GeoExtent &ext)
2333{
2334    if (!_map.valid()) {
2335        ERROR("No map");
2336        return false;
2337    }
2338    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
2339    if (layer != NULL) {
2340        osgEarth::TileSource *ts = layer->getTileSource();
2341        if (ts != NULL) {
2342            ext = ts->getDataExtentsUnion();
2343            if (!ext.isValid() && ts->getProfile() != NULL) {
2344                ext = ts->getProfile()->getExtent();
2345            }
2346            TRACE("Image layer %s srs: %s extent: %g %g %g %g",
2347                 name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
2348                 ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
2349            return true;
2350        }
2351    } else {
2352        TRACE("Image layer not found: %s", name);
2353    }
2354    return false;
2355}
2356
2357bool Renderer::getElevationLayerExtent(const char *name, osgEarth::GeoExtent &ext)
2358{
2359    if (!_map.valid()) {
2360        ERROR("No map");
2361        return false;
2362    }
2363    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
2364    if (layer != NULL) {
2365        osgEarth::TileSource *ts = layer->getTileSource();
2366        if (ts != NULL) {
2367            ext = ts->getDataExtentsUnion();
2368            if (!ext.isValid() && ts->getProfile() != NULL) {
2369                ext = ts->getProfile()->getExtent();
2370            }
2371            TRACE("Elevation Layer %s srs: %s extent: %g %g %g %g",
2372                 name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
2373                 ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
2374            return true;
2375        }
2376    } else {
2377        TRACE("Elevation layer not found: %s", name);
2378    }
2379    return false;
2380}
2381
2382bool Renderer::getModelLayerExtent(const char *name, osgEarth::GeoExtent &ext)
2383{
2384    if (!_map.valid()) {
2385        ERROR("No map");
2386        return false;
2387    }
2388    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2389    if (layer != NULL) {
2390        osgEarth::ModelSource *ms = layer->getModelSource();
2391        if (ms != NULL) {
2392            osgEarth::DataExtentList& dataExtents = ms->getDataExtents();
2393            if (dataExtents.size() > 0) {
2394                ext = dataExtents[0];
2395                for (unsigned int i = 1; i < dataExtents.size(); i++) {
2396                    ext.expandToInclude(dataExtents[i]);
2397                }
2398            }
2399            if (!ext.isValid()) {
2400                ERROR("Couldn't find extent of layer %s", name);
2401                return false;
2402            }
2403            TRACE("Model Layer %s srs: %s extent: %g %g %g %g",
2404                 name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
2405                 ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
2406            return true;
2407        }
2408    } else {
2409        TRACE("Model layer not found: %s", name);
2410    }
2411    return false;
2412}
2413
2414/**
2415 * \brief Set view to fit a bounding rectangle
2416 *
2417 * A camera distance is chosen such that the extent rectangle
2418 * will fit in the vertical field of view.
2419 *
2420 * \param ext input GeoExtent
2421 * \param durationSecs Animation duration for the view change.
2422 */
2423void Renderer::setViewpointFromExtent(const osgEarth::GeoExtent& ext,
2424                                      double durationSecs)
2425{
2426    setViewpointFromRect(ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax(),
2427                         ext.getSRS(), durationSecs);
2428}
2429
2430/**
2431 * \brief Set view to fit a bounding rectangle
2432 *
2433 * A camera distance is chosen such that the extent rectangle
2434 * will fit in the vertical field of view.
2435 *
2436 * \param xmin Minimum X coord in the given SRS
2437 * \param ymin Minimum Y coord in the given SRS
2438 * \param xmax Maximum X coord in the given SRS
2439 * \param ymax Maximum Y coord in the given SRS
2440 * \param srs Optional SRS of bounding points.  If omitted, wgs84 is assumed.
2441 * \param durationSecs Animation duration for the view change.
2442 */
2443void Renderer::setViewpointFromRect(double xmin, double ymin,
2444                                    double xmax, double ymax,
2445                                    const osgEarth::SpatialReference *srs,
2446                                    double durationSecs)
2447{
2448    if (!_viewer.valid() || !_manipulator.valid() || getMapSRS() == NULL)
2449        return;
2450
2451    double x = 0.0, y = 0.0, distance = 0.0;
2452    double x1 = xmin, y1 = ymin, x2 = xmax, y2 = ymax;
2453    osgEarth::Units distanceUnits = getMapSRS()->getUnits();
2454    if (getMapSRS()->isProjected() && !getMapSRS()->isPlateCarre()) {
2455        TRACE("Projected");
2456        osg::ref_ptr<const osgEarth::SpatialReference> fromSRS =
2457            srs ? srs : osgEarth::SpatialReference::create("wgs84");
2458        fromSRS->transformExtentToMBR(getMapSRS(), x1, y1, x2, y2);
2459        double height = y2 - y1;
2460        double fovy, aspect, near, far;
2461        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2462        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2463        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2464        x = x1 + (x2 - x1)/2.0;
2465        y = y1 + (y2 - y1)/2.0;
2466        TRACE("x: %g y: %g, dist: %g", x, y, distance);
2467    } else if (getMapSRS()->isGeographic() && !getMapSRS()->isPlateCarre()) {
2468        // World coords are ECEF
2469        TRACE("Geocentric");
2470        osg::ref_ptr<const osgEarth::SpatialReference> fromSRS =
2471            srs ? srs : osgEarth::SpatialReference::create("wgs84");
2472        fromSRS->transformExtentToMBR(getMapSRS(), x1, y1, x2, y2);
2473        TRACE("(%g, %g, %g, %g)", x1, y1, x2, y2);
2474        // These are angular units
2475        x = x1 + (x2 - x1)/2.0;
2476        y = y1 + (y2 - y1)/2.0;
2477        // These will be ECEF
2478        osg::Vec3d world1, world2, world3, world4;
2479        // bottom
2480        getMapSRS()->transformToWorld(osg::Vec3d(x, y1, 0), world1);
2481        // top
2482        getMapSRS()->transformToWorld(osg::Vec3d(x, y2, 0), world2);
2483        // Focal point on surface
2484        getMapSRS()->transformToWorld(osg::Vec3d(x, y, 0), world3);
2485        // point on line between top and bottom points
2486        world4 = world1 + (world2 - world1)/2.0;
2487        TRACE("world1: %g,%g,%g world2: %g,%g,%g",
2488             world1.x(), world1.y(), world1.z(),
2489             world2.x(), world2.y(), world2.z());
2490        TRACE("world3: %g,%g,%g world4: %g,%g,%g",
2491             world3.x(), world3.y(), world3.z(),
2492             world4.x(), world4.y(), world4.z());
2493        double height = (world2 - world1).length();
2494        double fovy, aspect, near, far;
2495        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2496        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2497        distance -= (world4 - world3).length();
2498        if (distance < 0.0) distance = 0.0;
2499        distanceUnits = osgEarth::Units::METERS;
2500        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2501        TRACE("x: %g y: %g, dist: %g", x, y, distance);
2502    } else {
2503        assert(getMapSRS()->isPlateCarre());
2504        ERROR("Plate Carree not supported");
2505        return;
2506    }
2507    TRACE("Map units: %d", getMapSRS()->getUnits().getType());
2508    osgEarth::Viewpoint vpt;
2509    vpt.focalPoint()->set(getMapSRS(), x, y, 0.0, osgEarth::ALTMODE_ABSOLUTE);
2510    vpt.range()->set(distance, distanceUnits);
2511    vpt.heading()->set(0.0, osgEarth::Units::DEGREES);
2512    vpt.pitch()->set(-90.0, osgEarth::Units::DEGREES);
2513
2514    _manipulator->setViewpoint(vpt, durationSecs);
2515    _needsRedraw = true;
2516}
2517
2518double Renderer::getMaxDistanceFromExtent(const osgEarth::GeoExtent& extent)
2519{
2520    if (!_viewer.valid() || extent.getSRS() == NULL)
2521        return 2.0e7;
2522
2523    double x = 0.0, y = 0.0, distance = 0.0;
2524    double x1 = extent.xMin(), y1 = extent.yMin(), x2 = extent.xMax(), y2 = extent.yMax();
2525    osgEarth::Units distanceUnits = extent.getSRS()->getUnits();
2526    if (extent.getSRS()->isProjected() && !extent.getSRS()->isPlateCarre()) {
2527        TRACE("Projected");
2528        double height = y2 - y1;
2529        double fovy, aspect, near, far;
2530        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2531        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2532        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2533        x = x1 + (x2 - x1)/2.0;
2534        y = y1 + (y2 - y1)/2.0;
2535        TRACE("x: %g y: %g, dist: %g", x, y, distance);
2536    } else if (extent.getSRS()->isGeographic() && !extent.getSRS()->isPlateCarre()) {
2537        // World coords are ECEF
2538        TRACE("Geocentric");
2539        TRACE("(%g, %g, %g, %g)", x1, y1, x2, y2);
2540        // These are angular units
2541        x = x1 + (x2 - x1)/2.0;
2542        y = y1 + (y2 - y1)/2.0;
2543        // These will be ECEF
2544        osg::Vec3d world1, world2, world3, world4;
2545        // bottom
2546        getMapSRS()->transformToWorld(osg::Vec3d(x, y1, 0), world1);
2547        // top
2548        getMapSRS()->transformToWorld(osg::Vec3d(x, y2, 0), world2);
2549        // Focal point on surface
2550        getMapSRS()->transformToWorld(osg::Vec3d(x, y, 0), world3);
2551        // point on line between top and bottom points
2552        world4 = world1 + (world2 - world1)/2.0;
2553        double height = (world2 - world1).length();
2554        double fovy, aspect, near, far;
2555        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2556        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2557        distance -= (world4 - world3).length();
2558        if (distance < 0.0) distance = 0.0;
2559        distanceUnits = osgEarth::Units::METERS;
2560        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2561        TRACE("x: %g y: %g, dist: %g", x, y, distance);
2562    } else {
2563        assert(extent.getSRS()->isPlateCarre());
2564        ERROR("Plate Carree not supported");
2565    }
2566
2567    return distance;
2568}
2569
2570/**
2571 * \brief Dolly camera to set distance from focal point
2572 *
2573 * \param[in] dist distance in world coordinate units (typically meters)
2574 */
2575void Renderer::setCameraDistance(double dist)
2576{
2577    TRACE("Enter: dist: %g", dist);
2578
2579    if (_manipulator.valid()) {
2580        TRACE("camDist: %g", _manipulator->getDistance());
2581
2582        _manipulator->setDistance(dist);
2583
2584        _needsRedraw = true;
2585    }
2586}
2587
2588void Renderer::keyPress(int key)
2589{
2590    osgGA::EventQueue *queue = getEventQueue();
2591    if (queue != NULL) {
2592        queue->keyPress(key);
2593        _needsRedraw = true;
2594    }
2595}
2596
2597void Renderer::keyRelease(int key)
2598{
2599    osgGA::EventQueue *queue = getEventQueue();
2600    if (queue != NULL) {
2601        queue->keyRelease(key);
2602        _needsRedraw = true;
2603    }
2604}
2605
2606void Renderer::setThrowingEnabled(bool state)
2607{
2608    if (_manipulator.valid()) {
2609        _manipulator->getSettings()->setThrowingEnabled(state);
2610    }
2611}
2612
2613void Renderer::mouseDoubleClick(int button, double x, double y)
2614{
2615    osgGA::EventQueue *queue = getEventQueue();
2616    if (queue != NULL) {
2617        queue->mouseDoubleButtonPress((float)x, (float)y, button);
2618        _needsRedraw = true;
2619    }
2620}
2621
2622void Renderer::mouseClick(int button, double x, double y)
2623{
2624    osgGA::EventQueue *queue = getEventQueue();
2625    if (queue != NULL) {
2626        queue->mouseButtonPress((float)x, (float)y, button);
2627        _needsRedraw = true;
2628    }
2629}
2630
2631void Renderer::mouseDrag(int button, double x, double y)
2632{
2633    osgGA::EventQueue *queue = getEventQueue();
2634    if (queue != NULL) {
2635        queue->mouseMotion((float)x, (float)y);
2636        _needsRedraw = true;
2637    }
2638}
2639
2640void Renderer::mouseRelease(int button, double x, double y)
2641{
2642    osgGA::EventQueue *queue = getEventQueue();
2643    if (queue != NULL) {
2644        queue->mouseButtonRelease((float)x, (float)y, button);
2645        _needsRedraw = true;
2646    }
2647}
2648
2649void Renderer::mouseMotion(double x, double y)
2650{
2651#if 1
2652    osgGA::EventQueue *queue = getEventQueue();
2653    if (queue != NULL) {
2654        queue->mouseMotion((float)x, (float)y);
2655        _needsRedraw = true;
2656    }
2657#else
2658    if (_mouseCoordsTool.valid()) {
2659        if (_viewer.valid() && _coordsCallback.valid()) {
2660            osgEarth::GeoPoint mapPt;
2661            if (mapMouseCoords((float)x, (float)y, mapPt)) {
2662                _coordsCallback->set(mapPt, _viewer->asView(), _mapNode);
2663            } else {
2664                _coordsCallback->reset(_viewer->asView(), _mapNode);
2665            }
2666            _needsRedraw = true;
2667        }
2668    }
2669#endif
2670}
2671
2672void Renderer::mouseScroll(int direction)
2673{
2674    osgGA::EventQueue *queue = getEventQueue();
2675    if (queue != NULL) {
2676        queue->mouseScroll((direction > 0 ? osgGA::GUIEventAdapter::SCROLL_UP : osgGA::GUIEventAdapter::SCROLL_DOWN));
2677        _needsRedraw = true;
2678    }
2679}
2680
2681/**
2682 * \brief Set the RGB background color to render into the image
2683 */
2684void Renderer::setBackgroundColor(float color[3])
2685{
2686    _bgColor[0] = color[0];
2687    _bgColor[1] = color[1];
2688    _bgColor[2] = color[2];
2689
2690    if (_viewer.valid()) {
2691        _viewer->getCamera()->setClearColor(osg::Vec4(color[0], color[1], color[2], 1));
2692
2693        _needsRedraw = true;
2694    }
2695}
2696
2697/**
2698 * \brief Sets flag to trigger rendering next time render() is called
2699 */
2700void Renderer::eventuallyRender()
2701{
2702    _needsRedraw = true;
2703}
2704
2705/**
2706 * \brief Get a timeout in usecs for select()
2707 *
2708 * If the paging thread is idle, returns <0 indicating that the
2709 * select call can block until data is available.  Otherwise,
2710 * if the frame render time was faster than the target frame
2711 * rate, return the remaining frame time.
2712 */
2713long Renderer::getTimeout()
2714{
2715    if (!checkNeedToDoFrame())
2716        // <0 means no timeout, block until socket has data
2717        return -1L;
2718    if (_lastFrameTime < _minFrameTime) {
2719        return (long)1.0e6*(_minFrameTime - _lastFrameTime);
2720    } else {
2721        // No timeout (poll)
2722        return 0L;
2723    }
2724}
2725
2726/**
2727 * \brief Check if paging thread is quiescent
2728 */
2729bool Renderer::isPagerIdle()
2730{
2731    if (!_viewer.valid())
2732        return true;
2733    else
2734        return (!_viewer->getDatabasePager()->requiresUpdateSceneGraph() &&
2735                !_viewer->getDatabasePager()->getRequestsInProgress());
2736}
2737
2738/**
2739 * \brief Check is frame call is necessary to render and/or update
2740 * in response to events or timed actions
2741 */
2742bool Renderer::checkNeedToDoFrame()
2743{
2744    return (_needsRedraw || _pickPending ||
2745            (_viewer.valid() && _viewer->checkNeedToDoFrame()));
2746}
2747
2748/**
2749 * \brief MapNode event phase
2750 *
2751 * This is called by the MapNode's event callback during the event
2752 * traversal of the viewer
2753 */
2754void Renderer::mapNodeUpdate()
2755{
2756    computeMapScale();
2757}
2758
2759void Renderer::markFrameStart()
2760{
2761    _startFrameTime = osg::Timer::instance()->tick();
2762}
2763
2764void Renderer::markFrameEnd()
2765{
2766    osg::Timer_t endFrameTick = osg::Timer::instance()->tick();
2767    _lastFrameTime = osg::Timer::instance()->delta_s(_startFrameTime, endFrameTick);
2768    if (_lastFrameTime > _minFrameTime) {
2769        FRAME("BROKE FRAME by %.2f msec", (_lastFrameTime - _minFrameTime)*1000.0f);
2770    } else {
2771        FRAME("Frame time: %.2f msec", _lastFrameTime*1000.0f);
2772    }
2773#ifdef USE_THROTTLING_SLEEP
2774    if (_lastFrameTime < _minFrameTime) {
2775        FRAME("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
2776        OpenThreads::Thread::microSleep(static_cast<unsigned int>(1000000.0*(_minFrameTime - _lastFrameTime)));
2777    }
2778#endif
2779}
2780
2781/**
2782 * \brief Cause the rendering to render a new image if needed
2783 *
2784 * The _needsRedraw flag indicates if a state change has occured since
2785 * the last rendered frame
2786 */
2787bool Renderer::render()
2788{
2789    if (_viewer.valid() && checkNeedToDoFrame()) {
2790        FRAME("Enter needsRedraw=%d",  _needsRedraw ? 1 : 0);
2791        _renderStartTime = osg::Timer::instance()->tick();
2792        FRAME("Before frame()");
2793        _viewer->frame();
2794        FRAME("After frame()");
2795        _renderStopTime = osg::Timer::instance()->tick();
2796        _renderTime = osg::Timer::instance()->delta_s(_renderStartTime, _renderStopTime);
2797        FRAME("Render time: %g msec", _renderTime * 1000.0);
2798#ifndef SLEEP_AFTER_QUEUE_FRAME
2799        _lastFrameTime = _renderTime;
2800#ifdef USE_THROTTLING_SLEEP
2801        if (_lastFrameTime < _minFrameTime) {
2802            FRAME("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
2803            OpenThreads::Thread::microSleep(static_cast<unsigned int>(1.0e6*(_minFrameTime - _lastFrameTime)));
2804        }
2805#endif
2806#endif
2807#ifdef WANT_FRAME
2808        if (_viewer->getViewerStats() != NULL) {
2809            _viewer->getViewerStats()->report(std::cerr, _viewer->getViewerStats()->getLatestFrameNumber());
2810        }
2811#endif
2812        _needsRedraw = false;
2813        return true;
2814    } else {
2815        _renderStartTime = _renderStopTime = osg::Timer::instance()->tick();
2816        _renderTime = 0;
2817        return false;
2818    }
2819}
2820
2821/**
2822 * \brief Read back the rendered framebuffer image
2823 */
2824osg::Image *Renderer::getRenderedFrame()
2825{
2826    if (_captureCallback.valid())
2827        return _captureCallback->getImage();
2828    else
2829        return NULL;
2830}
2831
2832void Renderer::setScaleBar(bool state)
2833{
2834    if (_scaleLabel.valid()) {
2835        _scaleLabel->setVisible(state);
2836    }
2837    if (_scaleBar.valid()) {
2838        _scaleBar->setVisible(state);
2839    }
2840    _needsRedraw = true;
2841}
2842
2843void Renderer::setScaleBarUnits(ScaleBarUnits units)
2844{
2845    _scaleBarUnits = units;
2846    _needsRedraw = true;
2847}
2848
2849/**
2850 * \brief Compute the scale ratio of the map based on a horizontal center line
2851 *
2852 * The idea here is to take 2 screen points on a horizontal line in the center
2853 * of the screen and convert to lat/long.  The lat/long coordinates are then
2854 * used to compute the great circle distance (assuming spherical earth) between
2855 * the points.
2856 *
2857 * We could use local projected map coordinates for the distance computation,
2858 * which would be faster; however, this would not show e.g. the change in
2859 * scale at different latitudes
2860 */
2861double Renderer::computeMapScale()
2862{
2863    if (!_scaleLabel.valid() || !_scaleLabel->visible()) {
2864        return -1.0;
2865    }
2866    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
2867        ERROR("No map");
2868        return -1.0;
2869    }
2870    if (!_viewer.valid()) {
2871        ERROR("No viewer");
2872        return -1.0;
2873    }
2874
2875    double x, y;
2876    double pixelWidth = _windowWidth * 0.1 * 2.0;
2877    if (pixelWidth < 10)
2878        pixelWidth = 10;
2879    if (pixelWidth > 150)
2880        pixelWidth = 150;
2881    x = (double)(_windowWidth -1)/2.0 - pixelWidth / 2.0;
2882    y = (double)(_windowHeight-1)/2.0;
2883
2884    osg::Vec3d world1, world2;
2885    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world1)) {
2886        // off map
2887        TRACE("Off map coords: %g %g", x, y);
2888        _scaleLabel->setText("");
2889        _scaleBar->setWidth(0);
2890        return -1.0;
2891    }
2892    x += pixelWidth;
2893    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world2)) {
2894        // off map
2895        TRACE("Off map coords: %g %g", x, y);
2896        _scaleLabel->setText("");
2897        _scaleBar->setWidth(0);
2898        return -1.0;
2899    }
2900
2901#if 0
2902    TRACE("w1: %g %g %g w2: %g %g %g",
2903          world1.x(), world1.y(), world1.z(),
2904          world2.x(), world2.y(), world2.z());
2905#endif
2906
2907    double meters;
2908    double radius = 6378137.0;
2909    if (_mapNode->getMapSRS() &&
2910        _mapNode->getMapSRS()->getEllipsoid()) {
2911        radius = _mapNode->getMapSRS()->getEllipsoid()->getRadiusEquator();
2912    }
2913    if (!_map->isGeocentric() &&
2914        _mapNode->getMapSRS() &&
2915        _mapNode->getMapSRS()->isGeographic()) {
2916        TRACE("Map is geographic");
2917        // World cords are already lat/long
2918        // Compute great circle distance
2919        meters =
2920            osgEarth::GeoMath::distance(world1, world2, _mapNode->getMapSRS());
2921    } else if (_mapNode->getMapSRS()) {
2922        // Get map coords in lat/long
2923        osgEarth::GeoPoint mapPoint1, mapPoint2;
2924        mapPoint1.fromWorld(_mapNode->getMapSRS(), world1);
2925        mapPoint1.makeGeographic();
2926        mapPoint2.fromWorld(_mapNode->getMapSRS(), world2);
2927        mapPoint2.makeGeographic();
2928        // Compute great circle distance
2929        meters =
2930            osgEarth::GeoMath::distance(osg::DegreesToRadians(mapPoint1.y()),
2931                                        osg::DegreesToRadians(mapPoint1.x()),
2932                                        osg::DegreesToRadians(mapPoint2.y()),
2933                                        osg::DegreesToRadians(mapPoint2.x()),
2934                                        radius);
2935    } else {
2936        // Assume geocentric?
2937        ERROR("No map SRS");
2938        _scaleLabel->setText("");
2939        _scaleBar->setWidth(0);
2940        return -1.0;
2941    }
2942
2943    double scale = meters / pixelWidth;
2944    // 1mi = 5280 feet
2945    //double scaleMiles = scale / 1609.344; // International mile = 1609.344m
2946    //double scaleNauticalMiles = scale / 1852.0; // nautical mile = 1852m
2947    //double scaleUSSurveyMiles = scale / 1609.347218694; // US survey mile = 5280 US survey feet
2948    //double scaleUSSurveyFeet = scale * 3937.0/1200.0; // US survey foot = 1200/3937 m
2949#if 0
2950    TRACE("m: %g px: %g m/px: %g", meters, pixelWidth, scale);
2951#endif
2952    _mapScale = scale;
2953    switch (_scaleBarUnits) {
2954    case UNITS_NAUTICAL_MILES: {
2955        double nmi = meters / 1852.0;
2956        scale = nmi / pixelWidth;
2957        nmi = normalizeScaleNauticalMiles(nmi);
2958        pixelWidth = nmi / scale;
2959        if (_scaleLabel.valid()) {
2960            _scaleLabel->setText(osgEarth::Stringify()
2961                                 << nmi
2962                                 << " nmi");
2963        }
2964    }
2965        break;
2966    case UNITS_US_SURVEY_FEET: {
2967        double feet = meters * 3937.0/1200.0;
2968        scale = feet / pixelWidth;
2969        feet = normalizeScaleFeet(feet);
2970        pixelWidth = feet / scale;
2971        if (_scaleLabel.valid()) {
2972            if (feet >= 5280) {
2973                _scaleLabel->setText(osgEarth::Stringify()
2974                                     << feet / 5280.0
2975                                     << " miUS");
2976             } else {
2977                _scaleLabel->setText(osgEarth::Stringify()
2978                                     << feet
2979                                     << " ftUS");
2980            }
2981        }
2982    }
2983        break;
2984    case UNITS_INTL_FEET: {
2985        double feet = 5280.0 * meters / 1609.344;
2986        scale = feet / pixelWidth;
2987        feet = normalizeScaleFeet(feet);
2988        pixelWidth = feet / scale;
2989        if (_scaleLabel.valid()) {
2990            if (feet >= 5280) {
2991                _scaleLabel->setText(osgEarth::Stringify()
2992                                     << feet / 5280.0
2993                                     << " mi");
2994            } else {
2995                _scaleLabel->setText(osgEarth::Stringify()
2996                                     << feet
2997                                     << " ft");
2998            }
2999        }
3000    }
3001        break;
3002    case UNITS_METERS:
3003    default: {
3004        meters = normalizeScaleMeters(meters);
3005        pixelWidth = meters / scale;
3006        if (_scaleLabel.valid()) {
3007            if (meters >= 1000) {
3008                _scaleLabel->setText(osgEarth::Stringify()
3009                                     << meters / 1000.0
3010                                     << " km");
3011            } else {
3012                _scaleLabel->setText(osgEarth::Stringify()
3013                                     << meters
3014                                     << " m");
3015            }
3016        }
3017    }
3018        break;
3019    }
3020    if (_scaleBar.valid()) {
3021        _scaleBar->setWidth(pixelWidth);
3022    }
3023    return scale;
3024}
3025
3026std::string Renderer::getCanonicalPath(const std::string& url) const
3027{
3028    std::string retStr;
3029    std::string proto = osgDB::getServerProtocol(url);
3030    if (proto.empty()) {
3031        retStr = osgDB::getRealPath(url);
3032        if (!osgDB::fileExists(retStr)) {
3033            if (!url.empty() && url[0] != '/') {
3034                // Relative URL, assume local://
3035                std::ostringstream oss;
3036                oss << getCacheDirectory() << "/" << url;
3037                retStr = oss.str();
3038            } else {
3039                retStr = "";
3040            }
3041        }
3042    } else if (proto == "local") {
3043        TRACE("Local protocol: '%s'", getLocalFilePath(url).c_str());
3044        std::ostringstream oss;
3045        oss << getCacheDirectory() << "/" << getLocalFilePath(url);
3046        retStr = oss.str();
3047    } else if (proto == "file") {
3048        TRACE("File: '/%s'", osgDB::getServerFileName(url).c_str());
3049        std::ostringstream oss;
3050        oss << "/" <<  osgDB::getServerFileName(url);
3051        retStr = oss.str();
3052    } else if (proto == "idata") {
3053        std::string fileName = osgDB::getServerFileName(url);
3054        TRACE("IData protocol: coll: '%s', '%s'", osgDB::getServerAddress(url).c_str(), fileName.c_str());
3055        int collection = atoi(osgDB::getServerAddress(url).c_str());
3056        {
3057            std::ostringstream oss;
3058            oss << getCacheDirectory() << "/" << fileName;
3059            retStr = oss.str();
3060            std::string dirPath = osgDB::getFilePath(retStr);
3061            osgDB::makeDirectory(dirPath);
3062        }
3063        if (!osgDB::fileExists(retStr)) {
3064            std::ostringstream oss;
3065            oss << "//" << fileName;
3066            IData::Buffer buf;
3067            IData::getFile(collection, oss.str().c_str(), &buf);
3068            std::ofstream file(retStr.c_str());
3069            file.write((char *)buf.data, buf.size);
3070            file.close();
3071        }
3072    } else {
3073        TRACE("Protocol: '%s' url: '%s'", proto.c_str(), url.c_str());
3074        retStr = url;
3075    }
3076    return retStr;
3077}
3078
3079void Renderer::writeScene(const std::string& file)
3080{
3081    if (_sceneRoot.valid()) {
3082        GraphPrintVisitor gpv;
3083        _sceneRoot->accept(gpv);
3084        //osgDB::writeNodeFile(*_sceneRoot.get(), file);
3085    }
3086}
Note: See TracBrowser for help on using the repository browser.