source: geovis/trunk/Renderer.cpp @ 5049

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

Add protocol to set map attribution/copyright

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