/* -*-c++-*- */ /* osgEarth - Geospatial SDK for OpenSceneGraph * Copyright 2018 Pelican Mapping * http://osgearth.org * * osgEarth is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include namespace osgEarth { using namespace osgEarth::Threading; class TerrainGUI : public ImGuiPanel { private: osg::observer_ptr _mapNode; std::unique_ptr _sampler; Future _sample; osg::ref_ptr _measureCursor; osg::ref_ptr _measureFeature; osg::ref_ptr _measureLabel; osg::ref_ptr _invalidateTool; float _x = 0.0f, _y = 0.0f; float _ax = 0.0f, _ay = 0.0f; float _measureDist = 0.0f; bool _installed = false; bool _measuring = false; bool _clamp_measure = true; bool _invalidating = false; public: TerrainGUI() : ImGuiPanel("Terrain") { } void load(const Config& conf) override { } void save(Config& conf) override { } void draw(osg::RenderInfo& ri) override { if (!isVisible()) return; if (!findNodeOrHide(_mapNode, ri)) return; if (!_installed) install(ri); ImGui::Begin(name(), visible()); { auto engine = _mapNode->getTerrainEngine(); if (!engine) { ImGui::TextColored(ImVec4(1, 0, 0, 1), "No terrain engine"); ImGui::End(); return; } auto options = _mapNode->getTerrainOptions(); if (ImGuiLTable::Begin("terraingui")) { ImGuiLTable::Text("Resident Tiles", "%u", engine->getNumResidentTiles());// , //engine->getNode()->getName().c_str()); int minResident = options.getMinResidentTiles(); if (ImGuiLTable::SliderInt("Resident Cache Size", &minResident, 0, 5000)) { options.setMinResidentTiles(minResident); engine->dirtyTerrainOptions(); } static bool terrain_visible = true; static osg::ref_ptr visible_cb; if (ImGuiLTable::Checkbox("Visible", &terrain_visible)) { if (!visible_cb) { visible_cb = new ToggleVisibleCullCallback(); _mapNode->getTerrainEngine()->getNode()->addCullCallback(visible_cb); } visible_cb->setVisible(terrain_visible); } static int max_max_lod = -1; static unsigned u32_one = 1; unsigned max_lod = options.getMaxLOD(); if (max_max_lod < 0) max_max_lod = max_lod; if (ImGuiLTable::InputScalar("Max LOD", ImGuiDataType_U32, &max_lod, &u32_one, nullptr, "%u")) { max_lod = clamp(max_lod, 0u, (unsigned)max_max_lod); options.setMaxLOD(max_lod); } bool progressive = options.getProgressive(); if (ImGuiLTable::Checkbox("Progressive load", &progressive)) { options.setProgressive(progressive); } LODMethod method = options.getLODMethod(); bool method_b = (method == LODMethod::SCREEN_SPACE); if (ImGuiLTable::Checkbox("Screen space LOD", &method_b)) { options.setLODMethod(method_b ? LODMethod::SCREEN_SPACE : LODMethod::CAMERA_DISTANCE); engine->dirtyTerrainOptions(); } if (options.getLODMethod() == LODMethod::SCREEN_SPACE) { float tileImagePixelSize = options.getTilePixelSize(); if (ImGuiLTable::SliderFloat(" Tile image size", &tileImagePixelSize, 64.0f, 1024.0f)) { options.setTilePixelSize(tileImagePixelSize); engine->dirtyTerrainOptions(); } } Color color = options.getColor(); if (ImGuiLTable::ColorEdit3("Background", color.ptr())) { options.setColor(color); engine->dirtyTerrainOptions(); } if (GLUtils::useNVGL()) { unsigned maxTex = options.getMaxTextureSize(); const char* maxTexItems[] = { "16", "32", "64", "128", "256", "512", "1024", "2048", "4096", "8192", "16384" }; unsigned maxTexIndex = 0; for (maxTexIndex = 0; maxTex > (unsigned)::atoi(maxTexItems[maxTexIndex]) && maxTexIndex < IM_ARRAYSIZE(maxTexItems) - 1; ++maxTexIndex); if (ImGuiLTable::BeginCombo("Max tex size", maxTexItems[maxTexIndex])) { for (int n = 0; n < IM_ARRAYSIZE(maxTexItems); n++) { const bool is_selected = (maxTexIndex == n); if (ImGui::Selectable(maxTexItems[n], is_selected)) { maxTexIndex = n; options.setMaxTextureSize(atoi(maxTexItems[maxTexIndex])); engine->dirtyTerrainOptions(); } if (is_selected) ImGui::SetItemDefaultFocus(); } ImGuiLTable::EndCombo(); } } if (options.getGPUTessellation()) { ImGui::Separator(); ImGuiLTable::Section("Tessellation"); float level = options.getTessellationLevel(); if (ImGuiLTable::SliderFloat("Level", &level, 1.0f, 12.0f)) { options.setTessellationLevel(level); engine->dirtyTerrainOptions(); } float range = options.getTessellationRange(); if (ImGuiLTable::SliderFloat("Range", &range, 0.0f, 5000.0f, "%.0f", ImGuiSliderFlags_Logarithmic)) { options.setTessellationRange(range); engine->dirtyTerrainOptions(); } ImGui::Separator(); } ImGuiLTable::End(); } GeoPoint mp; if (_mapNode->getGeoPointUnderMouse(view(ri), _x, _y, mp)) { ImGui::Separator(); if (mp.getSRS()->isGeographic()) { osg::Vec3d world; mp.toWorld(world); ImGui::Text("Lat %.3f Lon %.3f Z %.3f", mp.y(), mp.x(), mp.z()); ImGui::Text("X %d Y %d Z %d", (int)world.x(), (int)world.y(), (int)world.z()); } else { GeoPoint LL = mp.transform(mp.getSRS()->getGeographicSRS()); ImGui::Text("Lat %.3f Lon %.3f Z %.3f", LL.y(), LL.x(), LL.z()); ImGui::Text("X %d Y %d Z %d", (int)mp.x(), (int)mp.y(), (int)mp.z()); } osg::Vec3d world, eye, center, up; mp.toWorld(world); view(ri)->getCamera()->getViewMatrixAsLookAt(eye, center, up); double dist = (eye - world).length(); ImGui::Text("Distance from camera: %.1lf m", dist); ImGui::Text("Camera radius: %.1lf m", eye.length()); if (_sample.available()) { if (_sample->elevation().getValue() == NO_DATA_VALUE) { ImGui::Text("Elevation: 0.0m"); ImGui::Text("Resolution: n/a (NO DATA)"); } else { Distance cartRes = mp.transformResolution(_sample.value().resolution(), Units::METERS); const VerticalDatum* egm96 = VerticalDatum::get("egm96"); if (egm96) { double egm96z = _sample.value().elevation().getValue(); VerticalDatum::transform( mp.getSRS()->getVerticalDatum(), egm96, mp.y(), mp.x(), egm96z); Distance elevEGM96(egm96z, _sample.value().elevation().getUnits()); ImGui::Text("Elevation: %s MSL / %s HAE", elevEGM96.asParseableString().c_str(), _sample.value().elevation().asParseableString().c_str()); } else { ImGui::Text("Elevation: %s HAE", _sample.value().elevation().asParseableString().c_str()); } ImGui::Text("Resolution: %s", cartRes.asParseableString().c_str()); } } else { ImGui::Text("Elevation: ..."); ImGui::Text("Resolution: ..."); } if (fabs(_ax - _x) > 5 || fabs(_ay - _y) > 5) { _sample = _sampler->getSample(mp); _ax = _x, _ay = _y; } } ImGui::Separator(); ImGui::Text("SRS:"); ImGui::SameLine(); std::string proj = _mapNode->getMapSRS()->getName(); if (proj != "unknown") ImGui::TextWrapped("%s", proj.c_str()); ImGui::TextWrapped("(%s)", _mapNode->getMapSRS()->getHorizInitString().c_str()); if (!_mapNode->getMapSRS()->getVertInitString().empty()) ImGui::TextWrapped("vdatum = %s", _mapNode->getMapSRS()->getVertInitString().c_str()); ImGui::Separator(); if (ImGui::Checkbox("Measure", &_measuring)) { if (_measuring) { _measureFeature->getGeometry()->clear(); _measureCursor->dirty(); _measureCursor->setNodeMask(~0); _measureLabel->setNodeMask(~0); } } if (_measuring) { ImGui::SameLine(); if (ImGui::Checkbox("Follow", &_clamp_measure)) { Style s = _measureCursor->getStyle(); s.getOrCreate()->tessellation() = (_clamp_measure ? 64 : 0); _measureCursor->setStyle(s); } ImGui::SameLine(); float dist_m = 0.0f; auto& vec = _measureFeature->getGeometry()->asVector(); if (_mapNode->getMapSRS()->isGeographic()) { dist_m = GeoMath::distance(vec); } else { for (unsigned i = 1; i < vec.size(); ++i) dist_m += (vec[i] - vec[i - 1]).length(); } char buf[64]; sprintf(buf, "%.1f m", dist_m); _measureLabel->setText(buf); GeoPoint labelPos = _measureFeature->getExtent().getCentroid(); labelPos.altitudeMode() = ALTMODE_RELATIVE; _measureLabel->setPosition(labelPos); } else { _measureCursor->setNodeMask(0); _measureLabel->setNodeMask(0); } if (ImGui::Checkbox("Drag to invalidate", &_invalidating)) { _invalidateTool->setEnabled(_invalidating); } } ImGui::End(); } void install(osg::RenderInfo& ri) { EventRouter::get(view(ri)) .onMove([&](osg::View* v, float x, float y) { onMove(v, x, y); }, false) .onClick([&](osg::View* v, float x, float y) { onClick(v, x, y); }, false); _sampler = std::unique_ptr(new AsyncElevationSampler(_mapNode->getMap(), 1u)); Style measureStyle; measureStyle.getOrCreate()->stroke().mutable_value().width() = 4.0f; measureStyle.getOrCreate()->stroke().mutable_value().color().set(1, 0.5, 0.0, 1); measureStyle.getOrCreate()->stroke().mutable_value().stipplePattern() = 0xCCCC; measureStyle.getOrCreate()->tessellation() = (_clamp_measure ? 64 : 0); measureStyle.getOrCreate()->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN; measureStyle.getOrCreate()->technique() = AltitudeSymbol::TECHNIQUE_SCENE; measureStyle.getOrCreate()->depthOffset().mutable_value().enabled() = true; _measureFeature = new Feature(new LineString(), _mapNode->getMapSRS()); _measureCursor = new FeatureNode(_measureFeature.get(), measureStyle); _mapNode->addChild(_measureCursor); _measureLabel = new LabelNode(); _measureLabel->setDynamic(true); Style labelStyle; labelStyle.getOrCreate()->size() = 24.0f; labelStyle.getOrCreate()->alignment() = TextSymbol::ALIGN_CENTER_CENTER; labelStyle.getOrCreate()->fill().mutable_value().color().set(1, 1, 1, 1); labelStyle.getOrCreate()->halo().mutable_value().color().set(.3, .3, .3, 1); labelStyle.getOrCreate()->fill().mutable_value().color().set(1, 0.5, 0.0, 0.5); labelStyle.getOrCreate()->border().mutable_value().color().set(.8, .8, .8, 1); _measureLabel->setStyle(labelStyle); _measureLabel->setNodeMask(0); _mapNode->addChild(_measureLabel); // Tool to invalidate a map extent _invalidateTool = new Contrib::SelectExtentTool(_mapNode.get()); _invalidateTool->setEnabled(false); _invalidateTool->getStyle().getOrCreateSymbol()->stroke().mutable_value().color() = Color::Red; view(ri)->getEventHandlers().push_front(_invalidateTool); _invalidateTool->setCallback([&](const GeoExtent& ex) { OE_NOTICE << "Invalidating extent " << ex.toString() << std::endl; _mapNode->getTerrainEngine()->invalidateRegion(ex); _invalidateTool->clear(); _invalidateTool->setEnabled(false); _invalidating = false; }); _installed = true; } void onMove(osg::View* view, float x, float y) { _x = x, _y = y; if (_measuring) { GeoPoint p = getPointAtMouse(_mapNode.get(), view, x, y); if (!p.isValid()) return; Geometry* geom = _measureFeature->getGeometry(); if (geom->size() == 2) { GeoPoint pf = p.transform(_measureFeature->getSRS()); geom->back().set(pf.x(), pf.y(), 0.0f); _measureCursor->dirty(); } } } void onClick(osg::View* view, float x, float y) { if (_measuring) { Geometry* geom = _measureFeature->getGeometry(); if (geom->size() < 2) { GeoPoint p = getPointAtMouse(_mapNode.get(), view, x, y); if (!p.isValid()) return; GeoPoint pf = p.transform(_measureFeature->getSRS()); geom->push_back(pf.x(), pf.y()); geom->push_back(pf.x(), pf.y()); _measureCursor->dirty(); } else if (geom->size() == 2) { // do nothing geom->push_back(geom->back()); // trick. } else if (geom->size() > 2) { // start over geom->clear(); onClick(view, x, y); } } } }; }