source: geovis/trunk/Renderer.cpp @ 4973

Last change on this file since 4973 was 4973, checked in by ldelgass, 6 years ago

First pass at protocol to set ephemeris time (currently uses server local time)

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