source: geovis/trunk/Renderer.cpp @ 4645

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

Initial take on a selection box

File size: 71.1 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
1076/**
1077 * \brief Map screen mouse coordinates to map coordinates
1078 *
1079 * This method assumes that mouse Y coordinates are 0 at the top
1080 * of the screen and increase going down if invertY is set, and
1081 * 0 at the bottom and increase going up otherwise.
1082 */
1083bool Renderer::mapMouseCoords(float mouseX, float mouseY,
1084                              osgEarth::GeoPoint& map, bool invertY)
1085{
1086    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
1087        ERROR("No map");
1088        return false;
1089    }
1090    if (!_viewer.valid()) {
1091        ERROR("No viewer");
1092        return false;
1093    }
1094    if (invertY) {
1095        mouseY = ((float)_windowHeight - mouseY);
1096    }
1097    osg::Vec3d world;
1098    if (_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), mouseX, mouseY, world)) {
1099        map.fromWorld(getMapSRS(), world);
1100        return true;
1101    }
1102    return false;
1103}
1104
1105bool Renderer::addImageLayer(const char *name,
1106                             osgEarth::TileSourceOptions& opts,
1107                             bool makeShared,
1108                             bool visible)
1109{
1110    if (!_map.valid()) {
1111        ERROR("No map");
1112        return false;
1113    }
1114    TRACE("layer: %s", name);
1115    if (!opts.tileSize().isSet()) {
1116        opts.tileSize() = 256;
1117    }
1118    osgEarth::ImageLayerOptions layerOpts(name, opts);
1119#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1)
1120    layerOpts.textureCompression() = osg::Texture::USE_IMAGE_DATA_FORMAT;
1121#endif
1122    if (makeShared) {
1123        layerOpts.shared() = true;
1124    }
1125    if (!visible) {
1126        layerOpts.visible() = false;
1127    }
1128    osg::ref_ptr<osgEarth::ImageLayer> layer = new osgEarth::ImageLayer(layerOpts);
1129    _map->addImageLayer(layer.get());
1130    if (layer->getTileSource() == NULL || !layer->getTileSource()->isOK()) {
1131        ERROR("Failed to add image layer: %s", name);
1132        _map->removeImageLayer(layer.get());
1133        return false;
1134    }
1135    _needsRedraw = true;
1136    return true;
1137}
1138
1139void Renderer::addColorFilter(const char *name,
1140                              const char *shader)
1141{
1142    if (!_map.valid()) {
1143        ERROR("No map");
1144        return;
1145    }
1146    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1147    if (layer == NULL) {
1148        TRACE("Image layer not found: %s", name);
1149        return;
1150    }
1151    osgEarth::Util::GLSLColorFilter *filter = new osgEarth::Util::GLSLColorFilter;
1152    filter->setCode(shader);
1153    //filter->setCode("color.rgb = color.r > 0.5 ? vec3(1.0) : vec3(0.0);");
1154    layer->addColorFilter(filter);
1155    _needsRedraw = true;
1156}
1157
1158void Renderer::removeColorFilter(const char *name, int idx)
1159{
1160    if (!_map.valid()) {
1161        ERROR("No map");
1162        return;
1163    }
1164    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1165    if (layer == NULL) {
1166        TRACE("Image layer not found: %s", name);
1167        return;
1168    }
1169    if (idx < 0) {
1170        while (!layer->getColorFilters().empty()) {
1171            layer->removeColorFilter(layer->getColorFilters()[0]);
1172        }
1173    } else {
1174        layer->removeColorFilter(layer->getColorFilters().at(idx));
1175    }
1176    _needsRedraw = true;
1177}
1178
1179void Renderer::removeImageLayer(const char *name)
1180{
1181    if (!_map.valid()) {
1182        ERROR("No map");
1183        return;
1184    }
1185    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1186    if (layer != NULL) {
1187        _map->removeImageLayer(layer);
1188        _needsRedraw = true;
1189    } else {
1190        TRACE("Image layer not found: %s", name);
1191    }
1192}
1193
1194void Renderer::moveImageLayer(const char *name, unsigned int pos)
1195{
1196    if (!_map.valid()) {
1197        ERROR("No map");
1198        return;
1199    }
1200    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1201    if (layer != NULL) {
1202        _map->moveImageLayer(layer, pos);
1203        _needsRedraw = true;
1204    } else {
1205        TRACE("Image layer not found: %s", name);
1206    }
1207}
1208
1209void Renderer::setImageLayerOpacity(const char *name, double opacity)
1210{
1211    if (!_map.valid()) {
1212        ERROR("No map");
1213        return;
1214    }
1215    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1216    if (layer != NULL) {
1217        layer->setOpacity(opacity);
1218        _needsRedraw = true;
1219    } else {
1220        TRACE("Image layer not found: %s", name);
1221    }
1222}
1223
1224void Renderer::setImageLayerVisibility(const char *name, bool state)
1225{
1226#if OSGEARTH_MIN_VERSION_REQUIRED(2, 4, 0)
1227    if (!_map.valid()) {
1228        ERROR("No map");
1229        return;
1230    }
1231    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1232    if (layer != NULL) {
1233        layer->setVisible(state);
1234        _needsRedraw = true;
1235    } else {
1236        TRACE("Image layer not found: %s", name);
1237    }
1238#endif
1239}
1240
1241void Renderer::addElevationLayer(const char *name,
1242                                 osgEarth::TileSourceOptions& opts)
1243{
1244    if (!_map.valid()) {
1245        ERROR("No map");
1246        return;
1247    }
1248    TRACE("layer: %s", name);
1249    if (!opts.tileSize().isSet()) {
1250        opts.tileSize() = 15;
1251    }
1252    osgEarth::ElevationLayerOptions layerOpts(name, opts);
1253    // XXX: GDAL does not report vertical datum, it should be specified here
1254    // Common options: geodetic (default), egm96, egm84, egm2008
1255    //layerOpts.verticalDatum() = "egm96";
1256    _map->addElevationLayer(new osgEarth::ElevationLayer(layerOpts));
1257    _needsRedraw = true;
1258}
1259
1260void Renderer::removeElevationLayer(const char *name)
1261{
1262    if (!_map.valid()) {
1263        ERROR("No map");
1264        return;
1265    }
1266    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1267    if (layer != NULL) {
1268        _map->removeElevationLayer(layer);
1269        _needsRedraw = true;
1270    } else {
1271        TRACE("Elevation layer not found: %s", name);
1272    }
1273}
1274
1275void Renderer::moveElevationLayer(const char *name, unsigned int pos)
1276{
1277    if (!_map.valid()) {
1278        ERROR("No map");
1279        return;
1280    }
1281    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1282    if (layer != NULL) {
1283        _map->moveElevationLayer(layer, pos);
1284        _needsRedraw = true;
1285    } else {
1286        TRACE("Elevation layer not found: %s", name);
1287    }
1288}
1289
1290void Renderer::setElevationLayerVisibility(const char *name, bool state)
1291{
1292#if OSGEARTH_MIN_VERSION_REQUIRED(2, 4, 0)
1293    if (!_map.valid()) {
1294        ERROR("No map");
1295        return;
1296    }
1297    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1298    if (layer != NULL) {
1299        layer->setVisible(state);
1300        _needsRedraw = true;
1301    } else {
1302        TRACE("Elevation layer not found: %s", name);
1303    }
1304#endif
1305}
1306
1307void Renderer::initAnnotations()
1308{
1309    if (!_annotations.valid()) {
1310        _annotations = new osg::Group();
1311        _annotations->setName("Annotations");
1312        _sceneRoot->addChild(_annotations.get());
1313        _placeNodes = new osg::Group();
1314        _placeNodes->setName("Place Nodes");
1315        osgEarth::Decluttering::setEnabled(_placeNodes->getOrCreateStateSet(), true);
1316        _annotations->addChild(_placeNodes.get());
1317    }
1318}
1319
1320void Renderer::initBoxSelection(int x, int y)
1321{
1322    double latitude, longitude;
1323    if (!mouseToLatLong(x, y, &latitude, &longitude)) {
1324        return;
1325    }
1326    _anchorLat = latitude;
1327    _anchorLong = longitude;
1328    addRhumbBox(latitude, latitude, longitude, longitude);
1329}
1330
1331void Renderer::updateBoxSelection(int x, int y)
1332{
1333    osgEarth::Annotation::FeatureNode *node = _selectionBox.get();
1334    double nlat, nlong;
1335    if (!mouseToLatLong(x, y, &nlat, &nlong)) {
1336        return;
1337    }
1338    double latMin, latMax, longMin, longMax;
1339    if (nlong >= _anchorLong && nlat >= _anchorLat) {
1340        // +x +y
1341        longMin = _anchorLong;
1342        latMin = _anchorLat;
1343        longMax = nlong;
1344        latMax = nlat;
1345    } else if (nlong < _anchorLong && nlat >= _anchorLat) {
1346        // -x +y
1347        longMin = nlong;
1348        latMin = _anchorLat;
1349        longMax = _anchorLong;
1350        latMax = nlat;
1351    } else if (nlong < _anchorLong && nlat < _anchorLat) {
1352        // -x -y
1353        longMin = nlong;
1354        latMin = nlat;
1355        longMax = _anchorLong;
1356        latMax = _anchorLat;
1357    } else {
1358        // +x -y
1359        longMin = _anchorLong;
1360        latMin = nlat;
1361        longMax = nlong;
1362        latMax = _anchorLat;
1363    }
1364    osgEarth::Symbology::Geometry *geom = node->getFeature()->getGeometry();
1365    (*geom)[0] = osg::Vec3d(longMax, latMin, 0);
1366    (*geom)[1] = osg::Vec3d(longMin, latMin, 0);
1367    (*geom)[2] = osg::Vec3d(longMin, latMax, 0);
1368    (*geom)[3] = osg::Vec3d(longMax, latMax, 0);
1369    node->init();
1370    _needsRedraw = true;
1371}
1372
1373void Renderer::clearBoxSelection()
1374{
1375    if (_annotations.valid() && _selectionBox.valid()) {
1376        _annotations->removeChild(_selectionBox.get());
1377        _selectionBox = NULL;
1378    }
1379    _needsRedraw = true;
1380}
1381
1382void Renderer::addRhumbBox(double latMin, double latMax,
1383                           double longMin, double longMax)
1384{
1385    if (!_mapNode.valid()) {
1386        ERROR("No map node");
1387        return;
1388    }
1389    initAnnotations();
1390
1391    if (_selectionBox.valid()) {
1392        osgEarth::Symbology::Geometry *geom = _selectionBox->getFeature()->getGeometry();
1393        (*geom)[0] = osg::Vec3d(longMax, latMin, 0);
1394        (*geom)[1] = osg::Vec3d(longMin, latMin, 0);
1395        (*geom)[2] = osg::Vec3d(longMin, latMax, 0);
1396        (*geom)[3] = osg::Vec3d(longMax, latMax, 0);
1397        _selectionBox->init();
1398    } else {
1399        const osgEarth::SpatialReference* geoSRS = _mapNode->getMapSRS()->getGeographicSRS();
1400        osgEarth::Symbology::Geometry *geom = new osgEarth::Symbology::Polygon();
1401        geom->push_back(osg::Vec3d(longMax, latMin, 0));
1402        geom->push_back(osg::Vec3d(longMin, latMin, 0));
1403        geom->push_back(osg::Vec3d(longMin, latMax, 0));
1404        geom->push_back(osg::Vec3d(longMax, latMax, 0));
1405        osgEarth::Symbology::Style boxStyle;
1406#if 1
1407        osgEarth::Symbology::PolygonSymbol *poly = boxStyle.getOrCreate<osgEarth::Symbology::PolygonSymbol>();
1408        poly->fill()->color() = osgEarth::Symbology::Color::Cyan;
1409        poly->fill()->color().a() = 0.5;
1410        //#else
1411        osgEarth::Symbology::LineSymbol *line = boxStyle.getOrCreate<osgEarth::Symbology::LineSymbol>();
1412        line->stroke()->color() = osgEarth::Symbology::Color::Yellow;
1413        line->stroke()->width() = 2.0f;
1414        //line->creaseAngle() = 45.0f;
1415        line->tessellation() = 10;
1416#endif
1417        osgEarth::Symbology::AltitudeSymbol *alt = boxStyle.getOrCreate<osgEarth::Symbology::AltitudeSymbol>();
1418        alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
1419        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
1420        alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
1421        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_SCENE;
1422#if 0
1423        osgEarth::Symbology::RenderSymbol* rs = boxStyle.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1424        rs->depthOffset()->enabled() = true;
1425        rs->depthOffset()->minBias() = 1000;
1426#endif
1427        osgEarth::Features::Feature *feature = new osgEarth::Features::Feature(geom, geoSRS, boxStyle);
1428        //feature->geoInterp() = osgEarth::GEOINTERP_GREAT_CIRCLE;
1429        feature->geoInterp() = osgEarth::GEOINTERP_RHUMB_LINE;
1430        _selectionBox =
1431            new osgEarth::Annotation::FeatureNode(_mapNode, feature);
1432        _annotations->addChild(_selectionBox.get());
1433    }
1434
1435    _needsRedraw = true;
1436}
1437
1438void Renderer::addPlaceNode(double latitude, double longitude, char *labelText)
1439{
1440    if (!_mapNode.valid()) {
1441        ERROR("No map node");
1442        return;
1443    }
1444    initAnnotations();
1445
1446    const osgEarth::SpatialReference* geoSRS = _mapNode->getMapSRS()->getGeographicSRS();
1447
1448    osgEarth::Symbology::Style pin;
1449    pin.getOrCreate<osgEarth::Symbology::IconSymbol>()->url()->setLiteral(getPinIcon());
1450    osgEarth::Annotation::AnnotationNode *anno =
1451        new osgEarth::Annotation::PlaceNode(_mapNode, osgEarth::GeoPoint(geoSRS, longitude, latitude), labelText, pin);
1452    _placeNodes->addChild(anno);
1453    osgEarth::Annotation::DecorationInstaller
1454        highlightInstaller("hover", new osgEarth::Annotation::HighlightDecoration());
1455    _annotations->accept(highlightInstaller);
1456    // scale labels when hovering:
1457    osgEarth::Annotation::DecorationInstaller
1458        scaleInstaller("hover", new osgEarth::Annotation::ScaleDecoration(1.1f));
1459    _placeNodes->accept(scaleInstaller);
1460#if 0
1461    writeScene("/tmp/test.osg");
1462#endif
1463    _needsRedraw = true;
1464}
1465
1466void Renderer::hoverPlaceNode(int x, int y, bool invertY)
1467{
1468    osgEarth::Picker picker(_viewer.get(), _placeNodes.get());
1469    osgEarth::Picker::Hits hits;
1470    float mouseX = (float)x;
1471    float mouseY = (float)y;
1472    if (invertY) {
1473        mouseY = ((float)_windowHeight - mouseY);
1474    }
1475    std::set<osgEarth::Annotation::AnnotationNode*> toUnHover;
1476    for (std::set<osgEarth::Annotation::AnnotationNode*>::iterator itr = _hovered.begin();
1477         itr != _hovered.end(); ++itr) {
1478        toUnHover.insert(*itr);
1479    }
1480    if (picker.pick(mouseX, mouseY, hits)) {
1481        TRACE("Picker hit!");
1482        for (osgEarth::Picker::Hits::const_iterator hitr = hits.begin();
1483             hitr != hits.end(); ++hitr) {
1484            osgEarth::Annotation::AnnotationNode *anno =
1485                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
1486            if (anno != NULL) {
1487                if (_hovered.find(anno) == _hovered.end()) {
1488                    _hovered.insert(anno);
1489                    anno->setDecoration("hover");
1490                    _needsRedraw = true;
1491                }
1492                toUnHover.erase(anno);
1493            }
1494        }
1495#if 0
1496        writeScene("/tmp/test.osgt");
1497#endif
1498    }
1499    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = toUnHover.begin();
1500         itr != toUnHover.end(); ++itr) {
1501        _hovered.erase(*itr);
1502        (*itr)->clearDecoration();
1503        _needsRedraw = true;
1504    }
1505}
1506
1507void Renderer::deletePlaceNode(int x, int y, bool invertY)
1508{
1509    osgEarth::Picker picker(_viewer.get(), _placeNodes.get());
1510    osgEarth::Picker::Hits hits;
1511    float mouseX = (float)x;
1512    float mouseY = (float)y;
1513    if (invertY) {
1514        mouseY = ((float)_windowHeight - mouseY);
1515    }
1516    if (picker.pick(mouseX, mouseY, hits)) {
1517        TRACE("Picker hit!");
1518        // prevent multiple hits on the same instance
1519        std::set<osgEarth::Annotation::AnnotationNode *> fired;
1520        for (osgEarth::Picker::Hits::const_iterator hitr = hits.begin();
1521             hitr != hits.end(); ++hitr) {
1522            osgEarth::Annotation::AnnotationNode *anno =
1523                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
1524            if (anno != NULL && fired.find(anno) == fired.end()) {
1525                fired.insert(anno);
1526                _needsRedraw = true;
1527            }
1528        }
1529        for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = fired.begin();
1530             itr != fired.end(); ++itr) {
1531            (*itr)->clearDecoration();
1532            _placeNodes->removeChild(*itr);
1533            if (_hovered.find(*itr) != _hovered.end()) {
1534                _hovered.erase(*itr);
1535            }
1536        }
1537    } else {
1538        TRACE("NO Picker hits");
1539    }
1540#if 0
1541    writeScene("/tmp/test.osg");
1542#endif
1543}
1544
1545void Renderer::addModelLayer(const char *name, osgEarth::ModelSourceOptions& opts)
1546{
1547    if (!_map.valid()) {
1548        ERROR("No map");
1549        return;
1550    }
1551    TRACE("layer: %s", name);
1552    osgEarth::ModelLayerOptions layerOpts(name, opts);
1553    _map->addModelLayer(new osgEarth::ModelLayer(layerOpts));
1554    _needsRedraw = true;
1555}
1556
1557void Renderer::removeModelLayer(const char *name)
1558{
1559    if (!_map.valid()) {
1560        ERROR("No map");
1561        return;
1562    }
1563    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
1564    if (layer != NULL) {
1565        _map->removeModelLayer(layer);
1566        _needsRedraw = true;
1567    } else {
1568        TRACE("Model layer not found: %s", name);
1569    }
1570}
1571
1572void Renderer::moveModelLayer(const char *name, unsigned int pos)
1573{
1574    if (!_map.valid()) {
1575        ERROR("No map");
1576        return;
1577    }
1578    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
1579    if (layer != NULL) {
1580        _map->moveModelLayer(layer, pos);
1581        _needsRedraw = true;
1582    } else {
1583        TRACE("Model layer not found: %s", name);
1584    }
1585}
1586
1587void Renderer::setModelLayerOpacity(const char *name, double opacity)
1588{
1589#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 0)
1590    if (!_map.valid()) {
1591        ERROR("No map");
1592        return;
1593    }
1594    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
1595    if (layer != NULL) {
1596        layer->setOpacity(opacity);
1597        _needsRedraw = true;
1598    } else {
1599        TRACE("Model layer not found: %s", name);
1600    }
1601#endif
1602}
1603
1604void Renderer::setModelLayerVisibility(const char *name, bool state)
1605{
1606#if OSGEARTH_MIN_VERSION_REQUIRED(2, 4, 0)
1607    if (!_map.valid()) {
1608        ERROR("No map");
1609        return;
1610    }
1611    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
1612    if (layer != NULL) {
1613        layer->setVisible(state);
1614        _needsRedraw = true;
1615    } else {
1616        TRACE("Model layer not found: %s", name);
1617    }
1618#endif
1619}
1620
1621/**
1622 * \brief Resize the render window (image size for renderings)
1623 */
1624void Renderer::setWindowSize(int width, int height)
1625{
1626    if (_windowWidth == width &&
1627        _windowHeight == height) {
1628        TRACE("No change");
1629        return;
1630    }
1631
1632    TRACE("Setting window size to %dx%d", width, height);
1633
1634    double origBitrate = getMaximumBitrate();
1635
1636    _windowWidth = width;
1637    _windowHeight = height;
1638
1639    setMaximumBitrate(origBitrate);
1640    TRACE("Bandwidth target: %.2f Mbps", (float)(getMaximumBitrate()/1.0e6));
1641    TRACE("Frame rate target: %.2f Hz", (float)getMaximumFrameRateInHertz());
1642
1643    if (_viewer.valid()) {
1644#ifdef USE_OFFSCREEN_RENDERING
1645#ifdef USE_PBUFFER
1646        osg::ref_ptr<osg::GraphicsContext> pbuffer;
1647        osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
1648        traits->x = 0;
1649        traits->y = 0;
1650        traits->width = _windowWidth;
1651        traits->height = _windowHeight;
1652        traits->red = 8;
1653        traits->green = 8;
1654        traits->blue = 8;
1655        traits->alpha = 8;
1656        traits->windowDecoration = false;
1657        traits->pbuffer = true;
1658        traits->doubleBuffer = true;
1659        traits->sharedContext = 0;
1660
1661        pbuffer = osg::GraphicsContext::createGraphicsContext(traits.get());
1662        if (pbuffer.valid()) {
1663            TRACE("Pixel buffer has been created successfully.");
1664        } else {
1665            ERROR("Pixel buffer has not been created successfully.");
1666        }
1667        osg::Camera *camera = new osg::Camera;
1668        camera->setGraphicsContext(pbuffer.get());
1669        camera->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
1670        GLenum buffer = pbuffer->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
1671        camera->setDrawBuffer(buffer);
1672        camera->setReadBuffer(buffer);
1673        camera->setFinalDrawCallback(_captureCallback.get());
1674        _viewer->addSlave(camera, osg::Matrixd(), osg::Matrixd());
1675        _viewer->realize();
1676#else
1677        if (_captureCallback.valid()) {
1678            _captureCallback->getTexture()->setTextureSize(_windowWidth, _windowHeight);
1679        }
1680        osgViewer::ViewerBase::Windows windows;
1681        _viewer->getWindows(windows);
1682        if (windows.size() == 1) {
1683            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
1684        } else {
1685            ERROR("Num windows: %lu", windows.size());
1686        }
1687#endif
1688#else
1689        osgViewer::ViewerBase::Windows windows;
1690        _viewer->getWindows(windows);
1691        if (windows.size() == 1) {
1692            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
1693        } else {
1694            ERROR("Num windows: %lu", windows.size());
1695        }
1696#endif
1697        // HACK: Without this, the mouse coordinate mapping uses the old size
1698        // for 1 frame.
1699        assert(_viewer->getEventQueue() != NULL);
1700        //TRACE("Window EventQueue: %p", getEventQueue());
1701        //TRACE("Viewer EventQueue: %p", _viewer->getEventQueue());
1702        _viewer->getEventQueue()->windowResize(0, 0, _windowWidth, _windowHeight);
1703        _needsRedraw = true;
1704    }
1705}
1706
1707/**
1708 * \brief Set the orientation of the camera from a quaternion
1709 * rotation
1710 *
1711 * \param[in] quat A quaternion with scalar part first: w,x,y,z
1712 * \param[in] absolute Is rotation absolute or relative?
1713 */
1714void Renderer::setCameraOrientation(const double quat[4], bool absolute)
1715{
1716    if (_manipulator.valid()) {
1717        _manipulator->setRotation(osg::Quat(quat[1], quat[2], quat[3], quat[0]));
1718        _needsRedraw = true;
1719    }
1720}
1721
1722/**
1723 * \brief Reset pan, zoom, clipping planes and optionally rotation
1724 *
1725 * \param[in] resetOrientation Reset the camera rotation/orientation also
1726 */
1727void Renderer::resetCamera(bool resetOrientation)
1728{
1729    TRACE("Enter: resetOrientation=%d", resetOrientation ? 1 : 0);
1730    if (_viewer.valid()) {
1731        _viewer->home();
1732        _needsRedraw = true;
1733    }
1734}
1735
1736/**
1737 * \brief Perform a 2D translation of the camera
1738 *
1739 * x,y pan amount are specified as signed absolute pan amount in viewport
1740 * units -- i.e. 0 is no pan, .5 is half the viewport, 2 is twice the viewport,
1741 * etc.
1742 *
1743 * \param[in] x Viewport coordinate horizontal panning (positive number pans
1744 * camera left, object right)
1745 * \param[in] y Viewport coordinate vertical panning (positive number pans
1746 * camera up, object down)
1747 */
1748void Renderer::panCamera(double x, double y)
1749{
1750    TRACE("Enter: %g %g", x, y);
1751
1752    if (_manipulator.valid()) {
1753        // Wants mouse delta x,y in normalized screen coords
1754        _manipulator->pan(x, y);
1755        _needsRedraw = true;
1756    }
1757}
1758
1759void Renderer::rotateCamera(double x, double y)
1760{
1761    TRACE("Enter: %g %g", x, y);
1762
1763    if (_manipulator.valid()) {
1764        _manipulator->rotate(x, y);
1765        _needsRedraw = true;
1766    }
1767}
1768
1769/**
1770 * \brief Dolly camera or set orthographic scaling based on camera type
1771 *
1772 * \param[in] y Mouse y coordinate in normalized screen coords
1773 */
1774void Renderer::zoomCamera(double y)
1775{
1776    TRACE("Enter: y: %g", y);
1777
1778    if (_manipulator.valid()) {
1779        // +y = zoom out, -y = zoom in
1780        TRACE("camDist: %g", _manipulator->getDistance());
1781        if ((_mapScale < 0.0 && y > 0.0) ||
1782            (_mapScale < 0.1 && y < 0.0) ||
1783            (_mapScale > 40000.0 && y > 0.0))
1784            return;
1785#if 1
1786       _manipulator->zoom(0, y);
1787#else
1788        double dist = _manipulator->getDistance();
1789        dist *= (1.0 + y);
1790        _manipulator->setDistance(dist);
1791#endif
1792        _needsRedraw = true;
1793    }
1794}
1795
1796/**
1797 * \brief Dolly camera to set distance from focal point
1798 *
1799 * \param[in] dist distance in map? coordinates
1800 */
1801void Renderer::setCameraDistance(double dist)
1802{
1803    TRACE("Enter: dist: %g", dist);
1804
1805    if (_manipulator.valid()) {
1806        TRACE("camDist: %g", _manipulator->getDistance());
1807
1808        _manipulator->setDistance(dist);
1809
1810        _needsRedraw = true;
1811    }
1812}
1813
1814void Renderer::keyPress(int key)
1815{
1816    osgGA::EventQueue *queue = getEventQueue();
1817    if (queue != NULL) {
1818        queue->keyPress(key);
1819        _needsRedraw = true;
1820    }
1821}
1822
1823void Renderer::keyRelease(int key)
1824{
1825    osgGA::EventQueue *queue = getEventQueue();
1826    if (queue != NULL) {
1827        queue->keyRelease(key);
1828        _needsRedraw = true;
1829    }
1830}
1831
1832void Renderer::setThrowingEnabled(bool state)
1833{
1834    if (_manipulator.valid()) {
1835        _manipulator->getSettings()->setThrowingEnabled(state);
1836    }
1837}
1838
1839void Renderer::mouseDoubleClick(int button, double x, double y)
1840{
1841    osgGA::EventQueue *queue = getEventQueue();
1842    if (queue != NULL) {
1843        queue->mouseDoubleButtonPress((float)x, (float)y, button);
1844        _needsRedraw = true;
1845    }
1846}
1847
1848void Renderer::mouseClick(int button, double x, double y)
1849{
1850    osgGA::EventQueue *queue = getEventQueue();
1851    if (queue != NULL) {
1852        queue->mouseButtonPress((float)x, (float)y, button);
1853        _needsRedraw = true;
1854    }
1855}
1856
1857void Renderer::mouseDrag(int button, double x, double y)
1858{
1859    osgGA::EventQueue *queue = getEventQueue();
1860    if (queue != NULL) {
1861        queue->mouseMotion((float)x, (float)y);
1862        _needsRedraw = true;
1863    }
1864}
1865
1866void Renderer::mouseRelease(int button, double x, double y)
1867{
1868    osgGA::EventQueue *queue = getEventQueue();
1869    if (queue != NULL) {
1870        queue->mouseButtonRelease((float)x, (float)y, button);
1871        _needsRedraw = true;
1872    }
1873}
1874
1875void Renderer::mouseMotion(double x, double y)
1876{
1877    if (_mouseCoordsTool.valid()) {
1878#if 1
1879        osgGA::EventQueue *queue = getEventQueue();
1880        if (queue != NULL) {
1881            queue->mouseMotion((float)x, (float)y);
1882            _needsRedraw = true;
1883        }
1884#else
1885        if (_viewer.valid() && _coordsCallback.valid()) {
1886            osgEarth::GeoPoint mapPt;
1887            if (mapMouseCoords((float)x, (float)y, mapPt)) {
1888                _coordsCallback->set(mapPt, _viewer->asView(), _mapNode);
1889            } else {
1890                _coordsCallback->reset(_viewer->asView(), _mapNode);
1891            }
1892            _needsRedraw = true;
1893        }
1894#endif
1895    }
1896}
1897
1898void Renderer::mouseScroll(int direction)
1899{
1900    osgGA::EventQueue *queue = getEventQueue();
1901    if (queue != NULL) {
1902        queue->mouseScroll((direction > 0 ? osgGA::GUIEventAdapter::SCROLL_UP : osgGA::GUIEventAdapter::SCROLL_DOWN));
1903        _needsRedraw = true;
1904    }
1905}
1906
1907/**
1908 * \brief Set the RGB background color to render into the image
1909 */
1910void Renderer::setBackgroundColor(float color[3])
1911{
1912    _bgColor[0] = color[0];
1913    _bgColor[1] = color[1];
1914    _bgColor[2] = color[2];
1915
1916    if (_viewer.valid()) {
1917        _viewer->getCamera()->setClearColor(osg::Vec4(color[0], color[1], color[2], 1));
1918
1919        _needsRedraw = true;
1920    }
1921}
1922
1923/**
1924 * \brief Sets flag to trigger rendering next time render() is called
1925 */
1926void Renderer::eventuallyRender()
1927{
1928    _needsRedraw = true;
1929}
1930
1931/**
1932 * \brief Get a timeout in usecs for select()
1933 *
1934 * If the paging thread is idle, returns <0 indicating that the
1935 * select call can block until data is available.  Otherwise,
1936 * if the frame render time was faster than the target frame
1937 * rate, return the remaining frame time.
1938 */
1939long Renderer::getTimeout()
1940{
1941    if (!checkNeedToDoFrame())
1942        // <0 means no timeout, block until socket has data
1943        return -1L;
1944    if (_lastFrameTime < _minFrameTime) {
1945        return (long)1.0e6*(_minFrameTime - _lastFrameTime);
1946    } else {
1947        // No timeout (poll)
1948        return 0L;
1949    }
1950}
1951
1952/**
1953 * \brief Check if paging thread is quiescent
1954 */
1955bool Renderer::isPagerIdle()
1956{
1957    if (!_viewer.valid())
1958        return true;
1959    else
1960        return (!_viewer->getDatabasePager()->requiresUpdateSceneGraph() &&
1961                !_viewer->getDatabasePager()->getRequestsInProgress());
1962}
1963
1964/**
1965 * \brief Check is frame call is necessary to render and/or update
1966 * in response to events or timed actions
1967 */
1968bool Renderer::checkNeedToDoFrame()
1969{
1970    return (_needsRedraw ||
1971            (_viewer.valid() && _viewer->checkNeedToDoFrame()));
1972}
1973
1974/**
1975 * \brief MapNode event phase
1976 *
1977 * This is called by the MapNode's event callback during the event
1978 * traversal of the viewer
1979 */
1980void Renderer::mapNodeUpdate()
1981{
1982    computeMapScale();
1983}
1984
1985void Renderer::markFrameStart()
1986{
1987    _startFrameTime = osg::Timer::instance()->tick();
1988}
1989
1990void Renderer::markFrameEnd()
1991{
1992    osg::Timer_t endFrameTick = osg::Timer::instance()->tick();
1993    _lastFrameTime = osg::Timer::instance()->delta_s(_startFrameTime, endFrameTick);
1994    if (_lastFrameTime > _minFrameTime) {
1995        ERROR("BROKE FRAME by %.2f msec", (_lastFrameTime - _minFrameTime)*1000.0f);
1996    } else {
1997        TRACE("Frame time: %.2f msec", _lastFrameTime*1000.0f);
1998    }
1999#ifdef USE_THROTTLING_SLEEP
2000    if (_lastFrameTime < _minFrameTime) {
2001        TRACE("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
2002        OpenThreads::Thread::microSleep(static_cast<unsigned int>(1000000.0*(_minFrameTime - _lastFrameTime)));
2003    }
2004#endif
2005}
2006
2007/**
2008 * \brief Cause the rendering to render a new image if needed
2009 *
2010 * The _needsRedraw flag indicates if a state change has occured since
2011 * the last rendered frame
2012 */
2013bool Renderer::render()
2014{
2015    if (_viewer.valid() && checkNeedToDoFrame()) {
2016        TRACE("Enter needsRedraw=%d",  _needsRedraw ? 1 : 0);
2017        _renderStartTime = osg::Timer::instance()->tick();
2018        TRACE("Before frame()");
2019        _viewer->frame();
2020        TRACE("After frame()");
2021        _renderStopTime = osg::Timer::instance()->tick();
2022        _renderTime = osg::Timer::instance()->delta_s(_renderStartTime, _renderStopTime);
2023        TRACE("Render time: %g msec", _renderTime * 1000.0);
2024#ifndef SLEEP_AFTER_QUEUE_FRAME
2025        _lastFrameTime = _renderTime;
2026#ifdef USE_THROTTLING_SLEEP
2027        if (_lastFrameTime < _minFrameTime) {
2028            TRACE("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
2029            OpenThreads::Thread::microSleep(static_cast<unsigned int>(1.0e6*(_minFrameTime - _lastFrameTime)));
2030        }
2031#endif
2032#endif
2033#ifdef WANT_TRACE
2034        if (_viewer->getViewerStats() != NULL) {
2035            _viewer->getViewerStats()->report(std::cerr, _viewer->getViewerStats()->getLatestFrameNumber());
2036        }
2037#endif
2038        _needsRedraw = false;
2039        return true;
2040    } else {
2041        _renderStartTime = _renderStopTime = osg::Timer::instance()->tick();
2042        _renderTime = 0;
2043        return false;
2044    }
2045}
2046
2047/**
2048 * \brief Read back the rendered framebuffer image
2049 */
2050osg::Image *Renderer::getRenderedFrame()
2051{
2052    if (_captureCallback.valid())
2053        return _captureCallback->getImage();
2054    else
2055        return NULL;
2056}
2057
2058void Renderer::setScaleBar(bool state)
2059{
2060    if (_scaleLabel.valid()) {
2061        _scaleLabel->setVisible(state);
2062    }
2063    if (_scaleBar.valid()) {
2064        _scaleBar->setVisible(state);
2065    }
2066    _needsRedraw = true;
2067}
2068
2069void Renderer::setScaleBarUnits(ScaleBarUnits units)
2070{
2071    _scaleBarUnits = units;
2072    _needsRedraw = true;
2073}
2074
2075/**
2076 * \brief Compute the scale ratio of the map based on a horizontal center line
2077 *
2078 * The idea here is to take 2 screen points on a horizontal line in the center
2079 * of the screen and convert to lat/long.  The lat/long coordinates are then
2080 * used to compute the great circle distance (assuming spherical earth) between
2081 * the points.
2082 *
2083 * We could use local projected map coordinates for the distance computation,
2084 * which would be faster; however, this would not show e.g. the change in
2085 * scale at different latitudes
2086 */
2087double Renderer::computeMapScale()
2088{
2089    if (!_scaleLabel.valid() || !_scaleLabel->visible()) {
2090        return -1.0;
2091    }
2092    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
2093        ERROR("No map");
2094        return -1.0;
2095    }
2096    if (!_viewer.valid()) {
2097        ERROR("No viewer");
2098        return -1.0;
2099    }
2100
2101    double x, y;
2102    double pixelWidth = _windowWidth * 0.1 * 2.0;
2103    if (pixelWidth < 10)
2104        pixelWidth = 10;
2105    if (pixelWidth > 150)
2106        pixelWidth = 150;
2107    x = (double)(_windowWidth -1)/2.0 - pixelWidth / 2.0;
2108    y = (double)(_windowHeight-1)/2.0;
2109
2110    osg::Vec3d world1, world2;
2111    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world1)) {
2112        // off map
2113        TRACE("Off map coords: %g %g", x, y);
2114        _scaleLabel->setText("");
2115        _scaleBar->setWidth(0);
2116        return -1.0;
2117    }
2118    x += pixelWidth;
2119    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world2)) {
2120        // off map
2121        TRACE("Off map coords: %g %g", x, y);
2122        _scaleLabel->setText("");
2123        _scaleBar->setWidth(0);
2124        return -1.0;
2125    }
2126
2127    TRACE("w1: %g %g %g w2: %g %g %g",
2128          world1.x(), world1.y(), world1.z(),
2129          world2.x(), world2.y(), world2.z());
2130
2131    double meters;
2132    double radius = 6378137.0;
2133    if (_mapNode->getMapSRS() &&
2134        _mapNode->getMapSRS()->getEllipsoid()) {
2135        radius = _mapNode->getMapSRS()->getEllipsoid()->getRadiusEquator();
2136    }
2137    if (!_map->isGeocentric() &&
2138        _mapNode->getMapSRS() &&
2139        _mapNode->getMapSRS()->isGeographic()) {
2140        TRACE("Map is geographic");
2141        // World cords are already lat/long
2142        // Compute great circle distance
2143        meters =
2144            osgEarth::GeoMath::distance(world1, world2, _mapNode->getMapSRS());
2145    } else if (_mapNode->getMapSRS()) {
2146        // Get map coords in lat/long
2147        osgEarth::GeoPoint mapPoint1, mapPoint2;
2148        mapPoint1.fromWorld(_mapNode->getMapSRS(), world1);
2149        mapPoint1.makeGeographic();
2150        mapPoint2.fromWorld(_mapNode->getMapSRS(), world2);
2151        mapPoint2.makeGeographic();
2152        // Compute great circle distance
2153        meters =
2154            osgEarth::GeoMath::distance(osg::DegreesToRadians(mapPoint1.y()),
2155                                        osg::DegreesToRadians(mapPoint1.x()),
2156                                        osg::DegreesToRadians(mapPoint2.y()),
2157                                        osg::DegreesToRadians(mapPoint2.x()),
2158                                        radius);
2159    } else {
2160        // Assume geocentric?
2161        ERROR("No map SRS");
2162        _scaleLabel->setText("");
2163        _scaleBar->setWidth(0);
2164        return -1.0;
2165    }
2166
2167    double scale = meters / pixelWidth;
2168    // 1mi = 5280 feet
2169    //double scaleMiles = scale / 1609.344; // International mile = 1609.344m
2170    //double scaleNauticalMiles = scale / 1852.0; // nautical mile = 1852m
2171    //double scaleUSSurveyMiles = scale / 1609.347218694; // US survey mile = 5280 US survey feet
2172    //double scaleUSSurveyFeet = scale * 3937.0/1200.0; // US survey foot = 1200/3937 m
2173    TRACE("m: %g px: %g m/px: %g", meters, pixelWidth, scale);
2174    _mapScale = scale;
2175    switch (_scaleBarUnits) {
2176    case UNITS_NAUTICAL_MILES: {
2177        double nmi = meters / 1852.0;
2178        scale = nmi / pixelWidth;
2179        nmi = normalizeScaleNauticalMiles(nmi);
2180        pixelWidth = nmi / scale;
2181        if (_scaleLabel.valid()) {
2182            _scaleLabel->setText(osgEarth::Stringify()
2183                                 << nmi
2184                                 << " nmi");
2185        }
2186    }
2187        break;
2188    case UNITS_US_SURVEY_FEET: {
2189        double feet = meters * 3937.0/1200.0;
2190        scale = feet / pixelWidth;
2191        feet = normalizeScaleFeet(feet);
2192        pixelWidth = feet / scale;
2193        if (_scaleLabel.valid()) {
2194            if (feet >= 5280) {
2195                _scaleLabel->setText(osgEarth::Stringify()
2196                                     << feet / 5280.0
2197                                     << " miUS");
2198             } else {
2199                _scaleLabel->setText(osgEarth::Stringify()
2200                                     << feet
2201                                     << " ftUS");
2202            }
2203        }
2204    }
2205        break;
2206    case UNITS_INTL_FEET: {
2207        double feet = 5280.0 * meters / 1609.344;
2208        scale = feet / pixelWidth;
2209        feet = normalizeScaleFeet(feet);
2210        pixelWidth = feet / scale;
2211        if (_scaleLabel.valid()) {
2212            if (feet >= 5280) {
2213                _scaleLabel->setText(osgEarth::Stringify()
2214                                     << feet / 5280.0
2215                                     << " mi");
2216            } else {
2217                _scaleLabel->setText(osgEarth::Stringify()
2218                                     << feet
2219                                     << " ft");
2220            }
2221        }
2222    }
2223        break;
2224    case UNITS_METERS:
2225    default: {
2226        meters = normalizeScaleMeters(meters);
2227        pixelWidth = meters / scale;
2228        if (_scaleLabel.valid()) {
2229            if (meters >= 1000) {
2230                _scaleLabel->setText(osgEarth::Stringify()
2231                                     << meters / 1000.0
2232                                     << " km");
2233            } else {
2234                _scaleLabel->setText(osgEarth::Stringify()
2235                                     << meters
2236                                     << " m");
2237            }
2238        }
2239    }
2240        break;
2241    }
2242    if (_scaleBar.valid()) {
2243        _scaleBar->setWidth(pixelWidth);
2244    }
2245    return scale;
2246}
2247
2248std::string Renderer::getCanonicalPath(const std::string& url) const
2249{
2250    std::string retStr;
2251    std::string proto = osgDB::getServerProtocol(url);
2252    if (proto.empty()) {
2253        retStr = osgDB::getRealPath(url);
2254        if (!osgDB::fileExists(retStr)) {
2255            retStr = "";
2256        }
2257    } else {
2258        retStr = url;
2259    }
2260    return retStr;
2261}
2262
2263void Renderer::writeScene(const std::string& file)
2264{
2265    if (_sceneRoot.valid()) {
2266        osgDB::writeNodeFile(*_sceneRoot.get(), file);
2267    }
2268}
Note: See TracBrowser for help on using the repository browser.