Skip to content

Commit

Permalink
#5622: Basic algorithm to assemble a map fingerprint for all containe…
Browse files Browse the repository at this point in the history
…d nodes
  • Loading branch information
codereader committed May 22, 2021
1 parent 1a5db5d commit 059d6ab
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 0 deletions.
24 changes: 24 additions & 0 deletions include/icomparablenode.h
@@ -0,0 +1,24 @@
#pragma once

#include "inode.h"

namespace scene
{

/**
* Prototype of a comparable scene node, providing hash information
* for comparison to another node. Nodes of the same type can be compared against each other.
*/
class IComparableNode :
public virtual INode
{
public:
virtual ~IComparableNode() {}

// Returns the fingerprint (hash) of this node, to allow for quick
// matching against other nodes of the same type. Fingerprints of different
// types are not comparable, be sure to check the node type first.
virtual std::size_t getFingerprint() = 0;
};

}
21 changes: 21 additions & 0 deletions radiantcore/map/Map.cpp
Expand Up @@ -926,6 +926,11 @@ void Map::exportSelected(std::ostream& out, const MapFormatPtr& format)

void Map::mergeMap(const cmd::ArgumentList& args)
{
if (!getRoot())
{
throw cmd::ExecutionNotPossible(_("No map loaded, cannot merge"));
}

std::string candidate;

if (!args.empty())
Expand All @@ -944,6 +949,22 @@ void Map::mergeMap(const cmd::ArgumentList& args)
throw cmd::ExecutionFailure(fmt::format(_("File doesn't exist: {0}"), candidate));
}

auto resource = GlobalMapResourceManager().createFromPath(candidate);

try
{
if (resource->load())
{
const auto& otherRoot = resource->getRootNode();

// Compare the scenes and get the report
auto result = algorithm::compareGraphs(otherRoot, getRoot());
}
}
catch (const IMapResource::OperationException& ex)
{
radiant::NotificationMessage::SendError(ex.what());
}
}

void Map::emitMapEvent(MapEvent ev)
Expand Down
56 changes: 56 additions & 0 deletions radiantcore/map/algorithm/Import.cpp
Expand Up @@ -5,6 +5,7 @@

#include "i18n.h"
#include "imap.h"
#include "icomparablenode.h"
#include "imapformat.h"
#include "inamespace.h"
#include "iselectiongroup.h"
Expand Down Expand Up @@ -372,6 +373,61 @@ void importFromStream(std::istream& stream)
}
}

using Fingerprints = std::map<std::size_t, scene::INodePtr>;
using FingerprintsByType = std::map<scene::INode::Type, Fingerprints>;

FingerprintsByType collectFingerprints(const scene::INodePtr& node)
{
FingerprintsByType result;

node->foreachNode([&](const scene::INodePtr& node)
{
auto comparable = std::dynamic_pointer_cast<scene::IComparableNode>(node);

if (!comparable) return true; // skip

// Find or insert the map for this node type
auto fingerprints = result.try_emplace(comparable->getNodeType()).first->second;

// Store the fingerprint and check for collisions
auto insertResult = fingerprints.try_emplace(comparable->getFingerprint(), node);

if (!insertResult.second)
{
rWarning() << "More than one node with the same fingerprint found in the map " << node->name() << std::endl;
}

return true;
});

return result;
}

ComparisonResult::Ptr compareGraphs(const scene::IMapRootNodePtr& target, const scene::IMapRootNodePtr& source)
{
assert(source && target);

auto result = std::make_shared<ComparisonResult>();

auto sourceFingerprints = collectFingerprints(source);
auto targetFingerprints = collectFingerprints(target);

rMessage() << "Source Node Types: " << sourceFingerprints.size() << std::endl;
rMessage() << "Target Node Types: " << targetFingerprints.size() << std::endl;

for (const auto& pair : sourceFingerprints)
{
rMessage() << "Source Fingerprints for node type " << static_cast<std::size_t>(pair.first) << ": " << pair.second.size() << std::endl;
}

for (const auto& pair : targetFingerprints)
{
rMessage() << "Target Fingerprints for node type " << static_cast<std::size_t>(pair.first) << ": " << pair.second.size() << std::endl;
}

return result;
}

}

}
8 changes: 8 additions & 0 deletions radiantcore/map/algorithm/Import.h
Expand Up @@ -55,6 +55,14 @@ MapFormatPtr determineMapFormat(std::istream& stream, const std::string& type);
*/
MapFormatPtr determineMapFormat(std::istream& stream);

struct ComparisonResult
{
using Ptr = std::shared_ptr<ComparisonResult>;
};

// Runs a comparison of "source" (to be merged) against the "target" (merge target)
ComparisonResult::Ptr compareGraphs(const scene::IMapRootNodePtr& source, const scene::IMapRootNodePtr& target);

}

}
1 change: 1 addition & 0 deletions tools/msvc/include.vcxproj
Expand Up @@ -106,6 +106,7 @@
<ClInclude Include="..\..\include\iclipper.h" />
<ClInclude Include="..\..\include\icolourscheme.h" />
<ClInclude Include="..\..\include\icommandsystem.h" />
<ClInclude Include="..\..\include\icomparablenode.h" />
<ClInclude Include="..\..\include\icounter.h" />
<ClInclude Include="..\..\include\icurve.h" />
<ClInclude Include="..\..\include\idatastream.h" />
Expand Down

0 comments on commit 059d6ab

Please sign in to comment.