source: geovis/trunk/Renderer.cpp @ 4654

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

Crash fix for commands sent before map is set up

File size: 71.2 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    double nlat, nlong;
1334    if (!mouseToLatLong(x, y, &nlat, &nlong)) {
1335        return;
1336    }
1337    double latMin, latMax, longMin, longMax;
1338    if (nlong >= _anchorLong && nlat >= _anchorLat) {
1339        // +x +y
1340        longMin = _anchorLong;
1341        latMin = _anchorLat;
1342        longMax = nlong;
1343        latMax = nlat;
1344    } else if (nlong < _anchorLong && nlat >= _anchorLat) {
1345        // -x +y
1346        longMin = nlong;
1347        latMin = _anchorLat;
1348        longMax = _anchorLong;
1349        latMax = nlat;
1350    } else if (nlong < _anchorLong && nlat < _anchorLat) {
1351        // -x -y
1352        longMin = nlong;
1353        latMin = nlat;
1354        longMax = _anchorLong;
1355        latMax = _anchorLat;
1356    } else {
1357        // +x -y
1358        longMin = _anchorLong;
1359        latMin = nlat;
1360        longMax = nlong;
1361        latMax = _anchorLat;
1362    }
1363    osgEarth::Annotation::FeatureNode *node = _selectionBox.get();
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    if (!_placeNodes.valid()) {
1469        ERROR("No place nodes");
1470        return;
1471    }
1472    osgEarth::Picker picker(_viewer.get(), _placeNodes.get());
1473    osgEarth::Picker::Hits hits;
1474    float mouseX = (float)x;
1475    float mouseY = (float)y;
1476    if (invertY) {
1477        mouseY = ((float)_windowHeight - mouseY);
1478    }
1479    std::set<osgEarth::Annotation::AnnotationNode*> toUnHover;
1480    for (std::set<osgEarth::Annotation::AnnotationNode*>::iterator itr = _hovered.begin();
1481         itr != _hovered.end(); ++itr) {
1482        toUnHover.insert(*itr);
1483    }
1484    if (picker.pick(mouseX, mouseY, hits)) {
1485        TRACE("Picker hit!");
1486        for (osgEarth::Picker::Hits::const_iterator hitr = hits.begin();
1487             hitr != hits.end(); ++hitr) {
1488            osgEarth::Annotation::AnnotationNode *anno =
1489                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
1490            if (anno != NULL) {
1491                if (_hovered.find(anno) == _hovered.end()) {
1492                    _hovered.insert(anno);
1493                    anno->setDecoration("hover");
1494                    _needsRedraw = true;
1495                }
1496                toUnHover.erase(anno);
1497            }
1498        }
1499#if 0
1500        writeScene("/tmp/test.osgt");
1501#endif
1502    }
1503    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = toUnHover.begin();
1504         itr != toUnHover.end(); ++itr) {
1505        _hovered.erase(*itr);
1506        (*itr)->clearDecoration();
1507        _needsRedraw = true;
1508    }
1509}
1510
1511void Renderer::deletePlaceNode(int x, int y, bool invertY)
1512{
1513    if (!_placeNodes.valid()) {
1514        ERROR("No place nodes");
1515        return;
1516    }
1517    osgEarth::Picker picker(_viewer.get(), _placeNodes.get());
1518    osgEarth::Picker::Hits hits;
1519    float mouseX = (float)x;
1520    float mouseY = (float)y;
1521    if (invertY) {
1522        mouseY = ((float)_windowHeight - mouseY);
1523    }
1524    if (picker.pick(mouseX, mouseY, hits)) {
1525        TRACE("Picker hit!");
1526        // prevent multiple hits on the same instance
1527        std::set<osgEarth::Annotation::AnnotationNode *> fired;
1528        for (osgEarth::Picker::Hits::const_iterator hitr = hits.begin();
1529             hitr != hits.end(); ++hitr) {
1530            osgEarth::Annotation::AnnotationNode *anno =
1531                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
1532            if (anno != NULL && fired.find(anno) == fired.end()) {
1533                fired.insert(anno);
1534                _needsRedraw = true;
1535            }
1536        }
1537        for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = fired.begin();
1538             itr != fired.end(); ++itr) {
1539            (*itr)->clearDecoration();
1540            _placeNodes->removeChild(*itr);
1541            if (_hovered.find(*itr) != _hovered.end()) {
1542                _hovered.erase(*itr);
1543            }
1544        }
1545    } else {
1546        TRACE("NO Picker hits");
1547    }
1548#if 0
1549    writeScene("/tmp/test.osg");
1550#endif
1551}
1552
1553void Renderer::addModelLayer(const char *name, osgEarth::ModelSourceOptions& opts)
1554{
1555    if (!_map.valid()) {
1556        ERROR("No map");
1557        return;
1558    }
1559    TRACE("layer: %s", name);
1560    osgEarth::ModelLayerOptions layerOpts(name, opts);
1561    _map->addModelLayer(new osgEarth::ModelLayer(layerOpts));
1562    _needsRedraw = true;
1563}
1564
1565void Renderer::removeModelLayer(const char *name)
1566{
1567    if (!_map.valid()) {
1568        ERROR("No map");
1569        return;
1570    }
1571    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
1572    if (layer != NULL) {
1573        _map->removeModelLayer(layer);
1574        _needsRedraw = true;
1575    } else {
1576        TRACE("Model layer not found: %s", name);
1577    }
1578}
1579
1580void Renderer::moveModelLayer(const char *name, unsigned int pos)
1581{
1582    if (!_map.valid()) {
1583        ERROR("No map");
1584        return;
1585    }
1586    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
1587    if (layer != NULL) {
1588        _map->moveModelLayer(layer, pos);
1589        _needsRedraw = true;
1590    } else {
1591        TRACE("Model layer not found: %s", name);
1592    }
1593}
1594
1595void Renderer::setModelLayerOpacity(const char *name, double opacity)
1596{
1597#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 0)
1598    if (!_map.valid()) {
1599        ERROR("No map");
1600        return;
1601    }
1602    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
1603    if (layer != NULL) {
1604        layer->setOpacity(opacity);
1605        _needsRedraw = true;
1606    } else {
1607        TRACE("Model layer not found: %s", name);
1608    }
1609#endif
1610}
1611
1612void Renderer::setModelLayerVisibility(const char *name, bool state)
1613{
1614#if OSGEARTH_MIN_VERSION_REQUIRED(2, 4, 0)
1615    if (!_map.valid()) {
1616        ERROR("No map");
1617        return;
1618    }
1619    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
1620    if (layer != NULL) {
1621        layer->setVisible(state);
1622        _needsRedraw = true;
1623    } else {
1624        TRACE("Model layer not found: %s", name);
1625    }
1626#endif
1627}
1628
1629/**
1630 * \brief Resize the render window (image size for renderings)
1631 */
1632void Renderer::setWindowSize(int width, int height)
1633{
1634    if (_windowWidth == width &&
1635        _windowHeight == height) {
1636        TRACE("No change");
1637        return;
1638    }
1639
1640    TRACE("Setting window size to %dx%d", width, height);
1641
1642    double origBitrate = getMaximumBitrate();
1643
1644    _windowWidth = width;
1645    _windowHeight = height;
1646
1647    setMaximumBitrate(origBitrate);
1648    TRACE("Bandwidth target: %.2f Mbps", (float)(getMaximumBitrate()/1.0e6));
1649    TRACE("Frame rate target: %.2f Hz", (float)getMaximumFrameRateInHertz());
1650
1651    if (_viewer.valid()) {
1652#ifdef USE_OFFSCREEN_RENDERING
1653#ifdef USE_PBUFFER
1654        osg::ref_ptr<osg::GraphicsContext> pbuffer;
1655        osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
1656        traits->x = 0;
1657        traits->y = 0;
1658        traits->width = _windowWidth;
1659        traits->height = _windowHeight;
1660        traits->red = 8;
1661        traits->green = 8;
1662        traits->blue = 8;
1663        traits->alpha = 8;
1664        traits->windowDecoration = false;
1665        traits->pbuffer = true;
1666        traits->doubleBuffer = true;
1667        traits->sharedContext = 0;
1668
1669        pbuffer = osg::GraphicsContext::createGraphicsContext(traits.get());
1670        if (pbuffer.valid()) {
1671            TRACE("Pixel buffer has been created successfully.");
1672        } else {
1673            ERROR("Pixel buffer has not been created successfully.");
1674        }
1675        osg::Camera *camera = new osg::Camera;
1676        camera->setGraphicsContext(pbuffer.get());
1677        camera->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
1678        GLenum buffer = pbuffer->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
1679        camera->setDrawBuffer(buffer);
1680        camera->setReadBuffer(buffer);
1681        camera->setFinalDrawCallback(_captureCallback.get());
1682        _viewer->addSlave(camera, osg::Matrixd(), osg::Matrixd());
1683        _viewer->realize();
1684#else
1685        if (_captureCallback.valid()) {
1686            _captureCallback->getTexture()->setTextureSize(_windowWidth, _windowHeight);
1687        }
1688        osgViewer::ViewerBase::Windows windows;
1689        _viewer->getWindows(windows);
1690        if (windows.size() == 1) {
1691            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
1692        } else {
1693            ERROR("Num windows: %lu", windows.size());
1694        }
1695#endif
1696#else
1697        osgViewer::ViewerBase::Windows windows;
1698        _viewer->getWindows(windows);
1699        if (windows.size() == 1) {
1700            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
1701        } else {
1702            ERROR("Num windows: %lu", windows.size());
1703        }
1704#endif
1705        // HACK: Without this, the mouse coordinate mapping uses the old size
1706        // for 1 frame.
1707        assert(_viewer->getEventQueue() != NULL);
1708        //TRACE("Window EventQueue: %p", getEventQueue());
1709        //TRACE("Viewer EventQueue: %p", _viewer->getEventQueue());
1710        _viewer->getEventQueue()->windowResize(0, 0, _windowWidth, _windowHeight);
1711        _needsRedraw = true;
1712    }
1713}
1714
1715/**
1716 * \brief Set the orientation of the camera from a quaternion
1717 * rotation
1718 *
1719 * \param[in] quat A quaternion with scalar part first: w,x,y,z
1720 * \param[in] absolute Is rotation absolute or relative?
1721 */
1722void Renderer::setCameraOrientation(const double quat[4], bool absolute)
1723{
1724    if (_manipulator.valid()) {
1725        _manipulator->setRotation(osg::Quat(quat[1], quat[2], quat[3], quat[0]));
1726        _needsRedraw = true;
1727    }
1728}
1729
1730/**
1731 * \brief Reset pan, zoom, clipping planes and optionally rotation
1732 *
1733 * \param[in] resetOrientation Reset the camera rotation/orientation also
1734 */
1735void Renderer::resetCamera(bool resetOrientation)
1736{
1737    TRACE("Enter: resetOrientation=%d", resetOrientation ? 1 : 0);
1738    if (_viewer.valid()) {
1739        _viewer->home();
1740        _needsRedraw = true;
1741    }
1742}
1743
1744/**
1745 * \brief Perform a 2D translation of the camera
1746 *
1747 * x,y pan amount are specified as signed absolute pan amount in viewport
1748 * units -- i.e. 0 is no pan, .5 is half the viewport, 2 is twice the viewport,
1749 * etc.
1750 *
1751 * \param[in] x Viewport coordinate horizontal panning (positive number pans
1752 * camera left, object right)
1753 * \param[in] y Viewport coordinate vertical panning (positive number pans
1754 * camera up, object down)
1755 */
1756void Renderer::panCamera(double x, double y)
1757{
1758    TRACE("Enter: %g %g", x, y);
1759
1760    if (_manipulator.valid()) {
1761        // Wants mouse delta x,y in normalized screen coords
1762        _manipulator->pan(x, y);
1763        _needsRedraw = true;
1764    }
1765}
1766
1767void Renderer::rotateCamera(double x, double y)
1768{
1769    TRACE("Enter: %g %g", x, y);
1770
1771    if (_manipulator.valid()) {
1772        _manipulator->rotate(x, y);
1773        _needsRedraw = true;
1774    }
1775}
1776
1777/**
1778 * \brief Dolly camera or set orthographic scaling based on camera type
1779 *
1780 * \param[in] y Mouse y coordinate in normalized screen coords
1781 */
1782void Renderer::zoomCamera(double y)
1783{
1784    TRACE("Enter: y: %g", y);
1785
1786    if (_manipulator.valid()) {
1787        // +y = zoom out, -y = zoom in
1788        TRACE("camDist: %g", _manipulator->getDistance());
1789        if ((_mapScale < 0.0 && y > 0.0) ||
1790            (_mapScale < 0.1 && y < 0.0) ||
1791            (_mapScale > 40000.0 && y > 0.0))
1792            return;
1793#if 1
1794       _manipulator->zoom(0, y);
1795#else
1796        double dist = _manipulator->getDistance();
1797        dist *= (1.0 + y);
1798        _manipulator->setDistance(dist);
1799#endif
1800        _needsRedraw = true;
1801    }
1802}
1803
1804/**
1805 * \brief Dolly camera to set distance from focal point
1806 *
1807 * \param[in] dist distance in map? coordinates
1808 */
1809void Renderer::setCameraDistance(double dist)
1810{
1811    TRACE("Enter: dist: %g", dist);
1812
1813    if (_manipulator.valid()) {
1814        TRACE("camDist: %g", _manipulator->getDistance());
1815
1816        _manipulator->setDistance(dist);
1817
1818        _needsRedraw = true;
1819    }
1820}
1821
1822void Renderer::keyPress(int key)
1823{
1824    osgGA::EventQueue *queue = getEventQueue();
1825    if (queue != NULL) {
1826        queue->keyPress(key);
1827        _needsRedraw = true;
1828    }
1829}
1830
1831void Renderer::keyRelease(int key)
1832{
1833    osgGA::EventQueue *queue = getEventQueue();
1834    if (queue != NULL) {
1835        queue->keyRelease(key);
1836        _needsRedraw = true;
1837    }
1838}
1839
1840void Renderer::setThrowingEnabled(bool state)
1841{
1842    if (_manipulator.valid()) {
1843        _manipulator->getSettings()->setThrowingEnabled(state);
1844    }
1845}
1846
1847void Renderer::mouseDoubleClick(int button, double x, double y)
1848{
1849    osgGA::EventQueue *queue = getEventQueue();
1850    if (queue != NULL) {
1851        queue->mouseDoubleButtonPress((float)x, (float)y, button);
1852        _needsRedraw = true;
1853    }
1854}
1855
1856void Renderer::mouseClick(int button, double x, double y)
1857{
1858    osgGA::EventQueue *queue = getEventQueue();
1859    if (queue != NULL) {
1860        queue->mouseButtonPress((float)x, (float)y, button);
1861        _needsRedraw = true;
1862    }
1863}
1864
1865void Renderer::mouseDrag(int button, double x, double y)
1866{
1867    osgGA::EventQueue *queue = getEventQueue();
1868    if (queue != NULL) {
1869        queue->mouseMotion((float)x, (float)y);
1870        _needsRedraw = true;
1871    }
1872}
1873
1874void Renderer::mouseRelease(int button, double x, double y)
1875{
1876    osgGA::EventQueue *queue = getEventQueue();
1877    if (queue != NULL) {
1878        queue->mouseButtonRelease((float)x, (float)y, button);
1879        _needsRedraw = true;
1880    }
1881}
1882
1883void Renderer::mouseMotion(double x, double y)
1884{
1885    if (_mouseCoordsTool.valid()) {
1886#if 1
1887        osgGA::EventQueue *queue = getEventQueue();
1888        if (queue != NULL) {
1889            queue->mouseMotion((float)x, (float)y);
1890            _needsRedraw = true;
1891        }
1892#else
1893        if (_viewer.valid() && _coordsCallback.valid()) {
1894            osgEarth::GeoPoint mapPt;
1895            if (mapMouseCoords((float)x, (float)y, mapPt)) {
1896                _coordsCallback->set(mapPt, _viewer->asView(), _mapNode);
1897            } else {
1898                _coordsCallback->reset(_viewer->asView(), _mapNode);
1899            }
1900            _needsRedraw = true;
1901        }
1902#endif
1903    }
1904}
1905
1906void Renderer::mouseScroll(int direction)
1907{
1908    osgGA::EventQueue *queue = getEventQueue();
1909    if (queue != NULL) {
1910        queue->mouseScroll((direction > 0 ? osgGA::GUIEventAdapter::SCROLL_UP : osgGA::GUIEventAdapter::SCROLL_DOWN));
1911        _needsRedraw = true;
1912    }
1913}
1914
1915/**
1916 * \brief Set the RGB background color to render into the image
1917 */
1918void Renderer::setBackgroundColor(float color[3])
1919{
1920    _bgColor[0] = color[0];
1921    _bgColor[1] = color[1];
1922    _bgColor[2] = color[2];
1923
1924    if (_viewer.valid()) {
1925        _viewer->getCamera()->setClearColor(osg::Vec4(color[0], color[1], color[2], 1));
1926
1927        _needsRedraw = true;
1928    }
1929}
1930
1931/**
1932 * \brief Sets flag to trigger rendering next time render() is called
1933 */
1934void Renderer::eventuallyRender()
1935{
1936    _needsRedraw = true;
1937}
1938
1939/**
1940 * \brief Get a timeout in usecs for select()
1941 *
1942 * If the paging thread is idle, returns <0 indicating that the
1943 * select call can block until data is available.  Otherwise,
1944 * if the frame render time was faster than the target frame
1945 * rate, return the remaining frame time.
1946 */
1947long Renderer::getTimeout()
1948{
1949    if (!checkNeedToDoFrame())
1950        // <0 means no timeout, block until socket has data
1951        return -1L;
1952    if (_lastFrameTime < _minFrameTime) {
1953        return (long)1.0e6*(_minFrameTime - _lastFrameTime);
1954    } else {
1955        // No timeout (poll)
1956        return 0L;
1957    }
1958}
1959
1960/**
1961 * \brief Check if paging thread is quiescent
1962 */
1963bool Renderer::isPagerIdle()
1964{
1965    if (!_viewer.valid())
1966        return true;
1967    else
1968        return (!_viewer->getDatabasePager()->requiresUpdateSceneGraph() &&
1969                !_viewer->getDatabasePager()->getRequestsInProgress());
1970}
1971
1972/**
1973 * \brief Check is frame call is necessary to render and/or update
1974 * in response to events or timed actions
1975 */
1976bool Renderer::checkNeedToDoFrame()
1977{
1978    return (_needsRedraw ||
1979            (_viewer.valid() && _viewer->checkNeedToDoFrame()));
1980}
1981
1982/**
1983 * \brief MapNode event phase
1984 *
1985 * This is called by the MapNode's event callback during the event
1986 * traversal of the viewer
1987 */
1988void Renderer::mapNodeUpdate()
1989{
1990    computeMapScale();
1991}
1992
1993void Renderer::markFrameStart()
1994{
1995    _startFrameTime = osg::Timer::instance()->tick();
1996}
1997
1998void Renderer::markFrameEnd()
1999{
2000    osg::Timer_t endFrameTick = osg::Timer::instance()->tick();
2001    _lastFrameTime = osg::Timer::instance()->delta_s(_startFrameTime, endFrameTick);
2002    if (_lastFrameTime > _minFrameTime) {
2003        ERROR("BROKE FRAME by %.2f msec", (_lastFrameTime - _minFrameTime)*1000.0f);
2004    } else {
2005        TRACE("Frame time: %.2f msec", _lastFrameTime*1000.0f);
2006    }
2007#ifdef USE_THROTTLING_SLEEP
2008    if (_lastFrameTime < _minFrameTime) {
2009        TRACE("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
2010        OpenThreads::Thread::microSleep(static_cast<unsigned int>(1000000.0*(_minFrameTime - _lastFrameTime)));
2011    }
2012#endif
2013}
2014
2015/**
2016 * \brief Cause the rendering to render a new image if needed
2017 *
2018 * The _needsRedraw flag indicates if a state change has occured since
2019 * the last rendered frame
2020 */
2021bool Renderer::render()
2022{
2023    if (_viewer.valid() && checkNeedToDoFrame()) {
2024        TRACE("Enter needsRedraw=%d",  _needsRedraw ? 1 : 0);
2025        _renderStartTime = osg::Timer::instance()->tick();
2026        TRACE("Before frame()");
2027        _viewer->frame();
2028        TRACE("After frame()");
2029        _renderStopTime = osg::Timer::instance()->tick();
2030        _renderTime = osg::Timer::instance()->delta_s(_renderStartTime, _renderStopTime);
2031        TRACE("Render time: %g msec", _renderTime * 1000.0);
2032#ifndef SLEEP_AFTER_QUEUE_FRAME
2033        _lastFrameTime = _renderTime;
2034#ifdef USE_THROTTLING_SLEEP
2035        if (_lastFrameTime < _minFrameTime) {
2036            TRACE("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
2037            OpenThreads::Thread::microSleep(static_cast<unsigned int>(1.0e6*(_minFrameTime - _lastFrameTime)));
2038        }
2039#endif
2040#endif
2041#ifdef WANT_TRACE
2042        if (_viewer->getViewerStats() != NULL) {
2043            _viewer->getViewerStats()->report(std::cerr, _viewer->getViewerStats()->getLatestFrameNumber());
2044        }
2045#endif
2046        _needsRedraw = false;
2047        return true;
2048    } else {
2049        _renderStartTime = _renderStopTime = osg::Timer::instance()->tick();
2050        _renderTime = 0;
2051        return false;
2052    }
2053}
2054
2055/**
2056 * \brief Read back the rendered framebuffer image
2057 */
2058osg::Image *Renderer::getRenderedFrame()
2059{
2060    if (_captureCallback.valid())
2061        return _captureCallback->getImage();
2062    else
2063        return NULL;
2064}
2065
2066void Renderer::setScaleBar(bool state)
2067{
2068    if (_scaleLabel.valid()) {
2069        _scaleLabel->setVisible(state);
2070    }
2071    if (_scaleBar.valid()) {
2072        _scaleBar->setVisible(state);
2073    }
2074    _needsRedraw = true;
2075}
2076
2077void Renderer::setScaleBarUnits(ScaleBarUnits units)
2078{
2079    _scaleBarUnits = units;
2080    _needsRedraw = true;
2081}
2082
2083/**
2084 * \brief Compute the scale ratio of the map based on a horizontal center line
2085 *
2086 * The idea here is to take 2 screen points on a horizontal line in the center
2087 * of the screen and convert to lat/long.  The lat/long coordinates are then
2088 * used to compute the great circle distance (assuming spherical earth) between
2089 * the points.
2090 *
2091 * We could use local projected map coordinates for the distance computation,
2092 * which would be faster; however, this would not show e.g. the change in
2093 * scale at different latitudes
2094 */
2095double Renderer::computeMapScale()
2096{
2097    if (!_scaleLabel.valid() || !_scaleLabel->visible()) {
2098        return -1.0;
2099    }
2100    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
2101        ERROR("No map");
2102        return -1.0;
2103    }
2104    if (!_viewer.valid()) {
2105        ERROR("No viewer");
2106        return -1.0;
2107    }
2108
2109    double x, y;
2110    double pixelWidth = _windowWidth * 0.1 * 2.0;
2111    if (pixelWidth < 10)
2112        pixelWidth = 10;
2113    if (pixelWidth > 150)
2114        pixelWidth = 150;
2115    x = (double)(_windowWidth -1)/2.0 - pixelWidth / 2.0;
2116    y = (double)(_windowHeight-1)/2.0;
2117
2118    osg::Vec3d world1, world2;
2119    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world1)) {
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    x += pixelWidth;
2127    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world2)) {
2128        // off map
2129        TRACE("Off map coords: %g %g", x, y);
2130        _scaleLabel->setText("");
2131        _scaleBar->setWidth(0);
2132        return -1.0;
2133    }
2134
2135    TRACE("w1: %g %g %g w2: %g %g %g",
2136          world1.x(), world1.y(), world1.z(),
2137          world2.x(), world2.y(), world2.z());
2138
2139    double meters;
2140    double radius = 6378137.0;
2141    if (_mapNode->getMapSRS() &&
2142        _mapNode->getMapSRS()->getEllipsoid()) {
2143        radius = _mapNode->getMapSRS()->getEllipsoid()->getRadiusEquator();
2144    }
2145    if (!_map->isGeocentric() &&
2146        _mapNode->getMapSRS() &&
2147        _mapNode->getMapSRS()->isGeographic()) {
2148        TRACE("Map is geographic");
2149        // World cords are already lat/long
2150        // Compute great circle distance
2151        meters =
2152            osgEarth::GeoMath::distance(world1, world2, _mapNode->getMapSRS());
2153    } else if (_mapNode->getMapSRS()) {
2154        // Get map coords in lat/long
2155        osgEarth::GeoPoint mapPoint1, mapPoint2;
2156        mapPoint1.fromWorld(_mapNode->getMapSRS(), world1);
2157        mapPoint1.makeGeographic();
2158        mapPoint2.fromWorld(_mapNode->getMapSRS(), world2);
2159        mapPoint2.makeGeographic();
2160        // Compute great circle distance
2161        meters =
2162            osgEarth::GeoMath::distance(osg::DegreesToRadians(mapPoint1.y()),
2163                                        osg::DegreesToRadians(mapPoint1.x()),
2164                                        osg::DegreesToRadians(mapPoint2.y()),
2165                                        osg::DegreesToRadians(mapPoint2.x()),
2166                                        radius);
2167    } else {
2168        // Assume geocentric?
2169        ERROR("No map SRS");
2170        _scaleLabel->setText("");
2171        _scaleBar->setWidth(0);
2172        return -1.0;
2173    }
2174
2175    double scale = meters / pixelWidth;
2176    // 1mi = 5280 feet
2177    //double scaleMiles = scale / 1609.344; // International mile = 1609.344m
2178    //double scaleNauticalMiles = scale / 1852.0; // nautical mile = 1852m
2179    //double scaleUSSurveyMiles = scale / 1609.347218694; // US survey mile = 5280 US survey feet
2180    //double scaleUSSurveyFeet = scale * 3937.0/1200.0; // US survey foot = 1200/3937 m
2181    TRACE("m: %g px: %g m/px: %g", meters, pixelWidth, scale);
2182    _mapScale = scale;
2183    switch (_scaleBarUnits) {
2184    case UNITS_NAUTICAL_MILES: {
2185        double nmi = meters / 1852.0;
2186        scale = nmi / pixelWidth;
2187        nmi = normalizeScaleNauticalMiles(nmi);
2188        pixelWidth = nmi / scale;
2189        if (_scaleLabel.valid()) {
2190            _scaleLabel->setText(osgEarth::Stringify()
2191                                 << nmi
2192                                 << " nmi");
2193        }
2194    }
2195        break;
2196    case UNITS_US_SURVEY_FEET: {
2197        double feet = meters * 3937.0/1200.0;
2198        scale = feet / pixelWidth;
2199        feet = normalizeScaleFeet(feet);
2200        pixelWidth = feet / scale;
2201        if (_scaleLabel.valid()) {
2202            if (feet >= 5280) {
2203                _scaleLabel->setText(osgEarth::Stringify()
2204                                     << feet / 5280.0
2205                                     << " miUS");
2206             } else {
2207                _scaleLabel->setText(osgEarth::Stringify()
2208                                     << feet
2209                                     << " ftUS");
2210            }
2211        }
2212    }
2213        break;
2214    case UNITS_INTL_FEET: {
2215        double feet = 5280.0 * meters / 1609.344;
2216        scale = feet / pixelWidth;
2217        feet = normalizeScaleFeet(feet);
2218        pixelWidth = feet / scale;
2219        if (_scaleLabel.valid()) {
2220            if (feet >= 5280) {
2221                _scaleLabel->setText(osgEarth::Stringify()
2222                                     << feet / 5280.0
2223                                     << " mi");
2224            } else {
2225                _scaleLabel->setText(osgEarth::Stringify()
2226                                     << feet
2227                                     << " ft");
2228            }
2229        }
2230    }
2231        break;
2232    case UNITS_METERS:
2233    default: {
2234        meters = normalizeScaleMeters(meters);
2235        pixelWidth = meters / scale;
2236        if (_scaleLabel.valid()) {
2237            if (meters >= 1000) {
2238                _scaleLabel->setText(osgEarth::Stringify()
2239                                     << meters / 1000.0
2240                                     << " km");
2241            } else {
2242                _scaleLabel->setText(osgEarth::Stringify()
2243                                     << meters
2244                                     << " m");
2245            }
2246        }
2247    }
2248        break;
2249    }
2250    if (_scaleBar.valid()) {
2251        _scaleBar->setWidth(pixelWidth);
2252    }
2253    return scale;
2254}
2255
2256std::string Renderer::getCanonicalPath(const std::string& url) const
2257{
2258    std::string retStr;
2259    std::string proto = osgDB::getServerProtocol(url);
2260    if (proto.empty()) {
2261        retStr = osgDB::getRealPath(url);
2262        if (!osgDB::fileExists(retStr)) {
2263            retStr = "";
2264        }
2265    } else {
2266        retStr = url;
2267    }
2268    return retStr;
2269}
2270
2271void Renderer::writeScene(const std::string& file)
2272{
2273    if (_sceneRoot.valid()) {
2274        osgDB::writeNodeFile(*_sceneRoot.get(), file);
2275    }
2276}
Note: See TracBrowser for help on using the repository browser.