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

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

First pass at scale bar

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