source: trunk/packages/vizservers/geovis/Renderer.cpp @ 4578

Last change on this file since 4578 was 4575, checked in by ldelgass, 10 years ago

Add optional coordinate conversion for 'map coords' command, add 'screen coords'
command to project map coords to screen coords.

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