1415 lines
55 KiB
Plaintext
1415 lines
55 KiB
Plaintext
|
/* -*-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;
|
||
|
};
|
||
|
}
|