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

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

Error on missing file, set per-process cache directory, sky node fixes

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