/* -*-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 #include #include #include #include #if defined(__has_include) #if __has_include() #include #define HAS_PFD #endif #endif struct PrepareForWriting : public osg::NodeVisitor { PrepareForWriting() : osg::NodeVisitor() { setTraversalMode(TRAVERSE_ALL_CHILDREN); setNodeMaskOverride(~0); } void apply(osg::Node& node) { apply(node.getStateSet()); applyUserData(node); traverse(node); } void apply(osg::Drawable& drawable) { apply(drawable.getStateSet()); applyUserData(drawable); osg::Geometry* geom = drawable.asGeometry(); if (geom) apply(geom); } void apply(osg::Geometry* geom) { // This detects any NULL vertex attribute arrays and then populates them. // Do this because a NULL VAA will crash the OSG serialization reader (osg 3.4.0) osg::Geometry::ArrayList& arrays = geom->getVertexAttribArrayList(); for (osg::Geometry::ArrayList::iterator i = arrays.begin(); i != arrays.end(); ++i) { if (i->get() == 0L) { *i = new osg::FloatArray(); i->get()->setBinding(osg::Array::BIND_OFF); } } // Get rid of any kdtree since osg can't serialize it. geom->setShape(nullptr); } void apply(osg::StateSet* ss) { if (!ss) return; osg::StateSet::AttributeList& a0 = ss->getAttributeList(); for (osg::StateSet::AttributeList::iterator i = a0.begin(); i != a0.end(); ++i) { osg::StateAttribute* sa = i->second.first.get(); applyUserData(*sa); } // Disable the texture image-unref feature so we can share the resource // across cached tiles. osg::StateSet::TextureAttributeList& a = ss->getTextureAttributeList(); for (osg::StateSet::TextureAttributeList::iterator i = a.begin(); i != a.end(); ++i) { osg::StateSet::AttributeList& b = *i; for (osg::StateSet::AttributeList::iterator j = b.begin(); j != b.end(); ++j) { osg::StateAttribute* sa = j->second.first.get(); if (sa) { osg::Texture* tex = dynamic_cast(sa); if (tex) { tex->setUnRefImageDataAfterApply(false); } else { applyUserData(*sa); } } } } applyUserData(*ss); } void applyUserData(osg::Object& object) { object.setUserDataContainer(0L); } }; struct WriteExternalImages : public osgEarth::TextureAndImageVisitor { std::string _destinationPath; WriteExternalImages(const std::string& destinationPath) : TextureAndImageVisitor(), _destinationPath(destinationPath) { setTraversalMode(TRAVERSE_ALL_CHILDREN); setNodeMaskOverride(~0L); } void apply(osg::Texture& tex) { if (dynamic_cast(&tex) != 0L) { // skip texture buffers, they need no prep and // will be inlined as long as they have a write hint // set to STORE_INLINE. } else { osgEarth::TextureAndImageVisitor::apply(tex); } } void apply(osg::Image& image) { std::string path = image.getFileName(); if (path.empty()) { OE_WARN << "ERROR image with blank filename.\n"; return; } std::string format = "dds"; unsigned int hash = osgEarth::hashString(path); std::string relativeName = osgEarth::Util::Stringify() << "images/" << hash << "." << format; std::string filename = osgDB::concatPaths(_destinationPath, relativeName); image.setFileName(relativeName); image.setWriteHint(osg::Image::EXTERNAL_FILE); osg::ref_ptr < osgDB::Options > options = new osgDB::Options; options->setOptionString("ddsNoAutoFlipWrite"); osgDB::makeDirectoryForFile(filename); osgDB::writeImageFile(image, filename, options.get()); } }; namespace osgEarth { using namespace osgEarth::Util; static bool hideEmptyPagedNodes = false; static std::unordered_map hasGeom; struct ArrayStats { unsigned int arrayCount = 0; unsigned int elementCount = 0; unsigned int size = 0; }; struct MemoryStats { void reset() { arrayStats.clear(); totalDataSize = 0; totalGeometries = 0; totalPrimitiveSets = 0; computed = false; } void addArray(osg::Drawable::AttributeTypes index, const osg::Array* array) { if (array && array->getNumElements() > 0) { arrayStats[index].arrayCount += 1; arrayStats[index].elementCount += array->getNumElements();; arrayStats[index].size += array->getTotalDataSize(); totalDataSize += array->getTotalDataSize(); } computed = true; } void addGeometry(const osg::Geometry& geometry) { if (geometry.getVertexArray()) { addArray(osg::Geometry::VERTICES, geometry.getVertexArray()); } if (geometry.getNormalArray()) { addArray(osg::Geometry::NORMALS, geometry.getNormalArray()); } if (geometry.getColorArray()) { addArray(osg::Geometry::COLORS, geometry.getColorArray()); } if (geometry.getSecondaryColorArray()) { addArray(osg::Geometry::SECONDARY_COLORS, geometry.getSecondaryColorArray()); } if (geometry.getFogCoordArray()) { addArray(osg::Geometry::FOG_COORDS, geometry.getFogCoordArray()); } const auto& texCoordArrays = geometry.getTexCoordArrayList(); for (unsigned int i = 0; i < texCoordArrays.size(); ++i) { if (texCoordArrays[i].valid()) { addArray((osg::Drawable::AttributeTypes)(osg::Geometry::TEXTURE_COORDS_0 + i), texCoordArrays[i].get()); } } const osg::Geometry::ArrayList& arrays = geometry.getVertexAttribArrayList(); for (unsigned int i = 0; i < arrays.size(); ++i) { if (arrays[i].valid()) { addArray((osg::Drawable::AttributeTypes)i, arrays[i].get()); } } ++totalGeometries; totalPrimitiveSets += geometry.getNumPrimitiveSets(); } std::map< osg::Drawable::AttributeTypes, ArrayStats > arrayStats; unsigned int totalDataSize = 0; unsigned int totalGeometries = 0; unsigned int totalPrimitiveSets = 0; bool computed = false; }; class ComputeMemoryStatsVisitor : public osg::NodeVisitor { public: ComputeMemoryStatsVisitor() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) { } virtual void apply(osg::Geometry& geometry) override { stats.addGeometry(geometry); } MemoryStats stats; }; template std::string printArrayValue(const T* array) { std::stringstream buf; unsigned int size = array->size(); for (unsigned i = 0; i < size; ++i) { if (i > 0) { buf << ", "; } buf << (*array)[i]; } return buf.str(); } static const char* arrayBindingToString(osg::Array::Binding binding) { switch (binding) { case osg::Array::BIND_PER_VERTEX: return "BIND_PER_VERTEX"; case osg::Array::BIND_OVERALL: return "BIND_OVERALL"; case osg::Array::BIND_PER_PRIMITIVE_SET: return "BIND_PER_PRIMITIVE_SET"; case osg::Array::BIND_OFF: return "BIND_OFF"; default: return "BIND_UNDEFINED"; } } static const char* attributeTypeToString(osg::Drawable::AttributeType attributeType) { switch (attributeType) { case osg::Drawable::VERTICES: return "VERTICES"; case osg::Drawable::WEIGHTS: return "WEIGHTS"; case osg::Drawable::NORMALS: return "NORMALS"; case osg::Drawable::COLORS: return "COLORS"; case osg::Drawable::SECONDARY_COLORS: return "SECONDARY_COLORS"; case osg::Drawable::FOG_COORDS: return "FOG_COORDS"; case osg::Drawable::ATTRIBUTE_6: return "ATTRIBUTE_6"; case osg::Drawable::ATTRIBUTE_7: return "ATTRIBUTE_7"; case osg::Drawable::TEXTURE_COORDS_0: return "TEXTURE_COORDS_0"; case osg::Drawable::TEXTURE_COORDS_1: return "TEXTURE_COORDS_1"; case osg::Drawable::TEXTURE_COORDS_2: return "TEXTURE_COORDS_2"; case osg::Drawable::TEXTURE_COORDS_3: return "TEXTURE_COORDS_3"; case osg::Drawable::TEXTURE_COORDS_4: return "TEXTURE_COORDS_4"; case osg::Drawable::TEXTURE_COORDS_5: return "TEXTURE_COORDS_5"; case osg::Drawable::TEXTURE_COORDS_6: return "TEXTURE_COORDS_6"; case osg::Drawable::TEXTURE_COORDS_7: return "TEXTURE_COORDS_7"; default: return "Uknown Array"; } } template void printArrayTablePretty(const std::string& name, const T* array) { if (!array) return; static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_SizingFixedFit; const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing(); // When using ScrollX or ScrollY we need to specify a size for our table container! // Otherwise by default the table will fit all available space, like a BeginChild() call. ImGui::Text(typeid(*array).name()); ImGui::Text("Binding %s", arrayBindingToString(array->getBinding())); ImGui::Text("Size: %dkb", (unsigned int)(array->getTotalDataSize() / 1024.0f)); ImVec2 outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 8); if (ImGui::BeginTable(name.c_str(), 2, flags, outer_size)) { ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible ImGui::TableSetupColumn("Index", ImGuiTableColumnFlags_None); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_None); ImGui::TableHeadersRow(); ImGuiListClipper clipper; clipper.Begin(array->size()); while (clipper.Step()) { for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::Text("%d", i); ImGui::TableSetColumnIndex(1); std::stringstream val; val << (*array)[i]; ImGui::Text(val.str().c_str()); } } ImGui::EndTable(); } } inline void printArrayTable(const osg::Array* array) { switch (array->getType()) { case osg::Array::ByteArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::ShortArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::IntArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::UByteArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::UShortArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::UIntArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::FloatArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::DoubleArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec2bArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec3bArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec4bArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec2sArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec3sArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec4sArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec2iArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec3iArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec4iArrayType: printArrayTablePretty("Data", static_cast(array)); break; #if 0 case osg::Array::Vec2ubArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec3ubArrayType: printArrayTablePretty("Data", static_cast(array)); break; #endif case osg::Array::Vec4ubArrayType: printArrayTablePretty("Data", static_cast(array)); break; #if 0 case osg::Array::Vec2usArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec3usArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec4usArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec2uiArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec3uiArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec4uiArrayType: printArrayTablePretty("Data", static_cast(array)); break; #endif case osg::Array::Vec2ArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec3ArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec4ArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec2dArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec3dArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Vec4dArrayType: printArrayTablePretty("Data", static_cast(array)); break; #if 0 case osg::Array::MatrixArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::MatrixdArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::QuatArrayType: printArrayTablePretty("Data", static_cast(array)); break; #endif case osg::Array::UInt64ArrayType: printArrayTablePretty("Data", static_cast(array)); break; case osg::Array::Int64ArrayType: printArrayTablePretty("Data", static_cast(array)); break; default: ImGui::Text("Unknown array type"); break; } } static std::string printUniformValue(osg::Uniform* uniform) { if (uniform->getFloatArray()) return printArrayValue(uniform->getFloatArray()); if (uniform->getDoubleArray()) return printArrayValue(uniform->getDoubleArray()); if (uniform->getIntArray()) return printArrayValue(uniform->getIntArray()); if (uniform->getUIntArray()) return printArrayValue(uniform->getUIntArray()); if (uniform->getUInt64Array()) return printArrayValue(uniform->getUInt64Array()); if (uniform->getInt64Array()) return printArrayValue(uniform->getInt64Array()); return ""; } class SceneGraphGUI : public ImGuiPanel { public: osg::ref_ptr getSelectedNode() { if (!_selectedNodePath.empty()) { return _selectedNodePath.back(); } return nullptr; } const osg::RefNodePath& getSelectedNodePath() { return _selectedNodePath; } void setSelectedNodePath(const osg::NodePath& nodePath) { _selectedNodePath.clear(); for (auto itr = nodePath.begin(); itr != nodePath.end(); ++itr) { _selectedNodePath.push_back(*itr); } _boundsDirty = true; _memoryStats.reset(); } struct SelectNodeHandler : public osgGA::GUIEventHandler { SceneGraphGUI* _owner; SelectNodeHandler(SceneGraphGUI* owner) : _owner(owner) { } bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) { osgViewer::View* view = static_cast(aa.asView()); if (ea.getEventType() == ea.PUSH && ea.getButton() == ea.LEFT_MOUSE_BUTTON && ea.getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_CTRL) { float w = 5.0; float h = 5.0; float x = ea.getX(); float y = ea.getY(); osg::ref_ptr< osgUtil::PolytopeIntersector> picker = new osgUtil::PolytopeIntersector(osgUtil::Intersector::WINDOW, x - w, y - h, x + w, y + h); picker->setIntersectionLimit(osgUtil::Intersector::LIMIT_NEAREST); osgUtil::IntersectionVisitor iv(picker.get()); // This is a hack, but we set the node mask to something other than 1 << 2 which is what the "selected bounds" node mask is set to to avoid picking it. // We should rework this later into something more formal so we can add widget type nodes that aren't generally interacted with. iv.setTraversalMask((1 << 1)); view->getCamera()->accept(iv); if (picker->containsIntersections()) { osg::NodePath nodePath = picker->getIntersections().begin()->nodePath; nodePath.push_back(picker->getIntersections().begin()->drawable.get()); _owner->setSelectedNodePath(nodePath); } } return false; } }; class SceneHierarchyVisitor : public osg::NodeVisitor { public: SceneGraphGUI* _owner; SceneHierarchyVisitor(SceneGraphGUI* owner) : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN), _owner(owner) { setNodeMaskOverride(~0); } void apply(osg::Node& node) { // Non groups act as leaf nodes std::string label = getLabel(node); ImGuiTreeNodeFlags node_flags = base_flags; node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; if (_owner->getSelectedNode() == &node) { node_flags |= ImGuiTreeNodeFlags_Selected; } bool nodeOff = node.getNodeMask() == 0u; if (nodeOff) { ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]); } ImGui::TreeNodeEx(&node, node_flags, label.c_str()); if (nodeOff) { ImGui::PopStyleColor(); } if (ImGui::IsItemClicked()) { _owner->setSelectedNodePath(getNodePath()); } if (ImGui::BeginDragDropTarget()) { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("TEXTURE")) { osg::Texture2D* droppedTexture = *(osg::Texture2D**)payload->Data; if (droppedTexture) { node.getOrCreateStateSet()->setTextureAttributeAndModes(0, droppedTexture, osg::StateAttribute::ON); } } ImGui::EndDragDropTarget(); } } void apply(osg::Group& node) { if (hideEmptyPagedNodes) { auto pagedNode = dynamic_cast(&node); if (pagedNode) { auto i = hasGeom.emplace(&node, false); if (i.second) { // new osg::ComputeBoundsVisitor cb; node.accept(cb); osg::BoundingBox bb = cb.getBoundingBox(); i.first->second = bb.valid(); } if (!i.first->second) return; } } std::string label = getLabel(node); ImGuiTreeNodeFlags node_flags = base_flags; if (_owner->getSelectedNode() == &node) { node_flags |= ImGuiTreeNodeFlags_Selected; } if (isInSelectedNodePath(&node)) { ImGui::SetNextItemOpen(true, ImGuiCond_Once); } bool nodeOff = node.getNodeMask() == 0u; if (nodeOff) { ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]); } bool node_open = ImGui::TreeNodeEx(&node, node_flags, label.c_str()); if (nodeOff) { ImGui::PopStyleColor(); } if (ImGui::IsItemClicked()) { _owner->setSelectedNodePath(getNodePath()); } if (ImGui::BeginDragDropTarget()) { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("NODE")) { osg::Node* droppedNode = *(osg::Node**)payload->Data; if (droppedNode) { node.addChild(droppedNode); } } else if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("TEXTURE")) { osg::Texture2D* droppedTexture = *(osg::Texture2D**)payload->Data; if (droppedTexture) { node.getOrCreateStateSet()->setTextureAttributeAndModes(0, droppedTexture, osg::StateAttribute::ON); } } ImGui::EndDragDropTarget(); } if (node_open) { traverse(node); ImGui::TreePop(); } } std::string getLabel(osg::Node& node) { std::stringstream buf; buf << node.getName() << " [" << typeid(node).name() << "]"; osg::Group* group = node.asGroup(); if (group) { buf << " (" << group->getNumChildren() << ")"; } return buf.str(); } bool isInSelectedNodePath(osg::Node* node) { auto selectedNodePaths = _owner->getSelectedNodePath(); for (unsigned int i = 0; i < selectedNodePaths.size(); i++) { if (node == selectedNodePaths[i].get()) { return true; } } return false; } ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth; }; SceneGraphGUI() : ImGuiPanel("Scene Graph Inspector"), _installedSelectNodeHandler(false), _selectedBounds(0), _node(nullptr) { } void draw(osg::RenderInfo& ri) override { if (!isVisible()) return; if (ImGui::Begin(name(), visible())) { if (_node == nullptr) _node = ri.getCurrentCamera(); if (!_mapNode.valid()) _mapNode = osgEarth::findTopMostNodeOfType(ri.getCurrentCamera()); if (!_installedSelectNodeHandler) { auto view = dynamic_cast(ri.getView()); view->addEventHandler(new SelectNodeHandler(this)); _installedSelectNodeHandler = true; } auto size = ImGui::GetWindowSize(); if (ImGui::CollapsingHeader("Scene")) { // Change the size based on whether the properties panel is ImGui::BeginChild("Scene", ImVec2(0, getSelectedNode() && _propertiesExpanded ? 0.6f * size.y : 0.90f * size.y), false, ImGuiWindowFlags_HorizontalScrollbar); SceneHierarchyVisitor v(this); _node->accept(v); ImGui::EndChild(); } if (getSelectedNode()) { _propertiesExpanded = ImGui::CollapsingHeader("Properties", ImGuiTreeNodeFlags_DefaultOpen); if (_propertiesExpanded) { ImGui::BeginChild("Properties"); auto view = dynamic_cast(ri.getView()); auto manip = dynamic_cast(view->getCameraManipulator()); properties(getSelectedNode(), ri, manip, _mapNode.get()); ImGui::EndChild(); } } ImGui::End(); } // If they closed the tool deselect the node. if (!isVisible() && getSelectedNode()) { osg::NodePath empty; setSelectedNodePath(empty); } if (_boundsDirty) { updateBoundsDebug(_mapNode.get()); } } void updateBoundsDebug(MapNode* mapNode) { if (_selectedBounds) { mapNode->removeChild(_selectedBounds); _selectedBounds = nullptr; } auto selectedNode = getSelectedNode(); if (selectedNode) { osg::RefNodePath refNodePath = getSelectedNodePath(); osg::NodePath nodePath; for (unsigned int i = 0; i < refNodePath.size(); ++i) { nodePath.push_back(refNodePath[i].get()); } if (nodePath.back()->asTransform()) { nodePath.pop_back(); } osg::Matrixd localToWorld = osg::computeLocalToWorld(nodePath); osg::Drawable* drawable = selectedNode->asDrawable(); if (drawable) { osg::BoundingBoxd bb = drawable->getBoundingBox(); if (bb.valid()) { _selectedBounds = new osg::MatrixTransform; _selectedBounds->setName("Bounds"); _selectedBounds->setMatrix(localToWorld); osg::MatrixTransform* center = new osg::MatrixTransform; center->addChild(AnnotationUtils::createBoundingBox(bb, Color::Yellow)); _selectedBounds->addChild(center); _selectedBounds->setNodeMask(1 << 2); mapNode->addChild(_selectedBounds); } } else { osg::BoundingSphered bs = osg::BoundingSphered(selectedNode->getBound().center(), selectedNode->getBound().radius()); if (bs.radius() > 0) { _selectedBounds = new osg::MatrixTransform; _selectedBounds->setName("Bounds"); _selectedBounds->setMatrix(localToWorld); osg::MatrixTransform* center = new osg::MatrixTransform; center->setMatrix(osg::Matrix::translate(bs.center())); center->addChild(AnnotationUtils::createSphere(selectedNode->getBound().radius(), Color::Yellow)); _selectedBounds->addChild(center); _selectedBounds->getOrCreateStateSet()->setAttributeAndModes(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE), 1); _selectedBounds->setNodeMask(1 << 2); mapNode->addChild(_selectedBounds); } } } _boundsDirty = false; } void properties(osg::Node* node, osg::RenderInfo& ri, EarthManipulator* manip, MapNode* mapNode) { if (manip && ImGui::Button("Zoom to")) { osg::NodePath nodePath = node->getParentalNodePaths()[0]; osg::Matrixd localToWorld = osg::computeLocalToWorld(nodePath); osg::BoundingSphered bs(node->getBound().center(), node->getBound().radius()); if (bs.valid()) { bs.center() += localToWorld.getTrans(); osg::Vec3d c = bs.center(); double r = bs.radius(); const SpatialReference* mapSRS = mapNode->getMap()->getSRS(); std::vector points; GeoPoint p; p.fromWorld(mapSRS, osg::Vec3d(c.x() + r, c.y(), c.z())); points.push_back(p); p.fromWorld(mapSRS, osg::Vec3d(c.x() - r, c.y(), c.z())); points.push_back(p); p.fromWorld(mapSRS, osg::Vec3d(c.x(), c.y() + r, c.z())); points.push_back(p); p.fromWorld(mapSRS, osg::Vec3d(c.x(), c.y() - r, c.z())); points.push_back(p); p.fromWorld(mapSRS, osg::Vec3d(c.x(), c.y(), c.z() + r)); points.push_back(p); p.fromWorld(mapSRS, osg::Vec3d(c.x(), c.y(), c.z() - r)); points.push_back(p); ViewFitter fitter(mapNode->getMap()->getSRS(), ri.getCurrentCamera()); Viewpoint vp; if (fitter.createViewpoint(points, vp)) { manip->setViewpoint(vp, 2.0); } } } #ifdef HAS_PFD if (ImGui::Button("Export")) { auto f = pfd::save_file("Save file", pfd::path::home()); if (!f.result().empty()) { std::string filename = f.result(); osgDB::makeDirectoryForFile(filename); PrepareForWriting prepare; node->accept(prepare); std::string path = osgDB::getFilePath(filename); // Maybe make this optional WriteExternalImages write(path); node->accept(write); osg::ref_ptr< osgDB::Options > writeOptions = new osgDB::Options; osgDB::writeNodeFile(*node, filename, writeOptions.get()); } } osg::Group* group = node->asGroup(); if (group) { if (ImGui::Button("Add Node")) { auto f = pfd::open_file("Choose files to read", pfd::path::home(), { "All Files", "*" }, pfd::opt::multiselect); for (auto const& name : f.result()) { osg::ref_ptr< osg::Node > node = osgDB::readNodeFile(name); if (node.valid()) { Registry::instance()->shaderGenerator().run(node.get()); group->addChild(node.get()); } } } } #endif bool visible = node->getNodeMask() != 0; if (ImGui::Checkbox("Visible", &visible)) { node->setNodeMask(visible); } if (ImGui::TreeNode("General")) { ImGui::Text("Type %s", typeid(*node).name()); ImGui::Text("Name %s", node->getName().c_str()); osg::Drawable* drawable = node->asDrawable(); if (drawable) { osg::BoundingBox bbox = drawable->getBoundingBox(); double width = bbox.xMax() - bbox.xMin(); double depth = bbox.yMax() - bbox.yMin(); double height = bbox.zMax() - bbox.zMin(); ImGui::Text("Bounding box (local) center=(%.3f, %.3f, %.3f) dimensions=%.3f x %.3f %.3f", bbox.center().x(), bbox.center().y(), bbox.center().z(), width, depth, height); } else { osg::BoundingSphere bs = node->getBound(); ImGui::Text("Bounding sphere (local) center=(%.3f, %.3f, %.3f) radius=%.3f", bs.center().x(), bs.center().y(), bs.center().z(), bs.radius()); } ImGui::Text("Reference count %d", node->referenceCount()); ImGui::TreePop(); } if (ImGui::TreeNode("Memory Usage")) { if (_memoryStats.computed) { ImGui::Text("Geometry Count: %d", _memoryStats.totalGeometries); ImGui::Text("PrimitiveSets: %d", _memoryStats.totalPrimitiveSets); ImGui::Text("Total Data Size %dkb", (unsigned int)(_memoryStats.totalDataSize / 1024.0f)); if (ImGui::BeginTable("Memory", 4, ImGuiTableFlags_Borders)) { ImGui::TableSetupColumn("Attribute"); ImGui::TableSetupColumn("Array Count"); ImGui::TableSetupColumn("Element Count"); ImGui::TableSetupColumn("Size"); ImGui::TableHeadersRow(); for (auto& a : _memoryStats.arrayStats) { ImGui::TableNextColumn(); ImGui::Text(attributeTypeToString((osg::Drawable::AttributeType)a.first)); ImGui::TableNextColumn(); ImGui::Text("%d", a.second.arrayCount); ImGui::TableNextColumn(); ImGui::Text("%d", a.second.elementCount); ImGui::TableNextColumn(); ImGui::Text("%dkb", (unsigned int)(a.second.size / 1024.0f)); } ImGui::EndTable(); } } if (ImGui::Button("Compute")) { ComputeMemoryStatsVisitor visitor; getSelectedNode()->accept(visitor); _memoryStats = visitor.stats; } ImGui::TreePop(); } osg::MatrixTransform* matrixTransform = dynamic_cast(node); if (matrixTransform) { if (ImGui::TreeNode("Transform")) { bool dirty = false; osg::Matrix matrix = matrixTransform->getMatrix(); osg::Vec3d translate = matrixTransform->getMatrix().getTrans(); if (ImGui::InputScalarN("Translation", ImGuiDataType_Double, translate._v, 3)) { dirty = true; } osg::Vec3d scale = matrixTransform->getMatrix().getScale(); if (ImGui::InputScalarN("Scale", ImGuiDataType_Double, scale._v, 3)) { dirty = true; } osg::Quat rot = matrixTransform->getMatrix().getRotate(); if (ImGui::InputScalarN("Rotation", ImGuiDataType_Double, rot._v, 4)) { dirty = true; } if (dirty) { osg::Matrix newMatrix = osg::Matrix::translate(translate) * osg::Matrix::rotate(rot) * osg::Matrixd::scale(scale); matrixTransform->setMatrix(newMatrix); } ImGui::TreePop(); } } osgEarth::PagedNode2* pagedNode2 = dynamic_cast(node); if (pagedNode2) { if (ImGui::TreeNode("PagedNode")) { ImGui::Text("Min range %.3f", pagedNode2->getMinRange()); ImGui::Text("Max range %.3f", pagedNode2->getMaxRange()); ImGui::TreePop(); } } osgEarth::Contrib::ThreeDTiles::ThreeDTileNode* threeDTiles = dynamic_cast(node); if (threeDTiles) { if (ImGui::TreeNode("3D Tiles")) { std::string content = threeDTiles->getTile()->content()->getJSON().toStyledString(); ImGui::Text("Content"); ImGui::TextWrapped(content.c_str()); } } osg::Camera* cam = dynamic_cast(node); if (cam) { if (ImGui::TreeNode("osg::Camera")) { const std::string order[3] = { "PRE_RENDER", "NESTED_RENDER", "POST_RENDER" }; const std::string proj[2] = { "PERSP", "ORTHO" }; ImGui::Text("Render order: %s", order[(int)cam->getRenderOrder()].c_str()); ImGui::Text("Projection: %s", proj[ProjectionMatrix::isOrtho(cam->getProjectionMatrix()) ? 1 : 0].c_str()); ImGui::Text("Cull mask: %0x%.8X", cam->getCullMask()); ImGui::Text("Gfx context: %0x%.16X", (std::uintptr_t)cam->getGraphicsContext()); ImGui::Text("Gfx context ID: %d", cam->getGraphicsContext()->getState()->getContextID()); ImGui::TreePop(); } } osgEarth::MetadataNode* metadata = dynamic_cast(node); if (metadata) { if (ImGui::TreeNode("Metadata")) { static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_SizingFixedFit; const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing(); // When using ScrollX or ScrollY we need to specify a size for our table container! // Otherwise by default the table will fit all available space, like a BeginChild() call. ImGui::Text("Features %d", metadata->getNumFeatures()); ImVec2 outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 8); if (ImGui::BeginTable("Features", 4, flags, outer_size)) { ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible ImGui::TableSetupColumn("Index", ImGuiTableColumnFlags_None); ImGui::TableSetupColumn("Feature ID", ImGuiTableColumnFlags_None); ImGui::TableSetupColumn("Object ID", ImGuiTableColumnFlags_None); ImGui::TableSetupColumn("Visible", ImGuiTableColumnFlags_None); ImGui::TableHeadersRow(); ImGuiListClipper clipper; clipper.Begin(metadata->getNumFeatures()); while (clipper.Step()) { for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { ImGui::PushID(i); // TODO: Add feature properties ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::Text("%d", i); ImGui::TableSetColumnIndex(1); ImGui::Text("%I64d", metadata->getFeature(i)->getFID()); ImGui::TableSetColumnIndex(2); ImGui::Text("%I64d", metadata->getObjectID(i)); ImGui::TableSetColumnIndex(3); bool visible = metadata->getVisible(i); if (ImGui::Checkbox("", &visible)) { metadata->setVisible(i, visible); } #if 0 ImGui::TableSetColumnIndex(3); auto feature = metadata->getFeature(i); if (feature) { std::stringstream buf; for (auto& attr : feature->getAttrs()) { buf << attr.first << "=" << attr.second.getString() << std::endl; } //std::string geojson = feature->getGeoJSON(); ImGui::Text(buf.str().c_str()); } else { ImGui::Text("Null feature"); } #endif ImGui::PopID(); } } ImGui::EndTable(); } ImGui::TreePop(); } } osg::StateSet* stateset = node->getStateSet(); if (stateset) { if (ImGui::TreeNode("State Set")) { if (!stateset->getTextureAttributeList().empty() && ImGui::TreeNode("Textures")) { unsigned int textureWidth = 50; for (unsigned int i = 0; i < stateset->getTextureAttributeList().size(); ++i) { osg::Texture* texture = dynamic_cast(stateset->getTextureAttribute(i, osg::StateAttribute::TEXTURE)); if (texture) { osg::Texture2D* texture2D = dynamic_cast(texture); ImGui::PushID(texture); ImGui::BeginGroup(); ImGui::Text("Unit %d", i); ImGui::Text("Type %s", typeid(*texture).name()); ImGui::Text("Name %s", texture->getName().c_str()); if (texture2D) { ImGuiEx::OSGTexture(texture2D, ri, textureWidth); } ImGui::EndGroup(); } } ImGui::TreePop(); } VirtualProgram* virtualProgram = VirtualProgram::get(stateset); if (virtualProgram && ImGui::TreeNode("Shaders")) { VirtualProgram::ShaderMap shaderMap; virtualProgram->getShaderMap(shaderMap); for (auto& s : shaderMap) { ImGui::Text("Name %s", s.second._shader->getName().c_str()); ImGui::Text("Location %s", FunctionLocationToString(s.second._shader->getLocation())); ImGui::TextWrapped(s.second._shader->getShaderSource().c_str()); ImGui::Separator(); } ImGui::TreePop(); } if (!stateset->getUniformList().empty()) { if (ImGui::TreeNode("Uniforms")) { if (ImGui::BeginTable("Uniforms", 3, ImGuiTableFlags_Borders)) { for (auto& u : stateset->getUniformList()) { osg::Uniform* uniform = dynamic_cast(u.second.first.get()); if (uniform) { // Name ImGui::TableNextColumn(); ImGui::Text(u.first.c_str()); // Type ImGui::TableNextColumn(); ImGui::Text(osg::Uniform::getTypename(uniform->getType())); // Value ImGui::TableNextColumn(); ImGui::Text(printUniformValue(uniform).c_str()); } } ImGui::EndTable(); } } } ImGui::TreePop(); } } osg::Geometry* geometry = dynamic_cast(node); if (geometry && ImGui::TreeNode("Geometry Properties")) { ImGui::Text("Type %s", typeid(*geometry).name()); if (geometry->getVertexArray()) { if (ImGui::TreeNode("verts", "VERTICES %d", geometry->getVertexArray()->getNumElements())) { printArrayTable(geometry->getVertexArray()); ImGui::TreePop(); } } if (geometry->getColorArray()) { if (ImGui::TreeNode("colors", "COLORS %d", geometry->getColorArray()->getNumElements())) { printArrayTable(geometry->getColorArray()); ImGui::TreePop(); } } if (geometry->getNormalArray()) { if (ImGui::TreeNode("normals", "NORMALS %d", geometry->getNormalArray()->getNumElements())) { printArrayTable(geometry->getNormalArray()); ImGui::TreePop(); } } auto& texCoordArrays = geometry->getTexCoordArrayList(); for (unsigned int i = 0; i < texCoordArrays.size(); ++i) { if (texCoordArrays[i].valid()) { if (ImGui::TreeNode("texcoords", "TexCoords Unit %d %d", i, texCoordArrays[i]->getNumElements())) { printArrayTable(texCoordArrays[i].get()); ImGui::TreePop(); } } } // Vetex attrib arrays osg::Geometry::ArrayList& arrays = geometry->getVertexAttribArrayList(); bool validVertexAttributeArray = false; for (unsigned int i = 0; i < arrays.size(); ++i) { if (arrays[i].valid()) { validVertexAttributeArray = true; break; } } if (validVertexAttributeArray) { ImGui::Text("Vertex Attributes"); ImGui::Separator(); for (unsigned int i = 0; i < arrays.size(); ++i) { if (!arrays[i].valid() && arrays[i]->getBinding() != osg::Array::BIND_OFF) continue; const char* arrayName = attributeTypeToString((osg::Drawable::AttributeType)i); if (ImGui::TreeNode(arrayName, "%s %d", arrayName, arrays[i]->getNumElements())) { printArrayTable(arrays[i].get()); ImGui::TreePop(); } } } for (auto& p : geometry->getPrimitiveSetList()) { ImGui::Text("%s Mode=%s Primitives=%d Instances=%d", typeid(*p.get()).name(), GLModeToString(p->getMode()), p->getNumPrimitives(), p->getNumInstances()); } } ImGui::Separator(); ImGui::Checkbox("Hide empty PagedNodes", &hideEmptyPagedNodes); } const char* GLModeToString(GLenum mode) { switch (mode) { case(GL_POINTS): return "GL_POINTS"; case(GL_LINES): return "GL_LINES"; case(GL_TRIANGLES): return "GL_TRIANGLES"; case(GL_QUADS): return "GL_QUADS"; case(GL_LINE_STRIP): return "GL_LINE_STRIP"; case(GL_LINE_LOOP): return "GL_LINE_LOOP"; case(GL_TRIANGLE_STRIP): return "GL_TRIANGLE_STRIP"; case(GL_TRIANGLE_FAN): return "GL_TRIANGLE_FAN"; case(GL_QUAD_STRIP): return "GL_QUAD_STRIP"; case(GL_PATCHES): return "GL_PATCHES"; case(GL_POLYGON): return "GL_POLYGON"; default: return "Unknown"; } } const char* FunctionLocationToString(VirtualProgram::FunctionLocation location) { using namespace osgEarth::ShaderComp; switch (location) { case LOCATION_VERTEX_MODEL: return "LOCATION_VERTEX_MODEL"; case LOCATION_VERTEX_VIEW: return "LOCATION_VERTEX_VIEW"; case LOCATION_VERTEX_CLIP: return "LOCATION_VERTEX_CLIP"; case LOCATION_TESS_CONTROL: return "LOCATION_TESS_CONTROL"; case LOCATION_TESS_EVALUATION: return "LOCATION_TESS_EVALUATION"; case LOCATION_GEOMETRY: return "LOCATION_GEOMETRY"; case LOCATION_FRAGMENT_COLORING: return "LOCATION_FRAGMENT_COLORING"; case LOCATION_FRAGMENT_LIGHTING: return "LOCATION_FRAGMENT_LIGHTING"; case LOCATION_FRAGMENT_OUTPUT: return "LOCATION_FRAGMENT_OUTPUT"; case LOCATION_UNDEFINED: return "LOCATION_UNDEFINED"; default: return "Undefined"; } } bool _installedSelectNodeHandler; osg::MatrixTransform* _selectedBounds; osg::observer_ptr _node; osg::observer_ptr _mapNode; osg::RefNodePath _selectedNodePath; bool _propertiesExpanded = true; bool _boundsDirty = false; MemoryStats _memoryStats; }; }