From 192df8a9256b9a09094c2c518118c4ec2915d35d Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 10 Jul 2026 18:00:29 +0100 Subject: [PATCH] Implement ReplacementVisitor example --- examples/core/CMakeLists.txt | 1 + .../core/vsgreplacementvisitor/CMakeLists.txt | 12 ++ .../vsgreplacementvisitor.cpp | 181 ++++++++++++++++++ examples/core/vsgvalues/vsgvalues.cpp | 1 + 4 files changed, 195 insertions(+) create mode 100644 examples/core/vsgreplacementvisitor/CMakeLists.txt create mode 100644 examples/core/vsgreplacementvisitor/vsgreplacementvisitor.cpp diff --git a/examples/core/CMakeLists.txt b/examples/core/CMakeLists.txt index dc051752..33c30d15 100644 --- a/examples/core/CMakeLists.txt +++ b/examples/core/CMakeLists.txt @@ -2,6 +2,7 @@ add_subdirectory(vsgallocator) add_subdirectory(vsgarrays) add_subdirectory(vsgmemory) add_subdirectory(vsgpointer) +add_subdirectory(vsgreplacementvisitor) add_subdirectory(vsgtypes) add_subdirectory(vsgvalues) add_subdirectory(vsgvisitor) diff --git a/examples/core/vsgreplacementvisitor/CMakeLists.txt b/examples/core/vsgreplacementvisitor/CMakeLists.txt new file mode 100644 index 00000000..b187cc11 --- /dev/null +++ b/examples/core/vsgreplacementvisitor/CMakeLists.txt @@ -0,0 +1,12 @@ +set(SOURCES vsgreplacementvisitor.cpp) + +add_executable(vsgreplacementvisitor ${SOURCES}) + +target_link_libraries(vsgreplacementvisitor vsg::vsg) + +if (vsgXchange_FOUND) + target_compile_definitions(vsgreplacementvisitor PRIVATE vsgXchange_FOUND) + target_link_libraries(vsgreplacementvisitor vsgXchange::vsgXchange) +endif() + +install(TARGETS vsgreplacementvisitor RUNTIME DESTINATION bin) diff --git a/examples/core/vsgreplacementvisitor/vsgreplacementvisitor.cpp b/examples/core/vsgreplacementvisitor/vsgreplacementvisitor.cpp new file mode 100644 index 00000000..ff0cda9c --- /dev/null +++ b/examples/core/vsgreplacementvisitor/vsgreplacementvisitor.cpp @@ -0,0 +1,181 @@ +#include + +#ifdef vsgXchange_FOUND +# include +#endif + +#include +#include +#include +#include + +class MyGroup : public vsg::Inherit +{ +public: + using Children = std::unordered_map>; + Children namedChildren; + + MyGroup(const vsg::Group& group) : + Inherit(group), + namedChildren() + { + for (size_t i = 0; i < group.children.size(); ++i) + { + std::string name; + if (group.children[i]->getValue("name", name)) + { + namedChildren[name] = group.children[i]; + } + else + { + namedChildren[std::to_string(i)] = group.children[i]; + } + } + } + + template + static void t_traverse(N& node, V& visitor) + { + for (auto& [name, child] : node.namedChildren) child->accept(visitor); + } + + void traverse(vsg::Visitor& visitor) override { t_traverse(*this, visitor); } + void traverse(vsg::ConstVisitor& visitor) const override { t_traverse(*this, visitor); } + void traverse(vsg::RecordTraversal& visitor) const override { t_traverse(*this, visitor); } +}; + +class MyStateGroup : public vsg::Inherit +{ +public: + vsg::StateCommands stateCommands; + + vsg::ref_ptr prototypeArrayState; + + MyStateGroup(const vsg::StateGroup& stateGroup) : + Inherit(stateGroup), + stateCommands(stateGroup.stateCommands), + prototypeArrayState(stateGroup.prototypeArrayState) + { + } + + template + static void t_traverse(N& node, V& visitor) + { + for (auto& stateCommand : node.stateCommands) stateCommand->accept(visitor); + Inherit::t_traverse(node, visitor); + } + + void traverse(vsg::Visitor& visitor) override { t_traverse(*this, visitor); } + void traverse(vsg::ConstVisitor& visitor) const override { t_traverse(*this, visitor); } + void traverse(vsg::RecordTraversal& visitor) const override + { + visitor.state->push(stateCommands); + Inherit::t_traverse(*this, visitor); + visitor.state->pop(stateCommands); + } +}; + +class MyGroupVisitor : public vsg::ReplacementVisitor +{ +public: + using ReplacementVisitor::apply; + + // Sometimes the same node will be accessible via multiple node paths. + // Typically, it's beneficial to return the same replacement every time it's encountered. + // Use ref_ptr so the original doesn't go out of scope and have its address reused by an unrelated node. + // Don't use a map like this if you specifically don't want to return the same replacement, e.g. if replacing every reference to a node with a unique copy that can be safely edited. + std::map, std::optional>> replacementMap; + + bool getExistingReplacement(vsg::Object& object, std::optional>& replacement) + { + auto itr = replacementMap.find(vsg::ref_ptr(&object)); + if (itr == replacementMap.end()) + return false; + replacement = itr->second; + return true; + } + + void saveReplacement(vsg::Object& object, std::optional>& replacement) + { + replacementMap.emplace(&object, replacement); + } + + std::optional> apply(vsg::Object& object) override + { + object.traverse(*this); + return std::nullopt; + } + + std::optional> apply(vsg::Group& group) override + { + std::optional> replacement; + if (getExistingReplacement(group, replacement)) + return replacement; + + group.traverse(*this); + + replacement = MyGroup::create(group); + saveReplacement(group, replacement); + return replacement; + } + + std::optional> apply(vsg::StateGroup& group) override + { + std::optional> replacement; + if (getExistingReplacement(group, replacement)) + return replacement; + + group.traverse(*this); + + replacement = MyStateGroup::create(group); + saveReplacement(group, replacement); + return replacement; + } + + // don't replace special-purpose classes our custom class can't do the job of + std::optional> apply(vsg::Transform& transform) override + { + return apply(static_cast(transform)); + } +}; + +int main(int argc, char** argv) +{ + vsg::CommandLine arguments(&argc, argv); + + auto windowTraits = vsg::WindowTraits::create(arguments); + + // set up defaults and read command line arguments to override them + auto options = vsg::Options::create(); + options->sharedObjects = vsg::SharedObjects::create(); + options->fileCache = vsg::getEnv("VSG_FILE_CACHE"); + options->paths = vsg::getEnvPaths("VSG_FILE_PATH"); + +#ifdef vsgXchange_all + // add vsgXchange's support for reading and writing 3rd party file formats + options->add(vsgXchange::all::create()); +#endif + + options->readOptions(arguments); + if (arguments.errors()) return arguments.writeErrorMessages(std::cerr); + + vsg::ref_ptr scene; + + if (argc > 1) + { + vsg::Path filename = argv[1]; + auto model = vsg::read_cast(filename, options); + if (!model) + { + std::cout << "Failed to load " << filename << std::endl; + return 1; + } + + scene = model; + } + + MyGroupVisitor visitor; + scene->accept(visitor); + + return 0; +} diff --git a/examples/core/vsgvalues/vsgvalues.cpp b/examples/core/vsgvalues/vsgvalues.cpp index f7b22556..516b155e 100644 --- a/examples/core/vsgvalues/vsgvalues.cpp +++ b/examples/core/vsgvalues/vsgvalues.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include