/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* * Copyright (C) 2004-2013 HUBzero Foundation, LLC * * Author: Leif Delgass */ #include #include #include #include #include #include #include #include #include // For getpid() #include #ifdef WANT_TRACE #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1) #include #include #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "Renderer.h" #if 0 #include "SingleWindow.h" #endif #include "ScaleBar.h" #include "FileUtil.h" #include "Trace.h" #define MSECS_ELAPSED(t1, t2) \ ((t1).tv_sec == (t2).tv_sec ? (((t2).tv_usec - (t1).tv_usec)/1.0e+3) : \ (((t2).tv_sec - (t1).tv_sec))*1.0e+3 + (double)((t2).tv_usec - (t1).tv_usec)/1.0e+3) #define BASE_IMAGE "world.tif" #define PIN_ICON "placemark32.png" using namespace GeoVis; Renderer::Renderer() : _needsRedraw(false), _windowWidth(500), _windowHeight(500), _scaleBarUnits(UNITS_METERS) { TRACE("Enter"); _bgColor[0] = 0; _bgColor[1] = 0; _bgColor[2] = 0; //setMaximumFrameRateInHertz(15.0); // 100 Mbps setMaximumBitrate(1.0e8); _lastFrameTime = _minFrameTime; TRACE("Bandwidth target: %.2f Mbps", (float)(getMaximumBitrate()/1.0e6)); TRACE("Frame rate target: %.2f Hz", (float)getMaximumFrameRateInHertz()); TRACE("Frame time target: %.2f msec", _minFrameTime * 1000.0f); char *base = getenv("MAP_BASE_URI"); if (base != NULL) { _baseURI = base; TRACE("Setting base URI: %s", _baseURI.c_str()); } } osgGA::EventQueue *Renderer::getEventQueue() { if (_viewer.valid()) { osgViewer::ViewerBase::Windows windows; _viewer->getWindows(windows); if (windows.size() > 0) { return windows[0]->getEventQueue(); } } return NULL; } Renderer::~Renderer() { TRACE("Enter"); removeDirectory(_cacheDir.c_str()); TRACE("Leave"); } std::string Renderer::getBaseImage() { std::ostringstream oss; oss << _resourcePath << "/" << BASE_IMAGE; return oss.str(); } std::string Renderer::getPinIcon() { std::ostringstream oss; oss << _resourcePath << "/" << PIN_ICON; return oss.str(); } void Renderer::setupCache() { std::ostringstream dir; dir << "/tmp/geovis_cache" << getpid(); _cacheDir = dir.str(); const char *path = _cacheDir.c_str(); TRACE("Cache dir: %s", path); removeDirectory(path); if (!osgDB::makeDirectory(_cacheDir)) { ERROR("Failed to create directory '%s'", path); } } void Renderer::initColorMaps() { if (!_colorMaps.empty()) return; osg::TransferFunction1D *defaultColorMap = new osg::TransferFunction1D; defaultColorMap->allocate(256); defaultColorMap->setColor(0.00, osg::Vec4f(0,0,1,1), false); defaultColorMap->setColor(0.25, osg::Vec4f(0,1,1,1), false); defaultColorMap->setColor(0.50, osg::Vec4f(0,1,0,1), false); defaultColorMap->setColor(0.75, osg::Vec4f(1,1,0,1), false); defaultColorMap->setColor(1.00, osg::Vec4f(1,0,0,1), false); defaultColorMap->updateImage(); addColorMap("default", defaultColorMap); osg::TransferFunction1D *defaultGrayColorMap = new osg::TransferFunction1D; defaultGrayColorMap->allocate(256); defaultGrayColorMap->setColor(0, osg::Vec4f(0,0,0,1), false); defaultGrayColorMap->setColor(1, osg::Vec4f(1,1,1,1), false); defaultGrayColorMap->updateImage(); addColorMap("grayDefault", defaultGrayColorMap); } void Renderer::addColorMap(const ColorMapId& id, osg::TransferFunction1D *xfer) { _colorMaps[id] = xfer; } void Renderer::deleteColorMap(const ColorMapId& id) { ColorMapHashmap::iterator itr; bool doAll = false; if (id.compare("all") == 0) { itr = _colorMaps.begin(); doAll = true; } else { itr = _colorMaps.find(id); } if (itr == _colorMaps.end()) { ERROR("Unknown ColorMap %s", id.c_str()); return; } do { if (itr->first.compare("default") == 0 || itr->first.compare("grayDefault") == 0) { if (id.compare("all") != 0) { WARN("Cannot delete a default color map"); } continue; } TRACE("Deleting ColorMap %s", itr->first.c_str()); itr = _colorMaps.erase(itr); } while (doAll && itr != _colorMaps.end()); } void Renderer::setColorMapNumberOfTableEntries(const ColorMapId& id, int numEntries) { ColorMapHashmap::iterator itr; bool doAll = false; if (id.compare("all") == 0) { itr = _colorMaps.begin(); doAll = true; } else { itr = _colorMaps.find(id); } if (itr == _colorMaps.end()) { ERROR("Unknown ColorMap %s", id.c_str()); return; } do { itr->second->allocate(numEntries); itr->second->updateImage(); } while (doAll && ++itr != _colorMaps.end()); } void Renderer::initViewer() { if (_viewer.valid()) return; _viewer = new osgViewer::Viewer(); #if 1 osg::DisplaySettings *ds = _viewer->getDisplaySettings(); if (ds == NULL) { ds = osg::DisplaySettings::instance().get(); } ds->setDoubleBuffer(false); ds->setMinimumNumAlphaBits(8); ds->setMinimumNumStencilBits(8); ds->setNumMultiSamples(0); #endif _viewer->setThreadingModel(osgViewer::ViewerBase::SingleThreaded); _viewer->getDatabasePager()->setUnrefImageDataAfterApplyPolicy(false, false); _viewer->setReleaseContextAtEndOfFrameHint(false); //_viewer->setLightingMode(osg::View::SKY_LIGHT); _viewer->getCamera()->setClearColor(osg::Vec4(_bgColor[0], _bgColor[1], _bgColor[2], 1)); _viewer->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); _viewer->getCamera()->setNearFarRatio(0.00002); _viewer->getCamera()->setSmallFeatureCullingPixelSize(-1.0f); _stateManip = new osgGA::StateSetManipulator(_viewer->getCamera()->getOrCreateStateSet()); _viewer->addEventHandler(_stateManip); //_viewer->addEventHandler(new osgViewer::StatsHandler()); #ifdef DEBUG if (_viewer->getViewerStats() != NULL) { TRACE("Enabling stats"); _viewer->getViewerStats()->collectStats("scene", true); } #endif #if 0 osgViewer::ViewerBase::Windows windows; _viewer->getWindows(windows); if (windows.size() == 1) { windows[0]->setSyncToVBlank(false); } else { ERROR("Num windows: %lu", windows.size()); } #endif } void Renderer::finalizeViewer() { initViewer(); TRACE("Before _viewer->isRealized()"); if (!_viewer->isRealized()) { int screen = 0; const char *displayEnv = getenv("DISPLAY"); if (displayEnv != NULL) { TRACE("DISPLAY: %s", displayEnv); // 3 parts: host, display, screen int part = 0; for (size_t c = 0; c < strlen(displayEnv); c++) { if (displayEnv[c] == ':') { part = 1; } else if (part == 1 && displayEnv[c] == '.') { part = 2; } else if (part == 2) { screen = atoi(&displayEnv[c]); break; } } } TRACE("Using screen: %d", screen); #ifdef USE_OFFSCREEN_RENDERING #ifdef USE_PBUFFER osg::ref_ptr pbuffer; osg::ref_ptr traits = new osg::GraphicsContext::Traits; traits->x = 0; traits->y = 0; traits->width = _windowWidth; traits->height = _windowHeight; traits->red = 8; traits->green = 8; traits->blue = 8; traits->alpha = 8; traits->windowDecoration = false; traits->pbuffer = true; traits->doubleBuffer = true; traits->sharedContext = 0; pbuffer = osg::GraphicsContext::createGraphicsContext(traits.get()); if (pbuffer.valid()) { TRACE("Pixel buffer has been created successfully."); } else { ERROR("Pixel buffer has not been created successfully."); } osg::Camera *camera = new osg::Camera; camera->setGraphicsContext(pbuffer.get()); camera->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight)); GLenum buffer = pbuffer->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT; camera->setDrawBuffer(buffer); camera->setReadBuffer(buffer); _captureCallback = new ScreenCaptureCallback(); camera->setFinalDrawCallback(_captureCallback.get()); _viewer->addSlave(camera, osg::Matrixd(), osg::Matrixd()); #else _viewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); osg::Texture2D* texture2D = new osg::Texture2D; texture2D->setTextureSize(_windowWidth, _windowHeight); texture2D->setInternalFormat(GL_RGBA); texture2D->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST); texture2D->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST); _viewer->getCamera()->setImplicitBufferAttachmentMask(0, 0); _viewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, texture2D); //_viewer->getCamera()->attach(osg::Camera::DEPTH_BUFFER, GL_DEPTH_COMPONENT24); _viewer->getCamera()->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, GL_DEPTH24_STENCIL8_EXT); _captureCallback = new ScreenCaptureCallback(texture2D); _viewer->getCamera()->setFinalDrawCallback(_captureCallback.get()); _viewer->setUpViewInWindow(0, 0, _windowWidth, _windowHeight, screen); #endif #else _captureCallback = new ScreenCaptureCallback(); _viewer->getCamera()->setFinalDrawCallback(_captureCallback.get()); #if 1 _viewer->setUpViewInWindow(0, 0, _windowWidth, _windowHeight, screen); #else SingleWindow *windowConfig = new SingleWindow(0, 0, _windowWidth, _windowHeight, screen); osg::DisplaySettings *ds = windowConfig->getActiveDisplaySetting(*_viewer.get()); ds->setDoubleBuffer(false); ds->setMinimumNumAlphaBits(8); ds->setMinimumNumStencilBits(8); ds->setNumMultiSamples(0); windowConfig->setWindowDecoration(false); windowConfig->setOverrideRedirect(false); _viewer->apply(windowConfig); #endif #endif _viewer->realize(); initColorMaps(); // HACK: This seems to initialize something required for properly // mapping mouse coords assert(getEventQueue() != NULL); getEventQueue()->mouseMotion(_windowWidth/2, _windowHeight/2); } } void Renderer::initControls() { if (_hbox.valid()) return; _hbox = new osgEarth::Util::Controls::HBox(osgEarth::Util::Controls::Control::ALIGN_RIGHT, osgEarth::Util::Controls::Control::ALIGN_BOTTOM, osgEarth::Util::Controls::Gutter(0, 2, 2, 0), 2.0f); _copyrightLabel = new osgEarth::Util::Controls::LabelControl("Map data © 2014 ACME Corp.", 12.0f); _copyrightLabel->setForeColor(osg::Vec4f(1, 1, 1, 1)); _copyrightLabel->setHaloColor(osg::Vec4f(0, 0, 0, 1)); _copyrightLabel->setEncoding(osgText::String::ENCODING_UTF8); _scaleLabel = new osgEarth::Util::Controls::LabelControl("- km", 12.0f); _scaleLabel->setForeColor(osg::Vec4f(1, 1, 1, 1)); _scaleLabel->setHaloColor(osg::Vec4f(0, 0, 0, 1)); _scaleBar = new osgEarth::Util::Controls::Frame(); _scaleBar->setVertFill(true); _scaleBar->setForeColor(osg::Vec4f(0, 0, 0, 1)); _scaleBar->setBackColor(osg::Vec4f(1, 1, 1, 0.6)); _scaleBar->setBorderColor(osg::Vec4f(0, 0, 0 ,1)); _scaleBar->setBorderWidth(1.0); _hbox->addControl(_copyrightLabel.get()); _hbox->addControl(_scaleLabel.get()); _hbox->addControl(_scaleBar.get()); #if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1) osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->addControl(_hbox.get()); #else osgEarth::Util::Controls::ControlCanvas::get(_viewer.get(), true)->addControl(_hbox.get()); #endif // Install an event callback to handle scale bar updates // Can't use an update callback since that will trigger // constant rendering _mapNode->setEventCallback(new MapNodeCallback(this)); } void Renderer::setGraticule(bool enable, GraticuleType type) { if (!_mapNode.valid() || !_sceneRoot.valid()) return; if (enable) { if (_graticule.valid()) { _sceneRoot->removeChild(_graticule.get()); _graticule = NULL; } switch (type) { case GRATICULE_UTM: { osgEarth::Util::UTMGraticule *gr = new osgEarth::Util::UTMGraticule(_mapNode.get()); _sceneRoot->addChild(gr); _graticule = gr; } break; case GRATICULE_MGRS: { osgEarth::Util::MGRSGraticule *gr = new osgEarth::Util::MGRSGraticule(_mapNode.get()); _sceneRoot->addChild(gr); _graticule = gr; } break; case GRATICULE_GEODETIC: default: osgEarth::Util::GeodeticGraticule *gr = new osgEarth::Util::GeodeticGraticule(_mapNode.get()); osgEarth::Util::GeodeticGraticuleOptions opt = gr->getOptions(); opt.lineStyle()->getOrCreate()->stroke()->color().set(1,0,0,1); gr->setOptions(opt); _sceneRoot->addChild(gr); _graticule = gr; } } else if (_graticule.valid()) { _sceneRoot->removeChild(_graticule.get()); _graticule = NULL; } _needsRedraw = true; } void Renderer::setReadout(int x, int y) { if (!_coordsCallback.valid() || !_mapNode.valid() || !_viewer.valid()) return; osgEarth::GeoPoint mapCoord; if (mapMouseCoords(x, y, mapCoord)) { _coordsCallback->set(mapCoord, _viewer->asView(), _mapNode); } else { _coordsCallback->reset(_viewer->asView(), _mapNode); } _needsRedraw = true; } void Renderer::clearReadout() { if (_coordsCallback.valid()) { _coordsCallback->reset(_viewer->asView(), _mapNode); } _needsRedraw = true; } void Renderer::setCoordinateReadout(bool state, CoordinateDisplayType type, int precision) { if (!state) { if (_mouseCoordsTool.valid() && _viewer.valid()) { _viewer->removeEventHandler(_mouseCoordsTool.get()); _mouseCoordsTool = NULL; } if (_coordsCallback.valid() && _viewer.valid()) { osgEarth::Util::Controls::LabelControl *readout = _coordsCallback->getLabel(); #if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1) osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->removeControl(readout); #else osgEarth::Util::Controls::ControlCanvas::get(_viewer.get(), true)->removeControl(readout); #endif _coordsCallback = NULL; } } else { initMouseCoordsTool(type, precision); } _needsRedraw = true; } osgEarth::Util::MGRSFormatter::Precision Renderer::getMGRSPrecision(int precisionInMeters) { switch (precisionInMeters) { case 1: return osgEarth::Util::MGRSFormatter::PRECISION_1M; case 10: return osgEarth::Util::MGRSFormatter::PRECISION_10M; case 100: return osgEarth::Util::MGRSFormatter::PRECISION_100M; case 1000: return osgEarth::Util::MGRSFormatter::PRECISION_1000M; case 10000: return osgEarth::Util::MGRSFormatter::PRECISION_10000M; case 100000: return osgEarth::Util::MGRSFormatter::PRECISION_100000M; default: ERROR("Invalid precision: %d", precisionInMeters); return osgEarth::Util::MGRSFormatter::PRECISION_1M; } } void Renderer::initMouseCoordsTool(CoordinateDisplayType type, int precision) { if (!_viewer.valid()) return; if (_mouseCoordsTool.valid()) { _viewer->removeEventHandler(_mouseCoordsTool.get()); } _mouseCoordsTool = new MouseCoordsTool(_mapNode.get()); osgEarth::Util::Controls::LabelControl *readout; if (_coordsCallback.valid()) { readout = _coordsCallback->getLabel(); _coordsCallback = NULL; } else { readout = new osgEarth::Util::Controls::LabelControl("", 12.0f); #if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1) osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->addControl(readout); #else osgEarth::Util::Controls::ControlCanvas::get(_viewer.get(), true)->addControl(readout); #endif readout->setForeColor(osg::Vec4f(1, 1, 1, 1)); readout->setHaloColor(osg::Vec4f(0, 0, 0, 1)); } osgEarth::Util::Formatter *formatter = NULL; if (type == COORDS_MGRS) { osgEarth::Util::MGRSFormatter::Precision prec = osgEarth::Util::MGRSFormatter::PRECISION_1M; if (precision > 0) { prec = getMGRSPrecision(precision); } unsigned int opts = 0u; formatter = new osgEarth::Util::MGRSFormatter(prec, NULL, opts); } else { osgEarth::Util::LatLongFormatter::AngularFormat af; unsigned int opts = osgEarth::Util::LatLongFormatter::USE_SYMBOLS; switch (type) { case COORDS_LATLONG_DEGREES_DECIMAL_MINUTES: af = osgEarth::Util::LatLongFormatter::FORMAT_DEGREES_DECIMAL_MINUTES; break; case COORDS_LATLONG_DEGREES_MINUTES_SECONDS: af = osgEarth::Util::LatLongFormatter::FORMAT_DEGREES_MINUTES_SECONDS; break; default: af = osgEarth::Util::LatLongFormatter::FORMAT_DECIMAL_DEGREES; } osgEarth::Util::LatLongFormatter *latlong = new osgEarth::Util::LatLongFormatter(af, opts); if (precision > 0) { latlong->setPrecision(precision); } formatter = latlong; } _coordsCallback = new MouseCoordsCallback(readout, formatter); _mouseCoordsTool->addCallback(_coordsCallback.get()); _viewer->addEventHandler(_mouseCoordsTool.get()); } void Renderer::initEarthManipulator() { _manipulator = new osgEarth::Util::EarthManipulator; #if 1 osgEarth::Util::EarthManipulator::Settings *settings = _manipulator->getSettings(); settings->bindMouse(osgEarth::Util::EarthManipulator::ACTION_ROTATE, osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON, osgGA::GUIEventAdapter::MODKEY_ALT); osgEarth::Util::EarthManipulator::ActionOptions options; options.clear(); options.add(osgEarth::Util::EarthManipulator::OPTION_CONTINUOUS, true); settings->bindMouse(osgEarth::Util::EarthManipulator::ACTION_ZOOM, osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON, osgGA::GUIEventAdapter::MODKEY_ALT, options); _manipulator->applySettings(settings); #endif _viewer->setCameraManipulator(_manipulator.get()); _manipulator->setNode(NULL); _manipulator->setNode(_sceneRoot.get()); //_manipulator->computeHomePosition(); } void Renderer::loadEarthFile(const char *path) { TRACE("Loading %s", path); osg::Node *node = osgDB::readNodeFile(path); if (node == NULL) { ERROR("Couldn't load %s", path); return; } osgEarth::MapNode *mapNode = osgEarth::MapNode::findMapNode(node); if (mapNode == NULL) { ERROR("Couldn't find MapNode"); return; } else { initViewer(); _sceneRoot = new osg::Group; _sceneRoot->addChild(node); _map = mapNode->getMap(); } _mapNode = mapNode; if (_clipPlaneCullCallback.valid()) { _viewer->getCamera()->removeCullCallback(_clipPlaneCullCallback.get()); _clipPlaneCullCallback = NULL; } if (_map->isGeocentric()) { _clipPlaneCullCallback = new osgEarth::Util::AutoClipPlaneCullCallback(mapNode); _viewer->getCamera()->addCullCallback(_clipPlaneCullCallback.get()); } _viewer->setSceneData(_sceneRoot.get()); if (_mouseCoordsTool.valid()) { initMouseCoordsTool(); } initControls(); initEarthManipulator(); _viewer->home(); finalizeViewer(); _needsRedraw = true; } void Renderer::resetMap(osgEarth::MapOptions::CoordinateSystemType type, const char *profile, double bounds[4]) { TRACE("Restting map with type %d, profile %s", type, profile); osgEarth::MapOptions mapOpts; mapOpts.coordSysType() = type; if (profile != NULL) { if (bounds != NULL) { mapOpts.profile() = osgEarth::ProfileOptions(); if (strcmp(profile, "geodetic") == 0) { mapOpts.profile()->srsString() = "epsg:4326"; } else if (strcmp(profile, "spherical-mercator") == 0) { // Projection used by Google/Bing/OSM // aka epsg:900913 meters in x/y // aka WGS84 Web Mercator (Auxiliary Sphere) // X/Y: -20037508.34m to 20037508.34m mapOpts.profile()->srsString() = "epsg:3857"; } else { mapOpts.profile()->srsString() = profile; } TRACE("Setting profile bounds: %g %g %g %g", bounds[0], bounds[1], bounds[2], bounds[3]); mapOpts.profile()->bounds() = osgEarth::Bounds(bounds[0], bounds[1], bounds[2], bounds[3]); } else { mapOpts.profile() = osgEarth::ProfileOptions(profile); } } else if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) { mapOpts.profile() = osgEarth::ProfileOptions("global-mercator"); } #ifdef USE_CACHE setupCache(); osgEarth::Drivers::FileSystemCacheOptions cacheOpts; cacheOpts.rootPath() = _cacheDir; mapOpts.cache() = cacheOpts; #endif initViewer(); //mapOpts.referenceURI() = _baseURI; osgEarth::Map *map = new osgEarth::Map(mapOpts); _map = map; osgEarth::Drivers::GDALOptions bopts; bopts.url() = getBaseImage(); addImageLayer("base", bopts); #if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1) osgEarth::Drivers::MPTerrainEngine::MPTerrainEngineOptions mpOpt; #else osgEarth::Drivers::MPTerrainEngineOptions mpOpt; #endif // Set background layer color mpOpt.color() = osg::Vec4(1, 1, 1, 1); //mpOpt.minLOD() = 1; // Sets shader uniform for terrain renderer (config var defaults to false) mpOpt.enableLighting() = false; osgEarth::MapNodeOptions mapNodeOpts(mpOpt); // Sets GL_LIGHTING state in MapNode's StateSet (config var defaults to true) mapNodeOpts.enableLighting() = true; //mapNodeOpts.getTerrainOptions().loadingPolicy().mapLoadingThreadsPerCore() = 1; //mapNodeOpts.getTerrainOptions().loadingPolicy().numLoadingThreads() = 1; //mapNodeOpts.getTerrainOptions().loadingPolicy().numCompileThreadsPerCore() = 1; //mapNodeOpts.getTerrainOptions().loadingPolicy().numCompileThreads() = 1; osgEarth::MapNode *mapNode = new osgEarth::MapNode(map, mapNodeOpts); _mapNode = mapNode; if (_map->isGeocentric()) { osgEarth::DateTime now; #if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1) TRACE("Creating SkyNode"); osgEarth::Drivers::SimpleSky::SimpleSkyOptions skyOpts; skyOpts.hours() = now.hours(); skyOpts.ambient() = 0.2f; skyOpts.atmosphericLighting() = true; skyOpts.exposure() = 3.0; osgEarth::Util::SkyNode *sky = osgEarth::Util::SkyNode::create(skyOpts, mapNode); sky->addChild(mapNode); sky->attach(_viewer.get(), 0); _sceneRoot = sky; #else _sceneRoot = new osg::Group(); _sceneRoot->addChild(_mapNode.get()); TRACE("Creating SkyNode"); osgEarth::Util::SkyNode *sky = new osgEarth::Util::SkyNode(map); _sceneRoot->addChild(sky); sky->setAmbientBrightness(0.2f); sky->setDateTime(now); sky->attach(_viewer.get(), 0); #endif } else { _sceneRoot = new osg::Group(); _sceneRoot->addChild(_mapNode.get()); } if (_clipPlaneCullCallback.valid()) { _viewer->getCamera()->removeCullCallback(_clipPlaneCullCallback.get()); _clipPlaneCullCallback = NULL; } if (_map->isGeocentric()) { _clipPlaneCullCallback = new osgEarth::Util::AutoClipPlaneCullCallback(mapNode); _viewer->getCamera()->addCullCallback(_clipPlaneCullCallback.get()); } _viewer->setSceneData(_sceneRoot.get()); if (_mouseCoordsTool.valid()) { initMouseCoordsTool(); } initControls(); //_viewer->setSceneData(_sceneRoot.get()); initEarthManipulator(); _viewer->home(); finalizeViewer(); _needsRedraw = true; } void Renderer::clearMap() { if (_map.valid()) { _map->clear(); _needsRedraw = true; } } void Renderer::setLighting(bool state) { if (_mapNode.valid()) { TRACE("Setting lighting: %d", state ? 1 : 0); _mapNode->getOrCreateStateSet() ->setMode(GL_LIGHTING, state ? 1 : 0); _needsRedraw = true; } } void Renderer::setViewerLightType(osg::View::LightingMode mode) { if (_viewer.valid()) { _viewer->setLightingMode(mode); _needsRedraw = true; } } void Renderer::setTerrainVerticalScale(double scale) { if (_mapNode.valid()) { if (!_verticalScale.valid()) { _verticalScale = new osgEarth::Util::VerticalScale; _mapNode->getTerrainEngine()->addEffect(_verticalScale); } _verticalScale->setScale(scale); _needsRedraw = true; } } void Renderer::setTerrainLighting(bool state) { #if 1 if (!_mapNode.valid()) { ERROR("No map node"); return; } // XXX: HACK alert // Find the terrain engine container (might be above one or more decorators) osg::Group *group = _mapNode->getTerrainEngine(); while (group->getParent(0) != NULL && group->getParent(0) != _mapNode.get()) { group = group->getParent(0); } if (group != NULL && group->getParent(0) == _mapNode.get()) { TRACE("Setting terrain lighting: %d", state ? 1 : 0); if (group->getOrCreateStateSet()->getUniform("oe_mode_GL_LIGHTING") != NULL) { group->getStateSet()->getUniform("oe_mode_GL_LIGHTING")->set(state); } else { ERROR("Can't get terrain lighting uniform"); } } else { ERROR("Can't find terrain engine container"); } #else if (_stateManip.valid()) { _stateManip->setLightingEnabled(state); } #endif _needsRedraw = true; } void Renderer::setTerrainWireframe(bool state) { if (!_map.valid()) { ERROR("No map"); return; } #if 0 if (!_mapNode.valid()) { ERROR("No map node"); return; } TRACE("Setting terrain wireframe: %d", state ? 1 : 0); osg::StateSet *state = _mapNode->getOrCreateStateSet(); osg::PolygonMode *pmode = dynamic_cast< osg::PolygonMode* >(state->getAttribute(osg::StateAttribute::POLYGONMODE)); if (pmode == NULL) { pmode = new osg::PolygonMode; state->setAttribute(pmode); } if (state) { pmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE); } else { pmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL); } _needsRedraw = true; #else if (_stateManip.valid()) { _stateManip->setPolygonMode(state ? osg::PolygonMode::LINE : osg::PolygonMode::FILL); _needsRedraw = true; } #endif } void Renderer::saveNamedViewpoint(const char *name) { _viewpoints[name] = getViewpoint(); } bool Renderer::removeNamedViewpoint(const char *name) { ViewpointHashmap::iterator itr = _viewpoints.find(name); if (itr != _viewpoints.end()) { _viewpoints.erase(name); return true; } else { ERROR("Unknown viewpoint: '%s'", name); return false; } } bool Renderer::restoreNamedViewpoint(const char *name, double durationSecs) { ViewpointHashmap::iterator itr = _viewpoints.find(name); if (itr != _viewpoints.end()) { setViewpoint(itr->second, durationSecs); return true; } else { ERROR("Unknown viewpoint: '%s'", name); return false; } } void Renderer::setViewpoint(const osgEarth::Viewpoint& v, double durationSecs) { if (_manipulator.valid()) { TRACE("Setting viewpoint: %g %g %g %g %g %g", v.x(), v.y(), v.z(), v.getHeading(), v.getPitch(), v.getRange()); _manipulator->setViewpoint(v, durationSecs); _needsRedraw = true; } else { ERROR("No manipulator"); } } osgEarth::Viewpoint Renderer::getViewpoint() { if (_manipulator.valid()) { return _manipulator->getViewpoint(); } else { // Uninitialized, invalid viewpoint return osgEarth::Viewpoint(); } } static void srsInfo(const osgEarth::SpatialReference *srs) { TRACE("SRS: %s", srs->getName().c_str()); TRACE("horiz: \"%s\" vert: \"%s\"", srs->getHorizInitString().c_str(), srs->getVertInitString().c_str()); 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", srs->isGeographic() ? 1 : 0, srs->isGeodetic() ? 1 : 0, srs->isProjected() ? 1 : 0, srs->isECEF() ? 1 : 0, srs->isMercator() ? 1 : 0, srs->isSphericalMercator() ? 1 : 0, srs->isNorthPolar() ? 1 : 0, srs->isSouthPolar() ? 1 : 0, srs->isUserDefined() ? 1 : 0, srs->isContiguous() ? 1 : 0, srs->isCube() ? 1 : 0, srs->isLTP() ? 1 : 0, srs->isPlateCarre() ? 1 : 0); } bool Renderer::getWorldCoords(const osgEarth::GeoPoint& mapPt, osg::Vec3d *world) { if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) { ERROR("No map"); return false; } TRACE("Input SRS:"); srsInfo(mapPt.getSRS()); TRACE("Map SRS:"); srsInfo(_mapNode->getMapSRS()); bool ret = mapPt.toWorld(*world, _mapNode->getTerrain()); TRACE("In: %g,%g,%g Out: %g,%g,%g", mapPt.x(), mapPt.y(), mapPt.z(), world->x(), world->y(), world->z()); return ret; } bool Renderer::worldToScreen(const osg::Vec3d& world, osg::Vec3d *screen, bool invertY) { if (!_viewer.valid()) { ERROR("No viewer"); return false; } osg::Camera *cam = _viewer->getCamera(); osg::Matrixd MVP = cam->getViewMatrix() * cam->getProjectionMatrix(); // Get clip coords osg::Vec4d pt; pt = osg::Vec4d(world, 1.0) * MVP; // Clip if (pt.x() < -pt.w() || pt.x() > pt.w() || pt.y() < -pt.w() || pt.y() > pt.w() || pt.z() < -pt.w() || pt.z() > pt.w()) { // Outside frustum TRACE("invalid pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w()); return false; } TRACE("clip pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w()); // Perspective divide: now NDC pt /= pt.w(); const osg::Viewport *viewport = cam->getViewport(); #if 1 screen->x() = viewport->x() + viewport->width() * 0.5 + pt.x() * viewport->width() * 0.5; screen->y() = viewport->y() + viewport->height() * 0.5 + pt.y() * viewport->height() * 0.5; //double near = 0; //double far = 1; //screen->z() = (far + near) * 0.5 + (far - near) * 0.5 * pt.z(); screen->z() = 0.5 + 0.5 * pt.z(); #else *screen = osg::Vec3d(pt.x(), pt.y(), pt.z()) * cam->getViewport()->computeWindowMatrix(); #endif if (invertY) { screen->y() = viewport->height() - screen->y(); } TRACE("screen: %g,%g,%g", screen->x(), screen->y(), screen->z()); return true; } bool Renderer::worldToScreen(std::vector& coords, bool invertY) { if (!_viewer.valid()) { ERROR("No viewer"); return false; } bool ret = true; osg::Camera *cam = _viewer->getCamera(); osg::Matrixd MVP = cam->getViewMatrix() * cam->getProjectionMatrix(); const osg::Viewport *viewport = cam->getViewport(); for (std::vector::iterator itr = coords.begin(); itr != coords.end(); ++itr) { // Get clip coords osg::Vec4d pt; pt = osg::Vec4d(*itr, 1.0) * MVP; // Clip if (pt.x() < -pt.w() || pt.x() > pt.w() || pt.y() < -pt.w() || pt.y() > pt.w() || pt.z() < -pt.w() || pt.z() > pt.w()) { // Outside frustum TRACE("invalid pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w()); itr->set(std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()); ret = false; continue; } TRACE("clip pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w()); // Perspective divide: now NDC pt /= pt.w(); #if 1 itr->x() = viewport->x() + viewport->width() * 0.5 + pt.x() * viewport->width() * 0.5; itr->y() = viewport->y() + viewport->height() * 0.5 + pt.y() * viewport->height() * 0.5; //double near = 0; //double far = 1; //itr->z() = (far + near) * 0.5 + (far - near) * 0.5 * pt.z(); itr->z() = 0.5 + 0.5 * pt.z(); #else *itr = osg::Vec3d(pt.x(), pt.y(), pt.z()) * viewport->computeWindowMatrix(); #endif if (invertY) { itr->y() = viewport->height() - itr->y(); } TRACE("screen: %g,%g,%g", itr->x(), itr->y(), itr->z()); } return ret; } bool Renderer::mouseToLatLong(int mouseX, int mouseY, double *latitude, double *longitude) { if (getMapSRS() == NULL) return false; const osgEarth::SpatialReference *outSRS = getMapSRS()->getGeographicSRS(); if (outSRS == NULL) return false; osgEarth::GeoPoint mapPoint; if (mapMouseCoords(mouseX, mouseY, mapPoint)) { mapPoint = mapPoint.transform(outSRS); *longitude = mapPoint.x(); *latitude = mapPoint.y(); return true; } else { return false; } } /** * \brief Map screen mouse coordinates to map coordinates * * This method assumes that mouse Y coordinates are 0 at the top * of the screen and increase going down if invertY is set, and * 0 at the bottom and increase going up otherwise. */ bool Renderer::mapMouseCoords(float mouseX, float mouseY, osgEarth::GeoPoint& map, bool invertY) { if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) { ERROR("No map"); return false; } if (!_viewer.valid()) { ERROR("No viewer"); return false; } if (invertY) { mouseY = ((float)_windowHeight - mouseY); } osg::Vec3d world; if (_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), mouseX, mouseY, world)) { map.fromWorld(getMapSRS(), world); return true; } return false; } bool Renderer::addImageLayer(const char *name, osgEarth::TileSourceOptions& opts, bool makeShared, bool visible) { if (!_map.valid()) { ERROR("No map"); return false; } TRACE("layer: %s", name); if (!opts.tileSize().isSet()) { opts.tileSize() = 256; } osgEarth::ImageLayerOptions layerOpts(name, opts); #if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1) layerOpts.textureCompression() = osg::Texture::USE_IMAGE_DATA_FORMAT; #endif if (makeShared) { layerOpts.shared() = true; } if (!visible) { layerOpts.visible() = false; } osg::ref_ptr layer = new osgEarth::ImageLayer(layerOpts); _map->addImageLayer(layer.get()); if (layer->getTileSource() == NULL || !layer->getTileSource()->isOK()) { ERROR("Failed to add image layer: %s", name); _map->removeImageLayer(layer.get()); return false; } _needsRedraw = true; return true; } void Renderer::addColorFilter(const char *name, const char *shader) { if (!_map.valid()) { ERROR("No map"); return; } osgEarth::ImageLayer *layer = _map->getImageLayerByName(name); if (layer == NULL) { TRACE("Image layer not found: %s", name); return; } osgEarth::Util::GLSLColorFilter *filter = new osgEarth::Util::GLSLColorFilter; filter->setCode(shader); //filter->setCode("color.rgb = color.r > 0.5 ? vec3(1.0) : vec3(0.0);"); layer->addColorFilter(filter); _needsRedraw = true; } void Renderer::removeColorFilter(const char *name, int idx) { if (!_map.valid()) { ERROR("No map"); return; } osgEarth::ImageLayer *layer = _map->getImageLayerByName(name); if (layer == NULL) { TRACE("Image layer not found: %s", name); return; } if (idx < 0) { while (!layer->getColorFilters().empty()) { layer->removeColorFilter(layer->getColorFilters()[0]); } } else { layer->removeColorFilter(layer->getColorFilters().at(idx)); } _needsRedraw = true; } void Renderer::removeImageLayer(const char *name) { if (!_map.valid()) { ERROR("No map"); return; } osgEarth::ImageLayer *layer = _map->getImageLayerByName(name); if (layer != NULL) { _map->removeImageLayer(layer); _needsRedraw = true; } else { TRACE("Image layer not found: %s", name); } } void Renderer::moveImageLayer(const char *name, unsigned int pos) { if (!_map.valid()) { ERROR("No map"); return; } osgEarth::ImageLayer *layer = _map->getImageLayerByName(name); if (layer != NULL) { _map->moveImageLayer(layer, pos); _needsRedraw = true; } else { TRACE("Image layer not found: %s", name); } } void Renderer::setImageLayerOpacity(const char *name, double opacity) { if (!_map.valid()) { ERROR("No map"); return; } osgEarth::ImageLayer *layer = _map->getImageLayerByName(name); if (layer != NULL) { layer->setOpacity(opacity); _needsRedraw = true; } else { TRACE("Image layer not found: %s", name); } } void Renderer::setImageLayerVisibility(const char *name, bool state) { #if OSGEARTH_MIN_VERSION_REQUIRED(2, 4, 0) if (!_map.valid()) { ERROR("No map"); return; } osgEarth::ImageLayer *layer = _map->getImageLayerByName(name); if (layer != NULL) { layer->setVisible(state); _needsRedraw = true; } else { TRACE("Image layer not found: %s", name); } #endif } void Renderer::addElevationLayer(const char *name, osgEarth::TileSourceOptions& opts) { if (!_map.valid()) { ERROR("No map"); return; } TRACE("layer: %s", name); if (!opts.tileSize().isSet()) { opts.tileSize() = 15; } osgEarth::ElevationLayerOptions layerOpts(name, opts); // XXX: GDAL does not report vertical datum, it should be specified here // Common options: geodetic (default), egm96, egm84, egm2008 //layerOpts.verticalDatum() = "egm96"; _map->addElevationLayer(new osgEarth::ElevationLayer(layerOpts)); _needsRedraw = true; } void Renderer::removeElevationLayer(const char *name) { if (!_map.valid()) { ERROR("No map"); return; } osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name); if (layer != NULL) { _map->removeElevationLayer(layer); _needsRedraw = true; } else { TRACE("Elevation layer not found: %s", name); } } void Renderer::moveElevationLayer(const char *name, unsigned int pos) { if (!_map.valid()) { ERROR("No map"); return; } osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name); if (layer != NULL) { _map->moveElevationLayer(layer, pos); _needsRedraw = true; } else { TRACE("Elevation layer not found: %s", name); } } void Renderer::setElevationLayerVisibility(const char *name, bool state) { #if OSGEARTH_MIN_VERSION_REQUIRED(2, 4, 0) if (!_map.valid()) { ERROR("No map"); return; } osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name); if (layer != NULL) { layer->setVisible(state); _needsRedraw = true; } else { TRACE("Elevation layer not found: %s", name); } #endif } void Renderer::initAnnotations() { if (!_annotations.valid()) { _annotations = new osg::Group(); _annotations->setName("Annotations"); _sceneRoot->addChild(_annotations.get()); _placeNodes = new osg::Group(); _placeNodes->setName("Place Nodes"); osgEarth::Decluttering::setEnabled(_placeNodes->getOrCreateStateSet(), true); _annotations->addChild(_placeNodes.get()); } } void Renderer::initBoxSelection(int x, int y) { double latitude, longitude; if (!mouseToLatLong(x, y, &latitude, &longitude)) { return; } _anchorLat = latitude; _anchorLong = longitude; addRhumbBox(latitude, latitude, longitude, longitude); } void Renderer::updateBoxSelection(int x, int y) { osgEarth::Annotation::FeatureNode *node = _selectionBox.get(); double nlat, nlong; if (!mouseToLatLong(x, y, &nlat, &nlong)) { return; } double latMin, latMax, longMin, longMax; if (nlong >= _anchorLong && nlat >= _anchorLat) { // +x +y longMin = _anchorLong; latMin = _anchorLat; longMax = nlong; latMax = nlat; } else if (nlong < _anchorLong && nlat >= _anchorLat) { // -x +y longMin = nlong; latMin = _anchorLat; longMax = _anchorLong; latMax = nlat; } else if (nlong < _anchorLong && nlat < _anchorLat) { // -x -y longMin = nlong; latMin = nlat; longMax = _anchorLong; latMax = _anchorLat; } else { // +x -y longMin = _anchorLong; latMin = nlat; longMax = nlong; latMax = _anchorLat; } osgEarth::Symbology::Geometry *geom = node->getFeature()->getGeometry(); (*geom)[0] = osg::Vec3d(longMax, latMin, 0); (*geom)[1] = osg::Vec3d(longMin, latMin, 0); (*geom)[2] = osg::Vec3d(longMin, latMax, 0); (*geom)[3] = osg::Vec3d(longMax, latMax, 0); node->init(); _needsRedraw = true; } void Renderer::clearBoxSelection() { if (_annotations.valid() && _selectionBox.valid()) { _annotations->removeChild(_selectionBox.get()); _selectionBox = NULL; } _needsRedraw = true; } void Renderer::addRhumbBox(double latMin, double latMax, double longMin, double longMax) { if (!_mapNode.valid()) { ERROR("No map node"); return; } initAnnotations(); if (_selectionBox.valid()) { osgEarth::Symbology::Geometry *geom = _selectionBox->getFeature()->getGeometry(); (*geom)[0] = osg::Vec3d(longMax, latMin, 0); (*geom)[1] = osg::Vec3d(longMin, latMin, 0); (*geom)[2] = osg::Vec3d(longMin, latMax, 0); (*geom)[3] = osg::Vec3d(longMax, latMax, 0); _selectionBox->init(); } else { const osgEarth::SpatialReference* geoSRS = _mapNode->getMapSRS()->getGeographicSRS(); osgEarth::Symbology::Geometry *geom = new osgEarth::Symbology::Polygon(); geom->push_back(osg::Vec3d(longMax, latMin, 0)); geom->push_back(osg::Vec3d(longMin, latMin, 0)); geom->push_back(osg::Vec3d(longMin, latMax, 0)); geom->push_back(osg::Vec3d(longMax, latMax, 0)); osgEarth::Symbology::Style boxStyle; #if 1 osgEarth::Symbology::PolygonSymbol *poly = boxStyle.getOrCreate(); poly->fill()->color() = osgEarth::Symbology::Color::Cyan; poly->fill()->color().a() = 0.5; #else osgEarth::Symbology::LineSymbol *line = boxStyle.getOrCreate(); line->stroke()->color() = osgEarth::Symbology::Color::Yellow; line->stroke()->width() = 2.0f; //line->creaseAngle() = 45.0f; line->tessellation() = 10; #endif osgEarth::Symbology::AltitudeSymbol *alt = boxStyle.getOrCreate(); alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN; //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU; alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE; //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_SCENE; #if 0 osgEarth::Symbology::RenderSymbol* rs = boxStyle.getOrCreateSymbol(); rs->depthOffset()->enabled() = true; rs->depthOffset()->minBias() = 1000; #endif osgEarth::Features::Feature *feature = new osgEarth::Features::Feature(geom, geoSRS, boxStyle); //feature->geoInterp() = osgEarth::GEOINTERP_GREAT_CIRCLE; feature->geoInterp() = osgEarth::GEOINTERP_RHUMB_LINE; _selectionBox = new osgEarth::Annotation::FeatureNode(_mapNode, feature); _annotations->addChild(_selectionBox.get()); } _needsRedraw = true; } void Renderer::addPlaceNode(double latitude, double longitude, char *labelText) { if (!_mapNode.valid()) { ERROR("No map node"); return; } initAnnotations(); const osgEarth::SpatialReference* geoSRS = _mapNode->getMapSRS()->getGeographicSRS(); osgEarth::Symbology::Style pin; pin.getOrCreate()->url()->setLiteral(getPinIcon()); osgEarth::Annotation::AnnotationNode *anno = new osgEarth::Annotation::PlaceNode(_mapNode, osgEarth::GeoPoint(geoSRS, longitude, latitude), labelText, pin); _placeNodes->addChild(anno); osgEarth::Annotation::DecorationInstaller highlightInstaller("hover", new osgEarth::Annotation::HighlightDecoration()); _annotations->accept(highlightInstaller); // scale labels when hovering: osgEarth::Annotation::DecorationInstaller scaleInstaller("hover", new osgEarth::Annotation::ScaleDecoration(1.1f)); _placeNodes->accept(scaleInstaller); #if 0 writeScene("/tmp/test.osg"); #endif _needsRedraw = true; } void Renderer::hoverPlaceNode(int x, int y, bool invertY) { osgEarth::Picker picker(_viewer.get(), _placeNodes.get()); osgEarth::Picker::Hits hits; float mouseX = (float)x; float mouseY = (float)y; if (invertY) { mouseY = ((float)_windowHeight - mouseY); } std::set toUnHover; for (std::set::iterator itr = _hovered.begin(); itr != _hovered.end(); ++itr) { toUnHover.insert(*itr); } if (picker.pick(mouseX, mouseY, hits)) { TRACE("Picker hit!"); for (osgEarth::Picker::Hits::const_iterator hitr = hits.begin(); hitr != hits.end(); ++hitr) { osgEarth::Annotation::AnnotationNode *anno = picker.getNode(*hitr); if (anno != NULL) { if (_hovered.find(anno) == _hovered.end()) { _hovered.insert(anno); anno->setDecoration("hover"); _needsRedraw = true; } toUnHover.erase(anno); } } #if 0 writeScene("/tmp/test.osgt"); #endif } for (std::set::iterator itr = toUnHover.begin(); itr != toUnHover.end(); ++itr) { _hovered.erase(*itr); (*itr)->clearDecoration(); _needsRedraw = true; } } void Renderer::deletePlaceNode(int x, int y, bool invertY) { osgEarth::Picker picker(_viewer.get(), _placeNodes.get()); osgEarth::Picker::Hits hits; float mouseX = (float)x; float mouseY = (float)y; if (invertY) { mouseY = ((float)_windowHeight - mouseY); } if (picker.pick(mouseX, mouseY, hits)) { TRACE("Picker hit!"); // prevent multiple hits on the same instance std::set fired; for (osgEarth::Picker::Hits::const_iterator hitr = hits.begin(); hitr != hits.end(); ++hitr) { osgEarth::Annotation::AnnotationNode *anno = picker.getNode(*hitr); if (anno != NULL && fired.find(anno) == fired.end()) { fired.insert(anno); _needsRedraw = true; } } for (std::set::iterator itr = fired.begin(); itr != fired.end(); ++itr) { (*itr)->clearDecoration(); _placeNodes->removeChild(*itr); if (_hovered.find(*itr) != _hovered.end()) { _hovered.erase(*itr); } } } else { TRACE("NO Picker hits"); } #if 0 writeScene("/tmp/test.osg"); #endif } void Renderer::addModelLayer(const char *name, osgEarth::ModelSourceOptions& opts) { if (!_map.valid()) { ERROR("No map"); return; } TRACE("layer: %s", name); osgEarth::ModelLayerOptions layerOpts(name, opts); _map->addModelLayer(new osgEarth::ModelLayer(layerOpts)); _needsRedraw = true; } void Renderer::removeModelLayer(const char *name) { if (!_map.valid()) { ERROR("No map"); return; } osgEarth::ModelLayer *layer = _map->getModelLayerByName(name); if (layer != NULL) { _map->removeModelLayer(layer); _needsRedraw = true; } else { TRACE("Model layer not found: %s", name); } } void Renderer::moveModelLayer(const char *name, unsigned int pos) { if (!_map.valid()) { ERROR("No map"); return; } osgEarth::ModelLayer *layer = _map->getModelLayerByName(name); if (layer != NULL) { _map->moveModelLayer(layer, pos); _needsRedraw = true; } else { TRACE("Model layer not found: %s", name); } } void Renderer::setModelLayerOpacity(const char *name, double opacity) { #if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 0) if (!_map.valid()) { ERROR("No map"); return; } osgEarth::ModelLayer *layer = _map->getModelLayerByName(name); if (layer != NULL) { layer->setOpacity(opacity); _needsRedraw = true; } else { TRACE("Model layer not found: %s", name); } #endif } void Renderer::setModelLayerVisibility(const char *name, bool state) { #if OSGEARTH_MIN_VERSION_REQUIRED(2, 4, 0) if (!_map.valid()) { ERROR("No map"); return; } osgEarth::ModelLayer *layer = _map->getModelLayerByName(name); if (layer != NULL) { layer->setVisible(state); _needsRedraw = true; } else { TRACE("Model layer not found: %s", name); } #endif } /** * \brief Resize the render window (image size for renderings) */ void Renderer::setWindowSize(int width, int height) { if (_windowWidth == width && _windowHeight == height) { TRACE("No change"); return; } TRACE("Setting window size to %dx%d", width, height); double origBitrate = getMaximumBitrate(); _windowWidth = width; _windowHeight = height; setMaximumBitrate(origBitrate); TRACE("Bandwidth target: %.2f Mbps", (float)(getMaximumBitrate()/1.0e6)); TRACE("Frame rate target: %.2f Hz", (float)getMaximumFrameRateInHertz()); if (_viewer.valid()) { #ifdef USE_OFFSCREEN_RENDERING #ifdef USE_PBUFFER osg::ref_ptr pbuffer; osg::ref_ptr traits = new osg::GraphicsContext::Traits; traits->x = 0; traits->y = 0; traits->width = _windowWidth; traits->height = _windowHeight; traits->red = 8; traits->green = 8; traits->blue = 8; traits->alpha = 8; traits->windowDecoration = false; traits->pbuffer = true; traits->doubleBuffer = true; traits->sharedContext = 0; pbuffer = osg::GraphicsContext::createGraphicsContext(traits.get()); if (pbuffer.valid()) { TRACE("Pixel buffer has been created successfully."); } else { ERROR("Pixel buffer has not been created successfully."); } osg::Camera *camera = new osg::Camera; camera->setGraphicsContext(pbuffer.get()); camera->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight)); GLenum buffer = pbuffer->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT; camera->setDrawBuffer(buffer); camera->setReadBuffer(buffer); camera->setFinalDrawCallback(_captureCallback.get()); _viewer->addSlave(camera, osg::Matrixd(), osg::Matrixd()); _viewer->realize(); #else if (_captureCallback.valid()) { _captureCallback->getTexture()->setTextureSize(_windowWidth, _windowHeight); } osgViewer::ViewerBase::Windows windows; _viewer->getWindows(windows); if (windows.size() == 1) { windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight); } else { ERROR("Num windows: %lu", windows.size()); } #endif #else osgViewer::ViewerBase::Windows windows; _viewer->getWindows(windows); if (windows.size() == 1) { windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight); } else { ERROR("Num windows: %lu", windows.size()); } #endif // HACK: Without this, the mouse coordinate mapping uses the old size // for 1 frame. assert(_viewer->getEventQueue() != NULL); //TRACE("Window EventQueue: %p", getEventQueue()); //TRACE("Viewer EventQueue: %p", _viewer->getEventQueue()); _viewer->getEventQueue()->windowResize(0, 0, _windowWidth, _windowHeight); _needsRedraw = true; } } /** * \brief Set the orientation of the camera from a quaternion * rotation * * \param[in] quat A quaternion with scalar part first: w,x,y,z * \param[in] absolute Is rotation absolute or relative? */ void Renderer::setCameraOrientation(const double quat[4], bool absolute) { if (_manipulator.valid()) { _manipulator->setRotation(osg::Quat(quat[1], quat[2], quat[3], quat[0])); _needsRedraw = true; } } /** * \brief Reset pan, zoom, clipping planes and optionally rotation * * \param[in] resetOrientation Reset the camera rotation/orientation also */ void Renderer::resetCamera(bool resetOrientation) { TRACE("Enter: resetOrientation=%d", resetOrientation ? 1 : 0); if (_viewer.valid()) { _viewer->home(); _needsRedraw = true; } } /** * \brief Perform a 2D translation of the camera * * x,y pan amount are specified as signed absolute pan amount in viewport * units -- i.e. 0 is no pan, .5 is half the viewport, 2 is twice the viewport, * etc. * * \param[in] x Viewport coordinate horizontal panning (positive number pans * camera left, object right) * \param[in] y Viewport coordinate vertical panning (positive number pans * camera up, object down) */ void Renderer::panCamera(double x, double y) { TRACE("Enter: %g %g", x, y); if (_manipulator.valid()) { // Wants mouse delta x,y in normalized screen coords _manipulator->pan(x, y); _needsRedraw = true; } } void Renderer::rotateCamera(double x, double y) { TRACE("Enter: %g %g", x, y); if (_manipulator.valid()) { _manipulator->rotate(x, y); _needsRedraw = true; } } /** * \brief Dolly camera or set orthographic scaling based on camera type * * \param[in] y Mouse y coordinate in normalized screen coords */ void Renderer::zoomCamera(double y) { TRACE("Enter: y: %g", y); if (_manipulator.valid()) { // +y = zoom out, -y = zoom in TRACE("camDist: %g", _manipulator->getDistance()); if ((_mapScale < 0.0 && y > 0.0) || (_mapScale < 0.1 && y < 0.0) || (_mapScale > 40000.0 && y > 0.0)) return; #if 1 _manipulator->zoom(0, y); #else double dist = _manipulator->getDistance(); dist *= (1.0 + y); _manipulator->setDistance(dist); #endif _needsRedraw = true; } } /** * \brief Dolly camera to set distance from focal point * * \param[in] dist distance in map? coordinates */ void Renderer::setCameraDistance(double dist) { TRACE("Enter: dist: %g", dist); if (_manipulator.valid()) { TRACE("camDist: %g", _manipulator->getDistance()); _manipulator->setDistance(dist); _needsRedraw = true; } } void Renderer::keyPress(int key) { osgGA::EventQueue *queue = getEventQueue(); if (queue != NULL) { queue->keyPress(key); _needsRedraw = true; } } void Renderer::keyRelease(int key) { osgGA::EventQueue *queue = getEventQueue(); if (queue != NULL) { queue->keyRelease(key); _needsRedraw = true; } } void Renderer::setThrowingEnabled(bool state) { if (_manipulator.valid()) { _manipulator->getSettings()->setThrowingEnabled(state); } } void Renderer::mouseDoubleClick(int button, double x, double y) { osgGA::EventQueue *queue = getEventQueue(); if (queue != NULL) { queue->mouseDoubleButtonPress((float)x, (float)y, button); _needsRedraw = true; } } void Renderer::mouseClick(int button, double x, double y) { osgGA::EventQueue *queue = getEventQueue(); if (queue != NULL) { queue->mouseButtonPress((float)x, (float)y, button); _needsRedraw = true; } } void Renderer::mouseDrag(int button, double x, double y) { osgGA::EventQueue *queue = getEventQueue(); if (queue != NULL) { queue->mouseMotion((float)x, (float)y); _needsRedraw = true; } } void Renderer::mouseRelease(int button, double x, double y) { osgGA::EventQueue *queue = getEventQueue(); if (queue != NULL) { queue->mouseButtonRelease((float)x, (float)y, button); _needsRedraw = true; } } void Renderer::mouseMotion(double x, double y) { if (_mouseCoordsTool.valid()) { #if 1 osgGA::EventQueue *queue = getEventQueue(); if (queue != NULL) { queue->mouseMotion((float)x, (float)y); _needsRedraw = true; } #else if (_viewer.valid() && _coordsCallback.valid()) { osgEarth::GeoPoint mapPt; if (mapMouseCoords((float)x, (float)y, mapPt)) { _coordsCallback->set(mapPt, _viewer->asView(), _mapNode); } else { _coordsCallback->reset(_viewer->asView(), _mapNode); } _needsRedraw = true; } #endif } } void Renderer::mouseScroll(int direction) { osgGA::EventQueue *queue = getEventQueue(); if (queue != NULL) { queue->mouseScroll((direction > 0 ? osgGA::GUIEventAdapter::SCROLL_UP : osgGA::GUIEventAdapter::SCROLL_DOWN)); _needsRedraw = true; } } /** * \brief Set the RGB background color to render into the image */ void Renderer::setBackgroundColor(float color[3]) { _bgColor[0] = color[0]; _bgColor[1] = color[1]; _bgColor[2] = color[2]; if (_viewer.valid()) { _viewer->getCamera()->setClearColor(osg::Vec4(color[0], color[1], color[2], 1)); _needsRedraw = true; } } /** * \brief Sets flag to trigger rendering next time render() is called */ void Renderer::eventuallyRender() { _needsRedraw = true; } /** * \brief Get a timeout in usecs for select() * * If the paging thread is idle, returns <0 indicating that the * select call can block until data is available. Otherwise, * if the frame render time was faster than the target frame * rate, return the remaining frame time. */ long Renderer::getTimeout() { if (!checkNeedToDoFrame()) // <0 means no timeout, block until socket has data return -1L; if (_lastFrameTime < _minFrameTime) { return (long)1.0e6*(_minFrameTime - _lastFrameTime); } else { // No timeout (poll) return 0L; } } /** * \brief Check if paging thread is quiescent */ bool Renderer::isPagerIdle() { if (!_viewer.valid()) return true; else return (!_viewer->getDatabasePager()->requiresUpdateSceneGraph() && !_viewer->getDatabasePager()->getRequestsInProgress()); } /** * \brief Check is frame call is necessary to render and/or update * in response to events or timed actions */ bool Renderer::checkNeedToDoFrame() { return (_needsRedraw || (_viewer.valid() && _viewer->checkNeedToDoFrame())); } /** * \brief MapNode event phase * * This is called by the MapNode's event callback during the event * traversal of the viewer */ void Renderer::mapNodeUpdate() { computeMapScale(); } void Renderer::markFrameStart() { _startFrameTime = osg::Timer::instance()->tick(); } void Renderer::markFrameEnd() { osg::Timer_t endFrameTick = osg::Timer::instance()->tick(); _lastFrameTime = osg::Timer::instance()->delta_s(_startFrameTime, endFrameTick); if (_lastFrameTime > _minFrameTime) { ERROR("BROKE FRAME by %.2f msec", (_lastFrameTime - _minFrameTime)*1000.0f); } else { TRACE("Frame time: %.2f msec", _lastFrameTime*1000.0f); } #ifdef USE_THROTTLING_SLEEP if (_lastFrameTime < _minFrameTime) { TRACE("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f); OpenThreads::Thread::microSleep(static_cast(1000000.0*(_minFrameTime - _lastFrameTime))); } #endif } /** * \brief Cause the rendering to render a new image if needed * * The _needsRedraw flag indicates if a state change has occured since * the last rendered frame */ bool Renderer::render() { if (_viewer.valid() && checkNeedToDoFrame()) { TRACE("Enter needsRedraw=%d", _needsRedraw ? 1 : 0); _renderStartTime = osg::Timer::instance()->tick(); TRACE("Before frame()"); _viewer->frame(); TRACE("After frame()"); _renderStopTime = osg::Timer::instance()->tick(); _renderTime = osg::Timer::instance()->delta_s(_renderStartTime, _renderStopTime); TRACE("Render time: %g msec", _renderTime * 1000.0); #ifndef SLEEP_AFTER_QUEUE_FRAME _lastFrameTime = _renderTime; #ifdef USE_THROTTLING_SLEEP if (_lastFrameTime < _minFrameTime) { TRACE("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f); OpenThreads::Thread::microSleep(static_cast(1.0e6*(_minFrameTime - _lastFrameTime))); } #endif #endif #ifdef WANT_TRACE if (_viewer->getViewerStats() != NULL) { _viewer->getViewerStats()->report(std::cerr, _viewer->getViewerStats()->getLatestFrameNumber()); } #endif _needsRedraw = false; return true; } else { _renderStartTime = _renderStopTime = osg::Timer::instance()->tick(); _renderTime = 0; return false; } } /** * \brief Read back the rendered framebuffer image */ osg::Image *Renderer::getRenderedFrame() { if (_captureCallback.valid()) return _captureCallback->getImage(); else return NULL; } void Renderer::setScaleBar(bool state) { if (_scaleLabel.valid()) { _scaleLabel->setVisible(state); } if (_scaleBar.valid()) { _scaleBar->setVisible(state); } _needsRedraw = true; } void Renderer::setScaleBarUnits(ScaleBarUnits units) { _scaleBarUnits = units; _needsRedraw = true; } /** * \brief Compute the scale ratio of the map based on a horizontal center line * * The idea here is to take 2 screen points on a horizontal line in the center * of the screen and convert to lat/long. The lat/long coordinates are then * used to compute the great circle distance (assuming spherical earth) between * the points. * * We could use local projected map coordinates for the distance computation, * which would be faster; however, this would not show e.g. the change in * scale at different latitudes */ double Renderer::computeMapScale() { if (!_scaleLabel.valid() || !_scaleLabel->visible()) { return -1.0; } if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) { ERROR("No map"); return -1.0; } if (!_viewer.valid()) { ERROR("No viewer"); return -1.0; } double x, y; double pixelWidth = _windowWidth * 0.1 * 2.0; if (pixelWidth < 10) pixelWidth = 10; if (pixelWidth > 150) pixelWidth = 150; x = (double)(_windowWidth -1)/2.0 - pixelWidth / 2.0; y = (double)(_windowHeight-1)/2.0; osg::Vec3d world1, world2; if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world1)) { // off map TRACE("Off map coords: %g %g", x, y); _scaleLabel->setText(""); _scaleBar->setWidth(0); return -1.0; } x += pixelWidth; if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world2)) { // off map TRACE("Off map coords: %g %g", x, y); _scaleLabel->setText(""); _scaleBar->setWidth(0); return -1.0; } TRACE("w1: %g %g %g w2: %g %g %g", world1.x(), world1.y(), world1.z(), world2.x(), world2.y(), world2.z()); double meters; double radius = 6378137.0; if (_mapNode->getMapSRS() && _mapNode->getMapSRS()->getEllipsoid()) { radius = _mapNode->getMapSRS()->getEllipsoid()->getRadiusEquator(); } if (!_map->isGeocentric() && _mapNode->getMapSRS() && _mapNode->getMapSRS()->isGeographic()) { TRACE("Map is geographic"); // World cords are already lat/long // Compute great circle distance meters = osgEarth::GeoMath::distance(world1, world2, _mapNode->getMapSRS()); } else if (_mapNode->getMapSRS()) { // Get map coords in lat/long osgEarth::GeoPoint mapPoint1, mapPoint2; mapPoint1.fromWorld(_mapNode->getMapSRS(), world1); mapPoint1.makeGeographic(); mapPoint2.fromWorld(_mapNode->getMapSRS(), world2); mapPoint2.makeGeographic(); // Compute great circle distance meters = osgEarth::GeoMath::distance(osg::DegreesToRadians(mapPoint1.y()), osg::DegreesToRadians(mapPoint1.x()), osg::DegreesToRadians(mapPoint2.y()), osg::DegreesToRadians(mapPoint2.x()), radius); } else { // Assume geocentric? ERROR("No map SRS"); _scaleLabel->setText(""); _scaleBar->setWidth(0); return -1.0; } double scale = meters / pixelWidth; // 1mi = 5280 feet //double scaleMiles = scale / 1609.344; // International mile = 1609.344m //double scaleNauticalMiles = scale / 1852.0; // nautical mile = 1852m //double scaleUSSurveyMiles = scale / 1609.347218694; // US survey mile = 5280 US survey feet //double scaleUSSurveyFeet = scale * 3937.0/1200.0; // US survey foot = 1200/3937 m TRACE("m: %g px: %g m/px: %g", meters, pixelWidth, scale); _mapScale = scale; switch (_scaleBarUnits) { case UNITS_NAUTICAL_MILES: { double nmi = meters / 1852.0; scale = nmi / pixelWidth; nmi = normalizeScaleNauticalMiles(nmi); pixelWidth = nmi / scale; if (_scaleLabel.valid()) { _scaleLabel->setText(osgEarth::Stringify() << nmi << " nmi"); } } break; case UNITS_US_SURVEY_FEET: { double feet = meters * 3937.0/1200.0; scale = feet / pixelWidth; feet = normalizeScaleFeet(feet); pixelWidth = feet / scale; if (_scaleLabel.valid()) { if (feet >= 5280) { _scaleLabel->setText(osgEarth::Stringify() << feet / 5280.0 << " miUS"); } else { _scaleLabel->setText(osgEarth::Stringify() << feet << " ftUS"); } } } break; case UNITS_INTL_FEET: { double feet = 5280.0 * meters / 1609.344; scale = feet / pixelWidth; feet = normalizeScaleFeet(feet); pixelWidth = feet / scale; if (_scaleLabel.valid()) { if (feet >= 5280) { _scaleLabel->setText(osgEarth::Stringify() << feet / 5280.0 << " mi"); } else { _scaleLabel->setText(osgEarth::Stringify() << feet << " ft"); } } } break; case UNITS_METERS: default: { meters = normalizeScaleMeters(meters); pixelWidth = meters / scale; if (_scaleLabel.valid()) { if (meters >= 1000) { _scaleLabel->setText(osgEarth::Stringify() << meters / 1000.0 << " km"); } else { _scaleLabel->setText(osgEarth::Stringify() << meters << " m"); } } } break; } if (_scaleBar.valid()) { _scaleBar->setWidth(pixelWidth); } return scale; } std::string Renderer::getCanonicalPath(const std::string& url) const { std::string retStr; std::string proto = osgDB::getServerProtocol(url); if (proto.empty()) { retStr = osgDB::getRealPath(url); if (!osgDB::fileExists(retStr)) { retStr = ""; } } else { retStr = url; } return retStr; } void Renderer::writeScene(const std::string& file) { if (_sceneRoot.valid()) { osgDB::writeNodeFile(*_sceneRoot.get(), file); } }