source: geovis/trunk/Renderer.cpp @ 4648

Last change on this file since 4648 was 4648, checked in by ldelgass, 9 years ago

Having both line and polygon styles on selection rectangle causes problems, so
use only polygon style for now.

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