DYT/Tool/OpenSceneGraph-3.6.5/include/osgEarthImGui/SceneGraphGUI

1415 lines
55 KiB
Plaintext
Raw Permalink Normal View History

2024-12-24 23:49:36 +00:00
/* -*-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 <http://www.gnu.org/licenses/>
*/
#pragma once
#include <osgEarthImGui/ImGuiPanel>
#include <osgEarth/AnnotationUtils>
#include <osgEarth/TerrainEngineNode>
#include <osgEarth/PagedNode>
#include <osgEarth/MetadataNode>
#include <osgEarth/Viewpoint>
#include <osgEarth/ViewFitter>
#include <osgEarth/ThreeDTilesLayer>
#include <osgEarth/EarthManipulator>
#include <osgEarth/StringUtils>
#include <osgDB/FileUtils>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osg/TextureBuffer>
#include <osg/ComputeBoundsVisitor>
#include <osg/io_utils>
#include <osg/PolygonMode>
#if defined(__has_include)
#if __has_include(<third_party/portable-file-dialogs/portable-file-dialogs.h>)
#include <third_party/portable-file-dialogs/portable-file-dialogs.h>
#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<osg::Texture*>(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<osg::TextureBuffer*>(&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<osg::Node*, bool> 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<typename T>
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<typename T>
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<const osg::ByteArray*>(array));
break;
case osg::Array::ShortArrayType:
printArrayTablePretty("Data", static_cast<const osg::ShortArray*>(array));
break;
case osg::Array::IntArrayType:
printArrayTablePretty("Data", static_cast<const osg::IntArray*>(array));
break;
case osg::Array::UByteArrayType:
printArrayTablePretty("Data", static_cast<const osg::UByteArray*>(array));
break;
case osg::Array::UShortArrayType:
printArrayTablePretty("Data", static_cast<const osg::UShortArray*>(array));
break;
case osg::Array::UIntArrayType:
printArrayTablePretty("Data", static_cast<const osg::UIntArray*>(array));
break;
case osg::Array::FloatArrayType:
printArrayTablePretty("Data", static_cast<const osg::FloatArray*>(array));
break;
case osg::Array::DoubleArrayType:
printArrayTablePretty("Data", static_cast<const osg::DoubleArray*>(array));
break;
case osg::Array::Vec2bArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec2bArray*>(array));
break;
case osg::Array::Vec3bArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec3bArray*>(array));
break;
case osg::Array::Vec4bArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec4bArray*>(array));
break;
case osg::Array::Vec2sArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec2sArray*>(array));
break;
case osg::Array::Vec3sArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec3sArray*>(array));
break;
case osg::Array::Vec4sArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec4sArray*>(array));
break;
case osg::Array::Vec2iArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec2iArray*>(array));
break;
case osg::Array::Vec3iArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec3iArray*>(array));
break;
case osg::Array::Vec4iArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec4iArray*>(array));
break;
#if 0
case osg::Array::Vec2ubArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec2ubArray*>(array));
break;
case osg::Array::Vec3ubArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec3ubArray*>(array));
break;
#endif
case osg::Array::Vec4ubArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec4ubArray*>(array));
break;
#if 0
case osg::Array::Vec2usArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec2usArray*>(array));
break;
case osg::Array::Vec3usArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec3usArray*>(array));
break;
case osg::Array::Vec4usArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec4usArray*>(array));
break;
case osg::Array::Vec2uiArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec2uiArray*>(array));
break;
case osg::Array::Vec3uiArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec3uiArray*>(array));
break;
case osg::Array::Vec4uiArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec4uiArray*>(array));
break;
#endif
case osg::Array::Vec2ArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec2Array*>(array));
break;
case osg::Array::Vec3ArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec3Array*>(array));
break;
case osg::Array::Vec4ArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec4Array*>(array));
break;
case osg::Array::Vec2dArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec2dArray*>(array));
break;
case osg::Array::Vec3dArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec3dArray*>(array));
break;
case osg::Array::Vec4dArrayType:
printArrayTablePretty("Data", static_cast<const osg::Vec4dArray*>(array));
break;
#if 0
case osg::Array::MatrixArrayType:
printArrayTablePretty("Data", static_cast<const osg::MatrixfArray*>(array));
break;
case osg::Array::MatrixdArrayType:
printArrayTablePretty("Data", static_cast<const osg::MatrixdArray*>(array));
break;
case osg::Array::QuatArrayType:
printArrayTablePretty("Data", static_cast<const osg::QuatArray*>(array));
break;
#endif
case osg::Array::UInt64ArrayType:
printArrayTablePretty("Data", static_cast<const osg::UInt64Array*>(array));
break;
case osg::Array::Int64ArrayType:
printArrayTablePretty("Data", static_cast<const osg::Int64Array*>(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<osg::Node> 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<osgViewer::View*>(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<osgEarth::PagedNode2*>(&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<MapNode>(ri.getCurrentCamera());
if (!_installedSelectNodeHandler)
{
auto view = dynamic_cast<osgViewer::View*>(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<osgViewer::View*>(ri.getView());
auto manip = dynamic_cast<EarthManipulator*>(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<GeoPoint> 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<osg::MatrixTransform*>(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<osgEarth::PagedNode2*>(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<osgEarth::Contrib::ThreeDTiles::ThreeDTileNode*>(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<osg::Camera*>(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<osgEarth::MetadataNode*>(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<osg::Texture*>(stateset->getTextureAttribute(i, osg::StateAttribute::TEXTURE));
if (texture)
{
osg::Texture2D* texture2D = dynamic_cast<osg::Texture2D*>(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<osg::Uniform*>(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<osg::Geometry*>(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<osg::Node> _node;
osg::observer_ptr<MapNode> _mapNode;
osg::RefNodePath _selectedNodePath;
bool _propertiesExpanded = true;
bool _boundsDirty = false;
MemoryStats _memoryStats;
};
}