source: geovis/trunk/Renderer.cpp @ 4790

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

More selection testing

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